Skip to content

Commit

Permalink
Feature/29/scenario outline expander (#30)
Browse files Browse the repository at this point in the history
* created precompiler

* increased coverage

* Documentation created - ScenarioOutlineExpander
  • Loading branch information
sorosz89 committed Apr 20, 2018
1 parent 6269133 commit a8e4e1d
Show file tree
Hide file tree
Showing 19 changed files with 278 additions and 46 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## 1.3.0 - 2018-04-19

### Added

- Added predefined precompiler: scenarioOutlineExpander ([#29]
(https://github.com/judit-nahaj/gherkin-precompiler/issues/29))

### Changeed

- Upgraded for gherkin-assembler @2 and gherkin-ast

## 1.2.1 - 2018-02-14

### Fixed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ compiler.save('./features/dist/login.feature', ast, {
* [RemoveDuplicates](lib/builtIn/RemoveDuplicates.md) - Removes duplicated tags or example data table rows.
* [Replacer](lib/builtIn/Replacer.md) - Replaces keywords in the feature files.
* [ScenarioNumbering](lib/builtIn/ScenarioNumbering.md) - Adds an index to all scenario and scenario outline's name.
* [ScenarioOutlineExpander](lib/builtIn/ScenarioOutlineExpander.md) - Expand the Scenario Outlines to actual scenarios.
* [ScenarioOutlineNumbering](lib/builtIn/ScenarioOutlineNumbering.md) - Makes all scenario, generated from scenario outlines unique.
* [SteoGroups](lib/builtIn/StepGroups.md) - Corrects the gherkin keywords of steps to make the tests more readable.

Expand Down
2 changes: 1 addition & 1 deletion lib/builtIn/Macro.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const DefaultConfig = require('../DefaultConfig');
const MACROSTEP = /^macro (.*) ?is executed$/;
const {Step} = require('gherkin-assembler').AST;
const {Step} = require('gherkin-ast');

/**
* Macro to create a Cucumber step by combining a block of steps, simplifying often recurring steps.
Expand Down
2 changes: 1 addition & 1 deletion lib/builtIn/RemoveDuplicates.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const DefaultConfig = require('../DefaultConfig');
const {Tag} = require('gherkin-assembler').AST;
const {Tag} = require('gherkin-ast');
const ObjectSet = require('object-set-type');

/**
Expand Down
2 changes: 1 addition & 1 deletion lib/builtIn/ScenarioNumbering.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const DefaultConfig = require('../DefaultConfig');
const {Background} = require('gherkin-assembler').AST;
const {Background} = require('gherkin-ast');

/**
* @typedef {Object} ScenarioNumberConfiguration
Expand Down
35 changes: 35 additions & 0 deletions lib/builtIn/ScenarioOutlineExpander.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';

const DefaultConfig = require('../DefaultConfig');

/**
* @typedef {Object} ScenarioOutlineExpanderConfiguration
* @property {string} ignoreTag
*/
const DEFAULT_CONFIG = {
ignoreTag: '@notExpand'
};
/**
* The ScenarioOutlineExpander precompiler is responsible
* for converting ScenarioOutlines to single Scenarios using the
* first column of the Example table.
*/
class ScenarioOutlineExpander extends DefaultConfig {
/**
* @constructor
* @param {ScenarioOutlineExpanderConfig|Object} config
*/
constructor(config) {
super();
this.config = Object.assign({}, DEFAULT_CONFIG, config || {});
}

onScenarioOutline(outline) {
if (!outline.tags.length || !outline.tags.some(tag => this.config.ignoreTag === tag.name)) {
return outline.toScenario();
}
outline.tags = outline.tags.filter(tag => tag.name !== this.config.ignoreTag)
}
}

module.exports = ScenarioOutlineExpander;
44 changes: 44 additions & 0 deletions lib/builtIn/ScenarioOutlineExpander.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# ScenarioOutlineExpander for Gherkin precompiler

This precompiler is responsible for converting Scenario Outlines to single Scenarios as Cucumber would do and adds the first column as a tag.

Example:
```gherkin
@tag1
Scenario Outline: Test language (<language>)
Given I am on Home page <language> user
When <language> language is choosen
Then I should be on Home page
And the title should be "<title>"
@tag2
Examples:
| language | title |
| EN | Welcome |
| FR | Bienvenue |
```
It will be modified to:

```gherkin
@tag1 @tag2 @language(EN)
Scenario: Test language (EN)
Given I am on Home page EN user
When EN language is choosen
Then I should be on Home page
And the title should be "Welcome"
@tag1 @tag2 @language(FR)
Scenario: Test language (FR)
Given I am on Home page FR user
When FR language is choosen
Then I should be on Home page
And the title should be "Bienvenue"
```

## Usage

The precompiler accepts the following configuration:

| Option | type | Description |Default|
|:------:|:----:|:------------|:-----:|
|`ignoreTag`|`String`| Tag used to mark scenarios to be ignored during expanding Scenario Outlines |`@notExpand`|
2 changes: 1 addition & 1 deletion lib/builtIn/ScenarioOutlineNumbering.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const DefaultConfig = require('../DefaultConfig');
const {TableCell} = require('gherkin-assembler').AST;
const {TableCell} = require('gherkin-ast');

/**
* @typedef {Object} ScenarioOutlineNumberConfiguration
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ API.builtIn.Macro = require('./builtIn/Macro');
API.builtIn.RemoveDuplicates = require('./builtIn/RemoveDuplicates');
API.builtIn.Replacer = require('./builtIn/Replacer');
API.builtIn.ScenarioNumbering = require('./builtIn/ScenarioNumbering');
API.builtIn.ScenarioOutlineExpander = require('./builtIn/ScenarioOutlineExpander');
API.builtIn.ScenarioOutlineNumbering = require('./builtIn/ScenarioOutlineNumbering');
API.builtIn.StepGroups = require('./builtIn/StepGroups');

Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gherkin-precompiler",
"version": "1.2.1",
"version": "1.3.0",
"description": "Simple pre-compiler for Gherkin feature files",
"main": "lib/index.js",
"bin": {
Expand Down Expand Up @@ -48,7 +48,8 @@
"dependencies": {
"fs-extra": "^5.0.0",
"gherkin": "^5.0.1",
"gherkin-assembler": "^1.0.0",
"gherkin-assembler": "^2.0.0",
"gherkin-ast": "^1.1.0",
"glob": "^7.1.2",
"gulp-util": "^3.0.8",
"object-set-type": "^1.0.1",
Expand Down
20 changes: 10 additions & 10 deletions test/builtIn/ForLoop.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const ForLoop = require(path.resolve('lib/builtIn/ForLoop.js'));
const expect = require('chai').expect;
const API = require(path.resolve('lib'));
const sinon = require('sinon');
const assembler = require('gherkin-assembler');
const {Scenario, Tag} = require('gherkin-ast');


describe('builtIn.ForLoop', () => {
Expand Down Expand Up @@ -45,24 +45,24 @@ describe('builtIn.ForLoop', () => {
describe('getIterationNumber', () => {
it('should return 0 when there is no loop tag', () => {
const loop = new ForLoop();
const scenario = new assembler.AST.Scenario('Scenario', 'Scenario name');
const scenario = new Scenario('Scenario', 'Scenario name');

expect(loop.getIterationNumber(scenario)).to.eql(0);
});

it('should throw an error when provided iterator exceeds maximum', () => {
const loop = new ForLoop();
const scenario = new assembler.AST.Scenario('Scenario', 'Scenario name');
const tag = new assembler.AST.Tag('@loop(42)');
const scenario = new Scenario('Scenario', 'Scenario name');
const tag = new Tag('@loop(42)');
scenario.tags.push(tag);

expect(() => loop.getIterationNumber(scenario)).to.throw(Error);
});

it('should return the correct iterator', () => {
const loop = new ForLoop();
const scenario = new assembler.AST.Scenario('Scenario', 'Scenario name');
const tag = new assembler.AST.Tag('@loop(4)');
const scenario = new Scenario('Scenario', 'Scenario name');
const tag = new Tag('@loop(4)');
scenario.tags.push(tag);

expect(loop.getIterationNumber(scenario)).to.eql(4);
Expand All @@ -80,7 +80,7 @@ describe('builtIn.ForLoop', () => {

it('should repeat scenarios for the correct times', () => {
const loop = new ForLoop();
const scenario = new assembler.AST.Scenario('Scenario', 'Scenario name');
const scenario = new Scenario('Scenario', 'Scenario name');
loop.getIterationNumber = sinon.stub().returns(3);
const result = loop.looper(scenario);

Expand All @@ -90,7 +90,7 @@ describe('builtIn.ForLoop', () => {

it('should process scenario names', () => {
const loop = new ForLoop();
const scenario = new assembler.AST.Scenario('Scenario', 'Scenario name');
const scenario = new Scenario('Scenario', 'Scenario name');
loop.getIterationNumber = sinon.stub().returns(3);
const result = loop.looper(scenario);

Expand Down Expand Up @@ -118,14 +118,14 @@ describe('builtIn.ForLoop', () => {

it('should filter out loop tags', () => {
const loop = new ForLoop();
const tag = new assembler.AST.Tag('@loop(2)');
const tag = new Tag('@loop(2)');

expect(loop.preFilterTag(tag)).to.be.false;
});

it('should not filter out non loop tags', () => {
const loop = new ForLoop();
const tag = new assembler.AST.Tag('@not_loop(2)');
const tag = new Tag('@not_loop(2)');

expect(loop.preFilterTag(tag)).to.be.true;
});
Expand Down
50 changes: 25 additions & 25 deletions test/builtIn/Macro.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const path = require('path');
const Macro = require(path.resolve('lib/builtIn/Macro.js'));
const expect = require('chai').expect;
const API = require(path.resolve('lib'));
const assembler = require('gherkin-assembler');
const {Scenario, Tag, Step} = require('gherkin-ast');


describe('builtIn.Macro', () => {
Expand All @@ -21,26 +21,26 @@ describe('builtIn.Macro', () => {
});

it('should not filter out non macro scenarios', () => {
const scenario = new assembler.AST.Scenario();
const tag = new assembler.AST.Tag('@notmacro');
const scenario = new Scenario();
const tag = new Tag('@notmacro');

scenario.tags.push(tag);
expect(macro.preFilterScenario(scenario)).to.be.undefined;
});

it('should throw an error when no name is provided for macro', () => {
const scenario = new assembler.AST.Scenario();
const tag = new assembler.AST.Tag('@macro()');
const scenario = new Scenario();
const tag = new Tag('@macro()');

scenario.tags.push(tag);
expect(() => macro.preFilterScenario(scenario)).to.throw(Error)
});

it('should throw an error when a macro name is already defined', () => {
const scenario = new assembler.AST.Scenario();
const scenario_2 = new assembler.AST.Scenario();
const tag = new assembler.AST.Tag('@macro(testname)');
const step = new assembler.AST.Step('Given', 'step');
const scenario = new Scenario();
const scenario_2 = new Scenario();
const tag = new Tag('@macro(testname)');
const step = new Step('Given', 'step');

scenario.tags.push(tag);
scenario_2.tags.push(tag);
Expand All @@ -52,27 +52,27 @@ describe('builtIn.Macro', () => {
});

it('should throw an error when a macro does not contain any steps', () => {
const scenario = new assembler.AST.Scenario();
const tag = new assembler.AST.Tag('@macro(test)');
const scenario = new Scenario();
const tag = new Tag('@macro(test)');

scenario.tags.push(tag);
expect(() => macro.preFilterScenario(scenario)).to.throw(Error)
});

it('should throw an error when a macro contains a macro step', () => {
const scenario = new assembler.AST.Scenario();
const tag = new assembler.AST.Tag('@macro(test)');
const step = new assembler.AST.Step('And', 'macro test is executed');
const scenario = new Scenario();
const tag = new Tag('@macro(test)');
const step = new Step('And', 'macro test is executed');

scenario.tags.push(tag);
scenario.steps.push(step);
expect(() => macro.preFilterScenario(scenario)).to.throw(Error)
});

it('should process macro scenarios', () => {
const scenario = new assembler.AST.Scenario();
const tag = new assembler.AST.Tag('@macro(test_tag)');
const step = new assembler.AST.Step('Given', 'random step');
const scenario = new Scenario();
const tag = new Tag('@macro(test_tag)');
const step = new Step('Given', 'random step');

scenario.tags.push(tag);
scenario.steps.push(step);
Expand All @@ -83,27 +83,27 @@ describe('builtIn.Macro', () => {
});

it('should not change non macro steps', () => {
const step = new assembler.AST.Step('Given', 'random step');
const step = new Step('Given', 'random step');
expect(macro.onStep(step)).to.eql(undefined);
});

it('should throw an error when name is not provided for macro', () => {
const step = new assembler.AST.Step('And', 'macro is executed');
const step = new Step('And', 'macro is executed');
expect(() => {
macro.onStep(step);
}).to.throw(Error)
});

it('should throw an error when there is no macro by name provided', () => {
const step = new assembler.AST.Step('And', 'macro test2 is executed');
const step = new Step('And', 'macro test2 is executed');
expect(() => macro.onStep(step)).to.throw(Error)
});

it('should process macro steps', () => {
const scenario_macro = new assembler.AST.Scenario();
const tag = new assembler.AST.Tag('@macro(test)');
const step_macro = new assembler.AST.Step('When', 'test step');
const step_test = new assembler.AST.Step('And', 'macro test is executed');
const scenario_macro = new Scenario();
const tag = new Tag('@macro(test)');
const step_macro = new Step('When', 'test step');
const step_test = new Step('And', 'macro test is executed');

scenario_macro.tags.push(tag);
scenario_macro.steps.push(step_macro);
Expand All @@ -122,7 +122,7 @@ describe('builtIn.Macro', () => {

it('should have a method to create a macro step', () => {
const step = Macro.createStep("TestMacro");
expect(step).to.be.instanceOf(assembler.AST.Step);
expect(step).to.be.instanceOf(Step);
expect(step.toString()).to.equal("When macro TestMacro is executed");
})

Expand Down
2 changes: 1 addition & 1 deletion test/builtIn/RemoveDuplicates.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

const {resolve} = require('path');
const {Tag, Examples, TableRow, TableCell} = require('gherkin-assembler').AST;
const {Tag, Examples, TableRow, TableCell} = require('gherkin-ast');
const RemoveDuplicates = require(resolve('lib/builtIn/RemoveDuplicates.js'));
const API = require(resolve('lib'));
const expect = require('chai').expect;
Expand Down
Loading

0 comments on commit a8e4e1d

Please sign in to comment.