diff --git a/frontend/src/components/cme-form-view/FormBuilder.ts b/frontend/src/components/cme-form-view/FormBuilder.ts index f3bc617..a56276f 100644 --- a/frontend/src/components/cme-form-view/FormBuilder.ts +++ b/frontend/src/components/cme-form-view/FormBuilder.ts @@ -19,12 +19,23 @@ class FormBuilder { return this._tokens; } + set tokenValues(val: { [name: string]: string | string[] }) { + this._tokenValues = val; + } + + get tokenValues(): { [name: string]: string | string[] } { + return this._tokenValues; + } + set formItemChangeHandler(fn: () => void) { this._handleFormItemChange = fn; } build(): TemplateResult[] { const formElements = this._tokens.map((token) => { + if (token.isConditionalToken && token.linkedToken && token.matchValue && this.tokenValues?.[token.linkedToken.name] !== token.matchValue) { + return html`${nothing}`; + } switch (token.type) { case 'enum': return this._renderEnumTypeWidget(token); @@ -42,6 +53,8 @@ class FormBuilder { private _tokens: Token[] = []; + private _tokenValues: { [name: string]: string | string[] } = {}; + private _handleFormItemChange: () => void = noop; private _renderFormItem( diff --git a/frontend/src/components/cme-form-view/TemplateCompiler.ts b/frontend/src/components/cme-form-view/TemplateCompiler.ts index f203ea7..d378105 100644 --- a/frontend/src/components/cme-form-view/TemplateCompiler.ts +++ b/frontend/src/components/cme-form-view/TemplateCompiler.ts @@ -19,9 +19,10 @@ class TemplateCompiler { compile(): string { let compiled = this._template; - this._tokens.forEach(({name, prefix = '', suffix = ''}) => { + this._tokens.forEach(({ name, prefix = '', suffix = '', isConditionalToken, linkedToken, matchValue }) => { let value = this._tokenValues[name] || ''; - value = value ? prefix + value + suffix : ''; + const canShowConditionallyRendered = !isConditionalToken || !linkedToken || this._tokenValues[linkedToken.name] === matchValue; + value = value && canShowConditionallyRendered ? prefix + value + suffix : ''; compiled = compiled.replace(new RegExp(`{${name}}`, 'g'), value); }); diff --git a/frontend/src/components/cme-form-view/cme-form-view.ts b/frontend/src/components/cme-form-view/cme-form-view.ts index 3c0e3cd..a6ef648 100644 --- a/frontend/src/components/cme-form-view/cme-form-view.ts +++ b/frontend/src/components/cme-form-view/cme-form-view.ts @@ -219,6 +219,7 @@ export class FormView extends connect(store)(LitElement) { formBuilder.formItemChangeHandler = this._handleFormItemChange; formBuilder.tokens = this._tokens; + formBuilder.tokenValues = this._tokenValues; const formElements = formBuilder.build(); diff --git a/frontend/src/components/cme-settings-content.ts b/frontend/src/components/cme-settings-content.ts index a2d3ad5..fb56595 100644 --- a/frontend/src/components/cme-settings-content.ts +++ b/frontend/src/components/cme-settings-content.ts @@ -345,6 +345,7 @@ export class SettingsContent extends connect(store)(LitElement) { t.name !== this._name) ?? []; + } + private _onNameChange(ev: CustomEvent) { this._name = ev.detail; } @@ -259,6 +287,30 @@ export class TokenItemEdit extends LitElement { this._options = ev.detail; } + private _onIsConditionalTokenChange(ev: CustomEvent) { + this._isConditionalToken = ev.detail.checked; + if (!ev.detail.checked) { + this._linkedToken = undefined; + this._matchValue = ''; + } + } + + private _onLinkedTokenChange(ev: CustomEvent) { + const val = (ev.detail.value as string); + + this._linkedToken = this.tokenOptions.find((t) => t.name === val); + this._matchValue = ''; + } + + private _onMatchValueChange(ev: CustomEvent) { + this._matchValue = ev.detail.value as string; + } + + private _onMatchValueEnumChange(ev: CustomEvent) { + const valueLabel = ev.detail.value as string; + this._matchValue = this._linkedToken?.options?.find((o) => o.label === valueLabel)?.value as string; + } + private _onEditClick() { this.active = true; } @@ -646,6 +698,93 @@ export class TokenItemEdit extends LitElement { `; + const isConditionalTokenWidget = html` + + Conditional Token + + + `; + + const linkedTokenWidget = html` + + Linked Token + + + ${this.tokenOptions.map(token => html` + + ${token.name} + + `)} + + + `; + + const matchValueEnumWidget = html` + + Match Value + + ${this._linkedToken?.options?.map(option => html` + ${option.label} + `)} + + + `; + + const matchValueTextWidget = html` + + Match Value + + + `; + + const _matchValueBooleanWidget = html` + + Match Value + + true + false + + + `; + + const _matchValueWidget = !this._isConditionalToken || !this._linkedToken + ? nothing + : this._linkedToken.type === 'boolean' + ? _matchValueBooleanWidget + : this._linkedToken.type === 'enum' + ? matchValueEnumWidget + : matchValueTextWidget; + const activeView = html`
@@ -654,6 +793,9 @@ export class TokenItemEdit extends LitElement { ${multilineWidget} ${monospaceWidget} ${linesWidget} ${maxLinesWidget} ${maxLengthWidget} ${maxLineLengthWidget} ${multipleWidget} ${separatorWidget} ${comboboxWidget} ${optionsWidget} + ${isConditionalTokenWidget} + ${this._isConditionalToken ? linkedTokenWidget : nothing} + ${_matchValueWidget} ${this._isOptionsWindowVisible ? optionsWindow : nothing} Save diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts index 424432a..4a7627d 100644 --- a/frontend/src/global.d.ts +++ b/frontend/src/global.d.ts @@ -76,6 +76,9 @@ declare global { separator?: string; prefix?: string; suffix?: string; + isConditionalToken?: boolean; + linkedToken?: Token; + matchValue?: string; } type DefaultViewConfig = 'text' | 'form'; diff --git a/frontend/src/test/components/cme-form-view/TemplateCompiler.test.ts b/frontend/src/test/components/cme-form-view/TemplateCompiler.test.ts index 7b4a646..c088832 100644 --- a/frontend/src/test/components/cme-form-view/TemplateCompiler.test.ts +++ b/frontend/src/test/components/cme-form-view/TemplateCompiler.test.ts @@ -7,109 +7,147 @@ const createTemplate = () => [ '', '{body}', '', + '{root_cause}', + '', + '{fix}', + '', '{breaking_change}{footer}', ]; -const createTokens = (): Token[] => [ - { - label: 'Type', - name: 'type', +const createTokens = (): Token[] => { + const issueTypeToken: Token = { + label: 'Issue type', + name: 'issue_type', type: 'enum', options: [ { - label: '---', - value: '', - }, - { - label: 'build', - description: - 'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)', - }, - { - label: 'chore', - description: 'Updating grunt tasks etc; no production code change', - }, - { - label: 'ci', - description: - 'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)', - }, - { - label: 'docs', - description: 'Documentation only changes', - }, - { - label: 'feat', - description: 'A new feature', - }, - { - label: 'fix', - description: 'A bug fix', - }, - { - label: 'perf', - description: 'A code change that improves performance', - }, - { - label: 'refactor', - description: - 'A code change that neither fixes a bug nor adds a feature', - }, - { - label: 'revert', - }, - { - label: 'style', - description: - 'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)', + label: 'bug', }, { - label: 'test', - description: 'Adding missing tests or correcting existing tests', + label: 'feature', }, - ], - description: 'Type of changes', - }, - { - label: 'Scope', - name: 'scope', - description: - 'A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., "feat(parser): add ability to parse arrays".', - type: 'text', - multiline: false, - prefix: '(', - suffix: ')', - }, - { - label: 'Short description', - name: 'description', - description: 'Short description in the subject line.', - type: 'text', - multiline: false, - }, - { - label: 'Body', - name: 'body', - description: 'Optional body', - type: 'text', - multiline: true, - lines: 5, - maxLines: 10, - }, - { - label: 'Breaking change', - name: 'breaking_change', - type: 'boolean', - value: 'BREAKING CHANGE: ', - }, - { - label: 'Footer', - name: 'footer', - description: 'Optional footer', - type: 'text', - multiline: true, - }, -]; + ] + }; + return [ + { + label: 'Type', + name: 'type', + type: 'enum', + options: [ + { + label: '---', + value: '', + }, + { + label: 'build', + description: + 'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)', + }, + { + label: 'chore', + description: 'Updating grunt tasks etc; no production code change', + }, + { + label: 'ci', + description: + 'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)', + }, + { + label: 'docs', + description: 'Documentation only changes', + }, + { + label: 'feat', + description: 'A new feature', + }, + { + label: 'fix', + description: 'A bug fix', + }, + { + label: 'perf', + description: 'A code change that improves performance', + }, + { + label: 'refactor', + description: + 'A code change that neither fixes a bug nor adds a feature', + }, + { + label: 'revert', + }, + { + label: 'style', + description: + 'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)', + }, + { + label: 'test', + description: 'Adding missing tests or correcting existing tests', + }, + ], + description: 'Type of changes', + }, + { + label: 'Scope', + name: 'scope', + description: + 'A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., "feat(parser): add ability to parse arrays".', + type: 'text', + multiline: false, + prefix: '(', + suffix: ')', + }, + { + label: 'Short description', + name: 'description', + description: 'Short description in the subject line.', + type: 'text', + multiline: false, + }, + { + label: 'Body', + name: 'body', + description: 'Optional body', + type: 'text', + multiline: true, + lines: 5, + maxLines: 10, + }, + { + label: 'Breaking change', + name: 'breaking_change', + type: 'boolean', + value: 'BREAKING CHANGE: ', + }, + { + label: 'Footer', + name: 'footer', + description: 'Optional footer', + type: 'text', + multiline: true, + }, + issueTypeToken, + { + label: 'Root cause', + name: 'root_cause', + prefix: 'Root cause: ', + type: 'text', + isConditionalToken: true, + linkedToken: issueTypeToken, + matchValue: 'bug', + }, + { + label: 'Fix', + name: 'fix', + prefix: 'Fix: ', + type: 'text', + isConditionalToken: true, + linkedToken: issueTypeToken, + matchValue: 'bug', + }, + ] +}; const createTokenValues = (): TokenValueDTO => ({ type: 'feat', @@ -117,6 +155,9 @@ const createTokenValues = (): TokenValueDTO => ({ description: 'test description', body: 'Test body', breaking_change: 'BREAKING CHANGE: ', + issue_type: 'story', + root_cause: 'I\'m an idiot', + fix: 'Increased intelluct' }); describe('TemplateCompiler', () => { @@ -139,6 +180,32 @@ describe('TemplateCompiler', () => { expect(result).to.eq(expected); }); + it('conditional fields should be rendered', () => { + const template = createTemplate(); + const tokens = createTokens(); + const tokenValues = { + ...createTokenValues(), + issue_type: 'bug', + } + + const compiler = new TemplateCompiler(template, tokens, tokenValues); + const result = compiler.compile(); + + let expected = ''; + + expected += 'feat(lorem|ipsum): test description\n'; + expected += '\n'; + expected += 'Test body\n'; + expected += '\n'; + expected += 'Root cause: I\'m an idiot\n'; + expected += '\n'; + expected += 'Fix: Increased intelluct\n'; + expected += '\n'; + expected += 'BREAKING CHANGE: '; + + expect(result).to.eq(expected); + }); + it('empty lines should be reduced', () => { const template = [ '{test1}', diff --git a/frontend/src/test/components/cme-form-view/cme-form-view.test.ts b/frontend/src/test/components/cme-form-view/cme-form-view.test.ts index 37301ec..82d44e4 100644 --- a/frontend/src/test/components/cme-form-view/cme-form-view.test.ts +++ b/frontend/src/test/components/cme-form-view/cme-form-view.test.ts @@ -1,4 +1,4 @@ -import {expect, fixture, html, nextFrame} from '@open-wc/testing'; +import {expect, fixture, html} from '@open-wc/testing'; import {FormView} from '../../../components/cme-form-view/cme-form-view'; import store from '../../../store/store'; import { @@ -14,127 +14,165 @@ import {VscodeMultiSelect} from '@bendera/vscode-webview-elements/dist/vscode-mu import {VscodeInputbox} from '@bendera/vscode-webview-elements/dist/vscode-inputbox'; import {VscodeCheckbox} from '@bendera/vscode-webview-elements/dist/vscode-checkbox'; -const createConfig = (): ExtensionConfig => ({ - confirmAmend: true, - dynamicTemplate: [ - '{type}{scope}{gitmoji}: {description}', - '', - '{body}', - '', - '{breaking_change}{footer}', - ], - staticTemplate: [ - 'feat: Short description', - '', - 'Message body', - '', - 'Message footer', - ], - tokens: [ - { - label: 'Type', - name: 'type', - type: 'enum', - options: [ - { - label: 'build', - description: - 'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)', - }, - { - label: 'chore', - description: 'Updating grunt tasks etc; no production code change', - }, - ], - description: 'Type of changes', +const createConfig = (): ExtensionConfig => { + const issueTypeToken: Token = { + label: 'Issue type', + name: 'issue_type', + type: 'enum', + options: [ + { + label: 'feature', + }, + { + label: 'bug', + }, + ] + }; + return { + confirmAmend: true, + dynamicTemplate: [ + '{type}{scope}{gitmoji}: {description}', + '', + '{body}', + '', + '{root_cause}', + '', + '{fix}', + '', + '{breaking_change}{footer}', + ], + staticTemplate: [ + 'feat: Short description', + '', + 'Message body', + '', + 'Message footer', + ], + tokens: [ + { + label: 'Type', + name: 'type', + type: 'enum', + options: [ + { + label: 'build', + description: + 'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)', + }, + { + label: 'chore', + description: 'Updating grunt tasks etc; no production code change', + }, + ], + description: 'Type of changes', + }, + { + label: 'Scope', + name: 'scope', + description: + 'A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., "feat(parser): add ability to parse arrays".', + type: 'enum', + options: [ + { + label: 'Lorem', + value: 'lorem', + description: 'Example scope', + }, + { + label: 'Ipsum', + value: 'ipsum', + description: 'Another example scope', + }, + ], + multiple: true, + separator: '|', + prefix: '(', + suffix: ')', + }, + { + label: 'Gitmoji', + name: 'gitmoji', + description: 'Gitmoji example', + type: 'enum', + options: [ + { + label: '⚡️ zap', + value: '⚡️', + }, + { + label: '🔥 fire', + value: '🔥', + }, + { + label: '💚 green_heart', + value: '💚', + }, + ], + combobox: true, + filter: 'fuzzy', + }, + { + label: 'Short description', + name: 'description', + description: 'Short description in the subject line.', + type: 'text', + multiline: false, + }, + { + label: 'Body', + name: 'body', + description: 'Optional body', + type: 'text', + multiline: true, + }, + { + label: 'Breaking change', + name: 'breaking_change', + type: 'boolean', + value: 'BREAKING CHANGE: ', + }, + { + label: 'Footer', + name: 'footer', + description: 'Optional footer', + type: 'text', + multiline: true, + }, + issueTypeToken, + { + label: 'Root cause', + name: 'root_cause', + prefix: 'Root cause: ', + type: 'text', + isConditionalToken: true, + linkedToken: issueTypeToken, + matchValue: 'bug', + }, + { + label: 'Fix', + name: 'fix', + prefix: 'Fix: ', + type: 'text', + isConditionalToken: true, + linkedToken: issueTypeToken, + matchValue: 'bug', + }, + ], + reduceEmptyLines: true, + view: { + defaultView: 'text', + visibleViews: 'form', + fullWidth: false, + useMonospaceEditor: true, + tabSize: 2, + useTabs: true, + rulers: [50, 72], + visibleLines: 10, + showRecentCommits: true, + saveAndClose: true, }, - { - label: 'Scope', - name: 'scope', - description: - 'A scope may be provided to a commit’s type, to provide additional contextual information and is contained within parenthesis, e.g., "feat(parser): add ability to parse arrays".', - type: 'enum', - options: [ - { - label: 'Lorem', - value: 'lorem', - description: 'Example scope', - }, - { - label: 'Ipsum', - value: 'ipsum', - description: 'Another example scope', - }, - ], - multiple: true, - separator: '|', - prefix: '(', - suffix: ')', - }, - { - label: 'Gitmoji', - name: 'gitmoji', - description: 'Gitmoji example', - type: 'enum', - options: [ - { - label: '⚡️ zap', - value: '⚡️', - }, - { - label: '🔥 fire', - value: '🔥', - }, - { - label: '💚 green_heart', - value: '💚', - }, - ], - combobox: true, - filter: 'fuzzy', - }, - { - label: 'Short description', - name: 'description', - description: 'Short description in the subject line.', - type: 'text', - multiline: false, - }, - { - label: 'Body', - name: 'body', - description: 'Optional body', - type: 'text', - multiline: true, - }, - { - label: 'Breaking change', - name: 'breaking_change', - type: 'boolean', - value: 'BREAKING CHANGE: ', - }, - { - label: 'Footer', - name: 'footer', - description: 'Optional footer', - type: 'text', - multiline: true, - }, - ], - reduceEmptyLines: true, - view: { - defaultView: 'text', - visibleViews: 'form', - fullWidth: false, - useMonospaceEditor: true, - tabSize: 2, - useTabs: true, - rulers: [50, 72], - visibleLines: 10, - showRecentCommits: true, - saveAndClose: true, - }, -}); + }; +}; describe('cme-form-view', () => { let el: FormView | undefined; @@ -173,6 +211,7 @@ describe('cme-form-view', () => { body: 'body test', breaking_change: 'BREAKING CHANGE: ', footer: 'footer test', + issue_type: 'feature' }) ); @@ -199,6 +238,55 @@ describe('cme-form-view', () => { expect(calls[1].firstArg).to.deep.equal(closeTab()); }); + it('should render conditional field values if its visible', async () => { + const config = createConfig(); + const el: FormView = await fixture(html``); + + await el.updateComplete; + + store.dispatch(receiveConfig(config)); + store.dispatch( + updateTokenValues({ + type: 'feat', + scope: 'lorem', + gitmoji: '⚡️', + description: 'short description test', + body: 'body test', + breaking_change: 'BREAKING CHANGE: ', + footer: 'footer test', + issue_type: 'bug', + root_cause: 'I\'m an idiot', + fix: 'Increased intelluct' + }) + ); + + await el.updateComplete; + + storeSpy.resetHistory(); + const successButton = el.shadowRoot?.querySelector('#success-button-form'); + successButton?.dispatchEvent(new MouseEvent('click')); + const calls = storeSpy.getCalls(); + + let message = ''; + message += 'feat(lorem)⚡️: short description test\n'; + message += '\n'; + message += 'body test\n'; + message += '\n'; + message += 'Root cause: I\'m an idiot\n'; + message += '\n'; + message += 'Fix: Increased intelluct\n'; + message += '\n'; + message += 'BREAKING CHANGE: footer test'; + + expect(calls[0].firstArg).to.deep.equal( + copyToSCMInputBox({ + commitMessage: message, + selectedRepositoryPath: '', + }) + ); + expect(calls[1].firstArg).to.deep.equal(closeTab()); + }); + it('the source control input box should be updated then the extension tab should not be closed', async () => { const config = createConfig(); config.view.saveAndClose = false; @@ -216,6 +304,7 @@ describe('cme-form-view', () => { body: 'body test', breaking_change: 'BREAKING CHANGE: ', footer: 'footer test', + issue_type: 'feature' }) ); @@ -255,6 +344,7 @@ describe('cme-form-view', () => { body: 'body test', breaking_change: 'BREAKING CHANGE: ', footer: 'footer test', + issue_type: 'bug', }) ); @@ -298,7 +388,6 @@ describe('cme-form-view', () => { const el: FormView = await fixture(html``); await el.updateComplete; - await nextFrame(); storeSpy.resetHistory(); @@ -327,6 +416,27 @@ describe('cme-form-view', () => { cbBreakingChange.checked = true; cbBreakingChange?.dispatchEvent(new CustomEvent('vsc-change')); + const slIssueType = el.shadowRoot?.querySelector( + 'vscode-single-select[name="issue_type"]' + ) as VscodeSingleSelect; + slIssueType!.value = 'bug'; + slIssueType?.dispatchEvent(new CustomEvent('vsc-change')); + + expect(storeSpy.callCount).to.eq(5); + + const slRootCause = el.shadowRoot?.querySelector( + 'vscode-inputbox[name="root_cause"]' + ) as VscodeInputbox; + + slRootCause!.value = 'No idea man'; + slRootCause?.dispatchEvent(new CustomEvent('vsc-change')); + + const slFix = el.shadowRoot?.querySelector( + 'vscode-inputbox[name="fix"]' + ) as VscodeInputbox; + slFix!.value = 'Yet to figure out LOL'; + slFix?.dispatchEvent(new CustomEvent('vsc-change')); + const calls = storeSpy.getCalls(); expect(calls[0].firstArg).to.deep.equal( @@ -338,6 +448,9 @@ describe('cme-form-view', () => { gitmoji: '', scope: '', type: 'chore', + issue_type: 'feature', + root_cause: '', + fix: '' }) ); expect(calls[1].firstArg).to.deep.equal( @@ -349,6 +462,9 @@ describe('cme-form-view', () => { gitmoji: '', scope: 'lorem|ipsum', type: 'chore', + issue_type: 'feature', + root_cause: '', + fix: '' }) ); expect(calls[2].firstArg).to.deep.equal( @@ -360,6 +476,9 @@ describe('cme-form-view', () => { gitmoji: '', scope: 'lorem|ipsum', type: 'chore', + issue_type: 'feature', + root_cause: '', + fix: '' }) ); expect(calls[3].firstArg).to.deep.equal( @@ -371,8 +490,99 @@ describe('cme-form-view', () => { gitmoji: '', scope: 'lorem|ipsum', type: 'chore', + issue_type: 'feature', + root_cause: '', + fix: '' + }) + ); + expect(calls[4].firstArg).to.deep.equal( + updateTokenValues({ + body: '', + breaking_change: 'BREAKING CHANGE', + description: 'test description', + footer: '', + gitmoji: '', + scope: 'lorem|ipsum', + type: 'chore', + issue_type: 'bug', + root_cause: '', + fix: '' + }) + ); + expect(calls[5].firstArg).to.deep.equal( + updateTokenValues({ + body: '', + breaking_change: 'BREAKING CHANGE', + description: 'test description', + footer: '', + gitmoji: '', + scope: 'lorem|ipsum', + type: 'chore', + issue_type: 'bug', + root_cause: 'No idea man', + fix: '' + }) + ); + expect(calls[6].firstArg).to.deep.equal( + updateTokenValues({ + body: '', + breaking_change: 'BREAKING CHANGE', + description: 'test description', + footer: '', + gitmoji: '', + scope: 'lorem|ipsum', + type: 'chore', + issue_type: 'bug', + root_cause: 'No idea man', + fix: 'Yet to figure out LOL', }) ); - expect(calls[4]).to.be.undefined; + expect(calls[7]).to.be.undefined; + }); + + it('should not display conditionally rendered fields if the condition is not met', async () => { + const config = createConfig(); + const el: FormView = await fixture(html``); + await el.updateComplete; + + store.dispatch(receiveConfig(config)); + store.dispatch( + updateTokenValues({ + type: 'feat', + scope: 'lorem', + gitmoji: '⚡️', + description: 'short description test', + body: 'body test', + breaking_change: 'BREAKING CHANGE: ', + footer: 'footer test', + issue_type: 'feature' + }) + ); + + expect(el.shadowRoot?.querySelector('vscode-multi-select[name="root_cause"]')).to.be.null; + expect(el.shadowRoot?.querySelector('vscode-multi-select[name="fix"]')).to.be.null; + }); + + it('should display conditionally rendered fields if the condition is met', async () => { + const config = createConfig(); + const el: FormView = await fixture(html``); + await el.updateComplete; + + store.dispatch(receiveConfig(config)); + store.dispatch( + updateTokenValues({ + type: 'feat', + scope: 'lorem', + gitmoji: '⚡️', + description: 'short description test', + body: 'body test', + breaking_change: 'BREAKING CHANGE: ', + footer: 'footer test', + issue_type: 'bug' + }) + ); + + expect(el.shadowRoot?.querySelector('vscode-multi-select[name="root_cause"]')).not.to.be.undefined; + expect(el.shadowRoot?.querySelector('vscode-multi-select[name="fix"]')).not.to.be.undefined; }); }); diff --git a/frontend/src/test/components/cme-token-item-edit/cme-token-item-edit.test.ts b/frontend/src/test/components/cme-token-item-edit/cme-token-item-edit.test.ts index 2e9f3c2..af2143d 100644 --- a/frontend/src/test/components/cme-token-item-edit/cme-token-item-edit.test.ts +++ b/frontend/src/test/components/cme-token-item-edit/cme-token-item-edit.test.ts @@ -214,4 +214,169 @@ describe('cme-token-item-edit', () => { type: 'enum', }); }); + + describe('conditional tokens', () => { + it('should properly update conditional token related fields', async () => { + const el = (await fixture( + html`` + )) as TokenItemEdit; + + el.active = true; + el.tokens = [{ + name: 'test token', + label: 'Test token', + type: 'text', + }]; + + await el.updateComplete; + + const isConditionalToken = el.shadowRoot?.getElementById('isConditionalToken'); + + isConditionalToken?.dispatchEvent( + new CustomEvent('vsc-change', {detail: {checked: true}}) + ); + + await el.updateComplete; + const linkedToken = el.shadowRoot?.getElementById('linkedToken'); + + linkedToken?.dispatchEvent( + new CustomEvent('vsc-change', {detail: {value: 'test token'}}) + ); + + await el.updateComplete; + const matchValue = el.shadowRoot?.getElementById('matchValue'); + + matchValue?.dispatchEvent( + new CustomEvent('vsc-input', {detail: {value: 'test value'}}) + ); + + await el.updateComplete; + + expect(el.token).to.deep.eq({ + isConditionalToken: true, + linkedToken: el.tokens[0], + matchValue: 'test value', + label: '', + type: 'text', + name: '', + }) + }); + + it('should render Is conditional token checkbox', async () => { + const el = (await fixture( + html`` + )) as TokenItemEdit; + + el.active = true; + + await el.updateComplete; + + expect(el.shadowRoot?.getElementById('isConditionalToken')).to.exist; + }); + + it('should render a dropdown to select the linked token if conditional token is checked', async () => { + const el = (await fixture( + html`` + )) as TokenItemEdit; + + el.active = true; + el.token = { + ...el.token, + isConditionalToken: true + }; + + await el.updateComplete; + + expect(el.shadowRoot?.getElementById('linkedToken')).to.exist; + }); + + it('should render match value text input if linked token is selected and token is conditional', async () => { + const el = (await fixture( + html`` + )) as TokenItemEdit; + + el.active = true; + el.token = { + ...el.token, + isConditionalToken: true, + linkedToken: { + name: 'test token', + label: 'Test token', + type: 'text', + } + }; + + await el.updateComplete; + + const matchValueInput = el.shadowRoot?.getElementById('matchValue'); + expect(matchValueInput).to.exist; + expect(matchValueInput?.tagName.toLowerCase()).to.eq('vscode-inputbox'); + }); + + it('should render a select input if linked token is an enum token', async () => { + const el = (await fixture( + html`` + )) as TokenItemEdit; + + el.active = true; + el.token = { + ...el.token, + isConditionalToken: true, + linkedToken: { + name: 'test token', + label: 'Test token', + type: 'enum', + options: [ + { + label: 'Test option 1', + description: '', + value: 'testoption1' + }, + { + label: 'Test option 2', + description: '', + value: 'testoption2' + }, + ] + } + }; + + await el.updateComplete; + + const matchValueInput = el.shadowRoot?.getElementById('matchValue'); + expect(matchValueInput).to.exist; + expect(matchValueInput?.tagName.toLowerCase()).to.eq('vscode-single-select'); + const options = matchValueInput?.querySelectorAll('vscode-option'); + expect(options?.length).to.eq(2); + expect(options?.item(0)?.textContent).to.eq('Test option 1'); + expect(options?.item(1)?.textContent).to.eq('Test option 2'); + }); + + it('should render a select input if linked token is a boolean token', async () => { + const el = (await fixture( + html`` + )) as TokenItemEdit; + + el.active = true; + el.token = { + ...el.token, + isConditionalToken: true, + linkedToken: { + name: 'test token', + label: 'Test token', + type: 'boolean', + } + }; + + await el.updateComplete; + + const matchValueInput = el.shadowRoot?.getElementById('matchValue'); + expect(matchValueInput).to.exist; + expect(matchValueInput?.tagName.toLowerCase()).to.eq('vscode-single-select'); + const options = matchValueInput?.querySelectorAll('vscode-option'); + expect(options?.length).to.eq(2); + expect(options?.item(0)?.textContent).to.eq('true'); + expect(options?.item(1)?.textContent).to.eq('false'); + }); + }); });