UNPKG

@amiceli/vitest-cucumber

Version:

vitest tools to use Gherkin feature in unit tests

2,035 lines (2,013 loc) 125 kB
// src/vitest/configuration.ts function getDefaultConfiguration() { return { language: "en", includeTags: [], excludeTags: ["ignore"], onStepError: ({ error }) => { } }; } var globalConfiguration = {}; var getVitestCucumberConfiguration = (options) => { const defaultConfiguration = getDefaultConfiguration(); if (typeof window !== "undefined") { defaultConfiguration.includeTags?.push( ...import.meta.env.VITEST_INCLUDE_TAGS?.split(" ") || [] ); defaultConfiguration.excludeTags?.push( ...import.meta.env.VITEST_EXCLUDE_TAGS?.split(" ") || [] ); } else { defaultConfiguration.includeTags?.push( ...process.env.VITEST_INCLUDE_TAGS?.split(" ") || [] ); defaultConfiguration.excludeTags?.push( ...process.env.VITEST_EXCLUDE_TAGS?.split(" ") || [] ); } const mergedOptions = { ...defaultConfiguration, ...globalConfiguration, ...options || {} }; return mergedOptions; }; var setVitestCucumberConfiguration = (options) => { globalConfiguration = options; }; // src/vitest/describe-feature.ts import { afterAll as afterAll3, beforeAll as beforeAll3 } from "vitest"; // src/vitest/export-vitest.ts var describeFn; var testFn; var gThis = globalThis; if (typeof gThis.describe !== "undefined" && typeof globalThis.test !== "undefined") { describeFn = gThis.describe; testFn = gThis.test; } else { const vitest = await import("vitest"); describeFn = vitest.describe; testFn = vitest.test; } // src/vitest/describe/describeBackground.ts import { onTestFailed, test } from "vitest"; // src/errors/errors.ts var VitestsCucumberError = class extends Error { constructor(message, name) { super(message); this.stack = ``; this.name = name || this.constructor.name; } }; var NotScenarioOutlineError = class extends VitestsCucumberError { constructor(scenario) { super(`${scenario.getTitle()} is not a ScenarioOutline`); } }; var IsScenarioOutlineError = class extends VitestsCucumberError { constructor(scenario) { super(`${scenario.getTitle()} is a ScenarioOutline`); } }; var BackgroundNotCalledError = class extends VitestsCucumberError { constructor(background) { super(`${background.getTitle()} was not called`); } }; var ScenarioNotCalledError = class extends VitestsCucumberError { constructor(scenario) { super(`${scenario.getTitle()} was not called`); } }; var ScenarioOutlineVariableNotCalledInStepsError = class extends VitestsCucumberError { constructor(scenario, variableName) { super( `${scenario.getTitle()} ${variableName} was not called in steps` ); } }; var ScenarioOulineWithoutExamplesError = class extends VitestsCucumberError { constructor(scenario) { super(`${scenario.getTitle()} has an empty Examples`); } }; var ScenarioOutlineVariablesDeclaredWithoutExamplesError = class extends VitestsCucumberError { constructor(scenario) { super(`${scenario.getTitle()} variables declared without Examples`); } }; var MissingScenarioOutlineVariableValueError = class extends VitestsCucumberError { constructor(scenario, variableName) { super( `${scenario.getTitle()} missing ${variableName} value in Examples` ); } }; var FeatureUknowScenarioError = class extends VitestsCucumberError { constructor(feature, scenario) { super( `${scenario.getTitle()} does not exist in ${feature.getTitle()}` ); } }; var StepAbleUnknowStepError = class extends VitestsCucumberError { constructor(stepable, step) { super( `${stepable.getTitle()} ${step.type} ${step.details} does not exist` ); } }; var StepAbleStepExpressionError = class extends VitestsCucumberError { constructor(stepable, step) { super( [ `No step match with this expression`, ` ${stepable.getTitle()}`, ` ${step.getTitle()} \u274C` ].join(` `) ); } }; var StepAbleStepsNotCalledError = class extends VitestsCucumberError { constructor(stepable, step) { super( [ ``, ` ${stepable.getTitle()}`, ` ${step.getTitle()} \u274C` ].join(` `), `Missing steps in Scenario` ); } }; var RuleNotCalledError = class extends VitestsCucumberError { constructor(rule) { super(`${rule.getTitle()} was not called`); } }; var FeatureUknowRuleError = class extends VitestsCucumberError { constructor(feature, rule) { super( `${rule.getTitle()} does not exist in Feature: ${feature.name}` ); } }; var FeatureFileNotFoundError = class extends VitestsCucumberError { constructor(path) { super(`feature file ${path} does not exist`); } }; var NotAllowedBackgroundStepTypeError = class extends VitestsCucumberError { constructor(type) { super(`${type} step is not allowed in Background`); } }; var TwiceBackgroundError = class extends VitestsCucumberError { constructor() { super(`A background already exist`); } }; var BackgroundNotExistsError = class extends VitestsCucumberError { constructor(parent) { super(`${parent.getTitle()} has no background`); } }; var OnlyOneFeatureError = class extends VitestsCucumberError { constructor() { super(`Gherkin rule: only one Feature per file`); } }; var StepExpressionMatchError = class extends VitestsCucumberError { constructor(step, expression) { super(`${expression} no match with ${step.details}`); } }; var MissingFeature = class extends VitestsCucumberError { constructor(line) { super( [ `Missing Feature before add Scenario, Rule or Background`, ` ${line.trim()} \u274C` ].join("\n") ); } }; var MissingSteppableError = class extends VitestsCucumberError { constructor(line) { super( [ `Missing Scenario, ScenarioOutline or Background before add step`, ` ${line.trim()} \u274C` ].join("\n") ); } }; var MissingScnearioOutlineError = class extends VitestsCucumberError { constructor(line) { super( [ `Missing ScenarioOutline before add Examples`, ` ${line.trim()} \u274C` ].join("\n") ); } }; var MissingExamplesError = class extends VitestsCucumberError { constructor(line) { super( [`Missing Examples before add value`, ` ${line.trim()} \u274C`].join( "\n" ) ); } }; var SpokenKeywordError = class extends VitestsCucumberError { constructor(line, keywords) { super( [ `No keywords match for: ${line}`, ` Available keywords : ${keywords.join(", ")}` ].join("\n") ); } }; var ParentWithoutScenario = class extends VitestsCucumberError { constructor(feature) { super(`${feature.getTitle()} must have at least one scenario`); } }; var InvalidUrlParameterError = class extends VitestsCucumberError { constructor(arg) { super(`String '${arg}' was not recognized as a valid Url`); } }; var InvalidDateParameterError = class extends VitestsCucumberError { constructor(arg) { super(`String '${arg}' was not recognized as a valid Date`); } }; var InvalidCurrencyParameterError = class extends VitestsCucumberError { constructor(arg) { super(`String '${arg}' was not recognized as a valid currency`); } }; var BuiltinParameterExpressionAlreadyExistsError = class extends VitestsCucumberError { constructor(expressionName) { super(`You cannot redefine the built-in expression '${expressionName}'`); } }; var CustomParameterExpressionAlreadyExistsError = class extends VitestsCucumberError { constructor(expressionName) { super(`The custom expression '${expressionName}' already exists`); } }; var ItemAlreadyExistsError = class extends VitestsCucumberError { constructor(parent, child) { super(`${parent.getTitle()} already has ${child.getTitle()}`); } }; var RequiredTitleError = class extends VitestsCucumberError { constructor(line, keyword) { super([`${line} \u274C`, ` ${keyword} required a title`].join("\n")); } }; // src/parser/expression/regexes.ts import parsecurrency from "parsecurrency"; // src/parser/expression/symbolToCode.json var symbolToCode_default = { $: "USD", $CAD: "CAD", $U: "UYU", A$: "AUD", AED: "AED", ALL: "ALL", AMD: "AMD", AR$: "ARS", AU$: "AUD", Af: "AFN", "B/.": "PAB", BD: "BHD", BGN: "BGN", BN$: "BND", BWP: "BWP", BZ$: "BZD", Bs: "BOB", "Bs.F.": "VEF", C$: "NIO", CA$: "CAD", CDF: "CDF", CF: "KMF", CFA: "XOF", CFPF: "XPF", CHF: "CHF", CL$: "CLP", "CN\xA5": "CNY", CO$: "COP", CV$: "CVE", DA: "DZD", DT: "TND", Dkr: "DKK", EGP: "EGP", Ekr: "EEK", FBu: "BIF", FCFA: "XAF", FG: "GNF", FJ$: "FJD", Fdj: "DJF", Ft: "HUF", GEL: "GEL", "GH\u20B5": "GHS", GTQ: "GTQ", HK$: "HKD", HNL: "HNL", IQD: "IQD", IRR: "IRR", Ikr: "ISK", J$: "JMD", JD: "JOD", "JP\xA5": "JPY", KD: "KWD", KHR: "KHR", KM: "BAM", KZT: "KZT", Ksh: "KES", K\u010D: "CZK", L: "SZL", "LB\xA3": "LBP", LD: "LYD", Ls: "LVL", Lt: "LTL", MAD: "MAD", MDL: "MDL", MGA: "MGA", MKD: "MKD", MMK: "MMK", MOP$: "MOP", MTn: "MZN", MURs: "MUR", MX$: "MXN", N$: "NAD", NPRs: "NPR", NT$: "TWD", NZ$: "NZD", Nfk: "ERN", Nkr: "NOK", OMR: "OMR", PKRs: "PKR", QR: "QAR", R: "ZAR", R$: "BRL", RD$: "DOP", RM: "MYR", RON: "RON", RUB: "RUB", RWF: "RWF", Rp: "IDR", Rs: "INR", S$: "SGD", "S/.": "PEN", SDG: "SDG", SLRs: "LKR", SR: "SAR", "SY\xA3": "SYP", Skr: "SEK", Ssh: "SOS", T$: "TOP", TL: "TRY", TSh: "TZS", TT$: "TTD", Tk: "BDT", US$: "USD", USh: "UGX", UZS: "UZS", VT: "VUV", YR: "YER", ZK: "ZMW", ZWL$: "ZWL", "din.": "RSD", kn: "HRK", "man.": "AZN", z\u0142: "PLN", "\xA3": "GBP", "\xA5": "JPY", "\u0E3F": "THB", "\u20A1": "CRC", "\u20A3": "XPF", "\u20A6": "NGN", "\u20A9": "KRW", "\u20AA": "ILS", "\u20AB": "VND", "\u20AC": "EUR", "\u20B1": "PHP", "\u20B2": "PYG", "\u20B4": "UAH", "\u20B9": "INR" }; // src/parser/expression/regexes.ts var ExpressionRegex = class { keyword; keywordRegex; groupName; constructor(options) { this.keyword = options.keyword; this.keywordRegex = options.keywordRegex; this.groupName = options.groupName; } matchGroupName(str) { return str.startsWith(this.groupName); } get cloneKeywordRegex() { return new RegExp(this.keywordRegex.source, this.keywordRegex.flags); } resetExpressionStates() { } }; var BooleanRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{boolean}`, groupName: `boolean`, keywordRegex: /{boolean}/g }); } getRegex(index) { return `\\b(?<boolean${index}>(true|false))\\b`; } getValue(str) { return str === "true"; } }; var WordRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{word}`, groupName: `word`, keywordRegex: /{word}/g }); } getRegex(index) { return `\\b(?<word${index}>\\w+)\\b`; } getValue(str) { return str; } }; var CharRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{char}`, groupName: `char`, keywordRegex: /{char}/g }); } getRegex(index) { return `(?<char${index}>\\w)`; } getValue(str) { return str; } }; var StringRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{string}`, groupName: `string`, keywordRegex: /{string}/g }); } getRegex(index) { return `(?<string${index}>"[^"]*"|'[^']*')`; } getValue(str) { return str.replace(/^["']|["']$/g, ``); } }; var EmailRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{email}`, groupName: `email`, keywordRegex: /{email}/g }); } getRegex(index) { const emailRegex = `[^\\s@]+@[^\\s@]+\\.[^\\s@]+`; return `\\b(?<email${index}>${emailRegex})\\b`; } getValue(str) { return str.replace(/^["']|["']$/g, ``); } }; var UrlRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{url}`, groupName: `url`, keywordRegex: /{url}/g }); } getRegex(index) { const urlRegex = `(https?://)?([^\\s$.?#].[^\\s]*)`; return `\\b(?<url${index}>${urlRegex})\\b`; } getValue(str) { try { return new URL(str); } catch { throw new InvalidUrlParameterError(str); } } }; var IntRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{int}`, groupName: `int`, keywordRegex: /{int}/g }); } getRegex(index) { return `(?<int${index}>-?\\d+)`; } getValue(str) { return Number(str); } }; var NumberRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{number}`, groupName: `number`, keywordRegex: /{number}/g }); } getRegex(index) { return `(?<number${index}>-?\\d+(\\.\\d+)?)`; } getValue(str) { return Number(str); } }; var DateRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{date}`, groupName: `date`, keywordRegex: /{date}/g }); } getRegex(index) { const dateRegex = `[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}(?: [0-9]{2}:[0-9]{2}:[0-9]{2})?`; const isoDateRegex = `(?:\\d{4})-(?:\\d{2})-(?:\\d{2})`; const isoDatetimeRegex = `(?:\\d{4})-(?:\\d{2})-(?:\\d{2})T(?:\\d{2}):(?:\\d{2}):(?:\\d{2}(?:\\.\\d*)?)(?:(?:[-|+](?:\\d{2}):(?:\\d{2})|Z)?)`; const shortMonths = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; const longMonths = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; const shortOrLongMonthsRegex = shortMonths.concat(longMonths).join("|"); const shortOrLongDateRegex = `\\d{1,2},? (:?${shortOrLongMonthsRegex}),? \\d{4}`; const altShortOrLongDateRegex = `(:?${shortOrLongMonthsRegex}),? \\d{1,2},? \\d{4}`; return `\\b(?<date${index}>(${dateRegex})|(${isoDateRegex})|(${isoDatetimeRegex})|(${shortOrLongDateRegex})|(${altShortOrLongDateRegex}))\\b`; } getValue(str) { const value = new Date(str); if (Number.isNaN(value.getTime())) { throw new InvalidDateParameterError(str); } return value; } }; var CurrencyRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{currency}`, groupName: `currency`, keywordRegex: /{currency}/g }); } getRegex(index) { const currencyRegex = `(?:([-+]{1}) ?)?(?:([A-Z]{3}) ?)?(?:([^\\d ]+?) ?)?(((?:\\d{1,3}([,. \u2019'\\u00A0\\u202F]))*?\\d{1,})(([,.])\\d{1,2})?)(?: ?([^\\d]+?))??(?: ?([A-Z]{3}))?`; return `(?<!\\S)(?<currency${index}>(${currencyRegex}))(?!\\S)`; } getValue(str) { const value = parsecurrency(str); if (!value) { throw new InvalidCurrencyParameterError(str); } return { raw: value.raw, value: value.value, currency: value.currency || symbolToCode_default[value.symbol] }; } }; var ListRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{list}`, groupName: `list`, keywordRegex: /{list(?::(["'])([^"']?)\1)?\}/g }); } regexMatchSeparators = []; resetExpressionStates() { this.regexMatchSeparators = []; } getRegex(index, originalRegex) { const separator = this.cloneKeywordRegex.exec(originalRegex); const s = separator?.at(2) || ","; this.regexMatchSeparators.push(s); return `(?<list${index}>[a-zA-Z]+(?:${s} ?[a-zA-Z]+)*)`; } getValue(str, index) { return str.split(this.regexMatchSeparators[index]).map((t) => t.trim()); } }; var AnyRegex = class extends ExpressionRegex { constructor() { super({ keyword: `{any}`, groupName: `any`, keywordRegex: /{any}/g }); } getRegex(index) { return `(?<any${index}>.+)`; } getValue(str) { return str; } }; // src/parser/expression/custom.ts var CustomExpressionRegex = class extends ExpressionRegex { constructor(args) { super({ keyword: `{${args.name}}`, groupName: args.name, keywordRegex: new RegExp(`{${args.name}}`, `g`) }); this.args = args; } getRegex(index) { return `(?<${this.groupName}${index}>${this.args.regexp.source})`; } getValue(str) { return this.args.transformer(str); } }; var customExpressionRegEx = []; var defineParameterExpression = (args) => { if (customExpressionRegEx.some((r) => r.groupName === args.name)) { throw new CustomParameterExpressionAlreadyExistsError(args.name); } if (builtInExpressionRegEx.some((r) => r.groupName === args.name)) { throw new BuiltinParameterExpressionAlreadyExistsError(args.name); } customExpressionRegEx.push(new CustomExpressionRegex(args)); }; // src/parser/expression/ExpressionStep.ts var builtInExpressionRegEx = [ new BooleanRegex(), new WordRegex(), new CharRegex(), new StringRegex(), new EmailRegex(), new UrlRegex(), new IntRegex(), new NumberRegex(), new DateRegex(), new CurrencyRegex(), new ListRegex(), new AnyRegex() ]; var ExpressionStep = class { // biome-ignore lint/suspicious/noExplicitAny: <explanation> static matchStep(step, stepExpression) { const allExpressionRegEx = builtInExpressionRegEx.concat( customExpressionRegEx ); let regexString = stepExpression; const groupCount = {}; regexString = regexString.replace(/[?]/g, `\\$&`); for (const r of allExpressionRegEx) { r.resetExpressionStates(); groupCount[r.groupName] = 0; } for (const r of allExpressionRegEx) { regexString = regexString.replace( r.keywordRegex, (originalRegex) => { groupCount[r.groupName] += 1; return r.getRegex(groupCount[r.groupName], originalRegex); } ); } regexString = `^${regexString}$`; const regex = new RegExp(regexString, `g`); const matches = [...step.details.matchAll(regex)]; const result = matches.map((match) => { const res = []; if (!match.groups) { return; } Object.keys(match.groups).forEach((key, index) => { const matchRegex = allExpressionRegEx.find( (r) => r.matchGroupName(key) ); if (matchRegex) { res.push({ index, // biome-ignore lint/style/noNonNullAssertion: <explanation> value: matchRegex.getValue(match.groups[key], index) }); } else { res.push({ index, // biome-ignore lint/style/noNonNullAssertion: <explanation> value: new StringRegex().getValue(match.groups[key]) }); } }); return res; }); const allValues = result.flat().filter((t) => t !== void 0).map((r) => r.value); const hasRegex = allExpressionRegEx.some((r) => { return stepExpression.includes(r.keyword); }); if (hasRegex && allValues.length === 0) { throw new StepExpressionMatchError(step, stepExpression); } return allValues; } static stepContainsRegex(expression) { const allExpressionRegEx = builtInExpressionRegEx.concat( customExpressionRegEx ); return allExpressionRegEx.some((r) => { return expression.includes(r.keyword); }); } }; // src/vitest/describe/describeBackground.ts function createBackgroundDescribeHandler({ background, backgroundCallback }) { const backgroundStepsToRun = []; const config = getVitestCucumberConfiguration(); const createScenarioStepCallback = (stepType) => { return (stepDetails, scenarioStepCallback) => { const foundStep = background.checkIfStepExists( stepType, stepDetails ); const params = ExpressionStep.matchStep( foundStep, stepDetails ); foundStep.isCalled = true; backgroundStepsToRun.push({ key: foundStep.getTitle(), fn: scenarioStepCallback, step: foundStep, params: [ ...params, foundStep.dataTables.length > 0 ? foundStep.dataTables : null, foundStep.docStrings ].filter((p) => p !== null) }); }; }; const scenarioStepsCallback = { Given: createScenarioStepCallback(`Given`), And: createScenarioStepCallback(`And`) }; backgroundCallback(scenarioStepsCallback); background.checkIfStepWasCalled(); return function backgroundDescribe() { test.for( backgroundStepsToRun.map((s) => { return [s.key, s]; }) )(`%s`, async ([, scenarioStep], ctx) => { onTestFailed((e) => { const message = e.errors?.at(0)?.message; config.onStepError({ error: new Error( message || `${scenarioStep.step.details} failed` ), ctx, step: scenarioStep.step }); }); await scenarioStep.fn(ctx, ...scenarioStep.params); }); }; } // src/vitest/describe/describeScenario.ts import { afterAll, beforeAll, onTestFailed as onTestFailed2, test as test2 } from "vitest"; function createScenarioDescribeHandler({ scenario, scenarioTestCallback, afterEachScenarioHook, beforeEachScenarioHook }) { const scenarioStepsToRun = []; const config = getVitestCucumberConfiguration(); const createScenarioStepCallback = (stepType) => { return (stepDetails, scenarioStepCallback) => { const foundStep = scenario.checkIfStepExists(stepType, stepDetails); const params = ExpressionStep.matchStep( foundStep, stepDetails ); foundStep.isCalled = true; scenarioStepsToRun.push({ key: foundStep.getTitle(), fn: scenarioStepCallback, step: foundStep, params: [ ...params, foundStep.dataTables.length > 0 ? foundStep.dataTables : null, foundStep.docStrings ].filter((p) => p !== null) }); }; }; const scenarioStepsCallback = { Given: createScenarioStepCallback(`Given`), When: createScenarioStepCallback(`When`), And: createScenarioStepCallback(`And`), Then: createScenarioStepCallback(`Then`), But: createScenarioStepCallback(`But`) }; scenarioTestCallback(scenarioStepsCallback); scenario.checkIfStepWasCalled(); return function scenarioDescribe() { beforeAll(async () => { await beforeEachScenarioHook(); }); afterAll(async () => { await afterEachScenarioHook(); }); test2.for( scenarioStepsToRun.map((s) => { return [s.key, s]; }) )(`%s`, async ([, scenarioStep], ctx) => { onTestFailed2((e) => { const message = e.errors?.at(0)?.message; config.onStepError({ error: new Error( message || `${scenarioStep.step.details} failed` ), ctx, step: scenarioStep.step }); }); await scenarioStep.fn(ctx, ...scenarioStep.params); }); }; } // src/vitest/describe/describeScenarioOutline.ts import { afterAll as afterAll2, beforeAll as beforeAll2, onTestFailed as onTestFailed3, test as test3 } from "vitest"; function createScenarioOutlineDescribeHandler({ scenario, scenarioTestCallback, afterEachScenarioHook, beforeEachScenarioHook }) { let scenarioStepsToRun = []; const config = getVitestCucumberConfiguration(); const createScenarioStepCallback = (stepType) => { return (stepDetails, scenarioStepCallback) => { const foundStep = scenario.checkIfStepExists(stepType, stepDetails); const params = ExpressionStep.matchStep( foundStep, stepDetails ); foundStep.isCalled = true; scenarioStepsToRun.push({ key: foundStep.getTitle(), fn: scenarioStepCallback, step: foundStep, params: [ ...params, foundStep.dataTables.length > 0 ? foundStep.dataTables : null, foundStep.docStrings ].filter((p) => p !== null) }); }; }; const scenarioStepsCallback = { Given: createScenarioStepCallback(`Given`), When: createScenarioStepCallback(`When`), And: createScenarioStepCallback(`And`), Then: createScenarioStepCallback(`Then`), But: createScenarioStepCallback(`But`) }; const example = scenario.examples; if (example) { return example?.map((exampleVariables) => { scenarioStepsToRun = []; scenarioTestCallback(scenarioStepsCallback, exampleVariables); scenario.checkIfStepWasCalled(); return /* @__PURE__ */ ((steps) => function scenarioOutlineDescribe() { beforeAll2(async () => { await beforeEachScenarioHook(); }); afterAll2(async () => { await afterEachScenarioHook(); }); test3.for( steps.map((s) => { return [ scenario.getStepTitle(s.step, exampleVariables), s ]; }) )(`%s`, async ([, scenarioStep], ctx) => { if (scenarioStep.step.docStrings) { scenarioStep.params[scenarioStep.params.length - 1] = scenario.getStepDocStrings( scenarioStep.step, exampleVariables ); } onTestFailed3((e) => { const message = e.errors?.at(0)?.message; config.onStepError({ error: new Error( message || `${scenarioStep.step.details} failed` ), ctx, step: scenarioStep.step }); }); await scenarioStep.fn(ctx, ...scenarioStep.params); }); })([...scenarioStepsToRun]); }); } return []; } // src/parser/models/Taggable.ts var matchFilter = (filterItem, tags) => { if (Array.isArray(filterItem)) { return filterItem.every((item) => tags.has(item)); } return tags.has(filterItem); }; var Taggable = class { tags = /* @__PURE__ */ new Set(); /** * Simple matching filter mostly following the cucumber expression tag rules, * e.g. `[["alpha", "beta"], "vitests", "another"]` * will be equivalent to: * (`@alpha` and `@beta`) or `@vitests` or `@another` */ matchTags(filterItems) { return filterItems.some( (filterItem) => matchFilter(filterItem, this.tags) ); } shouldBeCalled(options) { return (options.includeTags.length <= 0 || this.matchTags(options.includeTags) === true) && this.matchTags(options.excludeTags) === false; } }; // src/parser/models/step.ts var Step = class { type; details; docStrings = null; isCalled; dataTables = []; title; constructor(type, details, title) { this.details = details; this.type = type; this.isCalled = false; this.title = title || `${type}`; } getTitle() { return `${this.title} ${this.details}`; } setDocStrings(docStrings) { this.docStrings = docStrings; } }; // src/parser/models/Stepable.ts var StepAble = class extends Taggable { isCalled; _steps; title; constructor(title) { super(); this.title = title; this.isCalled = false; this._steps = []; } stepFailedExpressionMatch = {}; findStepByTypeAndDetails(type, details) { this.stepFailedExpressionMatch[details] = 0; return this._steps.find((step) => { try { const sameType = step.type === type; const sameDetails = step.details === details; if (ExpressionStep.stepContainsRegex(details)) { const params = ExpressionStep.matchStep(step, details); return sameType && (sameDetails || params.length >= 0); } return sameType && sameDetails; } catch (e) { this.stepFailedExpressionMatch[details] += 1; return false; } }); } hasUnCalledSteps() { return this.getNoCalledStep() !== void 0; } getNoCalledStep() { return this._steps.find((s) => s.isCalled === false); } addStep(newStep) { const duplicatedStep = this._steps.find((step) => { return step.getTitle() === newStep.getTitle(); }); if (duplicatedStep) { throw new ItemAlreadyExistsError(this, newStep); } this._steps.push(newStep); } checkIfStepWasCalled() { const step = this.getNoCalledStep(); if (step) { throw new StepAbleStepsNotCalledError(this, step); } } checkIfStepExists(stepType, stepDetails) { const foundStep = this.findStepByTypeAndDetails(stepType, stepDetails); if (!foundStep) { if (this.stepFailedExpressionMatch[stepDetails] === this._steps.length) { throw new StepAbleStepExpressionError( this, new Step(stepType, stepDetails) ); } throw new StepAbleUnknowStepError( this, new Step(stepType, stepDetails) ); } return foundStep; } get lastStep() { return this._steps[this._steps.length - 1]; } get steps() { return this._steps; } }; // src/parser/models/scenario.ts var Scenario = class extends StepAble { description; constructor(description, title = "Scenario") { super(title); this.description = description; } getTitle() { return `${this.title}: ${this.description}`; } }; var ScenarioOutline = class extends Scenario { examples = []; missingExamplesKeyword = false; constructor(description, title = "Scenario Outline") { super(description, title); } getStepTitle(step, example) { let stepTitle = step.getTitle(); const exampleKeys = Object.keys(example); for (const key of exampleKeys) { stepTitle = stepTitle.replace(`<${key}>`, example[key]); } return stepTitle; } getStepDocStrings(step, example) { if (step.docStrings) { let docStrings = `${step.docStrings}`; for (const key in example) { docStrings = docStrings.replace(`<${key}>`, example[key]); } return docStrings; } return null; } }; // src/vitest/state-detectors/ScenarioStateDetector.ts var ScenarioStateDetector = class _ScenarioStateDetector { scenario; constructor(scenario) { this.scenario = scenario; } static forScenario(scenario) { return new _ScenarioStateDetector(scenario); } checkIfScenarioHasNoExample() { const { examples } = this.scenario; if (examples.length === 0) { throw new ScenarioOulineWithoutExamplesError( this.scenario ); } } detectMissingVariableInSteps() { const { examples } = this.scenario; const examplesKeys = Object.keys(examples[0]); const missingVariable = examplesKeys.find((v) => { return this.scenario.steps.map((s) => s.details).join(``).includes(`<${v}>`) === false; }); if (missingVariable) { throw new ScenarioOutlineVariableNotCalledInStepsError( this.scenario, missingVariable ); } } detectMissingVariableValue() { const { examples } = this.scenario; const examplesKeys = Object.keys(examples[0]); const missingVariable = examplesKeys.find((v) => { return examples.filter((values) => { return values[v] === void 0 || values[v] === null; }).length > 0; }); if (missingVariable) { throw new MissingScenarioOutlineVariableValueError( this.scenario, missingVariable ); } } checkExemples() { if (this.scenario instanceof ScenarioOutline) { if (this.scenario.missingExamplesKeyword) { throw new ScenarioOutlineVariablesDeclaredWithoutExamplesError( this.scenario ); } this.checkIfScenarioHasNoExample(); this.detectMissingVariableInSteps(); this.detectMissingVariableValue(); } } }; // src/vitest/describe-feature.ts var extractTagFilters = (filterItems) => { return filterItems.map((filterItem) => { if (Array.isArray(filterItem)) { return extractTagFilters(filterItem); } if (filterItem.startsWith("@")) { return filterItem.replace("@", ""); } return filterItem; }); }; function defineRuleScenarioToRun(options) { const describeToRun = options.describes.filter((d) => !d.skipped); const describeToSkip = options.describes.filter((d) => d.skipped); const finalDescribesToRun = []; for (const toRun of describeToRun) { if (options.featureBackground && !options.featureBackground.skipped) { finalDescribesToRun.push(options.featureBackground); } if (options.ruleBackground && !options.ruleBackground.skipped) { finalDescribesToRun.push(options.ruleBackground); } finalDescribesToRun.push(toRun); } return { describeToRun: finalDescribesToRun, describeToSkip }; } function defineScenarioToRun(options) { const describeToRun = options.describes.filter((d) => !d.skipped); const describeToSkip = options.describes.filter((d) => d.skipped); const finalDescribesToRun = []; for (const toRun of describeToRun) { if (options.featureBackground && !options.featureBackground.skipped) { finalDescribesToRun.push(options.featureBackground); } finalDescribesToRun.push(toRun); } describeToSkip.push(...options.describeRules.filter((s) => s.skipped)); finalDescribesToRun.push(...options.describeRules.filter((s) => !s.skipped)); return { describeToRun: finalDescribesToRun, describeToSkip }; } function describeFeature(feature, describeFeatureCallback, describeFeatureOptions) { let beforeAllScenarioHook = () => { }; let beforeEachScenarioHook = () => { }; let afterAllScenarioHook = () => { }; let afterEachScenarioHook = () => { }; const configuration = getVitestCucumberConfiguration(); const options = { includeTags: extractTagFilters( describeFeatureOptions?.includeTags || configuration.includeTags ), excludeTags: extractTagFilters( describeFeatureOptions?.excludeTags || configuration.excludeTags ) }; const describeScenarios = []; const describeRules = []; let describeBackground = null; const descibeFeatureParams = { Background: (backgroundCallback) => { const background = feature.getBackground(); background.isCalled = true; describeBackground = { skipped: !background.shouldBeCalled(options), describeTitle: background.getTitle(), describeHandler: createBackgroundDescribeHandler({ background, backgroundCallback }) }; }, Scenario: (scenarioDescription, scenarioTestCallback) => { const scenario = feature.getScenario(scenarioDescription); scenario.isCalled = true; describeScenarios.push({ skipped: !scenario.shouldBeCalled(options), describeTitle: scenario.getTitle(), describeHandler: createScenarioDescribeHandler({ scenario, scenarioTestCallback, beforeEachScenarioHook, afterEachScenarioHook }) }); }, ScenarioOutline: (scenarioDescription, scenarioTestCallback) => { const scenario = feature.getScenarioOutline(scenarioDescription); ScenarioStateDetector.forScenario(scenario).checkExemples(); scenario.isCalled = true; describeScenarios.push( ...createScenarioOutlineDescribeHandler({ scenario, scenarioTestCallback, beforeEachScenarioHook, afterEachScenarioHook }).map((t) => ({ skipped: !scenario.shouldBeCalled(options), describeTitle: scenario.getTitle(), describeHandler: t })) ); }, Rule: (ruleName, describeRuleCallback) => { const describeRuleScenarios = []; const currentRule = feature.checkIfRuleExists(ruleName); currentRule.isCalled = true; let describeRuleBackground = null; describeRuleCallback({ RuleBackground: (backgroundCallback) => { const background = currentRule.getBackground(); background.isCalled = true; describeRuleBackground = { skipped: !background.shouldBeCalled(options), describeTitle: background.getTitle(), describeHandler: createBackgroundDescribeHandler({ background, backgroundCallback }) }; }, RuleScenario: (scenarioDescription, scenarioTestCallback) => { const scenario = currentRule.getScenario(scenarioDescription); scenario.isCalled = true; describeRuleScenarios.push({ describeTitle: scenario.getTitle(), skipped: !scenario.shouldBeCalled(options), describeHandler: createScenarioDescribeHandler({ scenario, scenarioTestCallback, beforeEachScenarioHook, afterEachScenarioHook }) }); }, RuleScenarioOutline: (scenarioDescription, scenarioTestCallback) => { const scenario = currentRule.getScenarioOutline(scenarioDescription); ScenarioStateDetector.forScenario(scenario).checkExemples(); scenario.isCalled = true; describeRuleScenarios.push( ...createScenarioOutlineDescribeHandler({ scenario, scenarioTestCallback, beforeEachScenarioHook, afterEachScenarioHook }).map((t) => ({ skipped: !scenario.shouldBeCalled(options), describeTitle: scenario.getTitle(), describeHandler: t })) ); } }); currentRule.checkUncalledScenario(options).checkUncalledBackground(options); describeRules.push({ skipped: !currentRule.shouldBeCalled(options), describeTitle: currentRule.getTitle(), describeHandler: function describeRule() { beforeAll3(async () => { await beforeAllScenarioHook(); }); afterAll3(async () => { await afterAllScenarioHook(); }); const { describeToRun, describeToSkip } = defineRuleScenarioToRun({ describes: describeRuleScenarios, ruleBackground: describeRuleBackground, featureBackground: describeBackground }); describeFn.skip.each( describeToSkip.map((s) => { return [s.describeTitle, s]; }) )(`%s`, (_, { describeHandler }) => { describeHandler(); }); describeFn.each( describeToRun.map((s) => { return [s.describeTitle, s]; }) )(`%s`, (_, { describeHandler }) => { describeHandler(); }); } }); }, BeforeEachScenario: (fn) => { beforeEachScenarioHook = fn; }, BeforeAllScenarios: (fn) => { beforeAllScenarioHook = fn; }, AfterAllScenarios: (fn) => { afterAllScenarioHook = fn; }, AfterEachScenario: (fn) => { afterEachScenarioHook = fn; } }; describeFeatureCallback(descibeFeatureParams); feature.checkUncalledRule(options).checkUncalledScenario(options).checkUncalledBackground(options); describeFn(feature.getTitle(), async () => { beforeAll3(async () => { await beforeAllScenarioHook(); }); afterAll3(async () => { await afterAllScenarioHook(); }); const { describeToRun, describeToSkip } = defineScenarioToRun({ describes: describeScenarios, featureBackground: describeBackground, describeRules }); describeFn.skip.each( describeToSkip.map((s) => { return [s.describeTitle, s]; }) )(`%s`, (_, { describeHandler }) => { describeHandler(); }); describeFn.each( describeToRun.map((s) => { return [s.describeTitle, s]; }) )(`%s`, (_, { describeHandler }) => { describeHandler(); }); }); } // src/vitest/load-feature.ts import { dirname } from "node:path"; import callsites from "callsites"; // src/parser/readfile.ts import fs from "node:fs"; import readline from "node:readline"; // src/parser/lang/lang.json var lang_default = { af: { and: [ "* ", "En " ], background: [ "Agtergrond" ], but: [ "Maar " ], examples: [ "Voorbeelde" ], feature: [ "Funksie", "Besigheid Behoefte", "Vermo\xEB" ], given: [ "Gegewe " ], name: "Afrikaans", native: "Afrikaans", rule: [ "Regel" ], scenario: [ "Voorbeeld", "Situasie" ], scenarioOutline: [ "Situasie Uiteensetting" ], then: [ "Dan " ], when: [ "Wanneer " ] }, am: { and: [ "* ", "\u0535\u057E " ], background: [ "\u053F\u0578\u0576\u057F\u0565\u0584\u057D\u057F" ], but: [ "\u0532\u0561\u0575\u0581 " ], examples: [ "\u0555\u0580\u056B\u0576\u0561\u056F\u0576\u0565\u0580" ], feature: [ "\u0556\u0578\u0582\u0576\u056F\u0581\u056B\u0578\u0576\u0561\u056C\u0578\u0582\u0569\u0575\u0578\u0582\u0576", "\u0540\u0561\u057F\u056F\u0578\u0582\u0569\u0575\u0578\u0582\u0576" ], given: [ "\u0534\u056B\u0581\u0578\u0582\u0584 " ], name: "Armenian", native: "\u0570\u0561\u0575\u0565\u0580\u0565\u0576", rule: [ "Rule" ], scenario: [ "\u0555\u0580\u056B\u0576\u0561\u056F", "\u054D\u0581\u0565\u0576\u0561\u0580" ], scenarioOutline: [ "\u054D\u0581\u0565\u0576\u0561\u0580\u056B \u056F\u0561\u057C\u0578\u0582\u0581\u057E\u0561\u0581\u0584\u0568" ], then: [ "\u0531\u057A\u0561 " ], when: [ "\u0535\u0569\u0565 ", "\u0535\u0580\u0562 " ] }, an: { and: [ "* ", "Y ", "E " ], background: [ "Antecedents" ], but: [ "Pero " ], examples: [ "Eixemplos" ], feature: [ "Caracteristica" ], given: [ "Dau ", "Dada ", "Daus ", "Dadas " ], name: "Aragonese", native: "Aragon\xE9s", rule: [ "Rule" ], scenario: [ "Eixemplo", "Caso" ], scenarioOutline: [ "Esquema del caso" ], then: [ "Alavez ", "Allora ", "Antonces " ], when: [ "Cuan " ] }, ar: { and: [ "* ", "\u0648 " ], background: [ "\u0627\u0644\u062E\u0644\u0641\u064A\u0629" ], but: [ "\u0644\u0643\u0646 " ], examples: [ "\u0627\u0645\u062B\u0644\u0629" ], feature: [ "\u062E\u0627\u0635\u064A\u0629" ], given: [ "\u0628\u0641\u0631\u0636 " ], name: "Arabic", native: "\u0627\u0644\u0639\u0631\u0628\u064A\u0629", rule: [ "Rule" ], scenario: [ "\u0645\u062B\u0627\u0644", "\u0633\u064A\u0646\u0627\u0631\u064A\u0648" ], scenarioOutline: [ "\u0633\u064A\u0646\u0627\u0631\u064A\u0648 \u0645\u062E\u0637\u0637" ], then: [ "\u0627\u0630\u0627\u064B ", "\u062B\u0645 " ], when: [ "\u0645\u062A\u0649 ", "\u0639\u0646\u062F\u0645\u0627 " ] }, ast: { and: [ "* ", "Y ", "Ya " ], background: [ "Antecedentes" ], but: [ "Peru " ], examples: [ "Exemplos" ], feature: [ "Carauter\xEDstica" ], given: [ "D\xE1u ", "Dada ", "Daos ", "Daes " ], name: "Asturian", native: "asturianu", rule: [ "Rule" ], scenario: [ "Exemplo", "Casu" ], scenarioOutline: [ "Esbozu del casu" ], then: [ "Ent\xF3s " ], when: [ "Cuando " ] }, az: { and: [ "* ", "V\u0259 ", "H\u0259m " ], background: [ "Ke\xE7mi\u015F", "Kontekst" ], but: [ "Amma ", "Ancaq " ], examples: [ "N\xFCmun\u0259l\u0259r" ], feature: [ "\xD6z\u0259llik" ], given: [ "Tutaq ki ", "Verilir " ], name: "Azerbaijani", native: "Az\u0259rbaycanca", rule: [ "Rule" ], scenario: [ "N\xFCmun\u0259", "Ssenari" ], scenarioOutline: [ "Ssenarinin strukturu" ], then: [ "O halda " ], when: [ "\u018Fg\u0259r ", "N\u0259 vaxt ki " ] }, be: { and: [ "* ", "I ", "\u0414\u044B ", "\u0422\u0430\u043A\u0441\u0430\u043C\u0430 " ], background: [ "\u041A\u0430\u043D\u0442\u044D\u043A\u0441\u0442" ], but: [ "\u0410\u043B\u0435 ", "\u0406\u043D\u0430\u043A\u0448 " ], examples: [ "\u041F\u0440\u044B\u043A\u043B\u0430\u0434\u044B" ], feature: [ "\u0424\u0443\u043D\u043A\u0446\u044B\u044F\u043D\u0430\u043B\u044C\u043D\u0430\u0441\u0446\u044C", "\u0424\u0456\u0447\u0430" ], given: [ "\u041D\u044F\u0445\u0430\u0439 ", "\u0414\u0430\u0434\u0437\u0435\u043D\u0430 " ], name: "Belarusian", native: "\u0411\u0435\u043B\u0430\u0440\u0443\u0441\u043A\u0430\u044F", rule: [ "\u041F\u0440\u0430\u0432\u0456\u043B\u044B" ], scenario: [ "\u0421\u0446\u044D\u043D\u0430\u0440\u044B\u0439", "C\u0446\u044D\u043D\u0430\u0440" ], scenarioOutline: [ "\u0428\u0430\u0431\u043B\u043E\u043D \u0441\u0446\u044D\u043D\u0430\u0440\u044B\u044F", "\u0423\u0437\u043E\u0440 \u0441\u0446\u044D\u043D\u0430\u0440\u0430" ], then: [ "\u0422\u0430\u0434\u044B " ], when: [ "\u041A\u0430\u043B\u0456 " ] }, bg: { and: [ "* ", "\u0418 " ], background: [ "\u041F\u0440\u0435\u0434\u0438\u0441\u0442\u043E\u0440\u0438\u044F" ], but: [ "\u041D\u043E " ], examples: [ "\u041F\u0440\u0438\u043C\u0435\u0440\u0438" ], feature: [ "\u0424\u0443\u043D\u043A\u0446\u0438\u043E\u043D\u0430\u043B\u043D\u043E\u0441\u0442" ], given: [ "\u0414\u0430\u0434\u0435\u043D\u043E " ], name: "Bulgarian", native: "\u0431\u044A\u043B\u0433\u0430\u0440\u0441\u043A\u0438", rule: [ "\u041F\u0440\u0430\u0432\u0438\u043B\u043E" ], scenario: [ "\u041F\u0440\u0438\u043C\u0435\u0440", "\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439" ], scenarioOutline: [ "\u0420\u0430\u043C\u043A\u0430 \u043D\u0430 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439" ], then: [ "\u0422\u043E " ], when: [ "\u041A\u043E\u0433\u0430\u0442\u043E " ] }, bm: { and: [ "* ", "Dan " ], background: [ "Latar Belakang" ], but: [ "Tetapi ", "Tapi " ], examples: [ "Contoh" ], feature: [ "Fungsi" ], given: [ "Diberi ", "Bagi " ], name: "Malay", native: "Bahasa Melayu", rule: [ "Rule" ], scenario: [ "Senario", "Situasi", "Keadaan" ], scenarioOutline: [ "Kerangka Senario", "Kerangka Situasi", "Kerangka Keadaan", "Garis Panduan Senario" ], then: [ "Maka ", "Kemudian " ], when: [ "Apabila " ] }, bs: { and: [ "* ", "I ", "A " ], background: [ "Pozadina" ], but: [ "Ali " ], examples: [ "Primjeri" ], feature: [ "Karakteristika" ], given: [ "Dato " ], name: "Bosnian", native: "Bosanski", rule: [ "Rule" ], scenario: [ "Primjer", "Scenariju", "Scenario" ], scenarioOutline: [ "Scenariju-obris", "Scenario-outline" ], then: [ "Zatim " ], when: [ "Kada " ] }, ca: { and: [ "* ", "I " ], background: [ "Rerefons", "Antecedents" ], but: [ "Per\xF2 " ], examples: [ "Exemples" ], feature: [ "Caracter\xEDstica", "Funcionalitat" ], given: [ "Donat ", "Donada ", "At\xE8s ", "Atesa " ], name: "Catalan", native: "catal\xE0", rule: [ "Rule" ], scenario: [ "Exemple", "Escenari" ], scenarioOutline: [ "Esquema de l'escenari" ], then: [ "Aleshores ", "Cal " ], when: [ "Quan " ] }, cs: { and: [ "* ", "A tak\xE9 ", "A " ], background: [ "Pozad\xED", "Kontext" ], but: [ "Ale " ], examples: [ "P\u0159\xEDklady" ], feature: [ "Po\u017Eadavek" ], given: [ "Pokud ", "Za p\u0159edpokladu " ], name: "Czech", native: "\u010Cesky", rule: [ "Pravidlo" ], scenario: [ "P\u0159\xEDklad", "Sc\xE9n\xE1\u0159" ], scenarioOutline: [ "N\xE1\u010Drt Sc\xE9n\xE1\u0159e", "Osnova sc\xE9n\xE1\u0159e" ], then: [ "Pak " ], when: [ "Kdy\u017E " ] }, "cy-GB": { and: [ "* ", "A " ], background: [ "Cefndir" ], but: [ "Ond " ], examples: [ "Enghreifftiau" ], feature: [ "Arwedd" ], given: [ "Anrhegedig a " ], name: "Welsh", native: "Cymraeg", rule: [ "Rule" ], scenario: [ "Enghraifft", "Scenario" ], scenarioOutline: [ "Scenario Amlinellol" ], then: [ "Yna " ], when: [ "Pryd " ] }, da: { and: [ "* ", "Og " ], background: [ "Baggrund" ], but: [ "Men " ], examples: [ "Eksempler" ], feature: