Skip to content

Commit

Permalink
Add support for background (close #9)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbpros committed Sep 12, 2011
1 parent f616ba8 commit 42b0301
Show file tree
Hide file tree
Showing 13 changed files with 626 additions and 75 deletions.
50 changes: 50 additions & 0 deletions features/background.feature
@@ -0,0 +1,50 @@
Feature: Background

Background allows you to add some context to the scenarios in a
single feature. A Background is much like a scenario containing a
number of steps. The difference is when it is run. The background is
run before each of your scenarios but after any of your Before
Hooks.

Scenario: One scenario and a background
Given the following feature:
"""
Feature: testing scenarios
Background:
Given a background step
Scenario:
When a scenario step
"""
And the step "a background step" has a passing mapping
And the step "a scenario step" has a passing mapping
When Cucumber runs the feature
Then the feature passes
And the step "a background step" passes
And the step "a scenario step" passes

Scenario: Two scenarios and a background
Given the following feature:
"""
Feature: testing scenarios
Background:
Given a background step
Scenario:
When a scenario step
Scenario:
When a second scenario step
"""
And the step "a background step" has a passing mapping
And the step "a scenario step" has a passing mapping
And the step "a second scenario step" has a passing mapping
When Cucumber runs the feature
Then the feature passes
# todo: ensure the background step is called twice
And the step "a background step" passes
And the step "a scenario step" passes
And the step "a second scenario step" passes

# TODO: failing background steps, failing scenario steps...

11 changes: 11 additions & 0 deletions features/step_definitions/cucumber_steps.js
Expand Up @@ -103,6 +103,12 @@ var cucumberSteps = function() {
callback();
});

Then(/^the step "([^"]*)" passes$/, function(stepName, callback) {
assertPassedStep(stepName);
callback();
});


Then(/^the step "([^"]*)" is skipped$/, function(stepName, callback) {
assertSkippedStep(stepName);
callback();
Expand Down Expand Up @@ -186,6 +192,11 @@ var cucumberSteps = function() {
assertNoPartialOutput("# Scenario: " + scenarioName, lastRunOutput);
}

function assertPassedStep(stepName) {
if (!isStepTouched(stepName))
throw(new Error("Expected step \"" + stepName + "\" to have passed."));
}

