Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scenario Outlines / Examples #75

Closed
wants to merge 11 commits into from
70 changes: 70 additions & 0 deletions features/scenario_outlines.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Feature: Scenario Outlines and Examples

Scenario: Basic outline
Given the following feature:
"""
Feature: testing scenarios
Background:
Given a background step

Scenario Outline: outline
When a <some> step
Then i get <result>
Examples:
| some | result |
| passing | passed |
| failing | skipped |
"""
And the step "a background step" has a passing mapping
And the step "a passing step" has a passing mapping
And the step "a failing step" has a failing mapping
And the step "i get passed" has a passing mapping
And the step "i get skipped" has a passing mapping
When Cucumber runs the feature
Then the scenario called "outline" is reported as failing
And the step "a background step" passes
And the step "a passing step" passes
And the step "a failing step" passes
And the step "i get passed" passes
And the step "i get skipped" is skipped

Scenario: Outline with table
Given the following feature:
"""
Feature: testing scenarios
Scenario Outline: outline
When a table step:
| first | second |
| <first> | <second> |
Examples:
| first | second |
| 1 | 2 |
"""
And the step "a table step:" has a passing mapping that receives a data table
When Cucumber runs the feature
Then the received data table array equals the following:
"""
[["first","second"],["1","2"]]
"""

Scenario: Outline with doc string
Given the following feature:
"""
Feature: testing scenarios
Scenario Outline: outline
When a doc sting step:
\"\"\"
I am doc string in <example> example
And there are <string> string
\"\"\"
Examples:
| example | string |
| first | some |
"""
And the step "a doc sting step:" has a passing mapping that receives a doc string
When Cucumber runs the feature
Then the received doc string equals the following:
"""
I am doc string in first example
And there are some string
"""
13 changes: 13 additions & 0 deletions features/step_definitions/cucumber_steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ setTimeout(callback.pending, 10);\
callback();
});

Given(/^the step "([^"]*)" has a passing mapping that receives a doc string$/, function(stepName, callback) {
this.stepDefinitions += "Given(/^" + stepName + "$/, function(docString, callback) {\
world.docString = docString;\
callback();\
});";
callback();
});

Given(/^the following data table in a step:$/, function(dataTable, callback) {
this.featureSource += "Feature:\n";
this.featureSource += " Scenario:\n";
Expand Down Expand Up @@ -315,6 +323,11 @@ callback();\
callback();
});

Then(/^the received doc string equals the following:$/, function(docString, callback) {
this.assertEqual(docString, World.mostRecentInstance.docString);
callback();
});

