From 94902af5099b1772a6755b3e6686d18ec691bcfd Mon Sep 17 00:00:00 2001 From: David Hunt Date: Tue, 22 Oct 2024 12:13:59 +1300 Subject: [PATCH] feat: several additions to QuickPickleWorldInterface, fix explodeTags, refactor feat: add QuickPickleWorld.toString() function that renders to a single descriptive line feat: add QuickPickleWorld.info.stepIdx, to get the line number of the step within the scenario feat: add QuickPickleWorld.info.explodedIdx, to get the index number for exploded tags chore: renamed "qp" function to "gherkinStep", for better readability in traces fix: fixed the explodeTags functionality when rendering test: added tons of tests for the explodeTags functionality --- .changeset/silent-parrots-camp.md | 12 ++ packages/main/gherkin-example/example.feature | 12 ++ .../main/gherkin-example/example.feature.js | 136 ++++++++++++----- packages/main/src/index.ts | 4 +- packages/main/src/render.ts | 30 ++-- packages/main/src/world.ts | 11 ++ packages/main/tests/index.test.ts | 144 +++++++++++++++++- packages/main/tests/renderer.test.ts | 2 +- packages/main/tests/test.feature | 2 + 9 files changed, 295 insertions(+), 58 deletions(-) create mode 100644 .changeset/silent-parrots-camp.md diff --git a/.changeset/silent-parrots-camp.md b/.changeset/silent-parrots-camp.md new file mode 100644 index 0000000..b209a34 --- /dev/null +++ b/.changeset/silent-parrots-camp.md @@ -0,0 +1,12 @@ +--- +"quickpickle": minor +--- + +feat: several additions to QuickPickleWorldInterface, fix explodeTags, refactor + +feat: add QuickPickleWorld.toString() function that renders to a single descriptive line +feat: add QuickPickleWorld.info.stepIdx, to get the line number of the step within the scenario +feat: add QuickPickleWorld.info.explodedIdx, to get the index number for exploded tags +chore: renamed "qp" function to "gherkinStep", for better readability in traces +fix: fixed the explodeTags functionality when rendering +test: added tons of tests for the explodeTags functionality diff --git a/packages/main/gherkin-example/example.feature b/packages/main/gherkin-example/example.feature index 34c00b2..b356cba 100644 --- a/packages/main/gherkin-example/example.feature +++ b/packages/main/gherkin-example/example.feature @@ -110,3 +110,15 @@ Feature: QuickPickle's Comprehensive Gherkin Syntax Example Given a Rule statement When another Rule is indented below it Then the indented Rule is NOT a child of the previous Rule + + @1a @1b + Scenario: Exploded tags make multiple tests + Given an explodedTags config of [[ '@1a','@1b' ], [ '@2a','@2b' ]] + When this Scenario is run + Then it should be split into 2 tests + + @1a @1b @2a @2b @tag3 + Scenario: More tags make more tests + Given an explodedTags config of [[ '@1a','@1b' ], [ '@2a','@2b' ]] + When this Scenario is run + Then it should be split into 4 tests diff --git a/packages/main/gherkin-example/example.feature.js b/packages/main/gherkin-example/example.feature.js index 17bfc9c..1e324ca 100644 --- a/packages/main/gherkin-example/example.feature.js +++ b/packages/main/gherkin-example/example.feature.js @@ -1,7 +1,7 @@ // Generated by quickpickle import { test, describe, beforeAll, afterAll } from 'vitest'; import { - qp, + gherkinStep, applyBeforeAllHooks, applyBeforeHooks, applyAfterAllHooks, @@ -33,8 +33,8 @@ const initScenario = async(context, scenario, tags) => { state.info.scenario = scenario; state.info.tags = [...tags]; await applyBeforeHooks(state); - await qp('a common precondition', state, 10); - await qp('another common precondition', state, 11); + await gherkinStep('a common precondition', state, 10, -1); + await gherkinStep('another common precondition', state, 11, -2); return state; } @@ -42,9 +42,9 @@ describe('Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', () => { test('Scenario: Basic scenario example\'', async (context) => { let state = await initScenario(context, 'Basic scenario example\'', ['@tag', '@multiple_tags', '@scenario_tag']); - await qp('an initial context\'', state, 15); - await qp('an action is performed\'', state, 16); - await qp('a verifiable outcome is achieved\'', state, 17); + await gherkinStep('an initial context\'', state, 15, 1); + await gherkinStep('an action is performed\'', state, 16, 2); + await gherkinStep('a verifiable outcome is achieved\'', state, 17, 3); await afterScenario(state); }); @@ -52,22 +52,22 @@ describe('Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', () => { 'Scenario Outline: Parameterized scenario for $parameter, \'$another_parameter\'', async ({ parameter, another_parameter, expected_result }, context) => { let state = await initScenario(context, `Parameterized scenario for ${parameter}, '${another_parameter}'`, ['@tag', '@multiple_tags', '@concurrent']); - await qp(`a 'precondition' with ${parameter}`, state, 20); - await qp(`an 'action' is taken with ${another_parameter}`, state, 21); - await qp(`the 'outcome' is ${expected_result}`, state, 22); + await gherkinStep(`a 'precondition' with ${parameter}`, state, 20, 1); + await gherkinStep(`an 'action' is taken with ${another_parameter}`, state, 21, 2); + await gherkinStep(`the 'outcome' is ${expected_result}`, state, 22, 3); await afterScenario(state); } ); test('Scenario: Scenario with various DataTable types', async (context) => { let state = await initScenario(context, 'Scenario with various DataTable types', ['@tag', '@multiple_tags', '@data_table']); - await qp('a list of strings:', state, 31, [["Apple'"],["Banana`"],["Cherry\""]]); - await qp('a list of integers:', state, 35, [["1"],["2"],["3"]]); - await qp('a map of string to string:', state, 39, [["key1'","value1'"],["key2`","value2\""]]); - await qp('a list of maps:', state, 42, [["name'","age`","role\""],["Alice'","30","admin\""],["Bob`","25","user\""]]); - await qp('a map of string to list of string:', state, 46, [["fruits","Apple, Banana, Cherry"],["vegetables","Carrot, Potato, Onion"]]); - await qp('they are processed', state, 49); - await qp('the system behaves correctly', state, 50); + await gherkinStep('a list of strings:', state, 31, 1, undefined, [["Apple'"],["Banana`"],["Cherry\""]]); + await gherkinStep('a list of integers:', state, 35, 2, undefined, [["1"],["2"],["3"]]); + await gherkinStep('a map of string to string:', state, 39, 3, undefined, [["key1'","value1'"],["key2`","value2\""]]); + await gherkinStep('a list of maps:', state, 42, 4, undefined, [["name'","age`","role\""],["Alice'","30","admin\""],["Bob`","25","user\""]]); + await gherkinStep('a map of string to list of string:', state, 46, 5, undefined, [["fruits","Apple, Banana, Cherry"],["vegetables","Carrot, Potato, Onion"]]); + await gherkinStep('they are processed', state, 49, 6); + await gherkinStep('the system behaves correctly', state, 50, 7); await afterScenario(state); }); @@ -76,58 +76,58 @@ describe('Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', () => { const initRuleScenario = async (context, scenario, tags) => { let state = await initScenario(context, scenario, tags); state.info.rule = 'Business rule description\''; - await qp('a specific rule context', state, 57); - await qp('another specific rule context', state, 58); + await gherkinStep('a specific rule context', state, 57, -1); + await gherkinStep('another specific rule context', state, 58, -2); return state; } test('Example: Rule example scenario\'', async (context) => { let state = await initRuleScenario(context, 'Rule example scenario\'', ['@tag', '@multiple_tags', '@rule_tag']); - await qp('a specific rule context', state, 61); - await qp('a rule-related action occurs', state, 62); - await qp('the rule outcome is observed', state, 63); + await gherkinStep('a specific rule context', state, 61, 1); + await gherkinStep('a rule-related action occurs', state, 62, 2); + await gherkinStep('the rule outcome is observed', state, 63, 3); await afterScenario(state); }); test('Scenario: Also a rule example\'', async (context) => { let state = await initRuleScenario(context, 'Also a rule example\'', ['@tag', '@multiple_tags', '@rule_tag']); - await qp('a Rule statement', state, 66); - await qp('a scenario is below it', state, 67); - await qp('it is a child of the Rule, even if it isn\'t indented', state, 68); + await gherkinStep('a Rule statement', state, 66, 1); + await gherkinStep('a scenario is below it', state, 67, 2); + await gherkinStep('it is a child of the Rule, even if it isn\'t indented', state, 68, 3); await afterScenario(state); }); test.todo.skip('Scenario: Scenario with doc string', async (context) => { let state = await initRuleScenario(context, 'Scenario with doc string', ['@tag', '@multiple_tags', '@rule_tag', '@wip', '@skip']); - await qp('a document with the following content:', state, 74, {"content":"This is a doc string.\nIt can contain multiple lines.\nUseful for specifying larger text inputs."}); - await qp('the document is processed', state, 80); - await qp('the system handles it correctly', state, 81); + await gherkinStep('a document with the following content:', state, 74, 1, undefined, {"content":"This is a doc string.\nIt can contain multiple lines.\nUseful for specifying larger text inputs."}); + await gherkinStep('the document is processed', state, 80, 2); + await gherkinStep('the system handles it correctly', state, 81, 3); await afterScenario(state); }); test('Scenario: Scenario with content type doc string', async (context) => { let state = await initRuleScenario(context, 'Scenario with content type doc string', ['@tag', '@multiple_tags', '@rule_tag']); - await qp('a document with the following Markdown content:', state, 84, {"content":"Lorem Ipsum\n===============\nLorem ipsum dolor sit amet,\nconsectetur adipiscing elit.","mediaType":"markdown"}); + await gherkinStep('a document with the following Markdown content:', state, 84, 1, undefined, {"content":"Lorem Ipsum\n===============\nLorem ipsum dolor sit amet,\nconsectetur adipiscing elit.","mediaType":"markdown"}); await afterScenario(state); }); test.sequential('Scenario: Scenario with And and But steps', async (context) => { let state = await initRuleScenario(context, 'Scenario with And and But steps', ['@tag', '@multiple_tags', '@rule_tag', '@sequential']); - await qp('an initial state', state, 93); - await qp('some additional context', state, 94); - await qp('an action is performed', state, 95); - await qp('another action is performed', state, 96); - await qp('some assertion is made', state, 97); - await qp('some exception is also handled', state, 98); + await gherkinStep('an initial state', state, 93, 1); + await gherkinStep('some additional context', state, 94, 2); + await gherkinStep('an action is performed', state, 95, 3); + await gherkinStep('another action is performed', state, 96, 4); + await gherkinStep('some assertion is made', state, 97, 5); + await gherkinStep('some exception is also handled', state, 98, 6); await afterScenario(state); }); test.fails('Scenario: Failing scenario example', async (context) => { let state = await initRuleScenario(context, 'Failing scenario example', ['@tag', '@multiple_tags', '@rule_tag', '@fails']); - await qp('a condition that will fail', state, 102); - await qp('an impossible action is attempted', state, 103); - await qp('an unreachable assertion is made', state, 104); + await gherkinStep('a condition that will fail', state, 102, 1); + await gherkinStep('an impossible action is attempted', state, 103, 2); + await gherkinStep('an unreachable assertion is made', state, 104, 3); await afterScenario(state); }); @@ -146,9 +146,65 @@ describe('Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', () => { test('Example: This rule doesn\'t nest', async (context) => { let state = await initRuleScenario(context, 'This rule doesn\'t nest', ['@tag', '@multiple_tags']); - await qp('a Rule statement', state, 110); - await qp('another Rule is indented below it', state, 111); - await qp('the indented Rule is NOT a child of the previous Rule', state, 112); + await gherkinStep('a Rule statement', state, 110, 1); + await gherkinStep('another Rule is indented below it', state, 111, 2); + await gherkinStep('the indented Rule is NOT a child of the previous Rule', state, 112, 3); + await afterScenario(state); + }); + + test('Scenario: Exploded tags make multiple tests', async (context) => { + let state = await initRuleScenario(context, 'Exploded tags make multiple tests', ['@tag', '@multiple_tags', '@1a']); + await gherkinStep('an explodedTags config of [[ \'@1a\',\'@1b\' ], [ \'@2a\',\'@2b\' ]]', state, 116, 1, 1); + await gherkinStep('this Scenario is run', state, 117, 2, 1); + await gherkinStep('it should be split into 2 tests', state, 118, 3, 1); + await afterScenario(state); + }); + + + + test('Scenario: Exploded tags make multiple tests', async (context) => { + let state = await initRuleScenario(context, 'Exploded tags make multiple tests', ['@tag', '@multiple_tags', '@1b']); + await gherkinStep('an explodedTags config of [[ \'@1a\',\'@1b\' ], [ \'@2a\',\'@2b\' ]]', state, 116, 1, 2); + await gherkinStep('this Scenario is run', state, 117, 2, 2); + await gherkinStep('it should be split into 2 tests', state, 118, 3, 2); + await afterScenario(state); + }); + + test('Scenario: More tags make more tests', async (context) => { + let state = await initRuleScenario(context, 'More tags make more tests', ['@tag', '@multiple_tags', '@tag3', '@1a', '@2a']); + await gherkinStep('an explodedTags config of [[ \'@1a\',\'@1b\' ], [ \'@2a\',\'@2b\' ]]', state, 122, 1, 1); + await gherkinStep('this Scenario is run', state, 123, 2, 1); + await gherkinStep('it should be split into 4 tests', state, 124, 3, 1); + await afterScenario(state); + }); + + + + test('Scenario: More tags make more tests', async (context) => { + let state = await initRuleScenario(context, 'More tags make more tests', ['@tag', '@multiple_tags', '@tag3', '@1a', '@2b']); + await gherkinStep('an explodedTags config of [[ \'@1a\',\'@1b\' ], [ \'@2a\',\'@2b\' ]]', state, 122, 1, 2); + await gherkinStep('this Scenario is run', state, 123, 2, 2); + await gherkinStep('it should be split into 4 tests', state, 124, 3, 2); + await afterScenario(state); + }); + + + + test('Scenario: More tags make more tests', async (context) => { + let state = await initRuleScenario(context, 'More tags make more tests', ['@tag', '@multiple_tags', '@tag3', '@1b', '@2a']); + await gherkinStep('an explodedTags config of [[ \'@1a\',\'@1b\' ], [ \'@2a\',\'@2b\' ]]', state, 122, 1, 3); + await gherkinStep('this Scenario is run', state, 123, 2, 3); + await gherkinStep('it should be split into 4 tests', state, 124, 3, 3); + await afterScenario(state); + }); + + + + test('Scenario: More tags make more tests', async (context) => { + let state = await initRuleScenario(context, 'More tags make more tests', ['@tag', '@multiple_tags', '@tag3', '@1b', '@2b']); + await gherkinStep('an explodedTags config of [[ \'@1a\',\'@1b\' ], [ \'@2a\',\'@2b\' ]]', state, 122, 1, 4); + await gherkinStep('this Scenario is run', state, 123, 2, 4); + await gherkinStep('it should be split into 4 tests', state, 124, 3, 4); await afterScenario(state); }); diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts index 040ca7a..4aa2039 100644 --- a/packages/main/src/index.ts +++ b/packages/main/src/index.ts @@ -53,12 +53,14 @@ interface StepDefinitionMatch { parameters: any[]; } -export const qp = async (step: string, state: any, line: number, data?: any): Promise => { +export const gherkinStep = async (step: string, state: any, line: number, stepIdx:number, explodeIdx?:number, data?:any): Promise => { const stepDefinitionMatch: StepDefinitionMatch = findStepDefinitionMatch(step); // Set the state info state.info.step = step state.info.line = line + state.info.stepIdx = stepIdx + state.info.explodedIdx = explodeIdx // Sort out the DataTable or DocString if (Array.isArray(data)) { diff --git a/packages/main/src/render.ts b/packages/main/src/render.ts index 3a8c1e3..948f87d 100644 --- a/packages/main/src/render.ts +++ b/packages/main/src/render.ts @@ -25,7 +25,7 @@ export function renderGherkin(src:string, config:QuickPickleConfig, isMarkdown?: return `// Generated by quickpickle import { test, describe, beforeAll, afterAll } from 'vitest'; import { - qp, + gherkinStep, applyBeforeAllHooks, applyBeforeHooks, applyAfterAllHooks, @@ -95,7 +95,7 @@ function renderChildren(children:RuleChild[]|FeatureChild[], config:QuickPickleC if (!children.length) return output if (children[0].hasOwnProperty('background')) { - output.backgroundSteps = renderSteps(children.shift()!.background!.steps as Step[], config, sp) + output.backgroundSteps = renderSteps(children.shift()!.background!.steps as Step[], config, sp, '', true) } for (let child of children) { @@ -169,10 +169,10 @@ ${sp}test${attrs}.for(${JSON.stringify(paramValues)})( ${sp} '${q(child.scenario?.keyword || '')}: ${describe}', ${sp} async ({ ${paramNames?.join(', ')} }, context) => { ${sp} let state = await ${initFn}(context, \`${name}\`, ['${tags.join("', '") || ''}']); -${child.scenario?.steps.map((step,i) => { +${child.scenario?.steps.map((step,idx) => { let text = step.text.replace(/`/g, '\\`') text = replaceParamNames(text,true) - return `${sp} await qp(\`${text}\`, state, ${step.location.line}${isExploded ? '.' + explodedIdx : ''});` + return `${sp} await gherkinStep(\`${text}\`, state, ${step.location.line}, ${idx+1}${isExploded ? `, ${explodedIdx + 1}` : ''});` }).join('\n') } ${sp} await afterScenario(state); @@ -184,28 +184,29 @@ ${sp}); return ` ${sp}test${attrs}('${q(child.scenario!.keyword)}: ${q(child.scenario!.name)}', async (context) => { ${sp} let state = await ${initFn}(context, '${q(child.scenario!.name)}', ['${tags.join("', '") || ''}']); -${renderSteps(child.scenario!.steps as Step[], config, sp + ' ', isExploded ? `.${explodedIdx}` : '')} +${renderSteps(child.scenario!.steps as Step[], config, sp + ' ', isExploded ? `${explodedIdx+1}` : '')} ${sp} await afterScenario(state); ${sp}}); ` }).join('\n\n') } -function renderSteps(steps:Step[], config:QuickPickleConfig, sp = ' ', explodedText = '') { - return steps.map(step => { +function renderSteps(steps:Step[], config:QuickPickleConfig, sp = ' ', explodedText = '', isBackground:boolean = false) { + let minus = isBackground ? '-' : '' + return steps.map((step,idx) => { if (step.dataTable) { let data = JSON.stringify(step.dataTable.rows.map(r => { return r.cells.map(c => c.value) })) - return `${sp}await qp('${q(step.text)}', state, ${step.location.line}${explodedText}, ${data});` + return `${sp}await gherkinStep('${q(step.text)}', state, ${step.location.line}, ${minus}${idx+1}, ${explodedText || 'undefined'}, ${data});` } else if (step.docString) { let data = JSON.stringify(pick(step.docString, ['content','mediaType'])) - return `${sp}await qp('${q(step.text)}', state, ${step.location.line}${explodedText}, ${data});` + return `${sp}await gherkinStep('${q(step.text)}', state, ${step.location.line}, ${minus}${idx+1}, ${explodedText || 'undefined'}, ${data});` } - return `${sp}await qp('${q(step.text)}', state, ${step.location.line}${explodedText});` + return `${sp}await gherkinStep('${q(step.text)}', state, ${step.location.line}, ${minus}${idx+1}${explodedText ? `, ${explodedText}` : ''});` }).join('\n') } @@ -218,17 +219,20 @@ const q = (t:string) => (t.replace(/'/g, "\\'")) /** * Creates a 2d array of all possible combinations of the items in the input array - * @param arr Array + * @param arr A 2d array of strings * @returns A 2d array of all possible combinations of the items in the input array */ -function explodeArray(arr: string[][]): string[][] { +export function explodeArray(arr: string[][]): string[][] { if (arr.length === 0) return [[]]; + arr = arr.map(subArr => { + return subArr.length ? subArr : [''] + }) const [first, ...rest] = arr; const subCombinations = explodeArray(rest); return first.flatMap(item => - subCombinations.map(subCombo => [item, ...subCombo]) + subCombinations.map(subCombo => [item, ...subCombo].filter(Boolean)) ); } diff --git a/packages/main/src/world.ts b/packages/main/src/world.ts index 05672a3..889e5b2 100644 --- a/packages/main/src/world.ts +++ b/packages/main/src/world.ts @@ -8,6 +8,8 @@ export interface QuickPickleWorldInterface { rule?: string, step?: string, line?: number, + explodedIdx?: number, + stepIdx?: number, } context: TestContext, common: { @@ -33,6 +35,15 @@ export class QuickPickleWorld implements QuickPickleWorldInterface { tagsMatch(tags: string[]) { return tagsMatch(tags, this.info.tags) } + toString() { + let parts = [ + this.constructor.name, + this.info.feature, + this.info.scenario + (this.info.explodedIdx ? ` (${this.info.tags.join(',')})` : ''), + `${this.info.stepIdx?.toString().padStart(2,'0')} ${this.info.step}`, + ] + return parts.join('_') + } } export type WorldConstructor = new ( diff --git a/packages/main/tests/index.test.ts b/packages/main/tests/index.test.ts index 4474701..9f5fe9c 100644 --- a/packages/main/tests/index.test.ts +++ b/packages/main/tests/index.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test, ResolvedConfig } from 'vitest' -import { quickpickle } from '../src/index' +import { explodeTags, quickpickle } from '../src/index' +import { explodeArray } from '../src/render' describe('quickpickle plugin function', async () => { const passedConfig = { @@ -54,9 +55,146 @@ Feature: Exploding Tags let output2 = await plugin.transform(feature2, 'test.feature') test('exploding tags work as expected', () => { expect(output2).toMatch(/test\.concurrent[\s\S]+?test\.concurrent/m) - expect(output2).toMatch(/I run the tests', state, 5.0/m) - expect(output2).toMatch(/I run the tests', state, 5.1/m) + expect(output2).toMatch(/I run the tests', state, 5, 1, 1\)/m) + expect(output2).toMatch(/I run the tests', state, 5, 1, 2\)/m) expect(output2).not.toMatch(/test\.concurrent[\s\S]+?test\.concurrent[/s/S]+?test\.concurrent/m) }) }) + +describe('explodeArray function', () => { + test('explode [1a,1b]', () => { + expect(explodeArray([['@1a','@1b']])).toEqual([ + ['@1a'], + ['@1b'], + ]) + }) + test('explode [1a,1b],[]', () => { + expect(explodeArray([['@1a','@1b'],[]])).toEqual([ + ['@1a'], + ['@1b'], + ]) + }) + test('explodeArray [1a,1b],[2a]', () => { + expect(explodeArray([['@1a','@1b'], ['@2a']])).toEqual([ + ['@1a', '@2a'], + ['@1b', '@2a'], + ]) + }) + test('explodeArray [1a,1b],[2a,2b]', () => { + expect(explodeArray([['@1a','@1b'], ['@2a', '@2b']])).toEqual([ + ['@1a', '@2a'], + ['@1a', '@2b'], + ['@1b', '@2a'], + ['@1b', '@2b'], + ]) + }) + test('explodeArray [1a,1b],[2a],[3a]', () => { + expect(explodeArray([['@1a','@1b'], ['@2a'], ['@3a']])).toEqual([ + ['@1a', '@2a', '@3a'], + ['@1b', '@2a', '@3a'], + ]) + }) + test('explodeArray [1a,1b],[2a],[3a,3b]', () => { + expect(explodeArray([['@1a','@1b'], ['@2a'], ['@3a','@3b']])).toEqual([ + ['@1a', '@2a', '@3a'], + ['@1a', '@2a', '@3b'], + ['@1b', '@2a', '@3a'], + ['@1b', '@2a', '@3b'], + ]) + }) + test('explodeArray [],[2a,2b],[3a,3b]', () => { + expect(explodeArray([[], ['@2a', '@2b'], ['@3a','@3b']])).toEqual([ + ['@2a', '@3a'], + ['@2a', '@3b'], + ['@2b', '@3a'], + ['@2b', '@3b'], + ]) + }) + test('explodeArray [1a,1b],[2a,2b,2c],[3a,3b]', () => { + expect(explodeArray([['@1a','@1b'], ['@2a','@2b','@2c'], ['@3a','@3b']])).toEqual([ + ['@1a', '@2a', '@3a'], + ['@1a', '@2a', '@3b'], + ['@1a', '@2b', '@3a'], + ['@1a', '@2b', '@3b'], + ['@1a', '@2c', '@3a'], + ['@1a', '@2c', '@3b'], + ['@1b', '@2a', '@3a'], + ['@1b', '@2a', '@3b'], + ['@1b', '@2b', '@3a'], + ['@1b', '@2b', '@3b'], + ['@1b', '@2c', '@3a'], + ['@1b', '@2c', '@3b'], + ]) + }) +}) + +describe('explodeTags function', () => { + test('simple nojs setup - explode [nojs,js]', () => { + expect(explodeTags([['@nojs','@js']], ['@nojs', '@js'])).toEqual([ + ['@nojs'], + ['@js'], + ]) + }) + test('strange but valid nojs setup - explode [nojs,js]', () => { + expect(explodeTags([['@nojs','@js'],[]], ['@nojs', '@js'])).toEqual([ + ['@nojs'], + ['@js'], + ]) + }) + describe('common playwright setup', () => { + let explodedTags = [ + ['@nojs', '@js'], + ['@chromium', '@firefox', '@webkit'], + ['@mobile', '@tablet', '@desktop', '@widescreen'], + ] + test('explode [nojs,js]', () => { + expect(explodeTags(explodedTags, ['@nojs', '@js'])).toEqual([ + ['@nojs'], + ['@js'], + ]) + }) + test('explode [concurrent,sequential,nojs,js]', () => { + expect(explodeTags(explodedTags, ['@concurrent','@sequential','@nojs','@js'])).toEqual([ + ['@concurrent','@sequential','@nojs'], + ['@concurrent','@sequential','@js'], + ]) + }) + test('explode [concurrent,nojs,js,sequential]', () => { + expect(explodeTags(explodedTags, ['@concurrent','@nojs','@js','@sequential'])).toEqual([ + ['@concurrent','@sequential','@nojs'], + ['@concurrent','@sequential','@js'], + ]) + }) + test('explode [concurrent,nojs,js,sequential,webkit,mobile,desktop]', () => { + expect(explodeTags(explodedTags, ['@concurrent','@nojs','@js','@sequential','@webkit','@mobile','@desktop'])).toEqual([ + ['@concurrent','@sequential','@nojs','@webkit','@mobile'], + ['@concurrent','@sequential','@nojs','@webkit','@desktop'], + ['@concurrent','@sequential','@js','@webkit','@mobile'], + ['@concurrent','@sequential','@js','@webkit','@desktop'], + ]) + }) + test('explode [nojs,js,othertag]', () => { + expect(explodeTags(explodedTags, ['@nojs','@js','@othertag'])).toEqual([ + ['@othertag','@nojs'], + ['@othertag','@js'], + ]) + }) + test('explode [concurrent,nojs,js,sequential,mobile,tablet,desktop,firefox,chromium,screenshot,show-browser]', () => { + expect(explodeTags(explodedTags, ['@concurrent', '@nojs', '@js', '@sequential', '@mobile', '@tablet', '@desktop', '@firefox', '@chromium', '@screenshot', '@show-browser'])).toEqual([ + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@nojs', '@chromium', '@mobile'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@nojs', '@chromium', '@tablet'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@nojs', '@chromium', '@desktop'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@nojs', '@firefox', '@mobile'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@nojs', '@firefox', '@tablet'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@nojs', '@firefox', '@desktop'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@js', '@chromium', '@mobile'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@js', '@chromium', '@tablet'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@js', '@chromium', '@desktop'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@js', '@firefox', '@mobile'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@js', '@firefox', '@tablet'], + ['@concurrent', '@sequential', '@screenshot', '@show-browser', '@js', '@firefox', '@desktop'], + ]) + }) + }) +}) \ No newline at end of file diff --git a/packages/main/tests/renderer.test.ts b/packages/main/tests/renderer.test.ts index fe52394..5e9da97 100644 --- a/packages/main/tests/renderer.test.ts +++ b/packages/main/tests/renderer.test.ts @@ -7,5 +7,5 @@ const featureFile = fs.readFileSync(__dirname + '/../gherkin-example/example.fea const jsFile = fs.readFileSync(__dirname + '/../gherkin-example/example.feature.js', 'utf8') test('rendering the example feature file', () => { - expect(jsFile).toEqual(renderGherkin(featureFile, {...defaultConfig, failTags: normalizeTags('fail, fails')})) + expect(jsFile).toEqual(renderGherkin(featureFile, {...defaultConfig, failTags: normalizeTags('fail, fails'), explodeTags: [normalizeTags('1a,1b'),normalizeTags('2a,2b')] })) }) \ No newline at end of file diff --git a/packages/main/tests/test.feature b/packages/main/tests/test.feature index 8bd4994..8330a84 100644 --- a/packages/main/tests/test.feature +++ b/packages/main/tests/test.feature @@ -21,6 +21,8 @@ Feature: Basic Test And the property "info.tags" should include "@tag-test" And the property "info.step" should include "FWAH!!! (or really whatever you write here, since it's part of the step)" And the property "info.line" should include "23" + And the typeof "info.explodeIdx" should be "undefined" + And the property "info.stepIdx" should include "9" Rule: DataTables and DocStrings must work like in @cucumber/cucumber Because why re-invent what is pretty good