UNPKG

@amiceli/vitest-cucumber

Version:

vitest tools to use Gherkin feature in unit tests

385 lines (384 loc) 19.5 kB
import { afterAll, beforeAll, describe } from 'vitest'; import { StepTypes } from '../parser/models'; import { getVitestCucumberConfiguration, } from './configuration'; import { defineSharedStep, updatePredefinedStepsAccordingLevel, } from './describe/define-step-test'; import { createBackgroundDescribeHandler } from './describe/describe-background'; import { createScenarioDescribeHandler } from './describe/describe-scenario'; import { createScenarioOutlineDescribeHandler } from './describe/describe-scenario-outline'; import { defineRuleScenarioToRun, defineScenarioToRun, } from './describe/handle-skip-only'; import { ScenarioStateDetector } from './state-detectors/ScenarioStateDetector'; /** * Extract tag filters by removing the `@` prefix if present */ const extractTagFilters = (filterItems) => { return filterItems.map((filterItem) => { if (Array.isArray(filterItem)) { return extractTagFilters(filterItem); } if (filterItem.startsWith('@')) { return filterItem.replace('@', ''); } return filterItem; }); }; export 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), predefinedSteps: [], predefinedRuleSteps: [], }; const describeScenarios = []; const describeRules = []; let describeBackground = null; const descibeFeatureParams = { Background: (() => { const createBackgroundHandler = (backgroundCallback, skipped) => { const background = feature.getBackground(); background.isCalled = true; describeBackground = { only: false, skipped: skipped ?? !background.shouldBeCalled(options), describeTitle: background.getTitle(), describeHandler: createBackgroundDescribeHandler({ background, predefinedSteps: updatePredefinedStepsAccordingLevel({ globallyPredefinedSteps: configuration.predefinedSteps, featurePredefinedSteps: options.predefinedSteps, rulePredefinedSteps: [], }), backgroundCallback, }), }; }; const fn = (backgroundCallback) => { createBackgroundHandler(backgroundCallback); }; fn.skip = (backgroundCallback) => { createBackgroundHandler(backgroundCallback, true); }; return fn; })(), Scenario: (() => { const createScenarioHandler = (scenarioDescription, scenarioTestCallback, only, skipped) => { const scenario = feature.getScenario(scenarioDescription); scenario.isCalled = true; describeScenarios.push({ skipped: skipped ?? !scenario.shouldBeCalled(options), only, describeTitle: scenario.getTitle(), describeHandler: createScenarioDescribeHandler({ scenario, predefinedSteps: updatePredefinedStepsAccordingLevel({ globallyPredefinedSteps: configuration.predefinedSteps, featurePredefinedSteps: options.predefinedSteps, rulePredefinedSteps: [], }), scenarioTestCallback, beforeEachScenarioHook, afterEachScenarioHook, }), }); }; const fn = (scenarioDescription, scenarioTestCallback) => createScenarioHandler(scenarioDescription, scenarioTestCallback, false); fn.skip = (scenarioDescription, scenarioTestCallback) => createScenarioHandler(scenarioDescription, scenarioTestCallback, false, true); fn.only = (scenarioDescription, scenarioTestCallback) => createScenarioHandler(scenarioDescription, scenarioTestCallback, true, false); return fn; })(), ScenarioOutline: (() => { const createScenarioOutlineHandler = (scenarioDescription, scenarioTestCallback, only, skipped) => { const scenario = feature.getScenarioOutline(scenarioDescription); ScenarioStateDetector.forScenario(scenario).checkExemples(); scenario.isCalled = true; describeScenarios.push(...createScenarioOutlineDescribeHandler({ scenario, mappedExamples: configuration.mappedExamples, predefinedSteps: updatePredefinedStepsAccordingLevel({ globallyPredefinedSteps: configuration.predefinedSteps, featurePredefinedSteps: options.predefinedSteps, rulePredefinedSteps: [], }), scenarioTestCallback, beforeEachScenarioHook, afterEachScenarioHook, }).map((t) => ({ only, skipped: skipped ?? !scenario.shouldBeCalled(options), describeTitle: scenario.getTitle(), describeHandler: t, }))); }; const fn = (scenarioDescription, scenarioTestCallback) => { createScenarioOutlineHandler(scenarioDescription, scenarioTestCallback, false); }; fn.skip = (scenarioDescription, scenarioTestCallback) => { createScenarioOutlineHandler(scenarioDescription, scenarioTestCallback, false, true); }; fn.only = (scenarioDescription, scenarioTestCallback) => { createScenarioOutlineHandler(scenarioDescription, scenarioTestCallback, true, false); }; return fn; })(), Rule: (() => { const createRuleHandler = (ruleName, describeRuleCallback, ruleOnly, ruleSkipped) => { const describeRuleScenarios = []; const currentRule = feature.checkIfRuleExists(ruleName); currentRule.isCalled = true; let describeRuleBackground = null; describeRuleCallback({ RuleBackground: (() => { const createRuleBackgroundHandler = (backgroundCallback, skipped) => { const background = currentRule.getBackground(); background.isCalled = true; describeRuleBackground = { skipped: skipped ?? !background.shouldBeCalled(options), only: false, describeTitle: background.getTitle(), describeHandler: createBackgroundDescribeHandler({ background: background, predefinedSteps: updatePredefinedStepsAccordingLevel({ globallyPredefinedSteps: configuration.predefinedSteps, featurePredefinedSteps: options.predefinedSteps, rulePredefinedSteps: options.predefinedRuleSteps, }), backgroundCallback, }), }; }; const fn = (backgroundCallback) => { createRuleBackgroundHandler(backgroundCallback); }; fn.skip = (backgroundCallback) => { createRuleBackgroundHandler(backgroundCallback, true); }; return fn; })(), RuleScenario: (() => { const createRuleScenarioHandler = (scenarioDescription, scenarioTestCallback, only, skipped) => { const scenario = currentRule.getScenario(scenarioDescription); scenario.isCalled = true; describeRuleScenarios.push({ describeTitle: scenario.getTitle(), skipped: skipped ?? !scenario.shouldBeCalled(options), only, describeHandler: createScenarioDescribeHandler({ scenario, predefinedSteps: updatePredefinedStepsAccordingLevel({ globallyPredefinedSteps: configuration.predefinedSteps, featurePredefinedSteps: options.predefinedSteps, rulePredefinedSteps: options.predefinedRuleSteps, }), scenarioTestCallback, beforeEachScenarioHook, afterEachScenarioHook, }), }); }; const fn = (scenarioDescription, scenarioTestCallback) => { createRuleScenarioHandler(scenarioDescription, scenarioTestCallback, false); }; fn.skip = (scenarioDescription, scenarioTestCallback) => { createRuleScenarioHandler(scenarioDescription, scenarioTestCallback, false, true); }; fn.only = (scenarioDescription, scenarioTestCallback) => { createRuleScenarioHandler(scenarioDescription, scenarioTestCallback, true, false); }; return fn; })(), RuleScenarioOutline: (() => { const createRuleScenarioOutlineHandler = (scenarioDescription, scenarioTestCallback, only, skipped) => { const scenario = currentRule.getScenarioOutline(scenarioDescription); ScenarioStateDetector.forScenario(scenario).checkExemples(); scenario.isCalled = true; describeRuleScenarios.push(...createScenarioOutlineDescribeHandler({ scenario, mappedExamples: configuration.mappedExamples, predefinedSteps: updatePredefinedStepsAccordingLevel({ globallyPredefinedSteps: configuration.predefinedSteps, featurePredefinedSteps: options.predefinedSteps, rulePredefinedSteps: options.predefinedRuleSteps, }), scenarioTestCallback, beforeEachScenarioHook, afterEachScenarioHook, }).map((t) => ({ skipped: skipped ?? !scenario.shouldBeCalled(options), only, describeTitle: scenario.getTitle(), describeHandler: t, }))); }; const fn = (scenarioDescription, scenarioTestCallback) => { createRuleScenarioOutlineHandler(scenarioDescription, scenarioTestCallback, false); }; fn.skip = (scenarioDescription, scenarioTestCallback) => { createRuleScenarioOutlineHandler(scenarioDescription, scenarioTestCallback, false, true); }; fn.only = (scenarioDescription, scenarioTestCallback) => { createRuleScenarioOutlineHandler(scenarioDescription, scenarioTestCallback, true, false); }; return fn; })(), context: {}, defineSteps: (defineStepsCallback) => { defineStepsCallback({ Given: (name, callback) => { options.predefinedRuleSteps.push(defineSharedStep(StepTypes.GIVEN, name, callback)); }, And: (name, callback) => { options.predefinedRuleSteps.push(defineSharedStep(StepTypes.AND, name, callback)); }, Then: (name, callback) => { options.predefinedRuleSteps.push(defineSharedStep(StepTypes.THEN, name, callback)); }, When: (name, callback) => { options.predefinedRuleSteps.push(defineSharedStep(StepTypes.WHEN, name, callback)); }, But: (name, callback) => { options.predefinedRuleSteps.push(defineSharedStep(StepTypes.BUT, name, callback)); }, }); }, }); currentRule .checkUncalledScenario(options) .checkUncalledBackground(options); describeRules.push({ skipped: ruleSkipped ?? !currentRule.shouldBeCalled(options), only: ruleOnly, describeTitle: currentRule.getTitle(), describeHandler: function describeRule() { beforeAll(async () => { await beforeAllScenarioHook(); }); afterAll(async () => { await afterAllScenarioHook(); }); const { describeToRun, describeToSkip, onlyDescribeToRun, } = defineRuleScenarioToRun({ describes: describeRuleScenarios, ruleBackground: describeRuleBackground, featureBackground: describeBackground, }); describe.only.each(onlyDescribeToRun.map((s) => { return [ s.describeTitle, s, ]; }))(`%s`, (_, { describeHandler }) => { describeHandler(); }); describe.skip.each(describeToSkip.map((s) => { return [ s.describeTitle, s, ]; }))(`%s`, (_, { describeHandler }) => { describeHandler(); }); describe.each(describeToRun.map((s) => { return [ s.describeTitle, s, ]; }))(`%s`, (_, { describeHandler }) => { describeHandler(); }); }, }); }; const fn = (ruleName, describeRuleCallback) => { createRuleHandler(ruleName, describeRuleCallback, false); }; fn.skip = (ruleName, describeRuleCallback) => { createRuleHandler(ruleName, describeRuleCallback, false, true); }; fn.only = (ruleName, describeRuleCallback) => { createRuleHandler(ruleName, describeRuleCallback, true, false); }; return fn; })(), BeforeEachScenario: (fn) => { beforeEachScenarioHook = fn; }, BeforeAllScenarios: (fn) => { beforeAllScenarioHook = fn; }, AfterAllScenarios: (fn) => { afterAllScenarioHook = fn; }, AfterEachScenario: (fn) => { afterEachScenarioHook = fn; }, defineSteps: (defineStepsCallback) => { defineStepsCallback({ Given: (name, callback) => { options.predefinedSteps.push(defineSharedStep(StepTypes.GIVEN, name, callback)); }, And: (name, callback) => { options.predefinedSteps.push(defineSharedStep(StepTypes.AND, name, callback)); }, Then: (name, callback) => { options.predefinedSteps.push(defineSharedStep(StepTypes.THEN, name, callback)); }, When: (name, callback) => { options.predefinedSteps.push(defineSharedStep(StepTypes.WHEN, name, callback)); }, But: (name, callback) => { options.predefinedSteps.push(defineSharedStep(StepTypes.BUT, name, callback)); }, }); }, context: {}, }; describeFeatureCallback(descibeFeatureParams); feature .checkUncalledRule(options) .checkUncalledScenario(options) .checkUncalledBackground(options); describe(feature.getTitle(), async () => { beforeAll(async () => { await beforeAllScenarioHook(); }); afterAll(async () => { await afterAllScenarioHook(); }); const { describeToRun, describeToSkip, onlyDescribeToRun } = defineScenarioToRun({ describes: describeScenarios, featureBackground: describeBackground, describeRules, }); describe.only.each(onlyDescribeToRun.map((s) => { return [ s.describeTitle, s, ]; }))(`%s`, (_, { describeHandler }) => { describeHandler(); }); describe.skip.each(describeToSkip.map((s) => { return [ s.describeTitle, s, ]; }))(`%s`, (_, { describeHandler }) => { describeHandler(); }); // )(`%s`, async ([, scenarioStep], ctx) => { describe.each(describeToRun.map((s) => { return [ s.describeTitle, s, ]; }))(`%s`, (_, parent) => { parent.describeHandler(); }); }); }