From 4f6bcc45a98e5e579bb2f5c7fababe78359f5e28 Mon Sep 17 00:00:00 2001 From: Simon Dean Date: Sat, 30 Aug 2014 18:09:21 +0100 Subject: [PATCH] Scenario outline fixes This pull request fixes the following issues: - #177 Empty Scenario Outline still calls Before and After hooks - #180 Execution order of scenarios and scenario outlines in a feature - #185 Before/After hooks on Scenario Outline called incorrectly - #217 Scenario Outlines with multiple Examples sections only executing the very last Examples - #224 Issue with tables in feature in version 0.4.2 --- features/json_formatter.feature | 556 ++++++++++++++++-- lib/cucumber/ast/assembler.js | 60 +- lib/cucumber/ast/data_table.js | 2 +- lib/cucumber/ast/feature.js | 27 +- lib/cucumber/ast/scenario.js | 10 +- lib/cucumber/ast/scenario_outline.js | 36 +- lib/cucumber/parser.js | 2 +- lib/cucumber/runtime/ast_tree_walker.js | 6 +- .../step_definition_snippet_builder.js | 1 - lib/cucumber/type/collection.js | 16 + spec/cucumber/ast/assembler_spec.js | 180 ++---- spec/cucumber/ast/data_table_spec.js | 75 ++- spec/cucumber/ast/examples_spec.js | 10 +- spec/cucumber/ast/feature_spec.js | 139 ++++- spec/cucumber/ast/scenario_outline_spec.js | 45 +- spec/cucumber/ast/scenario_spec.js | 6 + spec/cucumber/runtime/ast_tree_walker_spec.js | 1 - spec/cucumber/type/collection_spec.js | 44 +- 18 files changed, 896 insertions(+), 320 deletions(-) diff --git a/features/json_formatter.feature b/features/json_formatter.feature index 913087422..efa53e6fa 100644 --- a/features/json_formatter.feature +++ b/features/json_formatter.feature @@ -116,7 +116,6 @@ Feature: JSON Formatter }; module.exports = cucumberSteps; """ - When I run `cucumber.js -f json` Then it outputs this json: """ @@ -179,7 +178,6 @@ Feature: JSON Formatter }; module.exports = cucumberSteps; """ - When I run `cucumber.js -f json` Then it outputs this json: """ @@ -323,7 +321,7 @@ Feature: JSON Formatter ] """ - Scenario: output JSON for a scenario with a passing step follwed by one that is pending and one that fails + Scenario: output JSON for a scenario with a passing step followed by one that is pending and one that fails Given a file named "features/a.feature" with: """ Feature: some feature @@ -397,10 +395,10 @@ Feature: JSON Formatter ] """ - Scenario: output JSON for a scenario with a pending step follwed by one that passes and one that fails + Scenario: output JSON for a scenario with a pending step followed by one that passes and one that fails Given a file named "features/a.feature" with: """ - Feature: some feature + Feature: some feature Scenario: I've declared one step which is passing, one pending and one failing. Given This step is pending @@ -550,17 +548,18 @@ Feature: JSON Formatter } ] """ + Scenario: output JSON for multiple features Given a file named "features/a.feature" with: """ - Feature: feature a + Feature: feature a Scenario: This is the first feature Given This step is passing """ And a file named "features/b.feature" with: """ - Feature: feature b + Feature: feature b Scenario: This is the second feature Given This step is passing @@ -676,10 +675,11 @@ Feature: JSON Formatter } ] """ + Scenario: output JSON for multiple features each with multiple scenarios Given a file named "features/a.feature" with: """ - Feature: feature a + Feature: feature a Scenario: This is the feature a scenario one Given This step is passing @@ -692,7 +692,7 @@ Feature: JSON Formatter """ And a file named "features/b.feature" with: """ - Feature: feature b + Feature: feature b Scenario: This is the feature b scenario one Given This step is passing @@ -705,7 +705,7 @@ Feature: JSON Formatter """ And a file named "features/c.feature" with: """ - Feature: feature c + Feature: feature c Scenario: This is the feature c scenario one Given This step is passing @@ -947,7 +947,7 @@ Feature: JSON Formatter Feature: some feature Background: - Given This applies to all scenarios + Given This applies to all scenarios """ And a file named "features/step_definitions/cucumber_steps.js" with: """ @@ -1106,13 +1106,13 @@ Feature: JSON Formatter Scenario: output JSON for background step with a DocString Given a file named "features/a.feature" with: """ - Feature: some feature + Feature: some feature - Background: Background with DocString - Given we have this DocString: - \"\"\" - This is a DocString - \"\"\" + Background: Background with DocString + Given we have this DocString: + \"\"\" + This is a DocString + \"\"\" """ And a file named "features/step_definitions/cucumber_steps.js" with: """ @@ -1123,39 +1123,39 @@ Feature: JSON Formatter """ When I run `cucumber.js -f json` Then it outputs this json: - """ - [ - { - "id": "some-feature", - "name": "some feature", - "description": "", - "line": 1, - "keyword": "Feature", - "uri": "/features/a.feature", - "elements": [ - { - "name": "Background with DocString", - "keyword": "Background", - "description": "", - "type": "background", - "line": 3, - "steps": [ - { - "name": "we have this DocString:", - "line": 4, - "keyword": "Given ", - "doc_string": { - "value": "This is a DocString", - "line": 5, - "content_type": "" + """ + [ + { + "id": "some-feature", + "name": "some feature", + "description": "", + "line": 1, + "keyword": "Feature", + "uri": "/features/a.feature", + "elements": [ + { + "name": "Background with DocString", + "keyword": "Background", + "description": "", + "type": "background", + "line": 3, + "steps": [ + { + "name": "we have this DocString:", + "line": 4, + "keyword": "Given ", + "doc_string": { + "value": "This is a DocString", + "line": 5, + "content_type": "" + } } - } - ] - } - ] - } - ] - """ + ] + } + ] + } + ] + """ Scenario: output JSON for a feature with tags Given a file named "features/a.feature" with: @@ -1414,3 +1414,461 @@ Feature: JSON Formatter } ] """ + + Scenario: output JSON for a feature with one scenario outline with no examples tables + Given a file named "features/a.feature" with: + """ + Feature: some feature + + Scenario Outline: I've declared one step which passes + Given This step is passing + """ + When I run `cucumber.js -f json` + Then it outputs this json: + """ + [ + { + "id": "some-feature", + "name": "some feature", + "description": "", + "line": 1, + "keyword": "Feature", + "uri": "/features/a.feature" + } + ] + """ + + Scenario: output JSON for a feature with one scenario outline with an examples table with no rows + Given a file named "features/a.feature" with: + """ + Feature: some feature + + Scenario Outline: I've declared one step which passes + Given This step is passing + + Examples: + | instance | + """ + When I run `cucumber.js -f json` + Then it outputs this json: + """ + [ + { + "id": "some-feature", + "name": "some feature", + "description": "", + "line": 1, + "keyword": "Feature", + "uri": "/features/a.feature" + } + ] + """ + + Scenario: output JSON for a feature with one scenario outline with an examples table with two rows + Given a file named "features/a.feature" with: + """ + Feature: some feature + + Scenario Outline: I've declared one step which passes + Given This step is passing + + Examples: + | instance | + | first | + | second | + """ + And a file named "features/step_definitions/cucumber_steps.js" with: + """ + var cucumberSteps = function() { + this.Given(/^This (first|second) step is passing$/, function(instance, callback) { callback(); }); + }; + module.exports = cucumberSteps; + """ + When I run `cucumber.js -f json` + Then it outputs this json: + """ + [ + { + "id": "some-feature", + "name": "some feature", + "description": "", + "line": 1, + "keyword": "Feature", + "uri": "/features/a.feature", + "elements": [ + { + "name": "I've declared one step which passes", + "id": "some-feature;i've-declared-one-step-which-passes", + "line": 3, + "keyword": "Scenario", + "description": "", + "type": "scenario", + "steps": [ + { + "name": "This first step is passing", + "line": 4, + "keyword": "Given ", + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + } + ] + }, + { + "name": "I've declared one step which passes", + "id": "some-feature;i've-declared-one-step-which-passes", + "line": 3, + "keyword": "Scenario", + "description": "", + "type": "scenario", + "steps": [ + { + "name": "This second step is passing", + "line": 4, + "keyword": "Given ", + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + } + ] + } + ] + } + ] + """ + + Scenario: output JSON for a feature with one scenario outline with an examples table with two rows and a background + Given a file named "features/a.feature" with: + """ + Feature: some feature + Background: + Given This applies to all scenarios + + Scenario Outline: I've declared one step which passes + Given This step is passing + + Examples: + | instance | + | first | + """ + And a file named "features/step_definitions/cucumber_steps.js" with: + """ + var cucumberSteps = function() { + this.Given(/^This applies to all scenarios$/, function(callback) { callback(); }); + this.Given(/^This (first|second) step is passing$/, function(instance, callback) { callback(); }); + }; + module.exports = cucumberSteps; + """ + When I run `cucumber.js -f json` + Then it outputs this json: + """ + [ + { + "id": "some-feature", + "name": "some feature", + "description": "", + "line": 1, + "keyword": "Feature", + "uri": "/features/a.feature", + "elements": [ + { + "name": "", + "keyword": "Background", + "description": "", + "type": "background", + "line": 2, + "steps": [ + { + "name": "This applies to all scenarios", + "line": 3, + "keyword": "Given " + } + ] + }, + { + "name": "I've declared one step which passes", + "id": "some-feature;i've-declared-one-step-which-passes", + "line": 5, + "keyword": "Scenario", + "description": "", + "type": "scenario", + "steps": [ + { + "name": "This applies to all scenarios", + "line": 3, + "keyword": "Given ", + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + }, + { + "name": "This first step is passing", + "line": 6, + "keyword": "Given ", + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + } + ] + } + ] + } + ] + """ + + Scenario: output JSON for a feature with one scenario outline with two examples tables + Given a file named "features/a.feature" with: + """ + Feature: some feature + + Scenario Outline: I've declared one step which passes + Given This step is passing + + Examples: + | instance | + | first | + + Examples: + | instance | + | second | + """ + And a file named "features/step_definitions/cucumber_steps.js" with: + """ + var cucumberSteps = function() { + this.Given(/^This (first|second) step is passing$/, function(instance, callback) { callback(); }); + }; + module.exports = cucumberSteps; + """ + When I run `cucumber.js -f json` + Then it outputs this json: + """ + [ + { + "id": "some-feature", + "name": "some feature", + "description": "", + "line": 1, + "keyword": "Feature", + "uri": "/features/a.feature", + "elements": [ + { + "name": "I've declared one step which passes", + "id": "some-feature;i've-declared-one-step-which-passes", + "line": 3, + "keyword": "Scenario", + "description": "", + "type": "scenario", + "steps": [ + { + "name": "This first step is passing", + "line": 4, + "keyword": "Given ", + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + } + ] + }, + { + "name": "I've declared one step which passes", + "id": "some-feature;i've-declared-one-step-which-passes", + "line": 3, + "keyword": "Scenario", + "description": "", + "type": "scenario", + "steps": [ + { + "name": "This second step is passing", + "line": 4, + "keyword": "Given ", + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + } + ] + } + ] + } + ] + """ + + Scenario: output JSON for a feature with one scenario outline with an examples table with two rows and before, after and around hooks + Given a file named "features/a.feature" with: + """ + Feature: some feature + + Scenario Outline: I've declared one step which passes + Given This step is passing + + Examples: + | instance | + | first | + | second | + """ + And a file named "features/step_definitions/cucumber_steps.js" with: + """ + var cucumberSteps = function() { + this.Given(/^This (first|second) step is passing$/, function(instance, callback) { callback(); }); + }; + module.exports = cucumberSteps; + """ + And a file named "features/support/hooks.js" with: + """ + var hooks = function () { + this.Before(function(callback) { + callback(); + }); + + this.After(function(callback) { + callback(); + }); + + this.Around(function(runScenario) { + runScenario(function(callback) { + callback(); + }); + }); + }; + + module.exports = hooks; + """ + When I run `cucumber.js -f json` + Then it outputs this json: + """ + [ + { + "id": "some-feature", + "name": "some feature", + "description": "", + "line": 1, + "keyword": "Feature", + "uri": "/features/a.feature", + "elements": [ + { + "name": "I've declared one step which passes", + "id": "some-feature;i've-declared-one-step-which-passes", + "line": 3, + "keyword": "Scenario", + "description": "", + "type": "scenario", + "steps": [ + { + "keyword": "Around ", + "hidden": true, + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + }, + { + "keyword": "Before ", + "hidden": true, + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + }, + { + "name": "This first step is passing", + "line": 4, + "keyword": "Given ", + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + }, + { + "keyword": "After ", + "hidden": true, + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + }, + { + "keyword": "Around ", + "hidden": true, + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + } + ] + }, + { + "name": "I've declared one step which passes", + "id": "some-feature;i've-declared-one-step-which-passes", + "line": 3, + "keyword": "Scenario", + "description": "", + "type": "scenario", + "steps": [ + { + "keyword": "Around ", + "hidden": true, + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + }, + { + "keyword": "Before ", + "hidden": true, + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + }, + { + "name": "This second step is passing", + "line": 4, + "keyword": "Given ", + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + }, + { + "keyword": "After ", + "hidden": true, + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + }, + { + "keyword": "Around ", + "hidden": true, + "result": { + "duration": "", + "status": "passed" + }, + "match": {} + } + ] + } + ] + } + ] + """ diff --git a/lib/cucumber/ast/assembler.js b/lib/cucumber/ast/assembler.js index d30e2da14..47c4d2c84 100644 --- a/lib/cucumber/ast/assembler.js +++ b/lib/cucumber/ast/assembler.js @@ -1,24 +1,24 @@ var Assembler = function (features, filter) { - var currentFeature, currentScenarioOrBackground, currentStep, suggestedFeature; + var currentFeature, currentFeatureElement, currentStep, suggestedFeature; var stashedTags = []; var self = { setCurrentFeature: function setCurrentFeature(feature) { currentFeature = feature; - self.setCurrentScenarioOrBackground(undefined); + self.setCurrentFeatureElement(undefined); }, getCurrentFeature: function getCurrentFeature() { return currentFeature; }, - setCurrentScenarioOrBackground: function setCurrentScenarioOrBackground(scenarioOrBackground) { - currentScenarioOrBackground = scenarioOrBackground; + setCurrentFeatureElement: function setCurrentFeatureElement(featureElement) { + currentFeatureElement = featureElement; self.setCurrentStep(undefined); }, - getCurrentScenarioOrBackground: function getCurrentScenarioOrBackground() { - return currentScenarioOrBackground; + getCurrentFeatureElement: function getCurrentFeatureElement() { + return currentFeatureElement; }, setCurrentStep: function setCurrentStep(step) { @@ -42,7 +42,7 @@ var Assembler = function (features, filter) { applyCurrentFeatureTagsToElement: function applyCurrentFeatureTagsToElement(element) { var currentFeature = self.getCurrentFeature(); var featureTags = currentFeature.getTags(); - element.addInheritedtags(featureTags); + element.addInheritedTags(featureTags); }, applyStashedTagsToElement: function applyStashedTagsToElement(element) { @@ -51,9 +51,9 @@ var Assembler = function (features, filter) { }, insertBackground: function insertBackground(background) { - self.setCurrentScenarioOrBackground(background); + self.setCurrentFeatureElement(background); var currentFeature = self.getCurrentFeature(); - currentFeature.addBackground(background); + currentFeature.setBackground(background); }, insertDataTableRow: function insertDataTableRow(dataTableRow) { @@ -76,17 +76,7 @@ var Assembler = function (features, filter) { insertScenario: function insertScenario(scenario) { self.applyCurrentFeatureTagsToElement(scenario); self.applyStashedTagsToElement(scenario); - self.setCurrentScenarioOrBackground(scenario); - if (filter.isElementEnrolled(scenario)) { - var currentFeature = self.getCurrentFeature(); - currentFeature.addFeatureElement(scenario); - } - }, - - insertOutlineScenario: function insertOutlineScenario(scenario, tags) { - self.applyCurrentFeatureTagsToElement(scenario); - scenario.addTags(tags); - self.setCurrentScenarioOrBackground(scenario); + self.setCurrentFeatureElement(scenario); if (filter.isElementEnrolled(scenario)) { var currentFeature = self.getCurrentFeature(); currentFeature.addFeatureElement(scenario); @@ -94,43 +84,25 @@ var Assembler = function (features, filter) { }, insertExamples: function insertExamples(examples) { - var currentScenarioOrBackground = self.getCurrentScenarioOrBackground(); - if (currentScenarioOrBackground.payloadType == 'scenarioOutline') - currentScenarioOrBackground.setExamples(examples); - else + var currentFeatureElement = self.getCurrentFeatureElement(); + if (!currentFeatureElement.isScenarioOutline()) throw new Error("Examples are allowed inside scenario outlines only"); + currentFeatureElement.addExamples(examples); self.setCurrentStep(examples); }, insertStep: function insertStep(step) { self.setCurrentStep(step); - var currentScenarioOrBackground = self.getCurrentScenarioOrBackground(); - currentScenarioOrBackground.addStep(step); + var currentFeatureElement = self.getCurrentFeatureElement(); + currentFeatureElement.addStep(step); }, insertTag: function insertTag(tag) { self.stashTag(tag); }, - convertScenarioOutlineToScenarios: function convertScenarioOutlineToScenarios(scenario){ - var subScenarios = scenario.buildScenarios(); - var subScenarioTags = scenario.getTags(); - subScenarios.syncForEach(function (scenario) { - self.insertOutlineScenario(scenario, subScenarioTags); - }); - }, - - convertScenarioOutlinesToScenarios: function convertScenarioOutlinesToScenarios(){ - var currentFeature = self.getCurrentFeature(); - var scenarios = currentFeature.getFeatureElements(); - - scenarios.syncForEach(function(scenario){ - self.convertScenarioOutlineToScenarios(scenario); - }); - }, - finish: function finish() { - self.convertScenarioOutlinesToScenarios(); + self.getCurrentFeature().convertScenarioOutlinesToScenarios(); self.tryEnrollingSuggestedFeature(); }, diff --git a/lib/cucumber/ast/data_table.js b/lib/cucumber/ast/data_table.js index 7d43b557a..135d444ea 100644 --- a/lib/cucumber/ast/data_table.js +++ b/lib/cucumber/ast/data_table.js @@ -32,7 +32,7 @@ var DataTable = function() { raw: function raw(){ rawRows = []; - rowsCollection.syncForEach(function(row, index) { + rowsCollection.syncForEach(function(row) { rawRows.push(row.raw()); }); return rawRows; diff --git a/lib/cucumber/ast/feature.js b/lib/cucumber/ast/feature.js index 9f6abae17..592d35506 100644 --- a/lib/cucumber/ast/feature.js +++ b/lib/cucumber/ast/feature.js @@ -26,7 +26,7 @@ var Feature = function(keyword, name, description, uri, line) { return line; }, - addBackground: function addBackground(newBackground) { + setBackground: function setBackground(newBackground) { background = newBackground; }, @@ -43,9 +43,30 @@ var Feature = function(keyword, name, description, uri, line) { featureElement.setBackground(background); featureElements.add(featureElement); }, + + insertFeatureElement: function insertFeatureElement(index, featureElement) { + var background = self.getBackground(); + featureElement.setBackground(background); + featureElements.insert(index, featureElement); + }, + + convertScenarioOutlinesToScenarios: function convertScenarioOutlinesToScenarios() { + featureElements.syncForEach(function(featureElement) { + if (featureElement.isScenarioOutline()) { + self.convertScenarioOutlineToScenarios(featureElement); + } + }); + }, - getFeatureElements: function getFeatureElements(){ - return featureElements; + convertScenarioOutlineToScenarios: function convertScenarioOutlineToScenarios(scenarioOutline) { + var scenarios = scenarioOutline.buildScenarios(); + var scenarioOutlineIndex = featureElements.indexOf(scenarioOutline); + featureElements.removeAtIndex(scenarioOutlineIndex); + var scenarioOutlineTags = scenarioOutline.getTags(); + scenarios.syncForEach(function (scenario, index) { + scenario.addTags(scenarioOutlineTags); + self.insertFeatureElement(scenarioOutlineIndex + index, scenario); + }); }, getLastFeatureElement: function getLastFeatureElement() { diff --git a/lib/cucumber/ast/scenario.js b/lib/cucumber/ast/scenario.js index bb9295572..92ca0bc85 100644 --- a/lib/cucumber/ast/scenario.js +++ b/lib/cucumber/ast/scenario.js @@ -7,16 +7,14 @@ var Scenario = function(keyword, name, description, uri, line) { var tags = []; var self = { - payloadType: 'scenario', + isScenarioOutline: function isScenarioOutline() { + return false; + }, setBackground: function setBackground(newBackground) { background = newBackground; }, - buildScenarios: function buildScenarios() { - return Cucumber.Type.Collection(); - }, - getKeyword: function getKeyword() { return keyword; }, @@ -63,7 +61,7 @@ var Scenario = function(keyword, name, description, uri, line) { tags = tags.concat(newTags); }, - addInheritedtags: function addInheritedtags(newTags) { + addInheritedTags: function addInheritedTags(newTags) { inheritedTags = tags.concat(newTags); }, diff --git a/lib/cucumber/ast/scenario_outline.js b/lib/cucumber/ast/scenario_outline.js index 0e46cac1f..8e6a29ada 100644 --- a/lib/cucumber/ast/scenario_outline.js +++ b/lib/cucumber/ast/scenario_outline.js @@ -1,42 +1,38 @@ var ScenarioOutline = function (keyword, name, description, uri, line) { var Cucumber = require('../../cucumber'); var self = Cucumber.Ast.Scenario(keyword, name, description, uri, line); - var examples = []; + var examplesCollection = Cucumber.Type.Collection(); - self.payloadType = 'scenarioOutline'; + self.isScenarioOutline = function () { + return true; + }; - self.setExamples = function (newExamples) { - examples = newExamples; + self.addExamples = function (examples) { + examplesCollection.add(examples); }; - function buildScenario(row) { - var newSteps = self.applyExampleRow(row.example, self.getSteps()); + function buildScenario(example) { + var newSteps = self.applyExampleRow(example, self.getSteps()); var subScenario = Cucumber.Ast.Scenario(keyword, name, description, uri, line); subScenario.setSteps(newSteps); return subScenario; } self.buildScenarios = function () { - var rows = examples.getDataTable().getRows(); - var firstRow = rows.shift().raw(); - - rows.syncForEach(function (row, index) { - row.example = {}; - row.id = index; - for (var i = 0, ii = firstRow.length; i < ii; i++) { - row.example[firstRow[i]] = row.raw()[i]; - } - }); - var scenarios = Cucumber.Type.Collection(); - rows.syncForEach(function (row) { - scenarios.add(buildScenario(row)); + + examplesCollection.syncForEach(function(examples) { + var exampleHashes = examples.getDataTable().hashes(); + exampleHashes.forEach(function(exampleHash) { + scenarios.add(buildScenario(exampleHash)); + }); }); + return scenarios; }; self.getExamples = function () { - return examples; + return examplesCollection; }; self.applyExampleRow = function (example, steps) { diff --git a/lib/cucumber/parser.js b/lib/cucumber/parser.js index c748539a1..08b93aa08 100644 --- a/lib/cucumber/parser.js +++ b/lib/cucumber/parser.js @@ -109,7 +109,7 @@ var Parser = function(featureSources, astFilter) { handleExamples: function handleExamples(keyword, name, description, line) { var examples = Cucumber.Ast.Examples(keyword, name, description, line); astAssembler.insertExamples(examples); - }, + } }; return self; }; diff --git a/lib/cucumber/runtime/ast_tree_walker.js b/lib/cucumber/runtime/ast_tree_walker.js index 0a975845f..569ad12f9 100644 --- a/lib/cucumber/runtime/ast_tree_walker.js +++ b/lib/cucumber/runtime/ast_tree_walker.js @@ -47,12 +47,11 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) { supportCodeLibrary.instantiateNewWorld(function(world) { self.setWorld(world); self.witnessNewScenario(scenario); - var payload = {}; - payload[scenario.payloadType] = scenario; self.createBeforeAndAfterStepsForAroundHooks(scenario); self.createBeforeStepsForBeforeHooks(scenario); self.createAfterStepsForAfterHooks(scenario); - var event = AstTreeWalker.Event(AstTreeWalker[scenario.payloadType.toUpperCase() + '_EVENT_NAME'], payload); + var payload = { scenario: scenario }; + var event = AstTreeWalker.Event(AstTreeWalker.SCENARIO_EVENT_NAME, payload); self.broadcastEventAroundUserFunction( event, function(callback) { @@ -293,7 +292,6 @@ AstTreeWalker.FEATURES_EVENT_NAME = 'Features'; AstTreeWalker.FEATURE_EVENT_NAME = 'Feature'; AstTreeWalker.BACKGROUND_EVENT_NAME = 'Background'; AstTreeWalker.SCENARIO_EVENT_NAME = 'Scenario'; -AstTreeWalker.SCENARIO_OUTLINE_EVENT_NAME = 'ScenarioOutline'; AstTreeWalker.STEP_EVENT_NAME = 'Step'; AstTreeWalker.STEP_RESULT_EVENT_NAME = 'StepResult'; AstTreeWalker.ROW_EVENT_NAME = 'ExampleRow'; diff --git a/lib/cucumber/support_code/step_definition_snippet_builder.js b/lib/cucumber/support_code/step_definition_snippet_builder.js index 99224b085..d3d41d9bd 100644 --- a/lib/cucumber/support_code/step_definition_snippet_builder.js +++ b/lib/cucumber/support_code/step_definition_snippet_builder.js @@ -1,5 +1,4 @@ var _ = require('underscore'); -var stepDefinitionSnippetBuilderSyntax = require('./step_definition_snippet_builder_syntax'); _.str = require('underscore.string'); var StepDefinitionSnippetBuilder = function (step, syntax) { diff --git a/lib/cucumber/type/collection.js b/lib/cucumber/type/collection.js index 7b1abd4a1..9a5f1d5fa 100644 --- a/lib/cucumber/type/collection.js +++ b/lib/cucumber/type/collection.js @@ -5,6 +5,14 @@ var Collection = function () { items.push(item); }, + insert: function insert(index, item) { + items.splice(index, 0, item); + }, + + removeAtIndex: function removeAtIndex(index) { + items.splice(index, 1); + }, + unshift: function unshift(item) { items.unshift(item); }, @@ -17,6 +25,14 @@ var Collection = function () { items.length = 0; }, + indexOf: function indexOf(item) { + return items.indexOf(item); + }, + + getAtIndex: function getAtIndex(index) { + return items[index]; + }, + getLast: function getLast() { return items[items.length - 1]; }, diff --git a/spec/cucumber/ast/assembler_spec.js b/spec/cucumber/ast/assembler_spec.js index aef9c2a0d..c9ce2e1fa 100644 --- a/spec/cucumber/ast/assembler_spec.js +++ b/spec/cucumber/ast/assembler_spec.js @@ -15,12 +15,12 @@ describe("Cucumber.Ast.Assembler", function() { beforeEach(function() { currentFeature = createSpy("current feature"); - spyOn(assembler, 'setCurrentScenarioOrBackground'); + spyOn(assembler, 'setCurrentFeatureElement'); }); it("unsets the current scenario", function() { assembler.setCurrentFeature(currentFeature); - expect(assembler.setCurrentScenarioOrBackground).toHaveBeenCalledWith(undefined); + expect(assembler.setCurrentFeatureElement).toHaveBeenCalledWith(undefined); }); }); @@ -37,30 +37,30 @@ describe("Cucumber.Ast.Assembler", function() { }); }); - describe("setCurrentScenarioOrBackground()", function() { - var currentScenarioOrBackground; + describe("setCurrentFeatureElement()", function() { + var currentFeatureElement; beforeEach(function() { - currentScenarioOrBackground = createSpy("current scenario or background"); + currentFeatureElement = createSpy("current feature element"); spyOn(assembler, 'setCurrentStep'); }); it("unsets the current step", function() { - assembler.setCurrentScenarioOrBackground(currentScenarioOrBackground); + assembler.setCurrentFeatureElement(currentFeatureElement); expect(assembler.setCurrentStep).toHaveBeenCalledWith(undefined); }); }); - describe("getCurrentScenarioOrBackground() [setCurrentScenarioOrBackground()]", function() { - var currentScenarioOrBackground; + describe("getCurrentFeatureElement() [setCurrentFeatureElement()]", function() { + var currentFeatureElement; beforeEach(function() { - currentScenarioOrBackground = createSpy("current scenario or background"); + currentFeatureElement = createSpy("current feature element"); }); - it("returns the current scenario or background", function() { - assembler.setCurrentScenarioOrBackground(currentScenarioOrBackground); - expect(assembler.getCurrentScenarioOrBackground()).toBe(currentScenarioOrBackground); + it("returns the current feature element", function() { + assembler.setCurrentFeatureElement(currentFeatureElement); + expect(assembler.getCurrentFeatureElement()).toBe(currentFeatureElement); }); }); @@ -103,7 +103,7 @@ describe("Cucumber.Ast.Assembler", function() { var feature, featureTags, element; beforeEach(function() { - element = createSpyWithStubs("AST element", {addInheritedtags: null}); + element = createSpyWithStubs("AST element", {addInheritedTags: null}); featureTags = createSpy("feature tags"); feature = createSpyWithStubs("current feature", {getTags: featureTags}); spyOn(assembler, 'getCurrentFeature').andReturn(feature); @@ -121,7 +121,7 @@ describe("Cucumber.Ast.Assembler", function() { it("adds the feature tags to the element", function() { assembler.applyCurrentFeatureTagsToElement(element); - expect(element.addInheritedtags).toHaveBeenCalledWith(featureTags); + expect(element.addInheritedTags).toHaveBeenCalledWith(featureTags); }); }); @@ -150,14 +150,14 @@ describe("Cucumber.Ast.Assembler", function() { beforeEach(function() { background = createSpy("background"); - currentFeature = createSpyWithStubs("current feature", {addBackground: null}); + currentFeature = createSpyWithStubs("current feature", {setBackground: null}); spyOn(assembler, 'getCurrentFeature').andReturn(currentFeature); - spyOn(assembler, 'setCurrentScenarioOrBackground'); + spyOn(assembler, 'setCurrentFeatureElement'); }); it("sets the background as the current background", function() { assembler.insertBackground(background); - expect(assembler.setCurrentScenarioOrBackground).toHaveBeenCalledWith(background); + expect(assembler.setCurrentFeatureElement).toHaveBeenCalledWith(background); }); it("gets the current feature", function() { @@ -167,7 +167,7 @@ describe("Cucumber.Ast.Assembler", function() { it("adds the background to the current feature", function() { assembler.insertBackground(background); - expect(currentFeature.addBackground).toHaveBeenCalledWith(background); + expect(currentFeature.setBackground).toHaveBeenCalledWith(background); }); }); @@ -253,7 +253,7 @@ describe("Cucumber.Ast.Assembler", function() { spyOn(assembler, 'applyStashedTagsToElement'); spyOn(assembler, 'applyCurrentFeatureTagsToElement'); spyOn(assembler, 'getCurrentFeature').andReturn(currentFeature); - spyOn(assembler, 'setCurrentScenarioOrBackground'); + spyOn(assembler, 'setCurrentFeatureElement'); }); it("applies the current feature tags to the scenario", function() { @@ -268,7 +268,7 @@ describe("Cucumber.Ast.Assembler", function() { it("sets the scenario as the current scenario", function() { assembler.insertScenario(scenario); - expect(assembler.setCurrentScenarioOrBackground).toHaveBeenCalledWith(scenario); + expect(assembler.setCurrentFeatureElement).toHaveBeenCalledWith(scenario); }); it("asks the filter if the scenario is enrolled", function() { @@ -309,80 +309,44 @@ describe("Cucumber.Ast.Assembler", function() { }); }); - describe("insertOutlineScenario()", function() { - var scenario, currentFeature, tags; + describe("insertExamples()", function() { + var examples, currentFeatureElement; beforeEach(function() { - tags = ['@test', '@test1']; - scenario = createSpy("scenario"); - scenario.addTags = createSpy("addTags"); - currentFeature = createSpyWithStubs("current feature", {addFeatureElement: null}); - spyOnStub(filter, 'isElementEnrolled'); - spyOn(assembler, 'applyCurrentFeatureTagsToElement'); - spyOn(assembler, 'getCurrentFeature').andReturn(currentFeature); - spyOn(assembler, 'setCurrentScenarioOrBackground'); - }); - - it("applies the current feature tags to the scenario", function() { - assembler.insertOutlineScenario(scenario); - expect(assembler.applyCurrentFeatureTagsToElement).toHaveBeenCalledWith(scenario); + examples = createSpy("examples"); + currentFeatureElement = createSpyWithStubs("current feature element", {isScenarioOutline: true, addExamples: null}); + spyOn(assembler, 'getCurrentFeatureElement').andReturn(currentFeatureElement); + spyOn(assembler, 'setCurrentStep'); }); - it("applies custom tags to the scenario", function() { - assembler.insertOutlineScenario(scenario, tags); - expect(scenario.addTags).toHaveBeenCalledWith(tags); + it("adds the examples to the current scenario outline", function() { + assembler.insertExamples(examples); + expect(currentFeatureElement.addExamples).toHaveBeenCalledWith(examples); }); - it("sets the scenario as the current scenario", function() { - assembler.insertOutlineScenario(scenario); - expect(assembler.setCurrentScenarioOrBackground).toHaveBeenCalledWith(scenario); - }); - - it("asks the filter if the scenario is enrolled", function() { - assembler.insertOutlineScenario(scenario); - expect(filter.isElementEnrolled).toHaveBeenCalledWith(scenario); + it("sets the examples as the current step", function() { + assembler.insertExamples(examples); + expect(assembler.setCurrentStep).toHaveBeenCalledWith(examples); }); - describe("when the scenario is enrolled", function() { + describe("when the current feature element is not a scenario outline", function() { beforeEach(function() { - filter.isElementEnrolled.andReturn(true); - }); - - it("gets the current feature", function() { - assembler.insertOutlineScenario(scenario); - expect(assembler.getCurrentFeature).toHaveBeenCalled(); + currentFeatureElement.isScenarioOutline.andReturn(false); }); - it("adds the scenario to the current feature", function() { - assembler.insertOutlineScenario(scenario); - expect(currentFeature.addFeatureElement).toHaveBeenCalledWith(scenario); - }); - }); - - describe("when the scenario is not enrolled", function() { - beforeEach(function() { - filter.isElementEnrolled.andReturn(false); - }); - - it("does not get the current feature", function() { - assembler.insertOutlineScenario(scenario); - expect(assembler.getCurrentFeature).not.toHaveBeenCalled(); - }); - - it("does not add the scenario to the current feature", function() { - assembler.insertOutlineScenario(scenario); - expect(currentFeature.addFeatureElement).not.toHaveBeenCalledWith(scenario); + it("throws an error", function() { + expect(function() { assembler.insertExamples(examples); }).toThrow(new Error("Examples are allowed inside scenario outlines only")); }); }); }); describe("insertStep()", function() { - var step, currentScenario; + var step, currentFeatureElement; beforeEach(function() { - step = createSpy("step"); - currentScenarioOrBackground = createSpyWithStubs("current scenario or background", {addStep: null}); - spyOn(assembler, 'getCurrentScenarioOrBackground').andReturn(currentScenarioOrBackground); + step = createSpy("step"); + currentFeatureElement = createSpyWithStubs("current feature element", {addStep: null}); + spyOn(assembler, 'getCurrentFeatureElement').andReturn(currentFeatureElement); spyOn(assembler, 'setCurrentStep'); }); @@ -391,14 +355,14 @@ describe("Cucumber.Ast.Assembler", function() { expect(assembler.setCurrentStep).toHaveBeenCalledWith(step); }); - it("gets the current scenario or background", function() { + it("gets the current feature element", function() { assembler.insertStep(step); - expect(assembler.getCurrentScenarioOrBackground).toHaveBeenCalled(); + expect(assembler.getCurrentFeatureElement).toHaveBeenCalled(); }); - it("adds the step to the scenario or background", function() { + it("adds the step to the feature element", function() { assembler.insertStep(step); - expect(currentScenarioOrBackground.addStep).toHaveBeenCalledWith(step); + expect(currentFeatureElement.addStep).toHaveBeenCalledWith(step); }); }); @@ -416,64 +380,18 @@ describe("Cucumber.Ast.Assembler", function() { }); }); - describe("convertScenarioOutlinesToScenarios()", function () { - var scenarioOutline, subScenario; - beforeEach(function() { - subScenario = createSpy('subScenario'); - var subScenarios = Cucumber.Type.Collection(); - subScenarios.add(subScenario); - scenarioOutline = createSpyWithStubs('scenario', {'buildScenarios': subScenarios}); - scenarioOutline.payload_type = 'scenarioOutline'; - scenarioOutline.getTags = function () { return '@test'; }; - - spyOn(assembler, 'insertOutlineScenario'); - }); - - it("it should get the scenarios from the current feature", function () { - assembler.convertScenarioOutlineToScenarios(scenarioOutline); - expect(assembler.insertOutlineScenario).toHaveBeenCalledWithValueAsNthParameter(subScenario,1); - }); - - it("it should pass tags from scenario outline to all subscenarios", function () { - assembler.convertScenarioOutlineToScenarios(scenarioOutline); - expect(assembler.insertOutlineScenario).toHaveBeenCalledWithValueAsNthParameter('@test',2); - }); - }); - - describe("convertScenarioOutlinesToScenarios()", function () { - var currentFeature, scenario, scenarios; - - beforeEach(function() { - spyOn(assembler, "getCurrentFeature"); - spyOn(assembler, "convertScenarioOutlineToScenarios"); - scenario = createSpy('scenario'); - scenarios = Cucumber.Type.Collection(); - scenarios.add(scenario); - currentFeature = createSpyWithStubs("currentFeature", {'getFeatureElements' : scenarios}) - assembler.getCurrentFeature.andReturn(currentFeature); - }); - - it("it should get the scenarios from the current feature", function () { - assembler.convertScenarioOutlinesToScenarios(); - expect(assembler.getCurrentFeature).toHaveBeenCalled(); - expect(currentFeature.getFeatureElements).toHaveBeenCalled(); - }); - - it("it should get the scenarios from the current feature", function () { - assembler.convertScenarioOutlinesToScenarios(); - expect(assembler.convertScenarioOutlineToScenarios).toHaveBeenCalledWith(scenario); - }); - }); - describe("finish()", function () { + var currentFeature; + beforeEach(function() { - spyOn(assembler, 'convertScenarioOutlinesToScenarios'); + currentFeature = createSpyWithStubs("current feature", {convertScenarioOutlinesToScenarios: null}); spyOn(assembler, 'tryEnrollingSuggestedFeature'); + spyOn(assembler, 'getCurrentFeature').andReturn(currentFeature); }); it("tries to convert scenario outlines to scenarios if any", function () { assembler.finish(); - expect(assembler.convertScenarioOutlinesToScenarios).toHaveBeenCalled(); + expect(currentFeature.convertScenarioOutlinesToScenarios).toHaveBeenCalled(); }); it("tries to enroll the suggested feature, if any", function () { diff --git a/spec/cucumber/ast/data_table_spec.js b/spec/cucumber/ast/data_table_spec.js index c3bf6b02b..e48e76ab6 100644 --- a/spec/cucumber/ast/data_table_spec.js +++ b/spec/cucumber/ast/data_table_spec.js @@ -3,30 +3,20 @@ require('../../support/spec_helper'); describe("Cucumber.Ast.DataTable", function() { var Cucumber = requireLib('cucumber'); - var dataTable, rows; + var dataTable; beforeEach(function() { - rows = Cucumber.Type.Collection(); - spyOn(Cucumber.Type, 'Collection').andReturn(rows); dataTable = Cucumber.Ast.DataTable(); }); - describe("constructor", function() { - it("creates a new collection to store rows", function() { - expect(Cucumber.Type.Collection).toHaveBeenCalledWith(); - }); - }); - - describe("attachRow()", function() { + describe("attachRow() [getRows]", function() { var row; - beforeEach(function() { - spyOnStub(rows, 'add'); - }); - it("adds the row to the row collection", function() { dataTable.attachRow(row); - expect(rows.add).toHaveBeenCalledWith(row); + var rows = dataTable.getRows(); + expect(rows.length()).toBe(1); + expect(rows.getAtIndex(0)).toBe(row); }); }); @@ -40,7 +30,7 @@ describe("Cucumber.Ast.DataTable", function() { var rowArray; beforeEach(function() { - rawRows = [ + var rawRows = [ createSpy("raw row 1"), createSpy("raw row 2") ]; @@ -48,14 +38,8 @@ describe("Cucumber.Ast.DataTable", function() { createSpyWithStubs("row 1", {raw: rawRows[0]}), createSpyWithStubs("row 2", {raw: rawRows[1]}) ]; - rows.add(rowArray[0]); - rows.add(rowArray[1]); - }); - - it("gets the raw representation of the row", function() { - dataTable.raw(); - expect(rowArray[0].raw).toHaveBeenCalled(); - expect(rowArray[1].raw).toHaveBeenCalled(); + dataTable.attachRow(rowArray[0]); + dataTable.attachRow(rowArray[1]); }); it("returns the raw representations in an array", function() { @@ -64,7 +48,8 @@ describe("Cucumber.Ast.DataTable", function() { }); describe("rows()", function() { - var rowArray; + var rawRows, rowArray; + beforeEach(function() { rawRows = [ createSpy("raw row 1"), @@ -73,17 +58,51 @@ describe("Cucumber.Ast.DataTable", function() { createSpyWithStubs("row 1", {raw: rawRows[0]}), createSpyWithStubs("row 2", {raw: rawRows[1]}) ]; - rows.add(rowArray[0]); - rows.add(rowArray[1]); + dataTable.attachRow(rowArray[0]); + dataTable.attachRow(rowArray[1]); }); it("gets the raw representation of the row without the header", function() { - dataTable.rows(); + var actualRows = dataTable.rows(); expect(rowArray[1].raw).toHaveBeenCalled(); expect(rowArray[0].raw).not.toHaveBeenCalled(); + expect(actualRows).toEqual([rawRows[1]]); }); }); + describe("getRows()", function() { + var rowArray; + + beforeEach(function() { + rowArray = [ + createSpyWithStubs("row 1"), + createSpyWithStubs("row 2") + ]; + dataTable.attachRow(rowArray[0]); + dataTable.attachRow(rowArray[1]); + }); + + it("gets the raw representation of the rows, including the header", function() { + var actualRows = dataTable.getRows(); + expect(actualRows.length()).toEqual(2); + expect(actualRows.getAtIndex(0)).toEqual(rowArray[0]); + expect(actualRows.getAtIndex(1)).toEqual(rowArray[1]); + }); + + it("returns a new row collection every time", function() { + var actualRows1 = dataTable.getRows(); + expect(actualRows1.length()).toEqual(2); + expect(actualRows1.getAtIndex(0)).toEqual(rowArray[0]); + expect(actualRows1.getAtIndex(1)).toEqual(rowArray[1]); + + var actualRows2 = dataTable.getRows(); + expect(actualRows2.length()).toEqual(2); + expect(actualRows2.getAtIndex(0)).toEqual(rowArray[0]); + expect(actualRows2.getAtIndex(1)).toEqual(rowArray[1]); + + expect(actualRows2).toNotBe(actualRows1); + }); + }); describe("hashes", function() { var raw, hashDataTable; diff --git a/spec/cucumber/ast/examples_spec.js b/spec/cucumber/ast/examples_spec.js index 996e1d88d..c935566ec 100644 --- a/spec/cucumber/ast/examples_spec.js +++ b/spec/cucumber/ast/examples_spec.js @@ -63,11 +63,17 @@ describe("Cucumber.Ast.Examples", function() { }); describe("attachDataTableRow [getDataTable()]", function(){ + var rawRow, row; + beforeEach(function(){ - examples.attachDataTableRow("row"); + rawRow = createSpy("raw row"); + row = createSpyWithStubs("row", {raw: rawRow}) + examples.attachDataTableRow(row); }); it("should have an attached data table with a single row", function(){ - expect(examples.getDataTable().getRows().length()).toBe(1); + rawDataTable = examples.getDataTable().raw(); + expect(rawDataTable.length).toBe(1); + expect(rawDataTable[0]).toBe(rawRow); }) }) diff --git a/spec/cucumber/ast/feature_spec.js b/spec/cucumber/ast/feature_spec.js index 7320271d6..2a5bc8231 100644 --- a/spec/cucumber/ast/feature_spec.js +++ b/spec/cucumber/ast/feature_spec.js @@ -10,7 +10,11 @@ describe("Cucumber.Ast.Feature", function () { scenarioCollection = new Object(); // bare objects because we need .length // which is not available on jasmine spies spyOnStub(scenarioCollection, 'add'); + spyOnStub(scenarioCollection, 'insert'); + spyOnStub(scenarioCollection, 'removeAtIndex'); + spyOnStub(scenarioCollection, 'indexOf'); spyOnStub(scenarioCollection, 'getLast').andReturn(lastScenario); + spyOnStub(scenarioCollection, 'syncForEach'); spyOnStub(scenarioCollection, 'forEach'); spyOn(Cucumber.Type, 'Collection').andReturn(scenarioCollection); keyword = createSpy("keyword"); @@ -57,13 +61,13 @@ describe("Cucumber.Ast.Feature", function () { }); }); - describe("getBackground() [addBackground()]", function () { + describe("getBackground() [setBackground()]", function () { describe("when a background was previously added", function () { var background; beforeEach(function () { background = createSpy("background"); - feature.addBackground(background); + feature.setBackground(background); }); it("returns the background", function () { @@ -79,31 +83,26 @@ describe("Cucumber.Ast.Feature", function () { }); describe("hasBackground()", function () { - it("returns true when a background was added", function () { + it("returns true when a background was set", function () { var background = createSpy("background"); - feature.addBackground(background); + feature.setBackground(background); expect(feature.hasBackground()).toBeTruthy(); }); - it("returns false when no background was added", function () { + it("returns false when no background was set", function () { expect(feature.hasBackground()).toBeFalsy(); }); }); - describe("addScenario()", function () { + describe("addFeatureElement()", function () { var scenario, background; beforeEach(function () { - scenario = createSpyWithStubs("scenario AST element", {setBackground: null}); + scenario = createSpyWithStubs("scenario AST element", {setBackground: null}); background = createSpy("scenario background"); spyOn(feature, 'getBackground').andReturn(background); }); - it("gets the background", function () { - feature.addFeatureElement(scenario); - expect(feature.getBackground).toHaveBeenCalled(); - }); - it("sets the background on the scenario", function () { feature.addFeatureElement(scenario); expect(scenario.setBackground).toHaveBeenCalledWith(background); @@ -115,6 +114,122 @@ describe("Cucumber.Ast.Feature", function () { }); }); + describe("insertFeatureElement()", function () { + var scenario, background; + + beforeEach(function () { + index = createSpy("index"); + scenario = createSpyWithStubs("scenario AST element", {setBackground: null}); + background = createSpy("scenario background"); + spyOn(feature, 'getBackground').andReturn(background); + }); + + it("sets the background on the scenario", function () { + feature.insertFeatureElement(index, scenario); + expect(scenario.setBackground).toHaveBeenCalledWith(background); + }); + + it("adds the scenario to the scenarios (collection)", function () { + feature.insertFeatureElement(index, scenario); + expect(scenarioCollection.insert).toHaveBeenCalledWith(index, scenario); + }); + }); + + describe("convertScenarioOutlinesToScenarios()", function() { + it ("iterates over the feature elements", function () { + feature.convertScenarioOutlinesToScenarios(); + expect(scenarioCollection.syncForEach).toHaveBeenCalled(); + expect(scenarioCollection.syncForEach).toHaveBeenCalledWithAFunctionAsNthParameter(1); + }); + + describe("for each feature element", function () { + var userFunction, featureElement; + + beforeEach(function () { + feature.convertScenarioOutlinesToScenarios(); + userFunction = scenarioCollection.syncForEach.mostRecentCall.args[0]; + featureElement = createSpyWithStubs("feature element", {isScenarioOutline: null}); + spyOn(feature, 'convertScenarioOutlineToScenarios'); + }); + + describe("when the feature element is a scenario outline", function() { + beforeEach(function() { + featureElement.isScenarioOutline.andReturn(true); + userFunction(featureElement); + }); + + it("converts the scenario outline into scenarios", function() { + expect(feature.convertScenarioOutlineToScenarios).toHaveBeenCalledWith(featureElement); + }); + }); + + describe("when the feature element is not a scenario outline", function() { + beforeEach(function() { + featureElement.isScenarioOutline.andReturn(false); + userFunction(featureElement); + }); + + it("converts the scenario outline into scenarios", function() { + expect(feature.convertScenarioOutlineToScenarios).not.toHaveBeenCalled(); + }); + }); + }); + }); + + describe("convertScenarioOutlineToScenarios()", function() { + var scenarios, scenarioOutlineTags, scenarioOutline, scenarioOutlineIndex; + + beforeEach(function() { + scenarios = createSpyWithStubs("scenarios", {syncForEach: null}); + scenarioOutlineTags = createSpy("tags"); + scenarioOutline = createSpyWithStubs("scenario outline", {buildScenarios: scenarios, getTags: scenarioOutlineTags}); + scenarioOutlineIndex = 1; + scenarioCollection.indexOf.andReturn(scenarioOutlineIndex); + feature.convertScenarioOutlineToScenarios(scenarioOutline); + }); + + it ("builds the scenarios for the scenario outline", function () { + expect(scenarioOutline.buildScenarios).toHaveBeenCalled(); + }); + + it ("gets the index of the scenario outline in the scenario collection", function () { + expect(scenarioCollection.indexOf).toHaveBeenCalledWith(scenarioOutline); + }); + + it("removes the scenario outline from the scenario collection", function() { + expect(scenarioCollection.removeAtIndex).toHaveBeenCalledWith(scenarioOutlineIndex); + }); + + it("gets the tags from the scenario outline just once", function() { + expect(scenarioOutline.getTags).toHaveBeenCalledNTimes(1); + }); + + it ("iterates over the scenarios", function () { + expect(scenarios.syncForEach).toHaveBeenCalled(); + expect(scenarios.syncForEach).toHaveBeenCalledWithAFunctionAsNthParameter(1); + }); + + describe("for each scenario", function () { + var userFunction, scenario; + + beforeEach(function () { + userFunction = scenarios.syncForEach.mostRecentCall.args[0]; + scenario = createSpyWithStubs("scenario", {addTags: null}); + index = 2; + spyOn(feature, 'insertFeatureElement'); + userFunction(scenario, index); + }); + + it("adds the scenario outline's tags to the scenario", function() { + expect(scenario.addTags).toHaveBeenCalledWith(scenarioOutlineTags); + }); + + it("inserts the scenario into the scenario collection", function() { + expect(feature.insertFeatureElement).toHaveBeenCalledWith(scenarioOutlineIndex + index, scenario); + }); + }); + }); + describe("getLastScenario()", function () { it("gets the last scenario from the collection", function () { feature.getLastFeatureElement(); diff --git a/spec/cucumber/ast/scenario_outline_spec.js b/spec/cucumber/ast/scenario_outline_spec.js index e56f6a2e8..ca3f6be02 100644 --- a/spec/cucumber/ast/scenario_outline_spec.js +++ b/spec/cucumber/ast/scenario_outline_spec.js @@ -4,45 +4,62 @@ describe("Cucumber.Ast.ScenarioOutline", function() { var Cucumber = requireLib('cucumber'); var steps; var examples; - var scenario, keyword, name, description, uri, line, lastStep; + var scenarioOutline, keyword, name, description, uri, line, lastStep; beforeEach(function() { - keyword = createSpy("scenario keyword"); - name = createSpy("scenario name"); - description = createSpy("scenario description"); + keyword = createSpy("scenario outline keyword"); + name = createSpy("scenario outline name"); + description = createSpy("scenario outline description"); uri = createSpy("uri"); - line = createSpy("starting scenario line number"); + line = createSpy("starting scenario outline line number"); lastStep = createSpy("last step"); steps = createSpy("step collection"); examples = createSpy("examples collection"); spyOnStub(steps, 'add'); spyOnStub(steps, 'getLast').andReturn(lastStep); - //spyOn(Cucumber.Type, 'Collection').andReturn(steps); - - scenario = Cucumber.Ast.ScenarioOutline(keyword, name, description, uri, line); + + scenarioOutline = Cucumber.Ast.ScenarioOutline(keyword, name, description, uri, line); }); - describe("getExamples() [setExamples()]", function() { + describe("isScenarioOutline()", function() { + it("returns true", function() { + expect(scenarioOutline.isScenarioOutline()).toBeTruthy(); + }); + }); + + describe("getExamples() [addExamples()]", function() { it("returns an empty set when no examples have been set", function() { - expect(scenario.getExamples()).toEqual([]); + expect(scenarioOutline.getExamples().length()).toEqual(0); }); it("returns the examples", function() { - scenario.setExamples(examples); - expect(scenario.getExamples()).toBe(examples); + scenarioOutline.addExamples(examples); + expect(scenarioOutline.getExamples().length()).toEqual(1); + expect(scenarioOutline.getExamples().getAtIndex(0)).toBe(examples); + }); + + describe("when adding more than 1 set of examples", function() { + it("returns all the examples", function() { + var examples2 = createSpy("second examples collection"); + scenarioOutline.addExamples(examples); + scenarioOutline.addExamples(examples2); + expect(scenarioOutline.getExamples().length()).toEqual(2); + expect(scenarioOutline.getExamples().getAtIndex(0)).toBe(examples); + expect(scenarioOutline.getExamples().getAtIndex(1)).toBe(examples2); + }); }); }); describe("acceptVisitor", function() { - var visitor, callback, data_row; + var visitor, callback; beforeEach(function() { callback = createSpy("Callback"); }); it("instructs the visitor to visit the row steps", function() { - scenario.acceptVisitor(visitor, callback); + scenarioOutline.acceptVisitor(visitor, callback); expect(callback).toHaveBeenCalled(); }); }); diff --git a/spec/cucumber/ast/scenario_spec.js b/spec/cucumber/ast/scenario_spec.js index c61de62ff..b0669ea78 100644 --- a/spec/cucumber/ast/scenario_spec.js +++ b/spec/cucumber/ast/scenario_spec.js @@ -55,6 +55,12 @@ describe("Cucumber.Ast.Scenario", function() { }); }); + describe("isScenarioOutline()", function() { + it("returns false", function() { + expect(scenario.isScenarioOutline()).toBeFalsy(); + }); + }); + describe("getBackground() [setBackground()]", function() { it("returns the background that was set as such", function() { var background = createSpy("background"); diff --git a/spec/cucumber/runtime/ast_tree_walker_spec.js b/spec/cucumber/runtime/ast_tree_walker_spec.js index d7d5d0d88..434487ca7 100644 --- a/spec/cucumber/runtime/ast_tree_walker_spec.js +++ b/spec/cucumber/runtime/ast_tree_walker_spec.js @@ -178,7 +178,6 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { beforeEach(function() { scenario = createSpyObj("scenario",['mock']); - scenario.payloadType = 'scenario'; callback = createSpy("Callback"); spyOnStub(supportCodeLibrary, 'instantiateNewWorld'); }); diff --git a/spec/cucumber/type/collection_spec.js b/spec/cucumber/type/collection_spec.js index 8a1f4cab1..f7a6a0c4e 100644 --- a/spec/cucumber/type/collection_spec.js +++ b/spec/cucumber/type/collection_spec.js @@ -8,6 +8,8 @@ describe("Cucumber.Type.Collection", function() { itemArray = [1, 2, 3]; spyOn(itemArray, 'push'); spyOn(itemArray, 'unshift'); + spyOn(itemArray, 'splice'); + spyOn(itemArray, 'indexOf'); spyOn(global, 'Array').andReturn(itemArray); collection = Cucumber.Type.Collection(); }); @@ -20,15 +22,32 @@ describe("Cucumber.Type.Collection", function() { describe("add()", function() { it("pushes the item onto the end of the item array", function() { - var item = createSpy("Collection item"); + var item = createSpy("collection item"); collection.add(item); expect(itemArray.push).toHaveBeenCalledWith(item); }); }); + describe("insert()", function() { + it("inserts an item at a specific index in the item array", function() { + var index = createSpy("index in the collection"); + var item = createSpy("collection item"); + collection.insert(index, item); + expect(itemArray.splice).toHaveBeenCalledWith(index, 0, item); + }); + }); + + describe("removeAtIndex()", function() { + it("removes an item at a specific index in the item array", function() { + var index = createSpy("index in the collection"); + collection.removeAtIndex(index); + expect(itemArray.splice).toHaveBeenCalledWith(index, 1); + }); + }); + describe("unshift()", function() { it("unshifts the item onto the start of the item array", function() { - var item = createSpy("Collection item"); + var item = createSpy("collection item"); collection.unshift(item); expect(itemArray.unshift).toHaveBeenCalledWith(item); }); @@ -36,13 +55,32 @@ describe("Cucumber.Type.Collection", function() { describe("clear()", function() { it("empties the item array", function() { - var item = createSpy("Collection item"); + var item = createSpy("collection item"); expect(itemArray.length).toEqual(3); collection.clear(); expect(itemArray.length).toEqual(0); }); }); + describe("indexOf()", function() { + it("gets the index of an item in the item array", function() { + var item = createSpy("collection item"); + var index = createSpy("index in the collection"); + itemArray.indexOf.andReturn(index); + var actualIndex = collection.indexOf(item); + expect(itemArray.indexOf).toHaveBeenCalledWith(item); + expect(actualIndex).toBe(index); + }); + }); + + describe("getAtIndex()", function() { + it("gets the item at a specific index in the item array", function() { + expect(collection.getAtIndex(0)).toEqual(1); + expect(collection.getAtIndex(1)).toEqual(2); + expect(collection.getAtIndex(2)).toEqual(3); + }); + }); + describe("getLast()", function() { it("returns the latest added item from the array", function() { var lastItem = createSpy("last item");