@amiceli/vitest-cucumber
Version:
vitest tools to use Gherkin feature in unit tests
385 lines (384 loc) • 19.5 kB
JavaScript
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();
});
});
}