Skip to content

Commit 14e4ef5

Browse files
committed
Allow World constructor to set explicit World object (close #50)
1 parent aad94c4 commit 14e4ef5

File tree

7 files changed

+121
-16
lines changed

7 files changed

+121
-16
lines changed

features/step_definitions/cucumber_js_mappings.rb

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
module CucumberJsMappings
2-
STEP_DEFINITIONS_FILE = "features/step_definitions/cucumber_steps.js"
3-
COFFEE_SCRIPT_DEFINITIONS_FILE = "features/step_definitions/cucumber_steps.coffee"
4-
FEATURE_FILE = "features/a_feature.feature"
5-
WORLD_VARIABLE_LOG_FILE = "world_variable.log"
6-
WORLD_FUNCTION_LOG_FILE = "world_function.log"
7-
DATA_TABLE_LOG_FILE = "data_table.log"
8-
CYCLE_LOG_FILE = "cycle.log"
9-
CYCLE_SEQUENCE_SEPARATOR = " -> "
2+
STEP_DEFINITIONS_FILE = "features/step_definitions/cucumber_steps.js"
3+
COFFEE_SCRIPT_DEFINITIONS_FILE = "features/step_definitions/cucumber_steps.coffee"
4+
FEATURE_FILE = "features/a_feature.feature"
5+
WORLD_VARIABLE_LOG_FILE = "world_variable.log"
6+
WORLD_FUNCTION_LOG_FILE = "world_function.log"
7+
EXPLICIT_WORLD_OBJECT_FUNCTION_LOG_FILE = "world_function.log";
8+
DATA_TABLE_LOG_FILE = "data_table.log"
9+
CYCLE_LOG_FILE = "cycle.log"
10+
CYCLE_SEQUENCE_SEPARATOR = " -> "
1011

1112
attr_accessor :support_code
1213

@@ -124,6 +125,14 @@ def write_custom_world_constructor
124125
append_support_code "this.World = function CustomWorld(callback) { callback(); };\n"
125126
end
126127

128+
def write_custom_world_constructor_calling_back_with_explicit_object
129+
append_support_code "this.World = function CustomWorldConstructor(callback) {
130+
callback({
131+
someFunction: function() { fs.writeFileSync(\"#{EXPLICIT_WORLD_OBJECT_FUNCTION_LOG_FILE}\", \"\")}
132+
});
133+
};\n"
134+
end
135+
127136
def write_world_function
128137
append_support_code <<-EOF
129138
this.World.prototype.someFunction = function() {
@@ -231,6 +240,10 @@ def assert_world_function_called
231240
check_file_presence [WORLD_FUNCTION_LOG_FILE], true
232241
end
233242

243+
def assert_explicit_world_object_function_called
244+
check_file_presence [EXPLICIT_WORLD_OBJECT_FUNCTION_LOG_FILE], true
245+
end
246+
234247
def assert_cycle_sequence *args
235248
expected_string = args.join CYCLE_SEQUENCE_SEPARATOR
236249
check_file_content(CucumberJsMappings::CYCLE_LOG_FILE, expected_string, true)

features/step_definitions/cucumber_steps.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ setTimeout(callback.pending, 10);\
126126
callback();
127127
});
128128

129+
Given(/a custom World constructor calling back with an explicit object$/, function(callback) {
130+
this.stepDefinitions += "this.World = function CustomWorldConstructor(callback) {\n\
131+
callback({someFunction: function () {world.explicitWorldFunctionCalled = true; }});\n\
132+
};\n";
133+
callback();
134+
});
135+
129136
Given(/^a scenario without any tags$/, function(callback) {
130137
this.addPassingScenarioWithoutTags();
131138
callback();
@@ -159,6 +166,11 @@ setTimeout(callback.pending, 10);\
159166
this.runAScenario(callback);
160167
});
161168

169+
When(/^Cucumber executes a scenario that calls a function on the explicit World object$/, function(callback) {
170+
// express the regexp above with the code you wish you had
171+
this.runAScenarioCallingWorldFunction(callback);
172+
});
173+
162174
When(/^Cucumber executes a scenario tagged with "([^"]*)"$/, function(tag, callback) {
163175
this.addPassingScenarioWithTags(tag);
164176
this.runFeature({}, callback);
@@ -276,6 +288,12 @@ callback();\
276288
callback();
277289
});
278290

291+
this.Then(/^the explicit World object function should have been called$/, function(callback) {
292+
this.assertTrue(this.explicitWorldFunctionCalled);
293+
callback();
294+
});
295+
296+
279297
Then(/^the (before|after) hook is fired (?:before|after) the scenario$/, function(hookType, callback) {
280298
if (hookType == 'before')
281299
this.assertCycleSequence(hookType, 'step 1');

features/step_definitions/cucumber_steps.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
write_asynchronously_failing_mapping_with_message(step_name, message)
1414
end
1515

16+
Given /^a custom World constructor calling back with an explicit object$/ do
17+
write_custom_world_constructor_calling_back_with_explicit_object
18+
end
19+
1620
Given /^an around hook tagged with "([^"]*)"$/ do |tag|
1721
write_passing_hook :type => "around", :tags => [tag], :log_cycle_event_as => "hook"
1822
end
@@ -26,10 +30,24 @@
2630
run_feature
2731
end
2832

