Skip to content

Commit

Permalink
Merge pull request #78 from oss-specs/feature/parsingSteps
Browse files Browse the repository at this point in the history
Add test and implementation for parsing steps.
  • Loading branch information
jimCresswell committed Aug 13, 2015
2 parents 856187e + 1f0b67b commit a6a12fa
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 40 deletions.
93 changes: 72 additions & 21 deletions features-support/step_definitions/parsing.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,74 @@ function unwrapSingleColumnTable(singleColumnTable) {
return (singleColumnTable.raw()).map(function (valueWrappedInArray) {return valueWrappedInArray[0]});
}

var scenarioNumberToIndex = {
"background": 0,
"default": 1,
"first": 1,
"second": 2,
"third": 3,
"fourth": 4
};

module.exports = function() {
// Shared variables
var featureText;
var features;

function compareFeatureValues(key) {
return function compare(table) {
var featureValues = features[0][key];
var expectedValues = unwrapSingleColumnTable(table);
featureValues.should.containDeepOrdered(expectedValues);
}
}

// TODO: FAR TOO COMPLICATED! Maybe remove the conditionals
// TODO: by having two different functions.
// key1 is a key on the scenario
// key2 is an optional key on a sub-object.
function compareScenarioValues(key1, key2) {
return function compare(scenarioNumber, table) {
var done = undefined;

/**
* Sometimes just a table is passed in the first argument slot,
* in that case adjust the parameters.
*
* CucumberJS determines if the step definition completion
* should be dependent on a callback by counting the number
* of arguments, so we have to cope with that.
**/
if (typeof scenarioNumber === "object") {
done = table;
table = scenarioNumber;
scenarioNumber = "default";
}

var scenarioIndex = scenarioNumberToIndex[scenarioNumber];
var scenario = features[0].scenarios[scenarioIndex];
var scenarioValues;

// If the suboject key is specified dig the values out of the objects.
// e.g. get the names of steps out of an array of steps.
// c.f. https://lodash.com/docs#pluck .
if (key2) {
scenarioValues = scenario[key1].map(function(subObject) {return subObject[key2]; });
} else {
scenarioValues = scenario[key1];
}

var expectedValues = unwrapSingleColumnTable(table);

// Do the test.
scenarioValues.should.containDeepOrdered(expectedValues);

if (typeof done === "function") {
done();
}
}
}

this.Given(/^the feature file\.?$/, function (string) {
featureText = string;
});
Expand All @@ -29,39 +92,27 @@ module.exports = function() {
features[0].name.should.be.exactly(featureTitle);
});

this.Then(/^a background with the title "([^"]*)"\.?$/, function (backgroundTitle) {
features[0].backgrounds[0].name.should.be.exactly(backgroundTitle);
this.Then(/^I get a background with the title "([^"]*)"\.?$/, function (backgroundTitle) {
features[0].scenarios[0].name.should.be.exactly(backgroundTitle);
});

