Permalink
Browse files

Add tags support (close #7)

  • Loading branch information...
1 parent ad62dfc commit c5dcf07a4c674becee147f6aeaa96e4a0f3bf1ee @jbpros jbpros committed Feb 3, 2012
2 features/cucumber-tck
@@ -1 +1 @@
-Subproject commit aa4f859f80a9f4eeb37525269a04bef17e99d1b2
+Subproject commit 5d3b3d5f54d98cacc31ca687f7eb41fa5bef9fe9
View
47 features/step_definitions/cucumber_js_mappings.rb
@@ -18,6 +18,15 @@ def run_feature
run_simple "#{cucumber_bin} #{FEATURE_FILE}", false
end
+ def run_feature_with_tag_groups tag_groups
+ write_main_step_definitions_file
+ command = "#{cucumber_bin} #{FEATURE_FILE}"
+ tag_groups.each do |tag_group|
+ command += " --tags #{tag_group.join(',')}"
+ end
+ run_simple command, false
+ end
+
def cucumber_bin
File.expand_path(File.dirname(__FILE__) + '/../../bin/cucumber.js')
end
@@ -131,17 +140,31 @@ def write_scenario
scenario_with_steps("A scenario", "Given a step")
end
- def provide_cycle_logging_facilities
- return if @cycle_logging_facilities_ready
+ def write_passing_scenario_with_tags(tags)
+ tags = [tags] unless tags.respond_to? :any?
+ @next_step_count ||= 0
+ step_name = nth_step_name @next_step_count += 1
+ provide_cycle_logging_facilities
+ append_step_definition(step_name, "this.logCycleEvent('#{step_name}');\ncallback();")
+ append_to_feature <<-EOF
- @cycle_logging_facilities_ready = true
- append_support_code <<-EOF
+ #{tags.join(' ')}
+ Scenario: scenario tagged with #{tags.join(', ')}
+ Given #{step_name}
+EOF
+ end
+
+ def provide_cycle_logging_facilities
+ unless @cycle_logging_facilities_ready
+ append_support_code <<-EOF
this.World.prototype.logCycleEvent = function logCycleEvent(name) {
fd = fs.openSync('#{CYCLE_LOG_FILE}', 'a');
fs.writeSync(fd, " -> " + name, null);
fs.closeSync(fd);
};
EOF
+ @cycle_logging_facilities_ready = true
+ end
end
def assert_passing_scenario
@@ -186,6 +209,11 @@ def assert_cycle_sequence *args
check_file_content(CucumberJsMappings::CYCLE_LOG_FILE, expected_string, true)
end
+ def assert_complete_cycle_sequence *args
+ expected_string = args.join " -> "
+ check_exact_file_content(CucumberJsMappings::CYCLE_LOG_FILE, expected_string)
+ end
+
def assert_data_table_equals_json(json)
prep_for_fs_check do
log_file_contents = IO.read(DATA_TABLE_LOG_FILE)
@@ -211,6 +239,13 @@ def assert_suggested_step_definition_snippet(stepdef_keyword, stepdef_pattern, p
assert_partial_output(expected_snippet, all_output)
end
+ def assert_executed_scenarios *scenario_offsets
+ sequence = scenario_offsets.inject('') do |sequence, scenario_offset|
+ "#{sequence} -> #{nth_step_name(scenario_offset)}"
+ end
+ assert_complete_cycle_sequence sequence
+ end
+
def failed_output
"failed"
end
@@ -258,5 +293,9 @@ def get_file_contents(file_path)
f.read
end
end
+
+ def nth_step_name n
+ "step #{n}"
+ end
end
World(CucumberJsMappings)
View
79 features/step_definitions/cucumber_steps.js
@@ -77,32 +77,79 @@ var cucumberSteps = function() {
callback();
});
+ Given(/^a scenario tagged with "([^"]*)"$/, function(tag, callback) {
+ this.addPassingScenarioWithTags([tag]);
+ callback();
+ });
+
+ Given(/^a scenario tagged with "([^"]*)" and "([^"]*)"$/, function(tag1, tag2, callback) {
+ this.addPassingScenarioWithTags([tag1, tag2]);
+ callback();
+ });
+
+ this.Given(/^a scenario tagged with "([^"]*)", "([^"]*)" and "([^"]*)"$/, function(tag1, tag2, tag3, callback) {
+ this.addPassingScenarioWithTags([tag1, tag2, tag3]);
+ callback();
+ });
+
When(/^Cucumber executes the scenario$/, function(callback) {
- this.runFeature(callback);
+ this.runFeature({}, callback);
});
When(/^Cucumber executes a scenario$/, function(callback) {
this.runAScenario(callback);
});
When(/^Cucumber runs the feature$/, function(callback) {
- this.runFeature(callback);
+ this.runFeature({}, callback);
});
When(/^Cucumber runs the scenario with steps for a calculator$/, function(callback) {
RpnCalculator = require('../support/rpn_calculator');
var supportCode = function() { require('./calculator_steps').initialize.call(this, RpnCalculator) };
- this.runFeatureWithSupportCodeSource(supportCode, callback);
+ this.runFeatureWithSupportCodeSource(supportCode, {}, callback);
});
When(/^the data table is passed to a step mapping that converts it to key\/value pairs$/, function(callback) {
this.stepDefinitions += "When(/^a step with data table:$/, function(dataTable, callback) {\
- world.dataTableLog = dataTable.hashes();\
- callback();\
+world.dataTableLog = dataTable.hashes();\
+callback();\
});\n";
- this.runFeature(callback);
+ this.runFeature({}, callback);
});
+ When(/^Cucumber executes scenarios tagged with "([^"]*)"$/, function(tag, callback) {
+ this.runFeature({tags: [[tag]]}, callback);
+ });
+
+ When(/^Cucumber executes scenarios not tagged with "([^"]*)"$/, function(tag, callback) {
+ this.runFeature({tags: [['~'+tag]]}, callback);
+ });
+
+ When(/^Cucumber executes scenarios tagged with "([^"]*)" or "([^"]*)"$/, function(tag1, tag2, callback) {
+ this.runFeature({tags: [[tag1, tag2]]}, callback);
+ });
+
+ When(/^Cucumber executes scenarios tagged with both "([^"]*)" and "([^"]*)"$/, function(tag1, tag2, callback) {
+ this.runFeature({tags: [[tag1], [tag2]]}, callback);
+ });
+
+ When(/^Cucumber executes scenarios not tagged with "([^"]*)" nor "([^"]*)"$/, function(tag1, tag2, callback) {
+ this.runFeature({tags: [['~'+tag1], ['~'+tag2]]}, callback);
+ });
+
+ When(/^Cucumber executes scenarios not tagged with both "([^"]*)" and "([^"]*)"$/, function(tag1, tag2, callback) {
+ this.runFeature({tags: [['~'+tag1, '~'+tag2]]}, callback);
+ });
+
+ When(/^Cucumber executes scenarios tagged with "([^"]*)" or without "([^"]*)"$/, function(tag1, tag2, callback) {
+ this.runFeature({tags: [[tag1, '~'+tag2]]}, callback);
+ });
+
+ When(/^Cucumber executes scenarios tagged with "([^"]*)" but not with both "([^"]*)" and "([^"]*)"$/, function(tag1, tag2, tag3, callback) {
+ this.runFeature({tags: [[tag1], ['~'+tag2], ['~'+tag3]]}, callback);
+});
+
Then(/^the scenario passes$/, function(callback) {
this.assertPassedScenario();
callback();
@@ -177,5 +224,25 @@ var cucumberSteps = function() {
this.assertFailureMessage("World constructor called back without World instance");
callback();
});
+
+ Then(/^only the first scenario is executed$/, function(callback) {
+ this.assertExecutedNumberedScenarios(1);
+ callback();
+ });
+
+ Then(/^only the first two scenarios are executed$/, function(callback) {
+ this.assertExecutedNumberedScenarios(1, 2);
+ callback();
+ });
+
+ Then(/^only the third scenario is executed$/, function(callback) {
+ this.assertExecutedNumberedScenarios(3);
+ callback();
+ });
+
+ Then(/^only the second, third and fourth scenarios are executed$/, function(callback) {
+ this.assertExecutedNumberedScenarios(2, 3, 4);
+ callback();
+ });
};
module.exports = cucumberSteps;
View
92 features/step_definitions/cucumber_world.js
@@ -4,28 +4,33 @@ var World = function(callback) {
this.stepDefinitions = "";
this.runOutput = "";
this.cycleEvents = "";
+ this.stepCount = 0;
this.runSucceeded = false;
World.mostRecentInstance = this;
callback(this);
};
var proto = World.prototype;
-proto.runFeature = function runFeature(callback) {
+proto.runFeature = function runFeature(options, callback) {
var supportCode;
var supportCodeSource = "supportCode = function() {\n var Given = When = Then = this.defineStep;\n" +
" var Before = this.Before, After = this.After;\n" +
this.stepDefinitions + "};\n";
var world = this;
eval(supportCodeSource);
- this.runFeatureWithSupportCodeSource(supportCode, callback);
+ this.runFeatureWithSupportCodeSource(supportCode, options, callback);
}
-proto.runFeatureWithSupportCodeSource = function runFeatureWithSupportCodeSource(supportCode, callback) {
+proto.runFeatureWithSupportCodeSource = function runFeatureWithSupportCodeSource(supportCode, options, callback) {
var world = this;
var Cucumber = require('../../lib/cucumber');
- var cucumber = Cucumber(this.featureSource, supportCode);
+ options = options || {};
+ var tags = options['tags'] || [];
+
+ var cucumber = Cucumber(this.featureSource, supportCode, {tags: tags});
var formatter = Cucumber.Listener.ProgressFormatter({logToConsole: false});
+
cucumber.attachListener(formatter);
try {
cucumber.start(function(succeeded) {
@@ -42,14 +47,12 @@ proto.runFeatureWithSupportCodeSource = function runFeatureWithSupportCodeSource
}
proto.runAScenario = function runAScenario(callback) {
- this.featureSource += "Feature:\n";
- this.featureSource += " Scenario:\n";
- this.featureSource += " Given a step\n";
- this.stepDefinitions += "Given(/^a step$/, function(callback) {\
+ this.addScenario("", "Given a step");
+ this.stepDefinitions += "Given(/^a step$/, function(callback) {\
world.logCycleEvent('step');\
callback();\
});";
- this.runFeature(callback);
+ this.runFeature({}, callback);
}
proto.logCycleEvent = function logCycleEvent(event) {
@@ -64,6 +67,40 @@ proto.isStepTouched = function isStepTouched(pattern) {
return (this.touchedSteps.indexOf(pattern) >= 0);
}
+proto.addScenario = function addScenario(name, contents, options) {
+ options = options || {};
+ var tags = options['tags'] || [];
+ var tagString = (tags.length > 0 ? tags.join(" ") + "\n" : "");
+ var scenarioName = tagString + "Scenario: " + name;
+ this.createEmptyFeature();
+ this.featureSource += this.indentCode(scenarioName, 1);
+ this.featureSource += this.indentCode(contents, 2);
+};
+
+proto.addPassingScenarioWithTags = function addPassingScenarioWithTags(tags) {
+ var stepName = this.makeNumberedStepName();
+ var scenarioName = "A scenario tagged with " + tags.join(', ');
+ var step = "Given " + stepName + "\n";
+ this.addScenario(scenarioName, step, {tags: tags});
+ this.stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\
+ world.logCycleEvent('" + stepName + "');\
+ callback();\
+});\n";
+};
+
+proto.createEmptyFeature = function createEmptyFeature() {
+ if (!this.emptyFeatureReady) {
+ this.featureSource += "Feature: A feature\n\n";
+ this.emptyFeatureReady = true;
+ }
+};
+
+proto.makeNumberedStepName = function makeNumberedStepName(index) {
+ var index = index || (++this.stepCount);
+ var stepName = "step " + index;
+ return stepName;
+}
+
proto.assertPassedFeature = function assertPassedFeature() {
this.assertNoPartialOutput("failed", this.runOutput);
this.assertSuccess();
@@ -140,10 +177,43 @@ proto.assertEqual = function assertRawDataTable(expected, actual) {
throw(new Error("Expected:\n\"" + actualJSON + "\"\nto match:\n\"" + expectedJSON + "\""));
}
-proto.assertCycleSequence = function assertCycleSequence(first, second) {
- var partialSequence = first + ' -> ' + second;
+proto.assertExecutedNumberedScenarios = function assertExecutedNumberedScenarios() {
+ var self = this;
+ var scenarioIndexes = Array.prototype.slice.apply(arguments);
+ var stepNames = [];
+ scenarioIndexes.forEach(function(scenarioIndex) {
+ var stepName = self.makeNumberedStepName(scenarioIndex);
+ stepNames.push(stepName);
+ });
+ this.assertCompleteCycleSequence.apply(this, stepNames);
+}
+
+proto.assertCycleSequence = function assertCycleSequence() {
+ var events = Array.prototype.slice.apply(arguments);
+ var partialSequence = ' -> ' + events.join(' -> ');
if (this.cycleEvents.indexOf(partialSequence) < 0)
throw(new Error("Expected cycle sequence \"" + this.cycleEvents + "\" to contain \"" + partialSequence + "\""));
}
+proto.assertCompleteCycleSequence = function assertCompleteCycleSequence() {
+ var events = Array.prototype.slice.apply(arguments);
+ var sequence = ' -> ' + events.join(' -> ');
+
+ if (this.cycleEvents != sequence)
+ throw(new Error("Expected cycle sequence \"" + this.cycleEvents + "\" to be \"" + sequence + "\""));
+
+}
+
+proto.indentCode = function indentCode(code, levels) {
+ var indented = '';
+ var lines = code.split("\n");
+ levels = levels || 1;
+
+ lines.forEach(function(line) {
+ var indent = (line == "" ? "" : Array(levels + 1).join(" "));
+ indented += indent + line + "\n";
+ });
+ return indented;
+};
+
exports.World = World;
View
4 lib/cucumber.js
@@ -1,5 +1,5 @@
-var Cucumber = function(featureSource, supportCodeInitializer) {
- var configuration = Cucumber.VolatileConfiguration(featureSource, supportCodeInitializer);
+var Cucumber = function(featureSource, supportCodeInitializer, options) {
+ var configuration = Cucumber.VolatileConfiguration(featureSource, supportCodeInitializer, options);
var runtime = Cucumber.Runtime(configuration);
return runtime;
};
View
2 lib/cucumber/ast.js
@@ -5,6 +5,8 @@ Ast.DataTable = require('./ast/data_table');
Ast.DocString = require('./ast/doc_string');
Ast.Feature = require('./ast/feature');
Ast.Features = require('./ast/features');
+Ast.Filter = require('./ast/filter');
Ast.Scenario = require('./ast/scenario');
Ast.Step = require('./ast/step');
+Ast.Tag = require('./ast/tag');
module.exports = Ast;
View
30 lib/cucumber/ast/assembler.js
@@ -1,5 +1,6 @@
-var Assembler = function(features) {
+var Assembler = function(features, filter) {
var currentFeature, currentScenarioOrBackground, currentStep;
+ var stashedTags = [];
var self = {
setCurrentFeature: function setCurrentFeature(feature) {
@@ -28,6 +29,21 @@ var Assembler = function(features) {
return currentStep;
},
+ stashTag: function stashTag(tag) {
+ stashedTags.push(tag);
+ },
+
+ revealTags: function revealTags() {
+ var revealedTags = stashedTags;
+ stashedTags = [];
+ return revealedTags;
+ },
+
+ applyStashedTagsToElement: function applyStashedTagsToElement(element) {
+ var revealedTags = self.revealTags();
+ element.setTags(revealedTags);
+ },
+
insertBackground: function insertBackground(background) {
self.setCurrentScenarioOrBackground(background);
var currentFeature = self.getCurrentFeature();
@@ -45,20 +61,28 @@ var Assembler = function(features) {
},
insertFeature: function insertFeature(feature) {
+ self.applyStashedTagsToElement(feature);
self.setCurrentFeature(feature);
features.addFeature(feature);
},
insertScenario: function insertScenario(scenario) {
+ self.applyStashedTagsToElement(scenario);
self.setCurrentScenarioOrBackground(scenario);
- var currentFeature = self.getCurrentFeature();
- currentFeature.addScenario(scenario);
+ if (filter.isScenarioEnrolled(scenario)) {
+ var currentFeature = self.getCurrentFeature();
+ currentFeature.addScenario(scenario);
+ }
},
insertStep: function insertStep(step) {
self.setCurrentStep(step);
var currentScenarioOrBackground = self.getCurrentScenarioOrBackground();
currentScenarioOrBackground.addStep(step);
+ },
+
+ insertTag: function insertTag(tag) {
+ self.stashTag(tag);
}
};
return self;
View
11 lib/cucumber/ast/feature.js
@@ -1,8 +1,9 @@
var Feature = function(keyword, name, description, line) {
var Cucumber = require('../../cucumber');
- var scenarios = Cucumber.Type.Collection();
var background;
+ var scenarios = Cucumber.Type.Collection();
+ var tags = [];
var self = {
getKeyword: function getKeyword() {
@@ -43,6 +44,14 @@ var Feature = function(keyword, name, description, line) {
return scenarios.getLast();
},
+ setTags: function setTags(newTags) {
+ tags = newTags;
+ },
+
+ getTags: function getTags() {
+ return tags;
+ },
+
acceptVisitor: function acceptVisitor(visitor, callback) {
self.instructVisitorToVisitBackground(visitor, function() {
self.instructVisitorToVisitScenarios(visitor, callback);
View
16 lib/cucumber/ast/filter.js
@@ -0,0 +1,16 @@
+var _ = require('underscore');
+
+var Filter = function(rules) {
+ var self = {
+ isScenarioEnrolled: function isScenarioEnrolled(scenario) {
+ var enrolled = _.all(rules, function(rule) {
+ return rule.isSatisfiedByElement(scenario);
+ });
+ return enrolled;
+ }
+ };
+ return self;
+};
+Filter.AnyOfTagsRule = require('./filter/any_of_tags_rule');
+Filter.ElementMatchingTagSpec = require('./filter/element_matching_tag_spec');
+module.exports = Filter;
View
17 lib/cucumber/ast/filter/any_of_tags_rule.js
@@ -0,0 +1,17 @@
+var _ = require('underscore');
+
+var AnyOfTagsRule = function(tags) {
+ var Cucumber = require('../../../cucumber');
+
+ var self = {
+ isSatisfiedByElement: function isSatisfiedByElement(element) {
+ var satisfied = _.any(tags, function(tag) {
+ var spec = Cucumber.Ast.Filter.ElementMatchingTagSpec(tag);
+ return spec.isMatching(element);
+ });
+ return satisfied;
+ }
+ };
+ return self;
+};
+module.exports = AnyOfTagsRule;
View
35 lib/cucumber/ast/filter/element_matching_tag_spec.js
@@ -0,0 +1,35 @@
+var _ = require('underscore');
+
+var ElementMatchingTagSpec = function(tagName) {
+ var self = {
+ isMatching: function isMatching(element) {
+ var elementTags = element.getTags();
+ var matching;
+ if (self.isExpectingTag())
+ matching = _.any(elementTags, self.isTagSatisfying);
+ else
+ matching = _.all(elementTags, self.isTagSatisfying);
+ return matching;
+ },
+
+ isTagSatisfying: function isTagSatisfying(tag) {
+ var checkedTagName = tag.getName();
+ var satisfying;
+ if (self.isExpectingTag())
+ satisfying = checkedTagName == tagName;
+ else {
+ var negatedCheckedTagName = ElementMatchingTagSpec.NEGATION_CHARACTER + checkedTagName;
+ satisfying = negatedCheckedTagName != tagName;
+ }
+ return satisfying;
+ },
+
+ isExpectingTag: function isExpectingTag() {
+ var expectingTag = tagName[0] != ElementMatchingTagSpec.NEGATION_CHARACTER;
+ return expectingTag;
+ }
+ };
+ return self;
+};
+ElementMatchingTagSpec.NEGATION_CHARACTER = '~';
+module.exports = ElementMatchingTagSpec;
View
11 lib/cucumber/ast/scenario.js
@@ -1,8 +1,9 @@
var Scenario = function(keyword, name, description, line) {
var Cucumber = require('../../cucumber');
- var steps = Cucumber.Type.Collection();
var background;
+ var steps = Cucumber.Type.Collection();
+ var tags = [];
var self = {
setBackground: function setBackground(newBackground) {
@@ -39,6 +40,14 @@ var Scenario = function(keyword, name, description, line) {
return steps.getLast();
},
+ setTags: function setTags(newTags) {
+ tags = newTags;
+ },
+
+ getTags: function getTags() {
+ return tags;
+ },
+
acceptVisitor: function acceptVisitor(visitor, callback) {
self.instructVisitorToVisitBackgroundSteps(visitor, function() {
self.instructVisitorToVisitScenarioSteps(visitor, callback);
View
11 lib/cucumber/ast/tag.js
@@ -0,0 +1,11 @@
+var Tag = function(name, line) {
+ var Cucumber = require('../../cucumber');
+
+ var self = {
+ getName: function getName() {
+ return name;
+ }
+ };
+ return self;
+};
+module.exports = Tag;
View
15 lib/cucumber/cli/argument_parser.js
@@ -1,3 +1,5 @@
+var _ = require('underscore');
+
var ArgumentParser = function(argv) {
var nopt = require('nopt');
var path = require('path');
@@ -49,9 +51,19 @@ var ArgumentParser = function(argv) {
return unexpandedSupportCodeFilePaths;
},
+ getTagGroups: function getTagGroups() {
+ var tagOptionValues = self.getOptionOrDefault(ArgumentParser.TAGS_OPTION_NAME, []);
+ var tagGroups = _.map(tagOptionValues, function(tagOptionValue) {
+ var tagGroup = tagOptionValue.split(ArgumentParser.TAG_OPTION_SEPARATOR);
+ return tagGroup;
+ });
+ return tagGroups;
+ },
+
getKnownOptionDefinitions: function getKnownOptionDefinitions() {
var definitions = {};
definitions[ArgumentParser.REQUIRE_OPTION_NAME] = [path, Array];
+ definitions[ArgumentParser.TAGS_OPTION_NAME] = [String, Array];
definitions[ArgumentParser.HELP_FLAG_NAME] = Boolean;
definitions[ArgumentParser.VERSION_FLAG_NAME] = Boolean;
return definitions;
@@ -96,6 +108,9 @@ ArgumentParser.FEATURE_FILENAME_REGEXP = /\/[^\/]+\.feature$/i;
ArgumentParser.LONG_OPTION_PREFIX = "--";
ArgumentParser.REQUIRE_OPTION_NAME = "require";
ArgumentParser.REQUIRE_OPTION_SHORT_NAME = "r";
+ArgumentParser.TAGS_OPTION_NAME = "tags";
+ArgumentParser.TAGS_OPTION_SHORT_NAME = "t";
+ArgumentParser.TAG_OPTION_SEPARATOR = ",";
ArgumentParser.HELP_FLAG_NAME = "help";
ArgumentParser.HELP_FLAG_SHORT_NAME = "h";
ArgumentParser.DEFAULT_HELP_FLAG_VALUE = false;
View
16 lib/cucumber/cli/configuration.js
@@ -12,13 +12,29 @@ var Configuration = function(argv) {
return featureSources;
},
+ getAstFilter: function getAstFilter() {
+ var tagRules = self.getTagAstFilterRules();
+ var astFilter = Cucumber.Ast.Filter(tagRules);
+ return astFilter;
+ },
+
getSupportCodeLibrary: function getSupportCodeLibrary() {
var supportCodeFilePaths = argumentParser.getSupportCodeFilePaths();
var supportCodeLoader = Cucumber.Cli.SupportCodeLoader(supportCodeFilePaths);
var supportCodeLibrary = supportCodeLoader.getSupportCodeLibrary();
return supportCodeLibrary;
},
+ getTagAstFilterRules: function getTagAstFilterRules() {
+ var tagGroups = argumentParser.getTagGroups();
+ var rules = [];
+ tagGroups.forEach(function(tags) {
+ var rule = Cucumber.Ast.Filter.AnyOfTagsRule(tags);
+ rules.push(rule);
+ });
+ return rules;
+ },
+
isHelpRequested: function isHelpRequested() {
var isHelpRequested = argumentParser.isHelpRequested();
return isHelpRequested;
View
12 lib/cucumber/parser.js
@@ -1,9 +1,9 @@
-var Parser = function(featureSources) {
+var Parser = function(featureSources, astFilter) {
var Gherkin = require('gherkin');
var Cucumber = require('../cucumber');
var features = Cucumber.Ast.Features();
- var astAssembler = Cucumber.Ast.Assembler(features);
+ var astAssembler = Cucumber.Ast.Assembler(features, astFilter);
var self = {
parse: function parse() {
@@ -25,10 +25,16 @@ var Parser = function(featureSources) {
feature: self.handleFeature,
row: self.handleDataTableRow,
scenario: self.handleScenario,
- step: self.handleStep
+ step: self.handleStep,
+ tag: self.handleTag
};
},
+ handleTag: function handleTag(tag, line) {
+ var tag = Cucumber.Ast.Tag(tag, line);
+ astAssembler.insertTag(tag);
+ },
+
handleBackground: function handleBackground(keyword, name, description, line) {
var background = Cucumber.Ast.Background(keyword, name, description, line);
astAssembler.insertBackground(background);
View
3 lib/cucumber/runtime.js
@@ -19,7 +19,8 @@ var Runtime = function(configuration) {
getFeatures: function getFeatures() {
var featureSources = configuration.getFeatureSources();
- var parser = Cucumber.Parser(featureSources);
+ var astFilter = configuration.getAstFilter();
+ var parser = Cucumber.Parser(featureSources, astFilter);
var features = parser.parse();
return features;
},
View
20 lib/cucumber/volatile_configuration.js
@@ -1,16 +1,34 @@
-var VolatileConfiguration = function VolatileConfiguration(featureSource, supportCodeInitializer) {
+var VolatileConfiguration = function VolatileConfiguration(featureSource, supportCodeInitializer, options) {
var Cucumber = require('../cucumber');
var supportCodeLibrary = Cucumber.SupportCode.Library(supportCodeInitializer);
+ options = options || {};
+ var tagGroups = options['tags'] || [];
+
var self = {
getFeatureSources: function getFeatureSources() {
var featureNameSourcePair = [VolatileConfiguration.FEATURE_SOURCE_NAME, featureSource];
return [featureNameSourcePair];
},
+ getAstFilter: function getAstFilter() {
+ var tagRules = self.getTagAstFilterRules();
+ var astFilter = Cucumber.Ast.Filter(tagRules);
+ return astFilter;
+ },
+
getSupportCodeLibrary: function getSupportCodeLibrary() {
return supportCodeLibrary;
+ },
+
+ getTagAstFilterRules: function getTagAstFilterRules() {
+ var rules = [];
+ tagGroups.forEach(function(tags) {
+ var rule = Cucumber.Ast.Filter.AnyOfTagsRule(tags);
+ rules.push(rule);
+ });
+ return rules;
}
};
return self;
View
115 spec/cucumber/ast/assembler_spec.js
@@ -2,11 +2,12 @@ require('../../support/spec_helper');
describe("Cucumber.Ast.Assembler", function() {
var Cucumber = requireLib('cucumber');
- var assembler, features;
+ var assembler, features, filter;
beforeEach(function() {
features = createSpy("features");
- assembler = Cucumber.Ast.Assembler(features);
+ filter = createSpy("filter");
+ assembler = Cucumber.Ast.Assembler(features, filter);
});
describe("setCurrentFeature()", function() {
@@ -76,6 +77,48 @@ describe("Cucumber.Ast.Assembler", function() {
});
});
+ describe("revealTags() [stashTag()]", function() {
+ var firstTag, secondTag;
+
+ beforeEach(function() {
+ firstTag = createSpy("first tag");
+ secondTag = createSpy("second tag");
+ assembler.stashTag(firstTag);
+ assembler.stashTag(secondTag);
+ });
+
+ it("returns the stashed tags", function() {
+ expect(assembler.revealTags()).toEqual([firstTag, secondTag]);
+ });
+
+ it("removes the tags from the stash", function() {
+ var thirdTag = createSpy("third tag");
+ assembler.revealTags();
+ assembler.stashTag(thirdTag);
+ expect(assembler.revealTags()).toEqual([thirdTag]);
+ });
+ });
+
+ describe("applyStashedTagsToElement()", function() {
+ var element, revealedTags;
+
+ beforeEach(function() {
+ element = createSpyWithStubs("any AST element accepting tags", {setTags: null});
+ revealedTags = createSpy("revealed tags");
+ spyOn(assembler, 'revealTags').andReturn(revealedTags);
+ });
+
+ it("reveals the tags", function() {
+ assembler.applyStashedTagsToElement(element);
+ expect(assembler.revealTags).toHaveBeenCalled();
+ });
+
+ it("sets the tags to the element", function() {
+ assembler.applyStashedTagsToElement(element);
+ expect(element.setTags).toHaveBeenCalledWith(revealedTags);
+ });
+ });
+
describe("insertBackground()", function() {
var background, currentFeature;
@@ -108,9 +151,15 @@ describe("Cucumber.Ast.Assembler", function() {
beforeEach(function() {
feature = createSpy("feature");
spyOnStub(features, 'addFeature');
+ spyOn(assembler, 'applyStashedTagsToElement');
spyOn(assembler, 'setCurrentFeature');
});
+ it("applies the stashed tags to the feature", function() {
+ assembler.insertFeature(feature);
+ expect(assembler.applyStashedTagsToElement).toHaveBeenCalledWith(feature);
+ });
+
it("sets the feature as the current feature", function() {
assembler.insertFeature(feature);
expect(assembler.setCurrentFeature).toHaveBeenCalledWith(feature);
@@ -166,25 +215,61 @@ describe("Cucumber.Ast.Assembler", function() {
var scenario, currentFeature;
beforeEach(function() {
- scenario = createSpy("scenario");
+ scenario = createSpy("scenario");
currentFeature = createSpyWithStubs("current feature", {addScenario: null});
+ spyOn(assembler, 'applyStashedTagsToElement');
+ spyOnStub(filter, 'isScenarioEnrolled');
spyOn(assembler, 'getCurrentFeature').andReturn(currentFeature);
spyOn(assembler, 'setCurrentScenarioOrBackground');
});
+ it("applies the stashed tags to the scenario", function() {
+ assembler.insertScenario(scenario);
+ expect(assembler.applyStashedTagsToElement).toHaveBeenCalledWith(scenario);
+ });
+
it("sets the scenario as the current scenario", function() {
assembler.insertScenario(scenario);
expect(assembler.setCurrentScenarioOrBackground).toHaveBeenCalledWith(scenario);
});
- it("gets the current feature", function() {
+ it("asks the filter if the scenario is enrolled", function() {
assembler.insertScenario(scenario);
- expect(assembler.getCurrentFeature).toHaveBeenCalled();
+ expect(filter.isScenarioEnrolled).toHaveBeenCalledWith(scenario);
});
- it("adds the scenario to the current feature", function() {
- assembler.insertScenario(scenario);
- expect(currentFeature.addScenario).toHaveBeenCalledWith(scenario);
+ describe("when the scenario is enrolled", function() {
+
+ beforeEach(function() {
+ filter.isScenarioEnrolled.andReturn(true);
+ });
+
+ it("gets the current feature", function() {
+ assembler.insertScenario(scenario);
+ expect(assembler.getCurrentFeature).toHaveBeenCalled();
+ });
+
+ it("adds the scenario to the current feature", function() {
+ assembler.insertScenario(scenario);
+ expect(currentFeature.addScenario).toHaveBeenCalledWith(scenario);
+ });
+ });
+
+ describe("when the scenario is not enrolled", function() {
+
+ beforeEach(function() {
+ filter.isScenarioEnrolled.andReturn(false);
+ });
+
+ it("does not get the current feature", function() {
+ assembler.insertScenario(scenario);
+ expect(assembler.getCurrentFeature).not.toHaveBeenCalled();
+ });
+
+ it("does not add the scenario to the current feature", function() {
+ assembler.insertScenario(scenario);
+ expect(currentFeature.addScenario).not.toHaveBeenCalledWith(scenario);
+ });
});
});
@@ -213,4 +298,18 @@ describe("Cucumber.Ast.Assembler", function() {
expect(currentScenarioOrBackground.addStep).toHaveBeenCalledWith(step);
});
});
+
+ describe("insertTag()", function() {
+ var tag;
+
+ beforeEach(function() {
+ tag = createSpy("tag");
+ spyOn(assembler, 'stashTag');
+ });
+
+ it("stashes the tag", function() {
+ assembler.insertTag(tag);
+ expect(assembler.stashTag).toHaveBeenCalledWith(tag);
+ });
+ });
});
View
8 spec/cucumber/ast/feature_spec.js
@@ -118,6 +118,14 @@ describe("Cucumber.Ast.Feature", function() {
});
});
+ describe("getTags() [setTags()]", function() {
+ it("returns the tags", function() {
+ var tags = createSpy("tags");
+ feature.setTags(tags);
+ expect(feature.getTags()).toBe(tags);
+ });
+ });
+
describe("acceptVisitor", function() {
var visitor, callback;
View
62 spec/cucumber/ast/filter/any_of_tags_rule_spec.js
@@ -0,0 +1,62 @@
+require('../../../support/spec_helper');
+
+describe("Cucumber.Ast.Filter.AnyOfTagsRule", function() {
+ var Cucumber = requireLib('cucumber');
+
+ var rule, tags;
+
+ beforeEach(function() {
+ tags = createSpy("tags");
+ rule = Cucumber.Ast.Filter.AnyOfTagsRule(tags);
+ });
+
+ describe("isSatisfiedByElement()", function() {
+ var _ = require('underscore');
+
+ var element, satisfyingElement;
+
+ beforeEach(function() {
+ element = createSpy("element");
+ satisfyingElement = createSpy("wether the element is satisfying");
+ spyOn(_, 'any').andReturn(satisfyingElement);
+ });
+
+ it("looks for a tag matching some condition", function() {
+ rule.isSatisfiedByElement(element);
+ expect(_.any).toHaveBeenCalled();
+ expect(_.any).toHaveBeenCalledWithValueAsNthParameter(tags, 1);
+ expect(_.any).toHaveBeenCalledWithAFunctionAsNthParameter(2);
+ });
+
+ describe("every tag condition", function() {
+ var spec, everyTagConditionFunc, tag, matchingSpec;
+
+ beforeEach(function() {
+ matchingSpec = createSpy("wether the spec is satisfied or not");
+ tag = createSpy("tag");
+ spec = createSpyWithStubs("element matching tag spec", {isMatching: matchingSpec});
+ rule.isSatisfiedByElement(element);
+ everyTagConditionFunc = _.any.mostRecentCall.args[1];
+ spyOn(Cucumber.Ast.Filter, 'ElementMatchingTagSpec').andReturn(spec);
+ });
+
+ it("instantiates an element matching tag spec", function() {
+ everyTagConditionFunc(tag);
+ expect(Cucumber.Ast.Filter.ElementMatchingTagSpec).toHaveBeenCalledWith(tag);
+ });
+
+ it("checks wether the element is matching the spec", function() {
+ everyTagConditionFunc(tag);
+ expect(spec.isMatching).toHaveBeenCalledWith(element);
+ });
+
+ it("returns match result", function() {
+ expect(everyTagConditionFunc(tag)).toBe(matchingSpec);
+ });
+ });
+
+ it("returns wether it found a matching tag or not", function() {
+ expect(rule.isSatisfiedByElement(element)).toBe(satisfyingElement);
+ });
+ });
+});
View
154 spec/cucumber/ast/filter/element_matching_tag_spec_spec.js
@@ -0,0 +1,154 @@
+require('../../../support/spec_helper');
+
+describe("Cucumber.Ast.Filter.ElementMatchingTagSpec", function() {
+ var Cucumber = requireLib('cucumber');
+
+ var spec, tagName;
+
+ beforeEach(function() {
+ tagName = "tag";
+ spec = Cucumber.Ast.Filter.ElementMatchingTagSpec(tagName);
+ });
+
+ describe("isMatching()", function() {
+ var _ = require('underscore');
+
+ var element, elementTags, matchingElement;
+
+ beforeEach(function() {
+ elementTags = createSpy("element tags");
+ element = createSpyWithStubs("element", {getTags: elementTags});
+ matchingElement = createSpy("wether the element is matching or not");
+ spyOn(spec, 'isExpectingTag');
+ });
+
+ it("gets the element tags", function() {
+ spec.isMatching(element);
+ expect(element.getTags).toHaveBeenCalled();
+ });
+
+ it("checks wether the spec tag is expected or not", function() {
+ spec.isMatching(element);
+ expect(spec.isExpectingTag).toHaveBeenCalled();
+ });
+
+ describe("when the spec tag is expected on the element", function() {
+ beforeEach(function() {
+ spec.isExpectingTag.andReturn(true);
+ spyOn(_, 'any').andReturn(matchingElement);
+ });
+
+ it("checks wether any of the element tags match the spec tag", function() {
+ spec.isMatching(element);
+ expect(_.any).toHaveBeenCalledWith(elementTags, spec.isTagSatisfying);
+ });
+
+ it("returns wether the element matched or not", function() {
+ expect(spec.isMatching(element)).toBe(matchingElement);
+ });
+ });
+
+ describe("when the spec tag is not expected on the element", function() {
+ beforeEach(function() {
+ spec.isExpectingTag.andReturn(false);
+ spyOn(_, 'all').andReturn(matchingElement);
+ });
+
+ it("checks wether any of the element tags match the spec tag", function() {
+ spec.isMatching(element);
+ expect(_.all).toHaveBeenCalledWith(elementTags, spec.isTagSatisfying);
+ });
+
+ it("returns wether the element matched or not", function() {
+ expect(spec.isMatching(element)).toBe(matchingElement);
+ });
+ });
+ });
+
+ describe("isTagSatisfying()", function() {
+ var checkedTag;
+
+ beforeEach(function() {
+ checkedTag = createSpyWithStubs("element tag", {getName: null});
+ spyOn(spec, 'isExpectingTag');
+ });
+
+ it("gets the name of the tag", function() {
+ spec.isTagSatisfying(checkedTag);
+ expect(checkedTag.getName).toHaveBeenCalled();
+ });
+
+ it("checks wether the spec tag is expected or not on the element", function() {
+ spec.isTagSatisfying(checkedTag);
+ expect(spec.isExpectingTag).toHaveBeenCalled();
+ });
+
+ describe("when the spec expects the tag to be present on the element", function() {
+ beforeEach(function() {
+ spec.isExpectingTag.andReturn(true);
+ });
+
+ describe("when the tag names are identical", function() {
+ beforeEach(function() {
+ checkedTag.getName.andReturn(tagName);
+ });
+
+ it("is truthy", function() {
+ expect(spec.isTagSatisfying(checkedTag)).toBeTruthy();
+ });
+ });
+
+ describe("when the tag names are different", function() {
+ beforeEach(function() {
+ checkedTag.getName.andReturn("@obscure_tag");
+ });
+
+ it("is falsy", function() {
+ expect(spec.isTagSatisfying(checkedTag)).toBeFalsy();
+ });
+ });
+ });
+
+ describe("when the spec expects the tag to be absent on the element", function() {
+ beforeEach(function() {
+ tagName = "tag";
+ spec = Cucumber.Ast.Filter.ElementMatchingTagSpec("~" + tagName);
+ spyOn(spec, 'isExpectingTag').andReturn(false);
+ });
+
+ describe("when the tag names are identical", function() {
+ beforeEach(function() {
+ checkedTag.getName.andReturn(tagName);
+ });
+
+ it("is truthy", function() {
+ expect(spec.isTagSatisfying(checkedTag)).toBeFalsy();
+ });
+ });
+
+ describe("when the tag names are different", function() {
+ beforeEach(function() {
+ checkedTag.getName.andReturn("@obscure_tag");
+ });
+
+ it("is falsy", function() {
+ expect(spec.isTagSatisfying(checkedTag)).toBeTruthy();
+ });
+ });
+ });
+ });
+
+ describe("isExpectingTag()", function() {
+ it("is truthy when the tag does not start with a tilde (~)", function() {
+ tagName = "tag";
+ spec = Cucumber.Ast.Filter.ElementMatchingTagSpec(tagName);
+ expect(spec.isExpectingTag()).toBeTruthy();
+ });
+
+ it("is falsy when the tag starts with a tilde (~)", function() {
+ tagName = "~tag";
+ spec = Cucumber.Ast.Filter.ElementMatchingTagSpec(tagName);
+ expect(spec.isExpectingTag()).toBeFalsy();
+ });
+ });
+});
View
55 spec/cucumber/ast/filter_spec.js
@@ -0,0 +1,55 @@
+require('../../support/spec_helper');
+
+describe("Cucumber.Ast.Filter", function() {
+ var Cucumber = requireLib('cucumber');
+
+ var filter, rules;
+
+ beforeEach(function() {
+ rules = createSpy("rules");
+ filter = Cucumber.Ast.Filter(rules);
+ });
+
+ describe("isScenarioEnrolled()", function() {
+ var _ = require('underscore');
+
+ var scenario, scenarioEnrolled;
+
+ beforeEach(function() {
+ scenario = createSpy("scenario");
+ scenarioEnrolled = createSpy("wether the scenario is enrolled or not");
+ spyOn(_, 'all').andReturn(scenarioEnrolled);
+ });
+
+ it("checks all the rules for a condition", function() {
+ filter.isScenarioEnrolled(scenario);
+ expect(_.all).toHaveBeenCalled();
+ expect(_.all).toHaveBeenCalledWithValueAsNthParameter(rules, 1);
+ expect(_.all).toHaveBeenCalledWithAFunctionAsNthParameter(2);
+ });
+
+ describe("every rule condition", function() {
+ var ruleConditionFunc, rule, ruleSatisfied;
+
+ beforeEach(function() {
+ ruleSatisfied = createSpy("wether the rule was satisfied or not");
+ rule = createSpyWithStubs("rule", {isSatisfiedByElement: ruleSatisfied});
+ filter.isScenarioEnrolled(scenario);
+ ruleConditionFunc = _.all.mostRecentCall.args[1];
+ });
+
+ it("checks wether the rule is satisfied by the scenario", function() {
+ ruleConditionFunc(rule);
+ expect(rule.isSatisfiedByElement).toHaveBeenCalledWith(scenario);
+ });
+
+ it("returns wether the rule wa satisfied or not", function() {
+ expect(ruleConditionFunc(rule)).toBe(ruleSatisfied);
+ });
+ });
+
+ it("returns wether the scenario was enrolled or not", function() {
+ expect(filter.isScenarioEnrolled(scenario)).toBe(scenarioEnrolled);
+ })
+ });
+});
View
8 spec/cucumber/ast/scenario_spec.js
@@ -92,6 +92,14 @@ describe("Cucumber.Ast.Scenario", function() {
});
});
+ describe("getTags() [setTags()]", function() {
+ it("returns the tags", function() {
+ var tags = createSpy("tags");
+ scenario.setTags(tags);
+ expect(scenario.getTags()).toBe(tags);
+ });
+ });
+
describe("acceptVisitor", function() {
var visitor, callback;
View
19 spec/cucumber/ast/tag_spec.js
@@ -0,0 +1,19 @@
+require('../../support/spec_helper');
+
+describe("Cucumber.Ast.Tag", function() {
+ var Cucumber = requireLib('cucumber');
+
+ var tag, name, line;
+
+ beforeEach(function() {
+ name = createSpy("tag name");
+ line = createSpy("tag line");
+ tag = Cucumber.Ast.Tag(name, line);
+ });
+
+ describe("getName()", function() {
+ it("returns the name of the tag", function() {
+ expect(tag.getName()).toBe(name);
+ });
+ });
+});
View
54 spec/cucumber/cli/argument_parser_spec.js
@@ -59,6 +59,11 @@ describe("Cucumber.Cli.ArgumentParser", function() {
expect(knownOptionDefinitions[Cucumber.Cli.ArgumentParser.REQUIRE_OPTION_NAME]).toEqual([path, Array]);
});
+ it("defines a --tags option to include and exclude tags", function() {
+ var knownOptionDefinitions = argumentParser.getKnownOptionDefinitions();
+ expect(knownOptionDefinitions[Cucumber.Cli.ArgumentParser.TAGS_OPTION_NAME]).toEqual([String, Array]);
+ });
+
it("defines a --help flag", function() {
var knownOptionDefinitions = argumentParser.getKnownOptionDefinitions();
expect(knownOptionDefinitions[Cucumber.Cli.ArgumentParser.HELP_FLAG_NAME]).toEqual(Boolean);
@@ -239,6 +244,55 @@ describe("Cucumber.Cli.ArgumentParser", function() {
});
});
+ describe("getTagGroups()", function() {
+ var _ = require('underscore');
+
+ var tagOptionValues, tagGroups;
+
+ beforeEach(function() {
+ tagOptionValues = createSpy("tag option values");
+ tagGroups = createSpy("tag groups");
+ spyOn(argumentParser, 'getOptionOrDefault').andReturn(tagOptionValues);
+ spyOn(_, 'map').andReturn(tagGroups);
+ });
+
+ it("gets the tag option values", function() {
+ argumentParser.getTagGroups();
+ expect(argumentParser.getOptionOrDefault).toHaveBeenCalledWith(Cucumber.Cli.ArgumentParser.TAGS_OPTION_NAME, []);
+ });
+
+ it("maps the tag groups", function() {
+ argumentParser.getTagGroups();
+ expect(_.map).toHaveBeenCalled();
+ expect(_.map).toHaveBeenCalledWithValueAsNthParameter(tagOptionValues, 1);
+ expect(_.map).toHaveBeenCalledWithAFunctionAsNthParameter(2);
+ });
+
+ describe("tag group mapper function", function() {
+ var tagGroupMapperFunc, tagOptionValue, tagGroup;
+
+ beforeEach(function() {
+ tagGroup = createSpy("tag group");
+ tagOptionValue = createSpyWithStubs("tag option value", {split: tagGroup});
+ argumentParser.getTagGroups();
+ tagGroupMapperFunc = _.map.mostRecentCall.args[1];
+ });
+
+ it("splits the tag option value based on commas", function() {
+ tagGroupMapperFunc(tagOptionValue);
+ expect(tagOptionValue.split).toHaveBeenCalledWith(',');
+ });
+
+ it("returns the splitted tag group", function() {
+ expect(tagGroupMapperFunc(tagOptionValue)).toBe(tagGroup);
+ });
+ });
+
+ it("returns the tag option values", function() {
+ expect(argumentParser.getTagGroups()).toBe(tagGroups);
+ });
+ });
+
describe("isHelpRequested()", function() {
var isHelpRequested;
View
52 spec/cucumber/cli/configuration_spec.js
@@ -60,6 +60,31 @@ describe("Cucumber.Cli.Configuration", function() {
});
});
+ describe("getAstFilter()", function() {
+ var astFilter, tagFilterRules;
+
+ beforeEach(function() {
+ astFilter = createSpyWithStubs("AST filter");
+ tagFilterRules = createSpy("tag specs");
+ spyOn(Cucumber.Ast, 'Filter').andReturn(astFilter);
+ spyOn(configuration, 'getTagAstFilterRules').andReturn(tagFilterRules);
+ });
+
+ it("gets the tag filter rules", function() {
+ configuration.getAstFilter();
+ expect(configuration.getTagAstFilterRules).toHaveBeenCalled();
+ });
+
+ it("instantiates an AST filter", function() {
+ configuration.getAstFilter();
+ expect(Cucumber.Ast.Filter).toHaveBeenCalledWith(tagFilterRules);
+ });
+
+ it("returns the AST filter", function() {
+ expect(configuration.getAstFilter()).toBe(astFilter);
+ });
+ });
+
describe("getSupportCodeLibrary()", function() {
var supportCodeFilePaths, supportCodeLoader, supportCodeLibrary;
@@ -92,6 +117,33 @@ describe("Cucumber.Cli.Configuration", function() {
});
});
+ describe("getTagAstFilterRules()", function() {
+ var tagGroups, rules;
+
+ beforeEach(function() {
+ tagGroups = [createSpy("tag group 1"), createSpy("tag group 2"), createSpy("tag group 3")];
+ rules = [createSpy("any of tags rule 1"), createSpy("any of tags rule 2"), createSpy("any of tags rule 3")];
+ spyOnStub(argumentParser, 'getTagGroups').andReturn(tagGroups);
+ spyOn(Cucumber.Ast.Filter, 'AnyOfTagsRule').andReturnSeveral(rules);
+ });
+
+ it("gets the tag groups from the argument parser", function() {
+ configuration.getTagAstFilterRules();
+ expect(argumentParser.getTagGroups).toHaveBeenCalled();
+ });
+
+ it("creates an 'any of tags' filter rule per each group", function() {
+ configuration.getTagAstFilterRules();
+ tagGroups.forEach(function(tagGroup) {
+ expect(Cucumber.Ast.Filter.AnyOfTagsRule).toHaveBeenCalledWith(tagGroup);
+ });
+ });
+
+ it("returns all the rules", function() {
+ expect(configuration.getTagAstFilterRules()).toEqual(rules);
+ });
+ });
+
describe("isHelpRequired()", function() {
beforeEach(function() {
spyOnStub(argumentParser, 'isHelpRequested');
View
35 spec/cucumber/parser_spec.js
@@ -3,18 +3,19 @@ require('../support/spec_helper');
describe("Cucumber.Parser", function() {
var Cucumber = requireLib('cucumber');
var parser, featureSources;
- var features, astAssembler;
+ var features, astFilter, astAssembler;
beforeEach(function() {
features = createSpy("Root 'features' AST element");
+ astFilter = createSpy("AST filter");
featureSources = [
["(feature:1)", createSpy('first feature source')],
["(feature:2)", createSpy('second feature source')]
];
astAssembler = createSpy("AST assembler");
spyOn(Cucumber.Ast, 'Features').andReturn(features);
spyOn(Cucumber.Ast, 'Assembler').andReturn(astAssembler);
- parser = Cucumber.Parser(featureSources);
+ parser = Cucumber.Parser(featureSources, astFilter);
});
describe("constructor", function() {
@@ -23,7 +24,7 @@ describe("Cucumber.Parser", function() {
});
it("instantiates an AST assembler", function() {
- expect(Cucumber.Ast.Assembler).toHaveBeenCalledWith(features);
+ expect(Cucumber.Ast.Assembler).toHaveBeenCalledWith(features, astFilter);
});
});
@@ -116,6 +117,12 @@ describe("Cucumber.Parser", function() {
eventHandlers = parser.getEventHandlers();
expect(eventHandlers['row']).toBe(parser.handleDataTableRow);
});
+
+ it("provides a 'tag' handler", function() {
+ spyOn(parser, 'handleTag');
+ eventHandlers = parser.getEventHandlers();
+ expect(eventHandlers['tag']).toBe(parser.handleTag);
+ });
});
describe("handleBackground()", function() {
@@ -275,4 +282,26 @@ describe("Cucumber.Parser", function() {
expect(astAssembler.insertStep).toHaveBeenCalledWith(step);
});
});
+
+ describe("handleTag()", function() {
+ var name, line;
+
+ beforeEach(function() {
+ name = createSpy("tag name");
+ line = createSpy("line number");
+ tag = createSpy("tag AST element");
+ spyOn(Cucumber.Ast, 'Tag').andReturn(tag);
+ spyOnStub(astAssembler, 'insertTag');
+ });
+
+ it("creates a new tag AST element", function() {
+ parser.handleTag(name, line);
+ expect(Cucumber.Ast.Tag).toHaveBeenCalledWith(name, line);
+ });
+
+ it("tells the AST assembler to insert the tag into the tree", function() {
+ parser.handleTag(name, line);
+ expect(astAssembler.insertTag).toHaveBeenCalledWith(tag);
+ });
+ });
});
View
13 spec/cucumber/runtime_spec.js
@@ -10,7 +10,7 @@ describe("Cucumber.Runtime", function() {
listeners = createSpyWithStubs("listener collection", {add: null});
configuration = createSpy("configuration");
spyOn(Cucumber.Type, 'Collection').andReturn(listeners);
- runtime = Cucumber.Runtime(configuration);
+ runtime = Cucumber.Runtime(configuration);
});
describe("constructor", function() {
@@ -74,13 +74,15 @@ describe("Cucumber.Runtime", function() {
});
describe("getFeatures()", function() {
- var featureSources, parser, features;
+ var featureSources, astFilter, parser, features;
beforeEach(function() {
featureSources = createSpy("feature sources");
+ astFilter = createSpy("AST filter");
features = createSpy("features (AST)");
parser = createSpyWithStubs("parser", {parse: features});
spyOnStub(configuration, 'getFeatureSources').andReturn(featureSources);
+ spyOnStub(configuration, 'getAstFilter').andReturn(astFilter);
spyOn(Cucumber, 'Parser').andReturn(parser);
});
@@ -89,9 +91,14 @@ describe("Cucumber.Runtime", function() {
expect(configuration.getFeatureSources).toHaveBeenCalled();
});
+ it("gets the AST filter from the configuration", function() {
+ runtime.getFeatures();
+ expect(configuration.getAstFilter).toHaveBeenCalled();
+ });
+
it("creates a new Cucumber parser for the feature sources", function() {
runtime.getFeatures();
- expect(Cucumber.Parser).toHaveBeenCalledWith(featureSources);
+ expect(Cucumber.Parser).toHaveBeenCalledWith(featureSources, astFilter);
});
it("tells the parser to parse the features", function() {
View
60 spec/cucumber/volatile_configuration_spec.js
@@ -33,9 +33,69 @@ describe("Cucumber.VolatileConfiguration", function() {
})
});
+ describe("getAstFilter()", function() {
+ var astFilter, tagFilterRules;
+
+ beforeEach(function() {
+ astFilter = createSpyWithStubs("AST filter");
+ tagFilterRules = createSpy("tag specs");
+ spyOn(Cucumber.Ast, 'Filter').andReturn(astFilter);
+ spyOn(configuration, 'getTagAstFilterRules').andReturn(tagFilterRules);
+ });
+
+ it("gets the tag filter rules", function() {
+ configuration.getAstFilter();
+ expect(configuration.getTagAstFilterRules).toHaveBeenCalled();
+ });
+
+ it("instantiates an AST filter", function() {
+ configuration.getAstFilter();
+ expect(Cucumber.Ast.Filter).toHaveBeenCalledWith(tagFilterRules);
+ });
+
+ it("returns the AST filter", function() {
+ expect(configuration.getAstFilter()).toBe(astFilter);
+ });
+ });
+
describe("getSupportCodeLibrary()", function() {
it("returns the support code library", function() {
expect(configuration.getSupportCodeLibrary()).toBe(supportCodeLibrary);
});
});
+
+ describe("getTagAstFilterRules()", function() {
+ describe("when there are no tags specified", function() {
+ beforeEach(function() {
+ configuration = Cucumber.VolatileConfiguration(featureSource, supportCodeInitializer);
+ });
+
+ it("returns an empty set of rules", function() {
+ expect(configuration.getTagAstFilterRules()).toEqual([]);
+ });
+ });
+
+ describe("when there are some tags", function() {
+ var options, tagGroups, rules;
+
+ beforeEach(function() {
+ tagGroups = [createSpy("tag group 1"), createSpy("tag group 2"), createSpy("tag group 3")];
+ rules = [createSpy("any of tags rule 1"), createSpy("any of tags rule 2"), createSpy("any of tags rule 3")];
+ spyOn(Cucumber.Ast.Filter, 'AnyOfTagsRule').andReturnSeveral(rules);
+ options = {tags: tagGroups};
+ configuration = Cucumber.VolatileConfiguration(featureSource, supportCodeInitializer, options);
+ });
+
+ it("creates an 'any of tags' filter rule per each group", function() {
+ configuration.getTagAstFilterRules();
+ tagGroups.forEach(function(tagGroup) {
+ expect(Cucumber.Ast.Filter.AnyOfTagsRule).toHaveBeenCalledWith(tagGroup);
+ });
+ });
+
+ it("returns all the rules", function() {
+ expect(configuration.getTagAstFilterRules()).toEqual(rules);
+ });
+ });
+ });
});
View
11 spec/cucumber_spec.js
@@ -3,28 +3,29 @@ require('./support/spec_helper');
describe("Cucumber", function() {
var Cucumber = requireLib('cucumber');
- var featureSource, supportCodeInitializer, configuration;
+ var featureSource, supportCodeInitializer, options, configuration;
beforeEach(function() {
featureSource = createSpy("feature source");
supportCodeInitializer = createSpy("support code initialize");
+ options = createSpy("other options");
configuration = createSpy("volatile configuration");
runtime = createSpy("Cucumber runtime");
spyOn(Cucumber, 'VolatileConfiguration').andReturn(configuration);
spyOn(Cucumber, 'Runtime').andReturn(runtime);
});
it("creates a volatile configuration with the feature source and support code definition", function() {
- Cucumber(featureSource, supportCodeInitializer);
- expect(Cucumber.VolatileConfiguration).toHaveBeenCalledWith(featureSource, supportCodeInitializer);
+ Cucumber(featureSource, supportCodeInitializer, options);
+ expect(Cucumber.VolatileConfiguration).toHaveBeenCalledWith(featureSource, supportCodeInitializer, options);
});
it("creates a Cucumber runtime with the configuration", function() {
- Cucumber(featureSource, supportCodeInitializer);
+ Cucumber(featureSource, supportCodeInitializer, options);
expect(Cucumber.Runtime).toHaveBeenCalledWith(configuration);
});
it("returns the Cucumber runtime", function() {
- expect(Cucumber(featureSource, supportCodeInitializer)).toBe(runtime);
+ expect(Cucumber(featureSource, supportCodeInitializer, options)).toBe(runtime);
});
});
View
4 spec/support/configurations_shared_examples.js
@@ -12,4 +12,8 @@ itBehavesLikeAllCucumberConfigurations = function itBehavesLikeAllCucumberConfig
it("supplies the support code library", function() {
expect(configuration.getSupportCodeLibrary).toBeAFunction();
});
+
+ it("supplies the AST filter", function() {
+ expect(configuration.getAstFilter).toBeAFunction();
+ });
}

0 comments on commit c5dcf07

Please sign in to comment.