33+
When /^Cucumber executes a scenario that calls a function on the explicit World object$/ do
34+
write_mapping_calling_world_function("I call the explicit world object function")
35+
write_feature <<-EOF
36+
Feature:
37+
Scenario:
38+
When I call the explicit world object function
39+
EOF
40+
run_feature
41+
end
42+
2943
Then /^the mapping is run$/ do
3044
assert_passed "a mapping"
3145
end
3246

47+
Then /^the explicit World object function should have been called$/ do
48+
assert_explicit_world_object_function_called
49+
end
50+
3351
Then /^I see the version of Cucumber$/ do
3452
assert_matching_output "\\d+\\.\\d+\\.\\d+\\n", all_output
3553
assert_success true

features/step_definitions/cucumber_world.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ proto.runAScenario = function runAScenario(callback) {
5555
this.runFeature({}, callback);
5656
}
5757

58+
proto.runAScenarioCallingWorldFunction = function runAScenarioCallingWorldFunction(callback) {
59+
this.addScenario("", "Given a step");
60+
this.stepDefinitions += "Given(/^a step$/, function(callback) {\
61+
world.logCycleEvent('step 1');\
62+
this.someFunction();\
63+
callback();\
64+
});";
65+
this.runFeature({}, callback);
66+
}
67+
5868
proto.logCycleEvent = function logCycleEvent(event) {
5969
this.cycleEvents += " -> " + event;
6070
}
@@ -188,6 +198,11 @@ proto.assertEqual = function assertRawDataTable(expected, actual) {
188198
throw(new Error("Expected:\n\"" + actualJSON + "\"\nto match:\n\"" + expectedJSON + "\""));
189199
}
190200

201+
proto.assertTrue = function assertTrue(value) {
202+
if (!value)
203+
throw(new Error("Expected:\n\"" + value + "\"\n to be true"));
204+
}
205+
191206
proto.assertExecutedNumberedScenarios = function assertExecutedNumberedScenarios() {
192207
var self = this;
193208
var scenarioIndexes = Array.prototype.slice.apply(arguments);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
Feature: World constructor callback with object
2+
It is possible for the World constructor function to tell Cucumber
3+
to use another object than itself as the World instance:
4+
5+
this.World = function WorldConstructor(callback) {
6+
var myCustomWorld = { dance: function() { /* ... */ } };
7+
callback(myCustomWorld); // tell Cucumber to use myCustomWorld
8+
// as the world object.
9+
};
10+
11+
If no parameter is passed to the callback, the WorldConstructor
12+
instance will be used by Cucumber:
13+
14+
this.World = function WorldConstructor(callback) {
15+
var myCustomWorld = {};
16+
callback(); // could have been written `callback(this);`
17+
};
18+
19+
Scenario: scenario calling function on explicit world instance
20+
Given a custom World constructor calling back with an explicit object
21+
When Cucumber executes a scenario that calls a function on the explicit World object
22+
Then the feature passes
23+
And the explicit World object function should have been called

lib/cucumber/support_code/library.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ var Library = function(supportCodeDefinition) {
5151
},
5252

5353
instantiateNewWorld: function instantiateNewWorld(callback) {
54-
var world = new worldConstructor(function() {
54+
var world = new worldConstructor(function(explicitWorld) {
5555
process.nextTick(function() { // release the constructor
56-
callback(world);
56+
callback(explicitWorld || world);
5757
});
5858
});
5959
}

spec/cucumber/support_code/library_spec.js

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -310,15 +310,33 @@ describe("Cucumber.SupportCode.Library", function() {
310310
describe("next tick registered function", function() {
311311
var nextTickFunction;
312312

313-
beforeEach(function() {
314-
worldConstructorCompletionCallback();
315-
nextTickFunction = process.nextTick.mostRecentCall.args[0];
313+
describe("when the world constructor called back without any argument", function() {
314+
beforeEach(function() {
315+
worldConstructorCompletionCallback();
316+
nextTickFunction = process.nextTick.mostRecentCall.args[0];
317+
});
318+
319+
it("calls back with the world instance", function() {
320+
nextTickFunction();
321+
expect(callback).toHaveBeenCalledWith(worldInstance);
322+
});
316323
});
317324

318-
it("calls back with the world instance", function() {
319-
nextTickFunction();
320-
expect(callback).toHaveBeenCalledWith(worldInstance);
325+
describe("when the world constructor called back with an explicit world object", function() {
326+
var explicitWorld;
327+
328+
beforeEach(function() {
329+
explicitWorld = createSpy("explicit world object");
330+
worldConstructorCompletionCallback(explicitWorld);
331+
nextTickFunction = process.nextTick.mostRecentCall.args[0];
332+
});
333+
334+
it("calls back with the world instance", function() {
335+
nextTickFunction();
336+
expect(callback).toHaveBeenCalledWith(explicitWorld);
337+
});
321338
});
339+
322340
});
323341
});
324342
});

0 commit comments

Comments
 (0)