@amiceli/vitest-cucumber
Version:
vitest tools to use Gherkin feature in unit tests
311 lines (310 loc) • 11.9 kB
JavaScript
import { MissingExamplesError, MissingFeature, MissingScnearioOutlineError, MissingSteppableError, OnlyOneFeatureError, TwiceBackgroundError, } from '../errors/errors';
import { SpokenParserFactory } from './lang/SpokenParser';
import { Background } from './models/Background';
import { Rule } from './models/Rule';
import { Feature } from './models/feature';
import { Scenario, ScenarioOutline } from './models/scenario';
import { Step } from './models/step';
var FeatureActions;
(function (FeatureActions) {
FeatureActions["FEATURE"] = "Feature";
FeatureActions["SCENARIO"] = "Scenario";
FeatureActions["BACKGROUND"] = "Background";
FeatureActions["STEP"] = "Step";
FeatureActions["RULE"] = "Rule";
FeatureActions["EXAMPLES"] = "Examples";
})(FeatureActions || (FeatureActions = {}));
export class GherkinParser {
spokenParser;
features = [];
currentFeatureIndex = -1;
currentScenarioIndex = -1;
currentRulenIndex = -1;
lastScenarioOutline = null;
currentExample = null;
currentExampleLine = -1;
exampleKeys = [];
currentDataTable = [];
currentStepDataTableLine = -1;
dataTanleKeys = [];
lastTags = [];
lastSteppableTag = null;
currentDocStrings = [];
parsingDocStrings = false;
previousAction = null;
lastStep = null;
resetStepDataTable() {
this.currentDataTable = [];
this.currentStepDataTableLine = -1;
this.dataTanleKeys = [];
this.lastStep = null;
}
constructor(options) {
this.spokenParser = SpokenParserFactory.fromLang(options.language);
}
hasFeature(line) {
if (!this.currentFeature) {
throw new MissingFeature(line);
}
return true;
}
hasSteppable(line) {
if (!(this.currentBackground || this.currentScenario)) {
throw new MissingSteppableError(line);
}
return true;
}
hasScenarioOutline(line) {
if (!this.lastScenarioOutline) {
throw new MissingScnearioOutlineError(line);
}
return true;
}
previousActionIs(value) {
return this.previousAction === value;
}
addLine(line) {
if (line.trim().startsWith(`#`)) {
return;
}
if (this.previousActionIs(FeatureActions.STEP)) {
if (this.lastStep) {
this.lastStep.dataTables = this.currentDataTable;
}
}
if (this.parsingDocStrings && !this.isDocStrings(line)) {
this.currentDocStrings.push(line.trim());
}
else if (this.spokenParser.isFeature(line)) {
this.previousAction = FeatureActions.FEATURE;
this.resetStepDataTable();
if (this.features.length > 0) {
throw new OnlyOneFeatureError();
}
this.currentFeatureIndex++;
this.currentScenarioIndex = -1;
this.currentRulenIndex = -1;
this.currentExampleLine = -1;
const { title, keyword } = this.spokenParser.getFeatureName(line);
const feature = new Feature(title, keyword);
this.features.push(feature);
this.addTagToParent(feature);
}
else if (this.spokenParser.isRule(line) && this.hasFeature(line)) {
this.previousAction = FeatureActions.RULE;
this.resetStepDataTable();
this.currentExampleLine = -1;
this.currentScenarioIndex = -1;
this.currentRulenIndex++;
const { title, keyword } = this.spokenParser.getRuleName(line);
const rule = new Rule(title, keyword);
this.addTagToParent(rule);
this.currentFeature.addRule(rule);
}
else if (this.spokenParser.isScenarioOutline(line) &&
this.hasFeature(line)) {
this.previousAction = FeatureActions.SCENARIO;
this.resetStepDataTable();
this.currentScenarioIndex++;
const { title, keyword } = this.spokenParser.getScenarioOutlineName(line);
const scenario = new ScenarioOutline(title, keyword);
this.lastScenarioOutline = scenario;
this.lastSteppableTag = `ScenarioOutline`;
this.addScenarioToParent(scenario);
this.addTagToParent(scenario);
}
else if (this.spokenParser.isExamples(line) &&
this.hasScenarioOutline(line)) {
this.previousAction = FeatureActions.EXAMPLES;
this.currentExample = [];
this.resetStepDataTable();
}
else if (line.trim().startsWith(`|`)) {
if (this.previousActionIs(FeatureActions.EXAMPLES) &&
this.currentExample) {
this.detectMissingExamplesKeyword();
this.updateScenarioExamples(line);
}
else if (this.previousActionIs(FeatureActions.STEP)) {
this.updateStepDataTable(line);
}
else {
throw new MissingExamplesError(line);
}
}
else if (this.spokenParser.isScenario(line) &&
this.hasFeature(line)) {
this.previousAction = FeatureActions.SCENARIO;
this.resetStepDataTable();
this.currentScenarioIndex++;
const { title, keyword } = this.spokenParser.getScenarioName(line);
const scenario = new Scenario(title, keyword);
this.lastSteppableTag = `Scenario`;
this.addScenarioToParent(scenario);
this.addTagToParent(scenario);
}
else if (this.spokenParser.isBackground(line) &&
this.hasFeature(line)) {
this.previousAction = FeatureActions.BACKGROUND;
this.resetStepDataTable();
if (this.currentBackground) {
throw new TwiceBackgroundError();
}
const background = new Background(this.spokenParser.getBackgroundKeyWord(line));
this.lastSteppableTag = `Background`;
this.addBackgroundToParent(background);
this.addTagToParent(background);
}
else if (line.trim().startsWith(`@`)) {
this.lastTags.push(...line
.split(` `)
.filter((l) => l.startsWith(`@`))
.map((l) => l.replace(`@`, ``)));
}
else if (this.isDocStrings(line)) {
if (this.parsingDocStrings) {
this.currentScenario.lastStep.docStrings =
this.currentDocStrings.join(`\n`);
this.parsingDocStrings = false;
this.currentDocStrings.splice(0, this.currentDocStrings.length);
}
else {
this.parsingDocStrings = true;
}
}
else if (!this.parsingDocStrings &&
this.spokenParser.isStep(line) &&
this.hasSteppable(line)) {
this.previousAction = FeatureActions.STEP;
this.resetStepDataTable();
const stepType = this.spokenParser.getStepType(line);
const { keyword, title } = this.spokenParser.getStepDetails(line, stepType);
const newStep = new Step(stepType, title, keyword);
this.lastStep = newStep;
this.currentScenario.addStep(newStep);
}
else if (this.currentExample !== null) {
if (this.currentExample.length === 0) {
this.currentExample.push(this.getEmptyExamplesValues());
}
if (this.lastScenarioOutline) {
this.lastScenarioOutline.examples = JSON.parse(JSON.stringify(this.currentExample));
}
this.currentExample = null;
this.currentExampleLine = -1;
}
}
addBackgroundToParent(background) {
if (this.currentRule) {
this.currentRule.background = background;
}
else {
this.currentFeature.background = background;
}
}
addScenarioToParent(scenario) {
if (this.currentRule) {
this.currentRule.addScenario(scenario);
}
else {
this.currentFeature.addScenario(scenario);
}
}
finish() {
if (this.lastScenarioOutline && this.currentExample) {
if (this.currentExample.length === 0) {
this.currentExample.push(this.getEmptyExamplesValues());
}
this.lastScenarioOutline.examples = JSON.parse(JSON.stringify(this.currentExample));
}
this.currentFeature?.mustHaveScenarioOrRules();
return this.features;
}
getVariablesFromLine(line) {
const exampleVariables = line
.trim()
.split(`|`)
.filter((n) => n.length > 0)
.map((n) => n.trim());
return exampleVariables;
}
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
getObjectWithValuesFromLine(line, keys) {
const exampleVariables = this.getVariablesFromLine(line);
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const res = exampleVariables.reduce((acc, cur, index) => {
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
// biome-ignore lint/style/noCommaOperator: <explanation>
return (acc[keys[index]] = cur), acc;
}, {});
return res;
}
addTagToParent(parent) {
if (this.lastTags.length > 0) {
for (const tag of this.lastTags) {
parent.tags.add(tag);
}
this.lastTags = [];
}
}
getEmptyExamplesValues() {
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
const res = this.exampleKeys.reduce((acc, cur, index) => {
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
// biome-ignore lint/style/noCommaOperator: <explanation>
return (acc[this.exampleKeys[index]] = null), acc;
}, {});
return res;
}
detectMissingExamplesKeyword() {
if (this.currentExample === null && this.lastScenarioOutline) {
this.lastScenarioOutline.missingExamplesKeyword = true;
}
}
updateStepDataTable(line) {
if (this.currentDataTable) {
this.currentStepDataTableLine++;
if (this.currentStepDataTableLine === 0) {
this.dataTanleKeys = this.getVariablesFromLine(line);
}
else {
this.currentDataTable.push(this.getObjectWithValuesFromLine(line, this.dataTanleKeys));
}
}
}
updateScenarioExamples(line) {
if (this.currentExample) {
this.currentExampleLine++;
if (this.currentExampleLine === 0) {
this.exampleKeys = this.getVariablesFromLine(line);
}
else {
this.currentExample.push(this.getObjectWithValuesFromLine(line, this.exampleKeys));
}
}
}
isDocStrings(line) {
return line.trim().startsWith(`"""`) || line.trim().startsWith(`\`\`\``);
}
get currentBackground() {
if (this.currentRule) {
return this.currentRule.background;
}
return this.currentFeature.background;
}
get currentRule() {
return this.currentFeature.rules[this.currentRulenIndex];
}
get currentFeature() {
return this.features[this.currentFeatureIndex];
}
get currentScenario() {
if (this.lastSteppableTag === `Background` && this.currentBackground) {
return this.currentBackground;
}
if (this.currentRule) {
return this.currentRule.scenarii[this.currentScenarioIndex];
}
return this.currentFeature.scenarii[this.currentScenarioIndex];
}
}