this.Then(/^the explicit World object function should have been called$/, function(callback) {
this.assertTrue(this.explicitWorldFunctionCalled);
callback();
Expand Down
2 changes: 2 additions & 0 deletions lib/cucumber/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Ast.Feature = require('./ast/feature');
Ast.Features = require('./ast/features');
Ast.Filter = require('./ast/filter');
Ast.Scenario = require('./ast/scenario');
Ast.ScenarioOutline = require('./ast/scenario_outline');
Ast.Examples = require('./ast/examples');
Ast.Step = require('./ast/step');
Ast.Tag = require('./ast/tag');
module.exports = Ast;
7 changes: 6 additions & 1 deletion lib/cucumber/ast/assembler.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var Assembler = function(features, filter) {
var Assembler = function (features, filter) {
var currentFeature, currentScenarioOrBackground, currentStep;
var stashedTags = [];

Expand Down Expand Up @@ -81,6 +81,11 @@ var Assembler = function(features, filter) {
currentFeature.addScenario(scenario);
}
},
insertExamples: function insertExamples(examples) {
var currentScenarioOrBackground = self.getCurrentScenarioOrBackground();
currentScenarioOrBackground.setExamples(examples);
self.setCurrentStep(examples);
},

insertStep: function insertStep(step) {
self.setCurrentStep(step);
Expand Down
10 changes: 9 additions & 1 deletion lib/cucumber/ast/data_table.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ var DataTable = function() {
return self;
},

getRows: function getRows(){
var newRows = Cucumber.Type.Collection();
rows.syncForEach(function(row){
newRows.add(row);
});
return newRows;
},

raw: function raw() {
rawRows = [];
var rawRows = [];
rows.syncForEach(function(row) {
var rawRow = row.raw();
rawRows.push(rawRow);
Expand Down
4 changes: 4 additions & 0 deletions lib/cucumber/ast/data_table/row.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ var Row = function(cells, line) {
self = {
raw: function raw() {
return cells;
},
getLine: function getLine(){
return line;
}

};
return self;
}
Expand Down
49 changes: 49 additions & 0 deletions lib/cucumber/ast/examples.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
var Examples = function (keyword, name, description, line) {
var Cucumber = require('../../cucumber'),
dataTable,
self;
return self = {
getKeyword: function getKeyword() {
return keyword;
},

getName: function getName() {
return name;
},

getDescription: function getKeyword() {
return keyword;
},

getLine: function getLine() {
return line;
},

getDataTable: function getDataTable() {
return dataTable;
},

hasDataTable: function hasDataTable() {
return !!dataTable;
},

attachDataTable: function attachDataTable(_dataTable) {
dataTable = _dataTable;
},

attachDataTableRow: function attachDataTableRow(row) {
self.ensureDataTableIsAttached();
var dataTable = self.getDataTable();
dataTable.attachRow(row);
},

ensureDataTableIsAttached: function ensureDataTableIsAttached() {
var dataTable = self.getDataTable();
if (!dataTable) {
dataTable = Cucumber.Ast.DataTable();
self.attachDataTable(dataTable);
}
}
}
};
module.exports = Examples;
5 changes: 5 additions & 0 deletions lib/cucumber/ast/scenario.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var Scenario = function(keyword, name, description, line) {
var tags = [];

var self = {
payload_type: 'scenario',
setBackground: function setBackground(newBackground) {
background = newBackground;
},
Expand Down Expand Up @@ -40,6 +41,10 @@ var Scenario = function(keyword, name, description, line) {
return steps.getLast();
},

getSteps: function getSteps(){
return steps;
},

addTags: function setTags(newTags) {
tags = tags.concat(newTags);
},
Expand Down
88 changes: 88 additions & 0 deletions lib/cucumber/ast/scenario_outline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
var ScenarioOutline = function (keyword, name, description, line) {
var Cucumber = require('../../cucumber'),
self = Cucumber.Ast.Scenario(keyword, name, description, line),
examples;
self.payload_type = 'scenarioOutline';
self.setExamples = function (newExamples) {
examples = newExamples;
};
self.getExamples = function () {
return examples;
};
function applyExampleRow(example, steps) {
return steps.syncMap(function (outline) {
var name = outline.getName(),
table = Cucumber.Ast.DataTable(),
rows = [],
hasDocString = outline.hasDocString(),
hasDataTable = outline.hasDataTable(),
oldDocString = hasDocString ? outline.getDocString() : null,
docString = hasDocString ? oldDocString.getContents() : null,
hashKey;
if (hasDataTable){
rows = [];
outline.getDataTable().getRows().syncForEach(function(row){
rows.push(
{ line: row.getLine(), cells: JSON.stringify(row.raw()) }
);
});

}
for (hashKey in example) {
if (Object.prototype.hasOwnProperty.call(example, hashKey)) {
name = name.replace('<' + hashKey + '>', example[hashKey]);
if (hasDataTable) {
rows = rows.map(function(row){
return {line:row.line, cells:row.cells.replace('<' + hashKey + '>', example[hashKey])};
});
}
if (hasDocString) {
docString = docString.replace('<' + hashKey + '>', example[hashKey]);
}
}
}
var step = Cucumber.Ast.Step(outline.getKeyword(), name, outline.getLine());
if (hasDataTable) {
rows.forEach(function(row){
table.attachRow( Cucumber.Ast.DataTable.Row( JSON.parse(row.cells), row.line) );
});
step.attachDataTable(table);
}
if (hasDocString) {
step.attachDocString( Cucumber.Ast.DocString(oldDocString.getContentType(), docString, oldDocString.getLine()));
}
return step;
});
}
self.acceptVisitor = function (visitor, callback) {
var rows = examples.getDataTable().getRows(),
first_row = rows.shift().raw();
rows.syncForEach(function(row, index){
var length = first_row.length,
i;
row.example = {};
row.id = index.toString();
for (i = 0; i < length; i++){
row.example[first_row[i]] = row.raw()[i];
}
});

rows.forEach(function (row, iterate){
self.instructVisitorToVisitRowSteps(visitor, row, iterate);
},callback)
};
self.instructVisitorToVisitRowSteps = function (visitor, row, callback) {
visitor.visitRow(row, self, callback);

};
self.visitRowSteps = function (visitor, row, callback) {
self.instructVisitorToVisitBackgroundSteps(visitor, function () {
var newSteps = self.applyExampleRow(row.example, self.getSteps());
self.instructVisitorToVisitSteps(visitor, newSteps, callback);
});
};

return self;
};
module.exports = ScenarioOutline;

36 changes: 22 additions & 14 deletions lib/cucumber/listener/progress_formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,24 @@ var ProgressFormatter = function(options) {
options = {};
if (options['logToConsole'] == undefined)
options['logToConsole'] = true;

function handleAfterScenarioEvent(payload){
return function(event, callback){
if (self.isCurrentScenarioFailing()) {
var scenario = event.getPayloadItem(payload);
self.storeFailedScenario(scenario);
self.witnessFailedScenario();
} else if (self.isCurrentScenarioUndefined()) {
self.witnessUndefinedScenario();
} else if (self.isCurrentScenarioPending()) {
self.witnessPendingScenario();
} else {
self.witnessPassedScenario();
}
callback();
}
}

var self = {
log: function log(string) {
logs += string;
Expand Down Expand Up @@ -67,6 +85,9 @@ var ProgressFormatter = function(options) {
callback();
},

handleBeforeScenarioOutlineEvent: this.handleBeforeScenarioEvent,
handleAfterScenarioOutlineEvent: handleAfterScenarioEvent('scenarioOutline'),

handleStepResultEvent: function handleStepResult(event, callback) {
var stepResult = event.getPayloadItem('stepResult');
if (stepResult.isSuccessful())
Expand Down Expand Up @@ -118,20 +139,7 @@ var ProgressFormatter = function(options) {
callback();
},

handleAfterScenarioEvent: function handleAfterScenarioEvent(event, callback) {
if (self.isCurrentScenarioFailing()) {
var scenario = event.getPayloadItem('scenario');
self.storeFailedScenario(scenario);
self.witnessFailedScenario();
} else if (self.isCurrentScenarioUndefined()) {
self.witnessUndefinedScenario();
} else if (self.isCurrentScenarioPending()) {
self.witnessPendingScenario();
} else {
self.witnessPassedScenario();
}
callback();
},
handleAfterScenarioEvent: handleAfterScenarioEvent('scenario'),

prepareBeforeScenario: function prepareBeforeScenario() {
currentScenarioFailing = false;
Expand Down
12 changes: 11 additions & 1 deletion lib/cucumber/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ var Parser = function(featureSources, astFilter) {
feature: self.handleFeature,
row: self.handleDataTableRow,
scenario: self.handleScenario,
scenario_outline: self.handleScenarioOutline,
examples: self.handleExamples,
step: self.handleStep,
tag: self.handleTag

};
},

Expand Down Expand Up @@ -63,7 +66,14 @@ var Parser = function(featureSources, astFilter) {
var scenario = Cucumber.Ast.Scenario(keyword, name, description, line);
astAssembler.insertScenario(scenario);
},

handleScenarioOutline: function handleScenarioOutline(keyword, name, description, line) {
var outline = Cucumber.Ast.ScenarioOutline(keyword, name, description, line);
astAssembler.insertScenario(outline);
},
handleExamples: function handleExamples(keyword, name, description, line) {
var examples = Cucumber.Ast.Examples(keyword, name, description, line);
astAssembler.insertExamples(examples);
},
handleStep: function handleStep(keyword, name, line) {
var step = Cucumber.Ast.Step(keyword, name, line);
astAssembler.insertStep(step);
Expand Down
Loading