this.Then(/^scenarios with titles$/, function (table) {
for(var i = 0; i < table.raw().length; i++) {
var row = table.raw()[i];
// TODO: Make more expressive.
this.Then(/^I get scenarios with titles\.?$/, function (table) {
var expectedValues = unwrapSingleColumnTable(table);
for(var i = 1; i < table.raw().length; i++) {
var row = table.raw()[i-1];
var scenario = features[0].scenarios[i];
scenario.name.should.be.exactly(row[0]);
}
});

function compareFeatureValues(key) {
return function compare(table) {
var featureValues = features[0][key];
var expectedValues = unwrapSingleColumnTable(table);
featureValues.should.containDeepOrdered(expectedValues);
}
}

function compareScenarioValues(key) {
return function compare(table) {
var featureValues = features[0].scenarios[0][key];
var expectedValues = unwrapSingleColumnTable(table);
featureValues.should.containDeepOrdered(expectedValues);
}
}

this.Then(/^feature tags are associated with features\.?$/, compareFeatureValues('tags'));

this.Then(/^scenario tags are associated with scenarios\.?$/, compareScenarioValues('tags'));

this.Then(/^feature comments are associated with features\.?$/, compareFeatureValues('comments'));

this.Then(/^scenario comments are associated with scenarios\.?$/, compareScenarioValues('comments'));

this.Then(/^the "([^"]*)" scenario has steps with the names\.?$/, compareScenarioValues('steps', 'name'));;
};
20 changes: 15 additions & 5 deletions features/feature-parsing/parsing-specifications.feature
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ Feature: Parsing specifications
Feature: coping with multiple features in a file.
"""

@parsing @dev
@parsing
Scenario: Parse titles
When I parse this specification
Then I get a feature with title "Feature title"
And a background with the title "Backgrounds exist"
And scenarios with titles
| Scenario 1 |
| Scenario 2 |
And I get a background with the title "Backgrounds exist"
And I get scenarios with titles
| Scenario 1 |
| Scenario 2 |

@parsing
Scenario: Parse tags
Expand All @@ -77,3 +77,13 @@ Feature: Parsing specifications
| # A feature comment. |
And scenario comments are associated with scenarios
| # A scenario comment. |

@parsing
Scenario: Parse steps
When I parse this specification
Then the "first" scenario has steps with the names
| something is true |
| there is an outcome |
And the "second" scenario has steps with the names
| I have an "argument" |
| I expect |
25 changes: 12 additions & 13 deletions lib/parser/featureModels/featureFactory.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
'use strict';
/* eslint camelcase: 0 */

var scenarioOrBackgroundFactory = require('./scenarioOrBackgroundFactory');

function Feature(config) {
this.token = 'feature';
this.backgrounds = [];

// Contains backgrounds and scenarios.
this.scenarios = [];
this.keyword = config.keyword;
this.name = config.name;
this.description = config.description;
this.line = config.line;
this.tags = config.tags;
this.comments = config.comments;
};
this.scenarios.getLatest = function() { return this[this.length - 1]; }

function ScenarioOrBackground(config) {
this.token = config.keyword.toLowerCase();
this.keyword = config.keyword;
this.name = config.name;
this.description = config.description;
this.line = config.line;
this.tags = config.tags;
this.comments = config.comments;
}
};

Feature.prototype.addScenarioOrBackground = function(config) {
var key = config.keyword.toLowerCase() + 's';
this[key].push(new ScenarioOrBackground(config));
this.scenarios.push(scenarioOrBackgroundFactory(config));
};

Feature.prototype.addStep = function(config) {
var currentScenario = this.scenarios.getLatest();
currentScenario.addStep(config);
};

// Export the feature factory.
Expand Down
28 changes: 28 additions & 0 deletions lib/parser/featureModels/scenarioOrBackgroundFactory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';
/* eslint camelcase: 0 */

function Step(config) {
this.token = 'step';
this.keyword = config.keyword;
this.name = config.name;
this.line = config.line;
}

function ScenarioOrBackground(config) {
this.steps = [];
this.token = config.keyword.toLowerCase();
this.keyword = config.keyword;
this.name = config.name;
this.description = config.description;
this.line = config.line;
this.tags = config.tags;
this.comments = config.comments;
}

ScenarioOrBackground.prototype.addStep = function(config) {
this.steps.push(new Step(config));
}

module.exports = function(config) {
return new ScenarioOrBackground(config);
}
15 changes: 14 additions & 1 deletion lib/parser/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ var commentsFactory = require('./featureModels/commentsFactory.js');

module.exports = function Visitor() {
var features = [];
features.getLatest = function() { return this[this.length - 1]; };

var tags = tagsFactory();
var comments = commentsFactory();

function backgroundOrScenarioParser(keyword, name, description, line) {
var localTags = tags.flush();
var localComments = comments.flush();

var feature = features[features.length - 1];
var feature = features.getLatest();
if (feature === undefined) {
throw new TypeError(keyword + ' found outside of feature, giving up parsing.');
}
Expand Down Expand Up @@ -57,6 +59,17 @@ module.exports = function Visitor() {
examples: function(keyword, name, description, line) {
},
step: function(keyword, name, line) {
var feature = features.getLatest();

if (feature === undefined) {
throw new TypeError(keyword + ' found outside of feature, giving up parsing.');
}

feature.addStep({
keyword: keyword,
name: name,
line: line
});
},
doc_string: function(content_type, string, line) {
},
Expand Down

0 comments on commit a6a12fa

Please sign in to comment.