diff --git a/packages/main/gherkin-example/example.feature b/packages/main/gherkin-example/example.feature index 4d2290a..34c00b2 100644 --- a/packages/main/gherkin-example/example.feature +++ b/packages/main/gherkin-example/example.feature @@ -15,7 +15,7 @@ Feature: QuickPickle's Comprehensive Gherkin Syntax Example Given an initial context' When an action is performed' Then a verifiable outcome is achieved' - + @concurrent Scenario Outline: Parameterized scenario for , '' Given a 'precondition' with When an 'action' is taken with @@ -88,7 +88,7 @@ Feature: QuickPickle's Comprehensive Gherkin Syntax Example Lorem ipsum dolor sit amet, consectetur adipiscing elit. """ - + @sequential Scenario: Scenario with And and But steps Given an initial state And some additional context @@ -97,7 +97,7 @@ Feature: QuickPickle's Comprehensive Gherkin Syntax Example Then some assertion is made But some exception is also handled - @failing + @fails Scenario: Failing scenario example Given a condition that will fail When an impossible action is attempted diff --git a/packages/main/gherkin-example/example.feature.js b/packages/main/gherkin-example/example.feature.js index 508333a..1c58fde 100644 --- a/packages/main/gherkin-example/example.feature.js +++ b/packages/main/gherkin-example/example.feature.js @@ -49,10 +49,10 @@ describe('Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', () => { await afterScenario(state); }); - test.for([{"parameter":"value1'","another_parameter":"value2'","expected_result":"result1'"},{"parameter":"value3`","another_parameter":"value4`","expected_result":"result2`"}])( + test.concurrent.for([{"parameter":"value1'","another_parameter":"value2'","expected_result":"result1'"},{"parameter":"value3`","another_parameter":"value4`","expected_result":"result2`"}])( 'Scenario Outline: Parameterized scenario for $parameter, \'$another_parameter\'', async ({ parameter, another_parameter, expected_result }) => { - let state = await initScenario(`Parameterized scenario for ${parameter}, '${another_parameter}'`, ['@tag', '@multiple_tags']); + let state = await initScenario(`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); @@ -99,7 +99,7 @@ describe('Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', () => { await afterScenario(state); }); - test('Scenario: Scenario with doc string', async () => { + test.todo.skip('Scenario: Scenario with doc string', async () => { let state = await initRuleScenario('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); @@ -113,8 +113,8 @@ describe('Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', () => { await afterScenario(state); }); - test('Scenario: Scenario with And and But steps', async () => { - let state = await initRuleScenario('Scenario with And and But steps', ['@tag', '@multiple_tags', '@rule_tag']); + test.sequential('Scenario: Scenario with And and But steps', async () => { + let state = await initRuleScenario('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); @@ -124,8 +124,8 @@ describe('Feature: QuickPickle\'s Comprehensive Gherkin Syntax Example', () => { await afterScenario(state); }); - test('Scenario: Failing scenario example', async () => { - let state = await initRuleScenario('Failing scenario example', ['@tag', '@multiple_tags', '@rule_tag', '@failing']); + test.fails('Scenario: Failing scenario example', async () => { + let state = await initRuleScenario('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); diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts index 46cf641..52450d9 100644 --- a/packages/main/src/index.ts +++ b/packages/main/src/index.ts @@ -80,9 +80,14 @@ export const qp = async (step: string, state: any, line: number, data?: any): Pr export type QuickPickleConfig = { import: string|string[] + todoTags: string|string[] + skipTags: string|string[] + failTags: string|string[] + concurrentTags: string|string[] + sequentialTags: string|string[] }; -const defaultConfig: QuickPickleConfig = { +export const defaultConfig: QuickPickleConfig = { /** * @deprecated -- use the Vitest config test.setupFiles insetad @@ -93,7 +98,32 @@ const defaultConfig: QuickPickleConfig = { import: [ '{features,test,tests}/**/*.steps.{ts,js,mjs}', '{features,test,tests}/**/*.world.{ts,js,mjs}' - ] + ], + + /** + * Tags to mark as todo, using Vitest's `test.todo` implementation. + */ + todoTags: ['@todo','@wip'], + + /** + * Tags to skip, using Vitest's `test.skip` implementation. + */ + skipTags: ['@skip'], + + /** + * Tags to mark as failing, using Vitest's `test.failing` implementation. + */ + failTags: ['@fails'], + + /** + * Tags to run in parallel, using Vitest's `test.concurrent` implementation. + */ + concurrentTags: ['@concurrent'], + + /** + * Tags to run sequentially, using Vitest's `test.sequential` implementation. + */ + sequentialTags: ['@sequential'], } @@ -103,6 +133,11 @@ interface ResolvedConfig { }; } +function normalizeTags(tags:string|string[]):string[] { + tags = Array.isArray(tags) ? tags : [tags] + return tags.filter(Boolean).map(tag => tag.startsWith('@') ? tag : `@${tag}`) +} + export const quickpickle = function() { let config: QuickPickleConfig; @@ -113,6 +148,8 @@ export const quickpickle = function() { defaultConfig, get(resolvedConfig, 'test.quickpickle') ) as QuickPickleConfig; + config.skipTags = normalizeTags(config.skipTags) + config.failTags = normalizeTags(config.failTags) }, transform: async (src: string, id: string): Promise => { if (featureRegex.test(id)) { diff --git a/packages/main/src/render.ts b/packages/main/src/render.ts index 9e64759..9634d62 100644 --- a/packages/main/src/render.ts +++ b/packages/main/src/render.ts @@ -3,7 +3,7 @@ import type { QuickPickleConfig } from '.' import * as Gherkin from '@cucumber/gherkin'; import * as Messages from '@cucumber/messages'; -import { fromPairs, pick } from "lodash-es"; +import { fromPairs, intersection, pick } from "lodash-es"; import fg from 'fast-glob' import path from 'path' @@ -140,6 +140,13 @@ function renderScenario(child:FeatureChild, config:QuickPickleConfig, tags:strin let initFn = sp.length > 2 ? 'initRuleScenario' : 'initScenario' tags = [...tags, ...child.scenario!.tags.map(t => t.name)] + let todo = (intersection(config.todoTags, tags).length > 0) ? '.todo' : '' + let skip = (intersection(config.skipTags, tags).length > 0) ? '.skip' : '' + let fails = (intersection(config.failTags, tags).length > 0) ? '.fails' : '' + let concurrent = (intersection(config.concurrentTags, tags).length > 0) ? '.concurrent' : '' + let sequential = (intersection(config.sequentialTags, tags).length > 0) ? '.sequential' : '' + let attrs = todo + skip + fails + concurrent + sequential + // For Scenario Outlines with examples if (child.scenario!.examples?.[0]?.tableHeader && child.scenario!.examples?.[0]?.tableBody) { @@ -159,7 +166,7 @@ function renderScenario(child:FeatureChild, config:QuickPickleConfig, tags:strin let name = replaceParamNames(child.scenario?.name ?? '', true).replace(/`/g, '\`') return ` -${sp}test.for(${JSON.stringify(paramValues)})( +${sp}test${attrs}.for(${JSON.stringify(paramValues)})( ${sp} '${q(child.scenario?.keyword || '')}: ${describe}', ${sp} async ({ ${paramNames?.join(', ')} }) => { ${sp} let state = await ${initFn}(\`${name}\`, ['${tags.join("', '") || ''}']); @@ -176,7 +183,7 @@ ${sp}); } return ` -${sp}test('${q(child.scenario!.keyword)}: ${q(child.scenario!.name)}', async () => { +${sp}test${attrs}('${q(child.scenario!.keyword)}: ${q(child.scenario!.name)}', async () => { ${sp} let state = await ${initFn}('${q(child.scenario!.name)}', ['${tags.join("', '") || ''}']); ${renderSteps(child.scenario!.steps as Step[], config, sp + ' ')} ${sp} await afterScenario(state); diff --git a/packages/main/tests/renderer.test.ts b/packages/main/tests/renderer.test.ts index 1088a34..acee3d7 100644 --- a/packages/main/tests/renderer.test.ts +++ b/packages/main/tests/renderer.test.ts @@ -1,10 +1,11 @@ import { expect, test } from 'vitest' import { renderGherkin } from '../src/render' +import { defaultConfig } from '../src' import fs from 'node:fs' const featureFile = fs.readFileSync(__dirname + '/../gherkin-example/example.feature', 'utf8') const jsFile = fs.readFileSync(__dirname + '/../gherkin-example/example.feature.js', 'utf8') test('rendering the example feature file', () => { - expect(jsFile).toEqual(renderGherkin(featureFile, {})) + expect(jsFile).toEqual(renderGherkin(featureFile, defaultConfig)) }) \ No newline at end of file diff --git a/packages/main/tests/test.feature b/packages/main/tests/test.feature index ed1a12b..8bd4994 100644 --- a/packages/main/tests/test.feature +++ b/packages/main/tests/test.feature @@ -65,4 +65,19 @@ Feature: Basic Test | 3 | 5 | 8 | | 5 | 8 | 13 | | 8 | 13 | 21 | - | 13 | 21 | 34 | \ No newline at end of file + | 13 | 21 | 34 | + + Rule: Vitest "todo", "skip", "fails" should work out of the box + + @todo + Example: I haven't written this test yet + + @skip + Example: This is a skipped test + Given I run the tests + Then the tests should pass + + @fails + Example: This is a failing test + Given I run the tests + Then the tests should fail diff --git a/packages/main/tests/tests.steps.ts b/packages/main/tests/tests.steps.ts index 1fdfdc3..c2b34c4 100644 --- a/packages/main/tests/tests.steps.ts +++ b/packages/main/tests/tests.steps.ts @@ -10,7 +10,11 @@ Then("the tests should pass", () => { expect(true).to.be.true; }) -Given('I have a number {int}', (world, int) => { +Then("the tests should fail", () => { + expect(true).to.be.false; +}) + +Given('(I have )a number {int}', (world, int) => { if (!world.numbers) world.numbers = [int] else world.numbers.push(int) })