Skip to content

Commit

Permalink
Version 5.0.5: no-dupe-scenario-names rule can apply every example ro…
Browse files Browse the repository at this point in the history
…w for a Scenario Outline
  • Loading branch information
Alesia Vysotskaya committed Feb 28, 2022
1 parent 8e2aa84 commit f325951
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 112 deletions.
19 changes: 4 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,25 +233,14 @@ To enable searching for duplicates in each individual feature (same scenario nam

```
{
"no-dupe-scenario-names": ["error", "in-feature"]
"no-dupe-scenario-names": ["error", { checkArguments: false, scope: "in-feature" }]
}
```

The default case is testing against all the features (same scenario name in different features will raise an error). To get that behavor use the following configuration:
The `no-dupe-scenario-names` supports some configuration options:

```
{
"no-dupe-scenario-names": "error"
}
```

or

```
{
"no-dupe-scenario-names": ["error", "anywhere"]
}
```
- `scope` ( `in-feature` | `anywhere` ) `anywhere` case is testing against all the features (same scenario name in different features will raise an error) - defaults to `in-feature`
- `checkArguments` (boolean) if you use arguments in the test scenario name, use `true` to apply every example row for a Scenario Outline - defaults to `false`


### no-restricted-tags
Expand Down
6 changes: 4 additions & 2 deletions demo/configs/.gherkin-lintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
"no-unnamed-features": "error",
"no-unnamed-scenarios": "error",
"no-dupe-scenario-names": [
"error",
"in-feature"
"error", {
"scope": "in-feature",
"checkArguments": true
}
],
"no-dupe-feature-names": "error",
"no-partially-commented-tag-lines": "error",
Expand Down
12 changes: 10 additions & 2 deletions demo/features/DuplicateScenarioName.feature
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@ Scenario: This is a Scenario for no-dupe-scenario-names
Given I have 2 scenarios with the same name
Then I should see a no-dupe-scenario-names error

Scenario Outline: This is a Scenario with non unique <argument> in name
Given I have duplicate names because of same arguments
Then I should see a no-dupe-scenario-names error
Examples:
| argument |
| argument |
| argument |

Rule: This is a Rule for no-dupe-scenario-names

Example: This is a Rule Example for no-dupe-scenario-names
Given I have 2 examples with the same name
Then I should see a no-dupe-scenario-names error
Given I have 2 examples with the same name
Then I should see a no-dupe-scenario-names error

Example: This is a Rule Example for no-dupe-scenario-names
Given I have 2 examples with the same name
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gherkin-lint-ts",
"version": "5.0.4",
"version": "5.0.5",
"description": "Gherkin features linter written in Typescript",
"main": "lib/src/index.js",
"bin": {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createRuleTest } from "../rule-test-base";
import * as rule from "../../../rules/no-dupe-scenario-names";

const runTest = createRuleTest(rule, "Scenario name is already used in: <%= location %>");
const runTest = createRuleTest(rule, "Scenario name \"<%= name %>\" is already used in: <%= location %>");

describe("No Duplicate Scenario Names Rule", function () {
it("doesn't raise errors when there are no duplicate scenario names in a single file", function () {
Expand All @@ -14,46 +14,97 @@ describe("No Duplicate Scenario Names Rule", function () {
});
});
it("raises errors when there duplicate Scenario and Scenario Outline names in a single file", function () {
return runTest("no-dupe-scenario-names/DublicateScenarioNames.feature", {}, [
return runTest("no-dupe-scenario-names/DuplicateScenarioNames.feature", {}, [
{
line: 9,
messageElements: { location: "src/__tests__/rules/no-dupe-scenario-names/DublicateScenarioNames.feature:6" },
messageElements: {
name: "This is a non unique name",
location: "src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNames.feature:6",
},
},
{
line: 17,
messageElements: { location: "src/__tests__/rules/no-dupe-scenario-names/DublicateScenarioNames.feature:6, src/__tests__/rules/no-dupe-scenario-names/DublicateScenarioNames.feature:9" },
messageElements: {
name: "This is a non unique name",
location: "src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNames.feature:6, " +
"src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNames.feature:9" },
},
]);
});
it("raises errors when there duplicate Scenario and Scenario Outline names in multiple files", function () {
return runTest("no-dupe-scenario-names/DublicateScenarioNamesAcrossFiles1.feature", {}, [])
return runTest("no-dupe-scenario-names/DuplicateScenarioNamesAcrossFiles1.feature", {}, [])
.then(() => {
return runTest("no-dupe-scenario-names/DublicateScenarioNamesAcrossFiles2.feature", {}, [
return runTest("no-dupe-scenario-names/DuplicateScenarioNamesAcrossFiles2.feature", {}, [
{
line: 6,
messageElements: {
location: "src/__tests__/rules/no-dupe-scenario-names/DublicateScenarioNamesAcrossFiles1.feature:6",
name: "This is a Scenario",
location: "src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNamesAcrossFiles1.feature:6",
},
},
{
line: 9,
messageElements: {
location: "src/__tests__/rules/no-dupe-scenario-names/DublicateScenarioNamesAcrossFiles1.feature:9",
name: "This is a Scenario Outline",
location: "src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNamesAcrossFiles1.feature:9",
},
},
{
line: 17,
messageElements: {
location: "src/__tests__/rules/no-dupe-scenario-names/DublicateScenarioNamesAcrossFiles1.feature:17",
name: "This is an Example",
location: "src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNamesAcrossFiles1.feature:17",
},
},
]);
});
});
it("doesn't raise errors when there are duplicate scenario names in different files", function () {
return runTest("no-dupe-scenario-names/DublicateScenarioNamesAcrossFiles1.feature", "in-feature", [])
return runTest("no-dupe-scenario-names/DuplicateScenarioNamesAcrossFiles1.feature", { scope: "in-feature" }, [])
.then(() => {
return runTest("no-dupe-scenario-names/DublicateScenarioNamesAcrossFiles2.feature", "in-feature", []);
return runTest("no-dupe-scenario-names/DuplicateScenarioNamesAcrossFiles2.feature",
{ scope: "in-feature" },
[]);
});
});
it("doesn't raise errors when duplicate arguments applied but checkArguments:false", function () {
return runTest("no-dupe-scenario-names/DuplicateScenarioNamesInExamples.feature",
{ checkArguments: false, scope: "in-feature" },
[]);
});
it("raises errors when there are duplicate Scenario names with arguments applied", function () {
return runTest("no-dupe-scenario-names/DuplicateScenarioNamesInExamples.feature",
{ checkArguments: true, scope: "in-feature" },
[
{
line: 9,
messageElements: {
name: "This is a non unique field",
location: "src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNamesInExamples.feature:6",
},
},
{
line: 18,
messageElements: {
name: "This is a non unique name",
location: "src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNamesInExamples.feature:9",
},
},
{
line: 21,
messageElements: {
name: "This is a non unique argument",
location: "src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNamesInExamples.feature:21",
},
},
{
line: 28,
messageElements: {
name: "This is a non unique argument",
location: "src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNamesInExamples.feature:21, " +
"src/__tests__/rules/no-dupe-scenario-names/DuplicateScenarioNamesInExamples.feature:21",
},
},
]);
});
});
99 changes: 69 additions & 30 deletions src/rules/no-dupe-scenario-names.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,96 @@
import { Feature, File, ResultError, Scenario } from "../types";
import { Examples, Feature, File, ResultError, Scenario, TableRow } from "../types";
import chalk from "chalk";

export const name = "no-dupe-scenario-names";
export const availableConfigs = [
"anywhere",
"in-feature",
];

type AvailableConfigs = { "scope": "anywhere" | "in-feature"; "checkArguments": boolean; };

export const availableConfigs: AvailableConfigs = {
"scope": "in-feature",
"checkArguments": false,
};

let scenarios = [];

export function run(feature: Feature, file: File, configuration): ResultError[] {
export function run(feature: Feature, file: File, configuration: AvailableConfigs): ResultError[] {
if (!feature) {
return [];
}
const errors: ResultError[] = [];
if (configuration === "in-feature") {
if (configuration.scope === "in-feature") {
scenarios = [];
}
feature.children?.forEach(child => {
if (child.rule) {
child.rule.children?.filter(it => it.scenario)
.forEach(ruleChild => checkScenario(ruleChild.scenario!, file, errors));
.forEach(ruleChild => checkScenario(ruleChild.scenario!, file, errors, configuration.checkArguments));
}
if (child.scenario) {
checkScenario(child.scenario, file, errors);
checkScenario(child.scenario, file, errors, configuration.checkArguments);
}
});
return errors;
}

function checkScenario(scenario: Scenario, file: File, errors: ResultError[]) {
if (scenario.name) {
if (scenario.name in scenarios) {
const dupes = getFileLinePairsAsStr(scenarios[scenario.name].locations);
scenarios[scenario.name].locations.push({
file: file.relativePath,
line: scenario.location?.line,
});
errors.push({
message: `Scenario name is already used in: ${chalk.underline(dupes)}`,
rule: name,
line: scenario.location?.line || 0,
function checkScenario(scenario: Scenario, file: File, errors: ResultError[], checkArguments: boolean) {
if (checkArguments && scenario.examples?.length || 0 > 0) {
scenario.examples!.forEach(table => {
for (const row of table.tableBody || []) {
const scenarioName = parseFullName(table, row, scenario);
checkScenarioName(scenarioName, file, scenario, errors);
}
});
} else {
scenarios[scenario.name] = {
locations: [
{
file: file.relativePath,
line: scenario.location?.line,
},
],
};
} else {
checkScenarioName(scenario.name || "unknown", file, scenario, errors);
}
}

function checkScenarioName(
scenarioName: string,
file: File,
scenario: Scenario,
errors: ResultError[]) {
const location = {
file: file.relativePath,
line: scenario.location?.line || 0,
};
if (scenarioName in scenarios) {
const dupes = getFileLinePairsAsStr(scenarios[scenarioName].locations);
const message = `Scenario name "${chalk.yellow(scenarioName)}" is already used in: ${chalk.underline(dupes)}`;

scenarios[scenarioName].locations.push(location);
errors.push({
message: message,
rule: name,
line: location.line,
});
} else {
scenarios[scenarioName] = { locations: [location] };
}
}

interface ArgumentsMap { [key: string]: string; }

function parseFullName(table: Examples, row: TableRow, scenario: Scenario) {
const item: ArgumentsMap = { };
const header = table.tableHeader!.cells!;
for (let i = 0; i < row.cells!.length; i++) {
item[header[i].value || ""] = row.cells![i]!.value || "";
}
return applyArguments(scenario.name || "unknown", item);
}

function applyArguments(text: string, exampleArguments: ArgumentsMap | undefined): string {
if (exampleArguments === undefined) {
return text;
}
for (const argName in exampleArguments) {
if (!exampleArguments[argName]) {
continue;
}
text = text.replace(new RegExp(`<${argName}>`, "g"), `${exampleArguments[argName]}`);
}
return text;
}

function getFileLinePairsAsStr(objects): string {
Expand Down

0 comments on commit f325951

Please sign in to comment.