function assertSkippedStep(stepName) {
if (isStepTouched(stepName))
throw(new Error("Expected step \"" + stepName + "\" to have been skipped."));
Expand Down
1 change: 1 addition & 0 deletions lib/cucumber/ast.js
@@ -1,4 +1,5 @@
var Ast = {};
Ast.Background = require('./ast/background');
Ast.Features = require('./ast/features');
Ast.Feature = require('./ast/feature');
Ast.Scenario = require('./ast/scenario');
Expand Down
37 changes: 37 additions & 0 deletions lib/cucumber/ast/background.js
@@ -0,0 +1,37 @@
var Background = function(keyword, name, description, line) {
var Cucumber = require('../../cucumber');

var steps = Cucumber.Type.Collection();

var self = {
getKeyword: function getKeyword() {
return keyword;
},

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

getDescription: function getDescription() {
return description;
},

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

addStep: function addStep(step) {
steps.add(step);
},

getLastStep: function getLastStep() {
return steps.getLast();
},

getSteps: function getSteps() {
return steps;
}
};
return self;
};
module.exports = Background;
28 changes: 28 additions & 0 deletions lib/cucumber/ast/feature.js
Expand Up @@ -2,6 +2,7 @@ var Feature = function(keyword, name, description, line) {
var Cucumber = require('../../cucumber');

var scenarios = Cucumber.Type.Collection();
var background;

var self = {
getKeyword: function getKeyword() {
Expand All @@ -16,6 +17,18 @@ var Feature = function(keyword, name, description, line) {
return description;
},

addBackground: function addBackground(newBackground) {
background = newBackground;
},

getBackground: function getBackground() {
return background;
},

hasBackground: function hasBackground() {
return (typeof(background) != 'undefined');
},

addScenario: function addScenario(scenario) {
scenarios.add(scenario);
},
Expand All @@ -25,6 +38,21 @@ var Feature = function(keyword, name, description, line) {
},

acceptVisitor: function acceptVisitor(visitor, callback) {
self.instructVisitorToVisitBackground(visitor, function() {
self.instructVisitorToVisitScenarios(visitor, callback);
});
},

instructVisitorToVisitBackground: function instructVisitorToVisitBackground(visitor, callback) {
if (self.hasBackground()) {
var background = self.getBackground();
visitor.visitBackground(background, callback);
} else {
callback();
}
},

instructVisitorToVisitScenarios: function instructVisitorToVisitScenarios(visitor, callback) {
scenarios.forEach(function(scenario, iterate) {
visitor.visitScenario(scenario, iterate);
}, callback);
Expand Down
25 changes: 24 additions & 1 deletion lib/cucumber/ast/scenario.js
@@ -1,4 +1,4 @@
var Scenario = function(keyword, name, description, line) {
var Scenario = function(keyword, name, description, line, background) {
var Cucumber = require('../../cucumber');

var steps = Cucumber.Type.Collection();
Expand All @@ -12,6 +12,10 @@ var Scenario = function(keyword, name, description, line) {
return name;
},

getDescription: function getDescription() {
return description;
},

getLine: function getLine() {
return line;
},
Expand All @@ -25,6 +29,25 @@ var Scenario = function(keyword, name, description, line) {
},

acceptVisitor: function acceptVisitor(visitor, callback) {
self.instructVisitorToVisitBackgroundSteps(visitor, function() {
self.instructVisitorToVisitScenarioSteps(visitor, callback);
});
},

instructVisitorToVisitBackgroundSteps: function instructVisitorToVisitBackgroundSteps(visitor, callback) {
if (typeof(background) != 'undefined') {
var steps = background.getSteps();
self.instructVisitorToVisitSteps(visitor, steps, callback);
} else {
callback();
}
},

instructVisitorToVisitScenarioSteps: function instructVisitorToVisitScenarioSteps(visitor, callback) {
self.instructVisitorToVisitSteps(visitor, steps, callback);
},

instructVisitorToVisitSteps: function instructVisitorToVisitSteps(visitor, steps, callback) {
steps.forEach(function(step, iterate) {
visitor.visitStep(step, iterate);
}, callback);
Expand Down
29 changes: 20 additions & 9 deletions lib/cucumber/parser.js
Expand Up @@ -18,6 +18,7 @@ var Parser = function(featureSources) {
getEventHandlers: function getEventHandlers() {
return {
feature: self.handleFeature,
background: self.handleBackground,
scenario: self.handleScenario,
step: self.handleStep,
doc_string: self.handleDocString,
Expand All @@ -30,31 +31,41 @@ var Parser = function(featureSources) {
return features.getLastFeature();
},

getCurrentScenario: function getCurrentScenario() {
var currentFeature = self.getCurrentFeature();
return currentFeature.getLastScenario();
getCurrentScenarioOrBackground: function getCurrentScenarioOrBackground() {
var currentFeature = self.getCurrentFeature();
var scenarioOrBackground = currentFeature.getLastScenario();
if (!scenarioOrBackground)
scenarioOrBackground = currentFeature.getBackground();
return scenarioOrBackground;
},

getCurrentStep: function getCurrentStep() {
var currentScenario = self.getCurrentScenario();
return currentScenario.getLastStep();
var currentScenarioOrBackground = self.getCurrentScenarioOrBackground();
return currentScenarioOrBackground.getLastStep();
},

handleFeature: function handleFeature(keyword, name, description, line) {
var feature = Cucumber.Ast.Feature(keyword, name, description, line);
features.addFeature(feature);
},

handleBackground: function handleBackground(keyword, name, description, line) {
var background = Cucumber.Ast.Background(keyword, name, description, line);
var currentFeature = self.getCurrentFeature();
currentFeature.addBackground(background);
},

handleScenario: function handleScenario(keyword, name, description, line) {
var scenario = Cucumber.Ast.Scenario(keyword, name, description, line);
var currentFeature = self.getCurrentFeature();
var background = currentFeature.getBackground();
var scenario = Cucumber.Ast.Scenario(keyword, name, description, line, background);
currentFeature.addScenario(scenario);
},

handleStep: function handleStep(keyword, name, line) {
var step = Cucumber.Ast.Step(keyword, name, line);
var currentScenario = self.getCurrentScenario();
currentScenario.addStep(step);
var step = Cucumber.Ast.Step(keyword, name, line);
var currentScenarioOrBackground = self.getCurrentScenarioOrBackground();
currentScenarioOrBackground.addStep(step);
},

handleDocString: function handleDocString(string, line) {
Expand Down
7 changes: 7 additions & 0 deletions lib/cucumber/runtime/ast_tree_walker.js
Expand Up @@ -30,6 +30,12 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
);
},

visitBackground: function visitBackground(background, callback) {
var payload = { background: background };
var event = AstTreeWalker.Event(AstTreeWalker.BACKGROUND_EVENT_NAME, payload);
self.broadcastEvent(event, callback);
},

visitScenario: function visitScenario(scenario, callback) {
self.witnessNewScenario();
var payload = { scenario: scenario };
Expand Down Expand Up @@ -155,6 +161,7 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
};
AstTreeWalker.FEATURES_EVENT_NAME = 'Features';
AstTreeWalker.FEATURE_EVENT_NAME = 'Feature';
AstTreeWalker.BACKGROUND_EVENT_NAME = 'Background';
AstTreeWalker.SCENARIO_EVENT_NAME = 'Scenario';
AstTreeWalker.STEP_EVENT_NAME = 'Step';
AstTreeWalker.UNDEFINED_STEP_EVENT_NAME = 'UndefinedStep';
Expand Down
75 changes: 75 additions & 0 deletions spec/cucumber/ast/background_spec.js
@@ -0,0 +1,75 @@
require('../../support/spec_helper');

describe("Cucumber.Ast.Background", function() {
var Cucumber = require('cucumber');
var steps;
var background, keyword, name, description, line, lastStep;

beforeEach(function() {
keyword = createSpy("background keyword");
name = createSpy("background name");
description = createSpy("background description");
line = createSpy("starting background line number");
lastStep = createSpy("Last step");
steps = createSpy("Step collection");
spyOnStub(steps, 'add');
spyOnStub(steps, 'getLast').andReturn(lastStep);
spyOn(Cucumber.Type, 'Collection').andReturn(steps);
background = Cucumber.Ast.Background(keyword, name, description, line);
});

describe("constructor", function() {
it("creates a new collection to store steps", function() {
expect(Cucumber.Type.Collection).toHaveBeenCalled();
});
});

describe("getKeyword()", function() {
it("returns the keyword of the background", function() {
expect(background.getKeyword()).toBe(keyword);
});
});

describe("getName()", function() {
it("returns the name of the background", function() {
expect(background.getName()).toBe(name);
});
});

describe("getDescription()", function() {
it("returns the description of the background", function() {
expect(background.getDescription()).toBe(description);
});
});

describe("getLine()", function() {
it("returns the line on which the background starts", function() {
expect(background.getLine()).toBe(line);
});
});

describe("addStep()", function() {
it("adds the step to the steps (collection)", function() {
var step = createSpy("step AST element");
background.addStep(step);
expect(steps.add).toHaveBeenCalledWith(step);
});
});

describe("getLastStep()", function() {
it("gets the last step from the collection", function() {
background.getLastStep();
expect(steps.getLast).toHaveBeenCalled();
});

it("returns that last step from the collection", function() {
expect(background.getLastStep()).toBe(lastStep);
});
});

describe("getSteps()", function() {
it("returns the steps", function() {
expect(background.getSteps()).toBe(steps);
});
});
});

0 comments on commit 42b0301

Please sign in to comment.