Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 55 additions & 28 deletions analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@ const path = require('path');
let workDir;
const invalidKeys = ['And', 'But'];

const getLocation = scenario => (scenario.tags.length ? scenario.tags[0].location.line - 1 : scenario.location.line - 1);
const getLocation = ({ feature, scenario, rule }) => {
const message = feature || scenario || rule;
if (message.tags && message.tags.length) {
return message.tags[0].location.line - 1;
}
return message.location.line - 1;
};

const getLocations = ({ feature, scenario, rule }) => {
if (feature) return feature.children.flatMap(getLocations);
if (scenario) return [ getLocation({ scenario}) ];
if (rule) return [getLocation({ rule }), ...rule.children.flatMap(getLocations)];
return [];
};

const getTitle = scenario => {
let { name } = scenario;
Expand All @@ -28,37 +41,51 @@ const getScenarioCode = (source, feature, file) => {
const sourceArray = source.split('\n');
const fileName = path.relative(workDir, file);
const scenarios = [];

for (let i = 0; i < feature.children.length; i += 1) {
const { scenario } = feature.children[i];
if (scenario) {
if (!scenario.name) {
console.log(chalk.red('Title of scenario cannot be empty, skipping this'));
const [_, ...endLocations] = [...getLocations({ feature }), sourceArray.length];
let inRule = false;

const handleScenario = (scenario) => {
if (!scenario.name) {
console.log(chalk.red('Title of scenario cannot be empty, skipping this'));
} else {
console.log(inRule ? ' - ' : ' - ', scenario.name);
}
const steps = [];
let previousValidStep = '';
const scenarioJson = { name: scenario.name, file: fileName };
const start = getLocation({ scenario });
const end = endLocations.shift();
for (const step of scenario.steps) {
let keyword = step.keyword.trim();
if (invalidKeys.includes(keyword)) {
keyword = previousValidStep;
} else {
console.log(' - ', scenario.name);
previousValidStep = keyword;
}
const steps = [];
let previousValidStep = '';
const scenarioJson = { name: scenario.name, file: fileName };
const start = getLocation(scenario);
const end = ((i === feature.children.length - 1) ? sourceArray.length : getLocation(feature.children[i + 1].scenario));
for (const step of scenario.steps) {
let keyword = step.keyword.trim();
if (invalidKeys.includes(keyword)) {
keyword = previousValidStep;
} else {
previousValidStep = keyword;
}
steps.push({ title: step.text, keyword });
}
scenarioJson.line = start;
scenarioJson.tags = scenario.tags.map(t => t.name.slice(1));
scenarioJson.code = sourceArray.slice(start, end).join('\n');
scenarioJson.steps = steps;
scenarios.push(scenarioJson);
steps.push({ title: step.text, keyword });
}
scenarioJson.line = start;
scenarioJson.tags = scenario.tags.map(t => t.name.slice(1));
scenarioJson.code = sourceArray.slice(start, end).join('\n');
scenarioJson.steps = steps;
scenarios.push(scenarioJson);
};

const handleRule = (rule) => {
console.log(' - ', rule.name);
endLocations.shift();
inRule = true;
rule.children.forEach(handleChild);
inRule = false;
};

const handleChild = ({ scenario, rule }) => {
if (scenario) handleScenario(scenario);
if (rule) handleRule(rule);
}

feature.children.forEach(handleChild);

return scenarios;
};

Expand Down Expand Up @@ -89,7 +116,7 @@ const parseFile = file => new Promise((resolve, reject) => {
console.log(chalk.red('Title for feature is empty, skipping'));
featureData.error = `${fileName} : Empty feature`;
}
featureData.line = getLocation(data[1].gherkinDocument.feature) + 1;
featureData.line = getLocation({feature: data[1].gherkinDocument.feature}) + 1;
featureData.tags = data[1].gherkinDocument.feature.tags.map(t => t.name.slice(1));
featureData.scenario = getScenarioCode(data[0].source.data, data[1].gherkinDocument.feature, file);
} else {
Expand Down
74 changes: 74 additions & 0 deletions example/features/rules.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
Feature: A feature with multiple rules
Description of the feature

Rule: Rule 1
Description of first rule

Scenario: Scenario 1.1
Description of first scenario

Given I have something
When I do something
Then something happens

Scenario: Scenario 1.2
Description of second scenario

Given I have something
When I do something
Then something happens

Scenario: Scenario 1.3
Description of third scenario

Given I have something
When I do something
Then something happens

Rule: Rule 2
Description of second rule

Scenario: Scenario 2.1
Description of first scenario

Given I have something
When I do something
Then something happens

Scenario: Scenario 2.2
Description of second scenario

Given I have something
When I do something
Then something happens

Scenario: Scenario 2.3
Description of third scenario

Given I have something
When I do something
Then something happens

Rule: Rule 3
Description of thrid rule

Scenario: Scenario 3.1
Description of first scenario

Given I have something
When I do something
Then something happens

Scenario: Scenario 3.2
Description of second scenario

Given I have something
When I do something
Then something happens

Scenario: Scenario 3.3
Description of third scenario

Given I have something
When I do something
Then something happens
34 changes: 34 additions & 0 deletions tests/analyzer_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,38 @@ describe('Analyzer', () => {
const features = await analyse('**/empty.feature', path.join(__dirname, '..', 'example'));
expect(features[0].error).not.equal(undefined);
});

it('Should include scenarios from rules', async () => {
const features = await analyse('**/rules.feature', path.join(__dirname, '..', 'example'));
const scenarios = features.reduce((acc, feature) => {
acc.push(...feature.scenario);
return acc;
}, []);
const scenariosTitles = scenarios.map(scenarioData => scenarioData.name);

expect(features.length).equal(1);
expect(scenariosTitles).to.include('Scenario 1.1');
expect(scenariosTitles).to.include('Scenario 1.2');
expect(scenariosTitles).to.include('Scenario 1.3');
expect(scenariosTitles).to.include('Scenario 2.1');
expect(scenariosTitles).to.include('Scenario 2.2');
expect(scenariosTitles).to.include('Scenario 2.3');
expect(scenariosTitles).to.include('Scenario 3.1');
expect(scenariosTitles).to.include('Scenario 3.2');
expect(scenariosTitles).to.include('Scenario 3.3');
expect(scenarios.length).equal(9);
});
it('Should extract the code of scenarios in rules', async () => {
const features = await analyse('**/rules.feature', path.join(__dirname, '..', 'example'));
const scenarios = features.reduce((acc, feature) => {
acc.push(...feature.scenario);
return acc;
}, []);

for (let scenario of scenarios) {
expect(scenario.code).to.include("Scenario");
expect(scenario.code).to.not.include("Rule");
expect(scenario.code.split('\n').length).to.equal(7);
}
});
});