From d6e2d4180e32f7a30ac023d2ebf99b25bc159ac5 Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Wed, 19 Oct 2011 19:12:34 -0500 Subject: [PATCH 01/14] Initial work on Before and After hooks. --- features/step_definitions/cucumber_steps.js | 20 ++++++ lib/cucumber/runtime/ast_tree_walker.js | 12 +++- lib/cucumber/support_code.js | 1 + lib/cucumber/support_code/callback.js | 18 +++++ lib/cucumber/support_code/library.js | 22 ++++++ spec/cucumber/runtime/ast_tree_walker_spec.js | 20 +++++- spec/cucumber/support_code/library_spec.js | 70 ++++++++++++++++++- 7 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 lib/cucumber/support_code/callback.js diff --git a/features/step_definitions/cucumber_steps.js b/features/step_definitions/cucumber_steps.js index 30466c5d9..d6971929e 100644 --- a/features/step_definitions/cucumber_steps.js +++ b/features/step_definitions/cucumber_steps.js @@ -18,6 +18,26 @@ var cucumberSteps = function() { callback(); }); + Given(/^a passing before hook$/, function(callback) { + stepDefinitions += "Before(function(callback) { touchStep(\"Before\"); callback(); });\n"; + callback(); + }); + + Then(/^the before hook is fired before the scenario$/, function(callback) { + assertPassedStep("Before"); + callback(); + }); + + Given(/^a passing after hook$/, function(callback) { + stepDefinitions += "After(function(callback) { touchStep(\"After\"); callback(); });\n"; + callback(); + }); + + Then(/^the after hook is fired after the scenario$/, function(callback) { + assertPassedStep("After"); + callback(); + }); + Given(/^the step "([^"]*)" has a failing mapping$/, function(stepName, callback) { this.stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\ world.touchStep(\"" + stepName + "\");\ diff --git a/lib/cucumber/runtime/ast_tree_walker.js b/lib/cucumber/runtime/ast_tree_walker.js index e4a402184..ae0b1e1f8 100644 --- a/lib/cucumber/runtime/ast_tree_walker.js +++ b/lib/cucumber/runtime/ast_tree_walker.js @@ -26,7 +26,17 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) { var event = AstTreeWalker.Event(AstTreeWalker.FEATURE_EVENT_NAME, payload); self.broadcastEventAroundUserFunction( event, - function(callback) { feature.acceptVisitor(self, callback); }, + function(callback) { + supportCodeLibrary.getBeforeCallbacks().forEach(function(beforeCallback, callback) { + beforeCallback.invoke(world, callback) + }, function() { + feature.acceptVisitor(self, function() { + supportCodeLibrary.getAfterCallbacks().forEach(function(afterCallback, callback) { + afterCallback.invoke(world, callback) + }, callback); + }); + }); + }, callback ); }, diff --git a/lib/cucumber/support_code.js b/lib/cucumber/support_code.js index 47a89e6ec..492d2a5b0 100644 --- a/lib/cucumber/support_code.js +++ b/lib/cucumber/support_code.js @@ -1,5 +1,6 @@ var SupportCode = {}; SupportCode.Library = require('./support_code/library'); +SupportCode.Callback = require('./support_code/callback'); SupportCode.StepDefinition = require('./support_code/step_definition'); SupportCode.StepDefinitionSnippetBuilder = require('./support_code/step_definition_snippet_builder'); SupportCode.WorldConstructor = require('./support_code/world_constructor'); diff --git a/lib/cucumber/support_code/callback.js b/lib/cucumber/support_code/callback.js new file mode 100644 index 000000000..4b16daadd --- /dev/null +++ b/lib/cucumber/support_code/callback.js @@ -0,0 +1,18 @@ +var Callback = function(code) { + var Cucumber = require('../../cucumber'); + + var self = { + invoke: function(world, callback) { + try { + code.apply(world, [callback]); + } catch(exception) { + if (exception) + Cucumber.Debug.warn(exception.stack || exception, 'exception inside before callback', 3); + + callback(); + } + } + }; + return self; +}; +module.exports = Callback; diff --git a/lib/cucumber/support_code/library.js b/lib/cucumber/support_code/library.js index 3d64272b9..485835007 100644 --- a/lib/cucumber/support_code/library.js +++ b/lib/cucumber/support_code/library.js @@ -1,6 +1,8 @@ var Library = function(supportCodeDefinition) { var Cucumber = require('../../cucumber'); + var beforeCallbacks = Cucumber.Type.Collection(); + var afterCallbacks = Cucumber.Type.Collection(); var stepDefinitions = Cucumber.Type.Collection(); var worldConstructor = Cucumber.SupportCode.WorldConstructor(); @@ -21,6 +23,24 @@ var Library = function(supportCodeDefinition) { return (stepDefinition != undefined); }, + defineBefore: function defineBefore(code) { + var beforeCallback = Cucumber.SupportCode.Callback(code); + beforeCallbacks.add(beforeCallback); + }, + + getBeforeCallbacks: function() { + return beforeCallbacks; + }, + + defineAfter: function defineAfter(code) { + var afterCallback = Cucumber.SupportCode.Callback(code); + afterCallbacks.add(afterCallback); + }, + + getAfterCallbacks: function() { + return afterCallbacks; + }, + defineStep: function defineStep(name, code) { var stepDefinition = Cucumber.SupportCode.StepDefinition(name, code); stepDefinitions.add(stepDefinition); @@ -32,6 +52,8 @@ var Library = function(supportCodeDefinition) { }; var supportCodeHelper = { + Before : self.defineBefore, + After : self.defineAfter, Given : self.defineStep, When : self.defineStep, Then : self.defineStep, diff --git a/spec/cucumber/runtime/ast_tree_walker_spec.js b/spec/cucumber/runtime/ast_tree_walker_spec.js index dd9252101..d2d030907 100644 --- a/spec/cucumber/runtime/ast_tree_walker_spec.js +++ b/spec/cucumber/runtime/ast_tree_walker_spec.js @@ -95,12 +95,17 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { describe("visitFeature()", function() { var feature, callback, event, payload; + var beforeCallbacks, afterCallbacks; beforeEach(function() { feature = createSpyWithStubs("Feature AST element", {acceptVisitor: null}); callback = createSpy("Callback"); event = createSpy("Event"); payload = {feature: feature}; + beforeCallbacks = Cucumber.Type.Collection(); + afterCallbacks = Cucumber.Type.Collection(); + spyOnStub(supportCodeLibrary, 'getBeforeCallbacks').andReturn(beforeCallbacks); + spyOnStub(supportCodeLibrary, 'getAfterCallbacks').andReturn(afterCallbacks); spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event); spyOn(treeWalker, 'broadcastEventAroundUserFunction'); }); @@ -110,6 +115,12 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { expect(Cucumber.Runtime.AstTreeWalker.Event).toHaveBeenCalledWith(Cucumber.Runtime.AstTreeWalker.FEATURE_EVENT_NAME, payload); }); + xit("calls the before function callbacks", function() { + }); + + xit("calls the after function callbacks", function() { + }); + it("broadcasts the visit of the feature", function() { treeWalker.visitFeature(feature, callback); expect(treeWalker.broadcastEventAroundUserFunction).toHaveBeenCalled(); @@ -130,9 +141,14 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { userFunction = treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1]; }); - it("visits the feature, passing it the received callback", function() { + it("visits the feature, passing it the tree walker", function() { userFunction(userFunctionCallback); - expect(feature.acceptVisitor).toHaveBeenCalledWith(treeWalker, userFunctionCallback); + expect(feature.acceptVisitor).toHaveBeenCalled(); + expect(feature.acceptVisitor). + toHaveBeenCalledWithValueAsNthParameter(treeWalker, 1); + }); + + xit("calls the user function callback after visiting the feature", function() { }); }); }); diff --git a/spec/cucumber/support_code/library_spec.js b/spec/cucumber/support_code/library_spec.js index cbc420d61..22df97e33 100644 --- a/spec/cucumber/support_code/library_spec.js +++ b/spec/cucumber/support_code/library_spec.js @@ -3,6 +3,8 @@ require('../../support/spec_helper'); describe("Cucumber.SupportCode.Library", function() { var Cucumber = requireLib('cucumber'); var library, rawSupportCode; + var beforeCallbackCollection; + var afterCallbackCollection; var stepDefinitionCollection; var worldConstructor; var spiesDuringSupportCodeDefinitionExecution = {}; @@ -10,6 +12,12 @@ describe("Cucumber.SupportCode.Library", function() { beforeEach(function() { rawSupportCode = createSpy("Raw support code"); + beforeCallbackCollection = [ + createSpyWithStubs("First before callback") + ]; + afterCallbackCollection = [ + createSpyWithStubs("First after callback") + ]; stepDefinitionCollection = [ createSpyWithStubs("First step definition", {matchesStepName:false}), createSpyWithStubs("Second step definition", {matchesStepName:false}), @@ -18,7 +26,15 @@ describe("Cucumber.SupportCode.Library", function() { worldConstructorCalled = false; worldConstructor = function() { worldConstructorCalled = true; }; spyOnStub(stepDefinitionCollection, 'syncForEach').andCallFake(function(cb) { stepDefinitionCollection.forEach(cb); }); - spyOn(Cucumber.Type, 'Collection').andReturn(stepDefinitionCollection); + spyOn(Cucumber.Type, 'Collection').andCallFake(function() { + if (this.Collection.callCount == 1) { + return beforeCallbackCollection; + } else if (this.Collection.callCount == 2) { + return afterCallbackCollection; + } else { + return stepDefinitionCollection; + } + }); spyOn(Cucumber.SupportCode, 'WorldConstructor').andReturn(worldConstructor); library = Cucumber.SupportCode.Library(rawSupportCode); }); @@ -47,6 +63,16 @@ describe("Cucumber.SupportCode.Library", function() { supportCodeHelper = rawSupportCode.mostRecentCall.object; }); + it("exposes a method to define Before methods", function() { + expect(supportCodeHelper.Before).toBeAFunction(); + expect(supportCodeHelper.Before).toBe(library.defineBefore); + }); + + it("exposes a method to define After methods", function() { + expect(supportCodeHelper.After).toBeAFunction(); + expect(supportCodeHelper.After).toBe(library.defineAfter); + }); + it("exposes a method to define Given steps", function() { expect(supportCodeHelper.Given).toBeAFunction(); expect(supportCodeHelper.Given).toBe(library.defineStep); @@ -132,6 +158,48 @@ describe("Cucumber.SupportCode.Library", function() { }); }); + describe("defineBefore", function() { + var code, beforeCallback; + + beforeEach(function() { + code = createSpy("before code"); + beforeCallback = createSpy("before callback"); + spyOn(Cucumber.SupportCode, "Callback").andReturn(beforeCallback); + spyOnStub(beforeCallbackCollection, "add"); + }); + + it("creates a before callback with the code", function() { + library.defineBefore(code); + expect(Cucumber.SupportCode.Callback).toHaveBeenCalledWith(code); + }); + + it("adds the step definition to the step collection", function() { + library.defineBefore(code); + expect(beforeCallbackCollection.add).toHaveBeenCalledWith(beforeCallback); + }); + }); + + describe("defineAfter", function() { + var code, afterCallback; + + beforeEach(function() { + code = createSpy("after code"); + afterCallback = createSpy("after callback"); + spyOn(Cucumber.SupportCode, "Callback").andReturn(afterCallback); + spyOnStub(afterCallbackCollection, "add"); + }); + + it("creates a after callback with the code", function() { + library.defineAfter(code); + expect(Cucumber.SupportCode.Callback).toHaveBeenCalledWith(code); + }); + + it("adds the step definition to the step collection", function() { + library.defineAfter(code); + expect(afterCallbackCollection.add).toHaveBeenCalledWith(afterCallback); + }); + }); + describe("defineStep()", function() { var name, code, stepDefinition; From 1c5dc502a2667e0b2a09c762f118ab89040f32f1 Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Thu, 20 Oct 2011 10:12:43 -0500 Subject: [PATCH 02/14] Rename Callback to Hook to match Cucumber's vocabulary. --- lib/cucumber/runtime/ast_tree_walker.js | 8 +-- lib/cucumber/support_code.js | 2 +- .../support_code/{callback.js => hook.js} | 6 +-- lib/cucumber/support_code/library.js | 22 ++++---- spec/cucumber/runtime/ast_tree_walker_spec.js | 18 +++---- spec/cucumber/support_code/library_spec.js | 52 +++++++++---------- 6 files changed, 54 insertions(+), 54 deletions(-) rename lib/cucumber/support_code/{callback.js => hook.js} (77%) diff --git a/lib/cucumber/runtime/ast_tree_walker.js b/lib/cucumber/runtime/ast_tree_walker.js index ae0b1e1f8..504674709 100644 --- a/lib/cucumber/runtime/ast_tree_walker.js +++ b/lib/cucumber/runtime/ast_tree_walker.js @@ -27,12 +27,12 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) { self.broadcastEventAroundUserFunction( event, function(callback) { - supportCodeLibrary.getBeforeCallbacks().forEach(function(beforeCallback, callback) { - beforeCallback.invoke(world, callback) + supportCodeLibrary.getBeforeHooks().forEach(function(beforeHook, callback) { + beforeHook.invoke(world, callback) }, function() { feature.acceptVisitor(self, function() { - supportCodeLibrary.getAfterCallbacks().forEach(function(afterCallback, callback) { - afterCallback.invoke(world, callback) + supportCodeLibrary.getAfterHooks().forEach(function(afterHook, callback) { + afterHook.invoke(world, callback) }, callback); }); }); diff --git a/lib/cucumber/support_code.js b/lib/cucumber/support_code.js index 492d2a5b0..d1322709f 100644 --- a/lib/cucumber/support_code.js +++ b/lib/cucumber/support_code.js @@ -1,6 +1,6 @@ var SupportCode = {}; +SupportCode.Hook = require('./support_code/hook'); SupportCode.Library = require('./support_code/library'); -SupportCode.Callback = require('./support_code/callback'); SupportCode.StepDefinition = require('./support_code/step_definition'); SupportCode.StepDefinitionSnippetBuilder = require('./support_code/step_definition_snippet_builder'); SupportCode.WorldConstructor = require('./support_code/world_constructor'); diff --git a/lib/cucumber/support_code/callback.js b/lib/cucumber/support_code/hook.js similarity index 77% rename from lib/cucumber/support_code/callback.js rename to lib/cucumber/support_code/hook.js index 4b16daadd..6950dbcb5 100644 --- a/lib/cucumber/support_code/callback.js +++ b/lib/cucumber/support_code/hook.js @@ -1,4 +1,4 @@ -var Callback = function(code) { +var Hook = function(code) { var Cucumber = require('../../cucumber'); var self = { @@ -7,7 +7,7 @@ var Callback = function(code) { code.apply(world, [callback]); } catch(exception) { if (exception) - Cucumber.Debug.warn(exception.stack || exception, 'exception inside before callback', 3); + Cucumber.Debug.warn(exception.stack || exception, 'exception inside hook', 3); callback(); } @@ -15,4 +15,4 @@ var Callback = function(code) { }; return self; }; -module.exports = Callback; +module.exports = Hook; diff --git a/lib/cucumber/support_code/library.js b/lib/cucumber/support_code/library.js index 485835007..28033d15a 100644 --- a/lib/cucumber/support_code/library.js +++ b/lib/cucumber/support_code/library.js @@ -1,9 +1,9 @@ var Library = function(supportCodeDefinition) { var Cucumber = require('../../cucumber'); - var beforeCallbacks = Cucumber.Type.Collection(); - var afterCallbacks = Cucumber.Type.Collection(); - var stepDefinitions = Cucumber.Type.Collection(); + var beforeHooks = Cucumber.Type.Collection(); + var afterHooks = Cucumber.Type.Collection(); + var stepDefinitions = Cucumber.Type.Collection(); var worldConstructor = Cucumber.SupportCode.WorldConstructor(); var self = { @@ -24,21 +24,21 @@ var Library = function(supportCodeDefinition) { }, defineBefore: function defineBefore(code) { - var beforeCallback = Cucumber.SupportCode.Callback(code); - beforeCallbacks.add(beforeCallback); + var beforeHook = Cucumber.SupportCode.Hook(code); + beforeHooks.add(beforeHook); }, - getBeforeCallbacks: function() { - return beforeCallbacks; + getBeforeHooks: function() { + return beforeHooks; }, defineAfter: function defineAfter(code) { - var afterCallback = Cucumber.SupportCode.Callback(code); - afterCallbacks.add(afterCallback); + var afterHook = Cucumber.SupportCode.Hook(code); + afterHooks.add(afterHook); }, - getAfterCallbacks: function() { - return afterCallbacks; + getAfterHooks: function() { + return afterHooks; }, defineStep: function defineStep(name, code) { diff --git a/spec/cucumber/runtime/ast_tree_walker_spec.js b/spec/cucumber/runtime/ast_tree_walker_spec.js index d2d030907..92a53e61b 100644 --- a/spec/cucumber/runtime/ast_tree_walker_spec.js +++ b/spec/cucumber/runtime/ast_tree_walker_spec.js @@ -95,17 +95,17 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { describe("visitFeature()", function() { var feature, callback, event, payload; - var beforeCallbacks, afterCallbacks; + var beforeHooks, afterHooks; beforeEach(function() { - feature = createSpyWithStubs("Feature AST element", {acceptVisitor: null}); - callback = createSpy("Callback"); - event = createSpy("Event"); - payload = {feature: feature}; - beforeCallbacks = Cucumber.Type.Collection(); - afterCallbacks = Cucumber.Type.Collection(); - spyOnStub(supportCodeLibrary, 'getBeforeCallbacks').andReturn(beforeCallbacks); - spyOnStub(supportCodeLibrary, 'getAfterCallbacks').andReturn(afterCallbacks); + feature = createSpyWithStubs("Feature AST element", {acceptVisitor: null}); + callback = createSpy("Callback"); + event = createSpy("Event"); + payload = {feature: feature}; + beforeHooks = Cucumber.Type.Collection(); + afterHooks = Cucumber.Type.Collection(); + spyOnStub(supportCodeLibrary, 'getBeforeHooks').andReturn(beforeHooks); + spyOnStub(supportCodeLibrary, 'getAfterHooks').andReturn(afterHooks); spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event); spyOn(treeWalker, 'broadcastEventAroundUserFunction'); }); diff --git a/spec/cucumber/support_code/library_spec.js b/spec/cucumber/support_code/library_spec.js index 22df97e33..edabfaaa0 100644 --- a/spec/cucumber/support_code/library_spec.js +++ b/spec/cucumber/support_code/library_spec.js @@ -3,8 +3,8 @@ require('../../support/spec_helper'); describe("Cucumber.SupportCode.Library", function() { var Cucumber = requireLib('cucumber'); var library, rawSupportCode; - var beforeCallbackCollection; - var afterCallbackCollection; + var beforeHookCollection; + var afterHookCollection; var stepDefinitionCollection; var worldConstructor; var spiesDuringSupportCodeDefinitionExecution = {}; @@ -12,11 +12,11 @@ describe("Cucumber.SupportCode.Library", function() { beforeEach(function() { rawSupportCode = createSpy("Raw support code"); - beforeCallbackCollection = [ - createSpyWithStubs("First before callback") + beforeHookCollection = [ + createSpyWithStubs("First before hook") ]; - afterCallbackCollection = [ - createSpyWithStubs("First after callback") + afterHookCollection = [ + createSpyWithStubs("First after hook") ]; stepDefinitionCollection = [ createSpyWithStubs("First step definition", {matchesStepName:false}), @@ -28,9 +28,9 @@ describe("Cucumber.SupportCode.Library", function() { spyOnStub(stepDefinitionCollection, 'syncForEach').andCallFake(function(cb) { stepDefinitionCollection.forEach(cb); }); spyOn(Cucumber.Type, 'Collection').andCallFake(function() { if (this.Collection.callCount == 1) { - return beforeCallbackCollection; + return beforeHookCollection; } else if (this.Collection.callCount == 2) { - return afterCallbackCollection; + return afterHookCollection; } else { return stepDefinitionCollection; } @@ -159,44 +159,44 @@ describe("Cucumber.SupportCode.Library", function() { }); describe("defineBefore", function() { - var code, beforeCallback; + var code, beforeHook; beforeEach(function() { - code = createSpy("before code"); - beforeCallback = createSpy("before callback"); - spyOn(Cucumber.SupportCode, "Callback").andReturn(beforeCallback); - spyOnStub(beforeCallbackCollection, "add"); + code = createSpy("before code"); + beforeHook = createSpy("before hook"); + spyOn(Cucumber.SupportCode, "Hook").andReturn(beforeHook); + spyOnStub(beforeHookCollection, "add"); }); - it("creates a before callback with the code", function() { + it("creates a before hook with the code", function() { library.defineBefore(code); - expect(Cucumber.SupportCode.Callback).toHaveBeenCalledWith(code); + expect(Cucumber.SupportCode.Hook).toHaveBeenCalledWith(code); }); - it("adds the step definition to the step collection", function() { + it("adds the before hook to the before hooks collection", function() { library.defineBefore(code); - expect(beforeCallbackCollection.add).toHaveBeenCalledWith(beforeCallback); + expect(beforeHookCollection.add).toHaveBeenCalledWith(beforeHook); }); }); describe("defineAfter", function() { - var code, afterCallback; + var code, afterHook; beforeEach(function() { - code = createSpy("after code"); - afterCallback = createSpy("after callback"); - spyOn(Cucumber.SupportCode, "Callback").andReturn(afterCallback); - spyOnStub(afterCallbackCollection, "add"); + code = createSpy("after code"); + afterHook = createSpy("after hook"); + spyOn(Cucumber.SupportCode, "Hook").andReturn(afterHook); + spyOnStub(afterHookCollection, "add"); }); - it("creates a after callback with the code", function() { + it("creates a after hook with the code", function() { library.defineAfter(code); - expect(Cucumber.SupportCode.Callback).toHaveBeenCalledWith(code); + expect(Cucumber.SupportCode.Hook).toHaveBeenCalledWith(code); }); - it("adds the step definition to the step collection", function() { + it("adds the after hook to the after hooks collection", function() { library.defineAfter(code); - expect(afterCallbackCollection.add).toHaveBeenCalledWith(afterCallback); + expect(afterHookCollection.add).toHaveBeenCalledWith(afterHook); }); }); From e60e24c1cc51e8fc384dfacbad2476ff51de8999 Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Thu, 20 Oct 2011 10:15:15 -0500 Subject: [PATCH 03/14] Include the hook type in the warning message. --- lib/cucumber/support_code/hook.js | 4 ++-- lib/cucumber/support_code/library.js | 4 ++-- spec/cucumber/support_code/library_spec.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/cucumber/support_code/hook.js b/lib/cucumber/support_code/hook.js index 6950dbcb5..36a4fe052 100644 --- a/lib/cucumber/support_code/hook.js +++ b/lib/cucumber/support_code/hook.js @@ -1,4 +1,4 @@ -var Hook = function(code) { +var Hook = function(type, code) { var Cucumber = require('../../cucumber'); var self = { @@ -7,7 +7,7 @@ var Hook = function(code) { code.apply(world, [callback]); } catch(exception) { if (exception) - Cucumber.Debug.warn(exception.stack || exception, 'exception inside hook', 3); + Cucumber.Debug.warn(exception.stack || exception, 'exception inside ' + type + ' hook', 3); callback(); } diff --git a/lib/cucumber/support_code/library.js b/lib/cucumber/support_code/library.js index 28033d15a..e2c85aba4 100644 --- a/lib/cucumber/support_code/library.js +++ b/lib/cucumber/support_code/library.js @@ -24,7 +24,7 @@ var Library = function(supportCodeDefinition) { }, defineBefore: function defineBefore(code) { - var beforeHook = Cucumber.SupportCode.Hook(code); + var beforeHook = Cucumber.SupportCode.Hook('before', code); beforeHooks.add(beforeHook); }, @@ -33,7 +33,7 @@ var Library = function(supportCodeDefinition) { }, defineAfter: function defineAfter(code) { - var afterHook = Cucumber.SupportCode.Hook(code); + var afterHook = Cucumber.SupportCode.Hook('after', code); afterHooks.add(afterHook); }, diff --git a/spec/cucumber/support_code/library_spec.js b/spec/cucumber/support_code/library_spec.js index edabfaaa0..e14177794 100644 --- a/spec/cucumber/support_code/library_spec.js +++ b/spec/cucumber/support_code/library_spec.js @@ -170,7 +170,7 @@ describe("Cucumber.SupportCode.Library", function() { it("creates a before hook with the code", function() { library.defineBefore(code); - expect(Cucumber.SupportCode.Hook).toHaveBeenCalledWith(code); + expect(Cucumber.SupportCode.Hook).toHaveBeenCalledWith('before', code); }); it("adds the before hook to the before hooks collection", function() { @@ -191,7 +191,7 @@ describe("Cucumber.SupportCode.Library", function() { it("creates a after hook with the code", function() { library.defineAfter(code); - expect(Cucumber.SupportCode.Hook).toHaveBeenCalledWith(code); + expect(Cucumber.SupportCode.Hook).toHaveBeenCalledWith('after', code); }); it("adds the after hook to the after hooks collection", function() { From 8f5942df6c309556eebaba8f6235aeaf9adc2093 Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Thu, 20 Oct 2011 10:18:10 -0500 Subject: [PATCH 04/14] Rename a couple more callback references to hook. --- spec/cucumber/runtime/ast_tree_walker_spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/cucumber/runtime/ast_tree_walker_spec.js b/spec/cucumber/runtime/ast_tree_walker_spec.js index 92a53e61b..fbd83f1fa 100644 --- a/spec/cucumber/runtime/ast_tree_walker_spec.js +++ b/spec/cucumber/runtime/ast_tree_walker_spec.js @@ -115,10 +115,10 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { expect(Cucumber.Runtime.AstTreeWalker.Event).toHaveBeenCalledWith(Cucumber.Runtime.AstTreeWalker.FEATURE_EVENT_NAME, payload); }); - xit("calls the before function callbacks", function() { + xit("calls the before hooks", function() { }); - xit("calls the after function callbacks", function() { + xit("calls the after hooks", function() { }); it("broadcasts the visit of the feature", function() { From 9de5445b8e07e415037de9daa4e2a68c0ee3b43b Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Thu, 20 Oct 2011 13:56:38 -0500 Subject: [PATCH 05/14] Unshift after hooks onto the collection. This is so they will run in LIFO order, as defined by the Cucumber specification. --- lib/cucumber/support_code/library.js | 2 +- lib/cucumber/type/collection.js | 1 + spec/cucumber/support_code/library_spec.js | 6 +++--- spec/cucumber/type/collection_spec.js | 9 +++++++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/cucumber/support_code/library.js b/lib/cucumber/support_code/library.js index e2c85aba4..f6724165e 100644 --- a/lib/cucumber/support_code/library.js +++ b/lib/cucumber/support_code/library.js @@ -34,7 +34,7 @@ var Library = function(supportCodeDefinition) { defineAfter: function defineAfter(code) { var afterHook = Cucumber.SupportCode.Hook('after', code); - afterHooks.add(afterHook); + afterHooks.unshift(afterHook); }, getAfterHooks: function() { diff --git a/lib/cucumber/type/collection.js b/lib/cucumber/type/collection.js index 30e2f4bce..4a45844ef 100644 --- a/lib/cucumber/type/collection.js +++ b/lib/cucumber/type/collection.js @@ -2,6 +2,7 @@ var Collection = function() { var items = new Array(); var self = { add: function add(item) { items.push(item); }, + unshift: function unshift(item) { items.unshift(item); }, getLast: function getLast() { return items[items.length-1]; }, syncForEach: function syncForEach(userFunction) { items.forEach(userFunction); }, forEach: function forEach(userFunction, callback) { diff --git a/spec/cucumber/support_code/library_spec.js b/spec/cucumber/support_code/library_spec.js index e14177794..bf83acd5c 100644 --- a/spec/cucumber/support_code/library_spec.js +++ b/spec/cucumber/support_code/library_spec.js @@ -186,7 +186,7 @@ describe("Cucumber.SupportCode.Library", function() { code = createSpy("after code"); afterHook = createSpy("after hook"); spyOn(Cucumber.SupportCode, "Hook").andReturn(afterHook); - spyOnStub(afterHookCollection, "add"); + spyOnStub(afterHookCollection, "unshift"); }); it("creates a after hook with the code", function() { @@ -194,9 +194,9 @@ describe("Cucumber.SupportCode.Library", function() { expect(Cucumber.SupportCode.Hook).toHaveBeenCalledWith('after', code); }); - it("adds the after hook to the after hooks collection", function() { + it("unshifts the after hook to the after hooks collection", function() { library.defineAfter(code); - expect(afterHookCollection.add).toHaveBeenCalledWith(afterHook); + expect(afterHookCollection.unshift).toHaveBeenCalledWith(afterHook); }); }); diff --git a/spec/cucumber/type/collection_spec.js b/spec/cucumber/type/collection_spec.js index 318c1f85e..f11addc3a 100644 --- a/spec/cucumber/type/collection_spec.js +++ b/spec/cucumber/type/collection_spec.js @@ -7,6 +7,7 @@ describe("Cucumber.Type.Collection", function() { beforeEach(function() { itemArray = [1, 2, 3]; spyOn(itemArray, 'push'); + spyOn(itemArray, 'unshift'); spyOn(global, 'Array').andReturn(itemArray); collection = Cucumber.Type.Collection(); }); @@ -25,6 +26,14 @@ describe("Cucumber.Type.Collection", function() { }); }); + describe("unshift()", function() { + it("unshifts the item onto the start of the item array", function() { + var item = createSpy("Collection item"); + collection.unshift(item); + expect(itemArray.unshift).toHaveBeenCalledWith(item); + }); + }); + describe("getLast()", function() { it("returns the latest added item from the array", function() { var lastItem = createSpy("last item"); From afeb922d58cf913db73bfc71907077634bf5557d Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Thu, 20 Oct 2011 16:35:52 -0500 Subject: [PATCH 06/14] Remove exception handling from hooks. --- lib/cucumber/support_code/hook.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/cucumber/support_code/hook.js b/lib/cucumber/support_code/hook.js index 36a4fe052..5f3589ef1 100644 --- a/lib/cucumber/support_code/hook.js +++ b/lib/cucumber/support_code/hook.js @@ -3,14 +3,7 @@ var Hook = function(type, code) { var self = { invoke: function(world, callback) { - try { - code.apply(world, [callback]); - } catch(exception) { - if (exception) - Cucumber.Debug.warn(exception.stack || exception, 'exception inside ' + type + ' hook', 3); - - callback(); - } + code.apply(world, [callback]); } }; return self; From fdfc7ecd2308b0fe2a738bbb350fae8ead5eb726 Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Thu, 20 Oct 2011 17:19:02 -0500 Subject: [PATCH 07/14] Correctly execute before and after hooks around scenarios, not features. Also added unit test coverage for invoking before and after hooks. --- lib/cucumber/runtime/ast_tree_walker.js | 28 ++++--- spec/cucumber/runtime/ast_tree_walker_spec.js | 73 ++++++++++++------- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/lib/cucumber/runtime/ast_tree_walker.js b/lib/cucumber/runtime/ast_tree_walker.js index 504674709..b25138b62 100644 --- a/lib/cucumber/runtime/ast_tree_walker.js +++ b/lib/cucumber/runtime/ast_tree_walker.js @@ -26,17 +26,7 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) { var event = AstTreeWalker.Event(AstTreeWalker.FEATURE_EVENT_NAME, payload); self.broadcastEventAroundUserFunction( event, - function(callback) { - supportCodeLibrary.getBeforeHooks().forEach(function(beforeHook, callback) { - beforeHook.invoke(world, callback) - }, function() { - feature.acceptVisitor(self, function() { - supportCodeLibrary.getAfterHooks().forEach(function(afterHook, callback) { - afterHook.invoke(world, callback) - }, callback); - }); - }); - }, + function(callback) { feature.acceptVisitor(self, callback); }, callback ); }, @@ -56,7 +46,21 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) { var event = AstTreeWalker.Event(AstTreeWalker.SCENARIO_EVENT_NAME, payload); self.broadcastEventAroundUserFunction( event, - function(callback) { scenario.acceptVisitor(self, callback); }, + function(callback) { + supportCodeLibrary.getBeforeHooks().forEach(function(beforeHook, callback) { + beforeHook.invoke(world, callback) + }, function() { + scenario.acceptVisitor(self, function() { + supportCodeLibrary.getAfterHooks().forEach(function(afterHook, callback) { + afterHook.invoke(world, callback) + }, function() { + if (callback) { + callback(); + } + }); + }); + }); + }, callback ); }, diff --git a/spec/cucumber/runtime/ast_tree_walker_spec.js b/spec/cucumber/runtime/ast_tree_walker_spec.js index fbd83f1fa..9410ce193 100644 --- a/spec/cucumber/runtime/ast_tree_walker_spec.js +++ b/spec/cucumber/runtime/ast_tree_walker_spec.js @@ -95,17 +95,12 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { describe("visitFeature()", function() { var feature, callback, event, payload; - var beforeHooks, afterHooks; beforeEach(function() { feature = createSpyWithStubs("Feature AST element", {acceptVisitor: null}); callback = createSpy("Callback"); event = createSpy("Event"); payload = {feature: feature}; - beforeHooks = Cucumber.Type.Collection(); - afterHooks = Cucumber.Type.Collection(); - spyOnStub(supportCodeLibrary, 'getBeforeHooks').andReturn(beforeHooks); - spyOnStub(supportCodeLibrary, 'getAfterHooks').andReturn(afterHooks); spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event); spyOn(treeWalker, 'broadcastEventAroundUserFunction'); }); @@ -115,12 +110,6 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { expect(Cucumber.Runtime.AstTreeWalker.Event).toHaveBeenCalledWith(Cucumber.Runtime.AstTreeWalker.FEATURE_EVENT_NAME, payload); }); - xit("calls the before hooks", function() { - }); - - xit("calls the after hooks", function() { - }); - it("broadcasts the visit of the feature", function() { treeWalker.visitFeature(feature, callback); expect(treeWalker.broadcastEventAroundUserFunction).toHaveBeenCalled(); @@ -141,14 +130,9 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { userFunction = treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1]; }); - it("visits the feature, passing it the tree walker", function() { + it("visits the feature, passing it the received callback", function() { userFunction(userFunctionCallback); - expect(feature.acceptVisitor).toHaveBeenCalled(); - expect(feature.acceptVisitor). - toHaveBeenCalledWithValueAsNthParameter(treeWalker, 1); - }); - - xit("calls the user function callback after visiting the feature", function() { + expect(feature.acceptVisitor).toHaveBeenCalledWith(treeWalker, userFunctionCallback); }); }); }); @@ -178,16 +162,20 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { describe("visitScenario()", function() { var scenario, callback, event, payload; - var world; + var world, beforeHooks, afterHooks; beforeEach(function() { - scenario = createSpyWithStubs("Scenario AST element", {acceptVisitor: null}); - callback = createSpy("Callback"); - event = createSpy("Event"); - payload = {scenario: scenario}; - world = createSpy("world instance"); + scenario = createSpyWithStubs("Scenario AST element", {acceptVisitor: null}); + callback = createSpy("Callback"); + event = createSpy("Event"); + payload = {scenario: scenario}; + world = createSpy("world instance"); + beforeHooks = Cucumber.Type.Collection(); + afterHooks = Cucumber.Type.Collection(); spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event); spyOnStub(supportCodeLibrary, 'instantiateNewWorld').andReturn(world); + spyOnStub(supportCodeLibrary, 'getBeforeHooks').andReturn(beforeHooks); + spyOnStub(supportCodeLibrary, 'getAfterHooks').andReturn(afterHooks); spyOn(treeWalker, 'broadcastEventAroundUserFunction'); spyOn(treeWalker, 'witnessNewScenario'); spyOn(treeWalker, 'setWorld'); @@ -224,6 +212,35 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { toHaveBeenCalledWithValueAsNthParameter(callback, 3); }); + it("invokes before hooks", function() { + var beforeHook = Cucumber.SupportCode.Hook('before', callback); + spyOn(beforeHook, 'invoke').andCallFake(function(world, callback) { callback(); }); + beforeHooks.add(beforeHook); + + treeWalker.visitScenario(scenario, callback); + treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1](); + + expect(beforeHook.invoke). + toHaveBeenCalledWithValueAsNthParameter(world, 1); + expect(beforeHook.invoke). + toHaveBeenCalledWithAFunctionAsNthParameter(2); + }); + + it("invokes after hooks", function() { + var afterHook = Cucumber.SupportCode.Hook('after', callback); + spyOn(afterHook, 'invoke').andCallFake(function(world, callback) { callback(); }); + afterHooks.add(afterHook); + + treeWalker.visitScenario(scenario, callback); + treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1](); + scenario.acceptVisitor.mostRecentCall.args[1](); + + expect(afterHook.invoke). + toHaveBeenCalledWithValueAsNthParameter(world, 1); + expect(afterHook.invoke). + toHaveBeenCalledWithAFunctionAsNthParameter(2); + }); + describe("user function", function() { var userFunction, userFunctionCallback; @@ -233,7 +250,13 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { userFunction = treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1]; }); - it("visits the scenario, passing it the received callback", function() { + it("visits the scenario, passing it the tree walker", function() { + userFunction(userFunctionCallback); + expect(scenario.acceptVisitor). + toHaveBeenCalledWithValueAsNthParameter(treeWalker, 1); + }); + + xit("visits the scenario, passing it the received callback", function() { userFunction(userFunctionCallback); expect(scenario.acceptVisitor).toHaveBeenCalledWith(treeWalker, userFunctionCallback); }); From f218f1b1e3587235241b56355677ce5e4a0f222a Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Fri, 21 Oct 2011 12:54:06 -0500 Subject: [PATCH 08/14] Start to refactor before and after hook code in AstTreeWalker. --- lib/cucumber/runtime/ast_tree_walker.js | 8 +- lib/cucumber/support_code/library.js | 20 +++-- spec/cucumber/runtime/ast_tree_walker_spec.js | 25 ++---- spec/cucumber/support_code/library_spec.js | 86 +++++++++++++++---- 4 files changed, 92 insertions(+), 47 deletions(-) diff --git a/lib/cucumber/runtime/ast_tree_walker.js b/lib/cucumber/runtime/ast_tree_walker.js index b25138b62..09b6c719a 100644 --- a/lib/cucumber/runtime/ast_tree_walker.js +++ b/lib/cucumber/runtime/ast_tree_walker.js @@ -47,13 +47,9 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) { self.broadcastEventAroundUserFunction( event, function(callback) { - supportCodeLibrary.getBeforeHooks().forEach(function(beforeHook, callback) { - beforeHook.invoke(world, callback) - }, function() { + supportCodeLibrary.triggerBeforeHooks(world, function() { scenario.acceptVisitor(self, function() { - supportCodeLibrary.getAfterHooks().forEach(function(afterHook, callback) { - afterHook.invoke(world, callback) - }, function() { + supportCodeLibrary.triggerAfterHooks(world, function() { if (callback) { callback(); } diff --git a/lib/cucumber/support_code/library.js b/lib/cucumber/support_code/library.js index f6724165e..3de43844d 100644 --- a/lib/cucumber/support_code/library.js +++ b/lib/cucumber/support_code/library.js @@ -23,22 +23,26 @@ var Library = function(supportCodeDefinition) { return (stepDefinition != undefined); }, - defineBefore: function defineBefore(code) { + defineBeforeHook: function defineBeforeHook(code) { var beforeHook = Cucumber.SupportCode.Hook('before', code); beforeHooks.add(beforeHook); }, - getBeforeHooks: function() { - return beforeHooks; + triggerBeforeHooks: function(world, callback) { + beforeHooks.forEach(function(beforeHook, callback) { + beforeHook.invoke(world, callback); + }, callback); }, - defineAfter: function defineAfter(code) { + defineAfterHook: function defineAfterHook(code) { var afterHook = Cucumber.SupportCode.Hook('after', code); afterHooks.unshift(afterHook); }, - getAfterHooks: function() { - return afterHooks; + triggerAfterHooks: function(world, callback) { + afterHooks.forEach(function(afterHook, callback) { + afterHook.invoke(world, callback); + }, callback); }, defineStep: function defineStep(name, code) { @@ -52,8 +56,8 @@ var Library = function(supportCodeDefinition) { }; var supportCodeHelper = { - Before : self.defineBefore, - After : self.defineAfter, + Before : self.defineBeforeHook, + After : self.defineAfterHook, Given : self.defineStep, When : self.defineStep, Then : self.defineStep, diff --git a/spec/cucumber/runtime/ast_tree_walker_spec.js b/spec/cucumber/runtime/ast_tree_walker_spec.js index 9410ce193..c0c275b00 100644 --- a/spec/cucumber/runtime/ast_tree_walker_spec.js +++ b/spec/cucumber/runtime/ast_tree_walker_spec.js @@ -162,7 +162,7 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { describe("visitScenario()", function() { var scenario, callback, event, payload; - var world, beforeHooks, afterHooks; + var world; beforeEach(function() { scenario = createSpyWithStubs("Scenario AST element", {acceptVisitor: null}); @@ -170,12 +170,11 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { event = createSpy("Event"); payload = {scenario: scenario}; world = createSpy("world instance"); - beforeHooks = Cucumber.Type.Collection(); - afterHooks = Cucumber.Type.Collection(); + triggerHookFake = function(world, callback) { callback(); }; spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event); spyOnStub(supportCodeLibrary, 'instantiateNewWorld').andReturn(world); - spyOnStub(supportCodeLibrary, 'getBeforeHooks').andReturn(beforeHooks); - spyOnStub(supportCodeLibrary, 'getAfterHooks').andReturn(afterHooks); + spyOnStub(supportCodeLibrary, 'triggerBeforeHooks').andCallFake(triggerHookFake); + spyOnStub(supportCodeLibrary, 'triggerAfterHooks').andCallFake(triggerHookFake); spyOn(treeWalker, 'broadcastEventAroundUserFunction'); spyOn(treeWalker, 'witnessNewScenario'); spyOn(treeWalker, 'setWorld'); @@ -213,31 +212,23 @@ describe("Cucumber.Runtime.AstTreeWalker", function() { }); it("invokes before hooks", function() { - var beforeHook = Cucumber.SupportCode.Hook('before', callback); - spyOn(beforeHook, 'invoke').andCallFake(function(world, callback) { callback(); }); - beforeHooks.add(beforeHook); - treeWalker.visitScenario(scenario, callback); treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1](); - expect(beforeHook.invoke). + expect(supportCodeLibrary.triggerBeforeHooks). toHaveBeenCalledWithValueAsNthParameter(world, 1); - expect(beforeHook.invoke). + expect(supportCodeLibrary.triggerBeforeHooks). toHaveBeenCalledWithAFunctionAsNthParameter(2); }); it("invokes after hooks", function() { - var afterHook = Cucumber.SupportCode.Hook('after', callback); - spyOn(afterHook, 'invoke').andCallFake(function(world, callback) { callback(); }); - afterHooks.add(afterHook); - treeWalker.visitScenario(scenario, callback); treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1](); scenario.acceptVisitor.mostRecentCall.args[1](); - expect(afterHook.invoke). + expect(supportCodeLibrary.triggerAfterHooks). toHaveBeenCalledWithValueAsNthParameter(world, 1); - expect(afterHook.invoke). + expect(supportCodeLibrary.triggerAfterHooks). toHaveBeenCalledWithAFunctionAsNthParameter(2); }); diff --git a/spec/cucumber/support_code/library_spec.js b/spec/cucumber/support_code/library_spec.js index bf83acd5c..d0c03b28f 100644 --- a/spec/cucumber/support_code/library_spec.js +++ b/spec/cucumber/support_code/library_spec.js @@ -11,13 +11,9 @@ describe("Cucumber.SupportCode.Library", function() { var worldConstructorCalled; beforeEach(function() { - rawSupportCode = createSpy("Raw support code"); - beforeHookCollection = [ - createSpyWithStubs("First before hook") - ]; - afterHookCollection = [ - createSpyWithStubs("First after hook") - ]; + rawSupportCode = createSpy("Raw support code"); + afterHookCollection = Cucumber.Type.Collection(); + beforeHookCollection = Cucumber.Type.Collection(); stepDefinitionCollection = [ createSpyWithStubs("First step definition", {matchesStepName:false}), createSpyWithStubs("Second step definition", {matchesStepName:false}), @@ -65,12 +61,12 @@ describe("Cucumber.SupportCode.Library", function() { it("exposes a method to define Before methods", function() { expect(supportCodeHelper.Before).toBeAFunction(); - expect(supportCodeHelper.Before).toBe(library.defineBefore); + expect(supportCodeHelper.Before).toBe(library.defineBeforeHook); }); it("exposes a method to define After methods", function() { expect(supportCodeHelper.After).toBeAFunction(); - expect(supportCodeHelper.After).toBe(library.defineAfter); + expect(supportCodeHelper.After).toBe(library.defineAfterHook); }); it("exposes a method to define Given steps", function() { @@ -158,8 +154,8 @@ describe("Cucumber.SupportCode.Library", function() { }); }); - describe("defineBefore", function() { - var code, beforeHook; + describe("defineBeforeHook", function() { + var beforeHook, code; beforeEach(function() { code = createSpy("before code"); @@ -169,17 +165,46 @@ describe("Cucumber.SupportCode.Library", function() { }); it("creates a before hook with the code", function() { - library.defineBefore(code); + library.defineBeforeHook(code); expect(Cucumber.SupportCode.Hook).toHaveBeenCalledWith('before', code); }); it("adds the before hook to the before hooks collection", function() { - library.defineBefore(code); + library.defineBeforeHook(code); expect(beforeHookCollection.add).toHaveBeenCalledWith(beforeHook); }); }); - describe("defineAfter", function() { + describe("triggerBeforeHooks", function() { + var beforeHook, callback, code, invokeSpy, world; + + beforeEach(function() { + code = createSpy("before code"); + world = library.instantiateNewWorld(); + callback = createSpy("callback"); + beforeHook = createSpy("before hook"); + invokeSpy = spyOnStub(beforeHook, "invoke"); + spyOn(Cucumber.SupportCode, "Hook").andReturn(beforeHook); + library.defineBeforeHook(code); + }); + + it("triggers each before hook", function() { + library.triggerBeforeHooks(world, function() { + expect(beforeHook, "invoke"). + toHaveBeenCalledWithValueAsNthParameter(world, 1); + expect(beforeHook, "invoke"). + toHaveBeenCalledWithAFunctionAsNthParameter(2); + }); + }); + + it("calls the callback when finished", function() { + invokeSpy.andCallFake(function(world, callback) { callback(); }); + library.triggerBeforeHooks(world, callback); + expect(callback).toHaveBeenCalled(); + }); + }); + + describe("defineAfterHook", function() { var code, afterHook; beforeEach(function() { @@ -190,16 +215,45 @@ describe("Cucumber.SupportCode.Library", function() { }); it("creates a after hook with the code", function() { - library.defineAfter(code); + library.defineAfterHook(code); expect(Cucumber.SupportCode.Hook).toHaveBeenCalledWith('after', code); }); it("unshifts the after hook to the after hooks collection", function() { - library.defineAfter(code); + library.defineAfterHook(code); expect(afterHookCollection.unshift).toHaveBeenCalledWith(afterHook); }); }); + describe("triggerAfterHooks", function() { + var afterHook, callback, code, invokeSpy, world; + + beforeEach(function() { + code = createSpy("after code"); + world = library.instantiateNewWorld(); + callback = createSpy("callback"); + afterHook = createSpy("after hook"); + invokeSpy = spyOnStub(afterHook, "invoke"); + spyOn(Cucumber.SupportCode, "Hook").andReturn(afterHook); + library.defineAfterHook(code); + }); + + it("triggers each after hook", function() { + library.triggerAfterHooks(world, function() { + expect(afterHook, "invoke"). + toHaveBeenCalledWithValueAsNthParameter(world, 1); + expect(afterHook, "invoke"). + toHaveBeenCalledWithAFunctionAsNthParameter(2); + }); + }); + + it("calls the callback when finished", function() { + invokeSpy.andCallFake(function(world, callback) { callback(); }); + library.triggerAfterHooks(world, callback); + expect(callback).toHaveBeenCalled(); + }); + }); + describe("defineStep()", function() { var name, code, stepDefinition; From 840ba21ca29c63ad364d87c2790e834dc562facb Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Wed, 26 Oct 2011 20:57:49 -0500 Subject: [PATCH 09/14] Actually get before and after hook features passing through cucumber.js. --- features/step_definitions/cucumber_steps.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/features/step_definitions/cucumber_steps.js b/features/step_definitions/cucumber_steps.js index d6971929e..c86e26f7a 100644 --- a/features/step_definitions/cucumber_steps.js +++ b/features/step_definitions/cucumber_steps.js @@ -19,6 +19,7 @@ var cucumberSteps = function() { }); Given(/^a passing before hook$/, function(callback) { + prepare(); stepDefinitions += "Before(function(callback) { touchStep(\"Before\"); callback(); });\n"; callback(); }); @@ -29,6 +30,7 @@ var cucumberSteps = function() { }); Given(/^a passing after hook$/, function(callback) { + prepare(); stepDefinitions += "After(function(callback) { touchStep(\"After\"); callback(); });\n"; callback(); }); From 7fa7f17b4dc09b4c56cc4312e5611bc150f492cf Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Wed, 26 Oct 2011 21:38:30 -0500 Subject: [PATCH 10/14] Feeble attempt at the somewhat convoluted process to test hooks. --- features/step_definitions/cucumber_steps.rb | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/features/step_definitions/cucumber_steps.rb b/features/step_definitions/cucumber_steps.rb index 4081eeadd..d6385fd68 100644 --- a/features/step_definitions/cucumber_steps.rb +++ b/features/step_definitions/cucumber_steps.rb @@ -17,3 +17,32 @@ Then /^the mapping is run$/ do assert_passed "a mapping" end + +Given /^a passing before hook$/ do + append_support_code <<-EOF +this.Before(function(callback) { + this.calls = ["Before"]; + callback(); +}); +EOF +end + +Given /^a passing after hook$/ do + append_support_code <<-EOF +this.After(function(callback) { + this.calls.push("After"); + console.log(this.calls.join(",")); + callback(); +}); +EOF +end + +When /^Cucumber executes a scenario$/ do + append_step_definition("Cucumber executes a step definition", "this.calls.push('Step');\ncallback();") + scenario_with_steps "A scenario", "Given Cucumber executes a step definition\n" + run_feature +end + +Then /^the (after|before) hook is fired (?:after|before) the scenario$/ do |type| + assert_partial_output("Before,Step,After", all_output) +end From c0f0bb8fd0e56ce50a25717692aac4dd006c0512 Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Thu, 27 Oct 2011 16:50:25 -0500 Subject: [PATCH 11/14] Update for improved step definitions. --- features/step_definitions/cucumber_steps.js | 16 ++++++++++------ features/step_definitions/cucumber_world.js | 1 + 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/features/step_definitions/cucumber_steps.js b/features/step_definitions/cucumber_steps.js index c86e26f7a..0dbba367d 100644 --- a/features/step_definitions/cucumber_steps.js +++ b/features/step_definitions/cucumber_steps.js @@ -19,24 +19,28 @@ var cucumberSteps = function() { }); Given(/^a passing before hook$/, function(callback) { - prepare(); - stepDefinitions += "Before(function(callback) { touchStep(\"Before\"); callback(); });\n"; + this.stepDefinitions += "Before(function(callback) {\ + world.touchStep(\"Before\");\ + callback();\ +});\n"; callback(); }); Then(/^the before hook is fired before the scenario$/, function(callback) { - assertPassedStep("Before"); + this.assertPassedStep("Before"); callback(); }); Given(/^a passing after hook$/, function(callback) { - prepare(); - stepDefinitions += "After(function(callback) { touchStep(\"After\"); callback(); });\n"; + this.stepDefinitions += "After(function(callback) {\ + world.touchStep(\"After\");\ + callback();\ +});\n"; callback(); }); Then(/^the after hook is fired after the scenario$/, function(callback) { - assertPassedStep("After"); + this.assertPassedStep("After"); callback(); }); diff --git a/features/step_definitions/cucumber_world.js b/features/step_definitions/cucumber_world.js index ac2e962ad..08c7d062f 100644 --- a/features/step_definitions/cucumber_world.js +++ b/features/step_definitions/cucumber_world.js @@ -12,6 +12,7 @@ var proto = CucumberWorld.prototype; proto.runFeature = function runFeature(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); From 9365cab5bd5e5148bd313017ab6f24c76df8b2ee Mon Sep 17 00:00:00 2001 From: Julien Biezemans Date: Fri, 28 Oct 2011 12:30:26 +0200 Subject: [PATCH 12/14] Refactor "hooks" step definitions (#31) --- features/step_definitions/cucumber_steps.js | 25 ++++----------------- features/step_definitions/cucumber_world.js | 2 +- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/features/step_definitions/cucumber_steps.js b/features/step_definitions/cucumber_steps.js index 0dbba367d..cb7cc12f3 100644 --- a/features/step_definitions/cucumber_steps.js +++ b/features/step_definitions/cucumber_steps.js @@ -18,32 +18,15 @@ var cucumberSteps = function() { callback(); }); - Given(/^a passing before hook$/, function(callback) { - this.stepDefinitions += "Before(function(callback) {\ - world.touchStep(\"Before\");\ + Given(/^a passing (before|after) hook$/, function(hookType, callback) { + var defineHook = (hookType == 'before' ? 'Before' : 'After'); + this.stepDefinitions += defineHook + "(function(callback) {\ + world.touchStep('" + hookType + "-hook');\ callback();\ });\n"; callback(); }); - Then(/^the before hook is fired before the scenario$/, function(callback) { - this.assertPassedStep("Before"); - callback(); - }); - - Given(/^a passing after hook$/, function(callback) { - this.stepDefinitions += "After(function(callback) {\ - world.touchStep(\"After\");\ - callback();\ -});\n"; - callback(); - }); - - Then(/^the after hook is fired after the scenario$/, function(callback) { - this.assertPassedStep("After"); - callback(); - }); - Given(/^the step "([^"]*)" has a failing mapping$/, function(stepName, callback) { this.stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\ world.touchStep(\"" + stepName + "\");\ diff --git a/features/step_definitions/cucumber_world.js b/features/step_definitions/cucumber_world.js index 08c7d062f..0e7bda956 100644 --- a/features/step_definitions/cucumber_world.js +++ b/features/step_definitions/cucumber_world.js @@ -12,7 +12,7 @@ var proto = CucumberWorld.prototype; proto.runFeature = function runFeature(callback) { var supportCode; var supportCodeSource = "supportCode = function() {\n var Given = When = Then = this.defineStep;\n" + - "var Before = this.Before, After = this.After;\n" + + " var Before = this.Before, After = this.After;\n" + this.stepDefinitions + "};\n"; var world = this; eval(supportCodeSource); From 1764a69da500262162ff050adf7cfba08c10f80b Mon Sep 17 00:00:00 2001 From: Julien Biezemans Date: Fri, 28 Oct 2011 15:31:09 +0200 Subject: [PATCH 13/14] Refactor "hooks" Ruby step definitions (#31) --- .../step_definitions/cucumber_js_mappings.rb | 17 +++++++ features/step_definitions/cucumber_steps.rb | 50 +++++++++---------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/features/step_definitions/cucumber_js_mappings.rb b/features/step_definitions/cucumber_js_mappings.rb index 31ebdaee9..9d2e4f634 100644 --- a/features/step_definitions/cucumber_js_mappings.rb +++ b/features/step_definitions/cucumber_js_mappings.rb @@ -5,6 +5,8 @@ module CucumberJsMappings WORLD_VARIABLE_LOG_FILE = "world_variable.log" WORLD_FUNCTION_LOG_FILE = "world_function.log" DATA_TABLE_LOG_FILE = "data_table.log" + CYCLE_LOG_FILE = "cycle.log" + attr_accessor :support_code def features_dir @@ -195,10 +197,25 @@ def append_support_code(code) def write_main_step_definitions_file append_to_file(STEP_DEFINITIONS_FILE, "var fs = require('fs');\nvar stepDefinitions = function() {\n"); + append_to_file(STEP_DEFINITIONS_FILE, world_code) append_to_file(STEP_DEFINITIONS_FILE, support_code); append_to_file(STEP_DEFINITIONS_FILE, "};\nmodule.exports = stepDefinitions;") end + def world_code + <<-EOF + this.World = function() { + this.callCount = 0; + }; + + this.World.prototype.logCycleEvent = function logCycleEvent(name) { + fd = fs.openSync('#{CYCLE_LOG_FILE}', 'a'); + fs.writeSync(fd, " -> " + name, null); + fs.closeSync(fd); + }; +EOF + end + def write_coffee_script_definition_file append_to_file COFFEE_SCRIPT_DEFINITIONS_FILE, <<-EOF fs = require('fs') diff --git a/features/step_definitions/cucumber_steps.rb b/features/step_definitions/cucumber_steps.rb index d6385fd68..5bf613162 100644 --- a/features/step_definitions/cucumber_steps.rb +++ b/features/step_definitions/cucumber_steps.rb @@ -5,44 +5,40 @@ write_coffee_script_definition_file end -When /^Cucumber executes a scenario using that mapping$/ do - write_feature <<-EOF -Feature: - Scenario: - Given a mapping -EOF - run_feature -end - -Then /^the mapping is run$/ do - assert_passed "a mapping" -end - -Given /^a passing before hook$/ do +# TODO: encapsulate and move to cucumber-features +Given /^a passing (before|after) hook$/ do |hook_type| + define_hook = hook_type.capitalize append_support_code <<-EOF -this.Before(function(callback) { - this.calls = ["Before"]; +this.#{define_hook}(function(callback) { + this.logCycleEvent('#{hook_type}'); callback(); }); EOF end -Given /^a passing after hook$/ do - append_support_code <<-EOF -this.After(function(callback) { - this.calls.push("After"); - console.log(this.calls.join(",")); - callback(); -}); +When /^Cucumber executes a scenario using that mapping$/ do + write_feature <<-EOF +Feature: + Scenario: + Given a mapping EOF + run_feature end +# TODO: encapsulate and move to cucumber-features When /^Cucumber executes a scenario$/ do - append_step_definition("Cucumber executes a step definition", "this.calls.push('Step');\ncallback();") - scenario_with_steps "A scenario", "Given Cucumber executes a step definition\n" + append_step_definition("a step", "this.logCycleEvent('step');\ncallback();") + scenario_with_steps "A scenario", "Given Cucumber executes a step definition" run_feature end -Then /^the (after|before) hook is fired (?:after|before) the scenario$/ do |type| - assert_partial_output("Before,Step,After", all_output) +# TODO: encapsulate and move to cucumber-features +Then /^the (after|before) hook is fired (?:after|before) the scenario$/ do |hook_type| + expected_string = (hook_type == 'before' ? 'before -> step' : 'step -> after') + check_file_content(CYCLE_LOG_FILE, expected_string, true) +end + +# TODO: encapsulate and move to cucumber-features +Then /^the mapping is run$/ do + assert_passed "a mapping" end From 7d9ad34bf30d81965c59bbe1937e4052a2486423 Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Mon, 5 Dec 2011 00:35:57 -0600 Subject: [PATCH 14/14] Fix an issue with the cycle log file constant. --- features/step_definitions/cucumber_steps.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/step_definitions/cucumber_steps.rb b/features/step_definitions/cucumber_steps.rb index 5bf613162..4b20ee084 100644 --- a/features/step_definitions/cucumber_steps.rb +++ b/features/step_definitions/cucumber_steps.rb @@ -35,7 +35,7 @@ # TODO: encapsulate and move to cucumber-features Then /^the (after|before) hook is fired (?:after|before) the scenario$/ do |hook_type| expected_string = (hook_type == 'before' ? 'before -> step' : 'step -> after') - check_file_content(CYCLE_LOG_FILE, expected_string, true) + check_file_content(CucumberJsMappings::CYCLE_LOG_FILE, expected_string, true) end # TODO: encapsulate and move to cucumber-features