Skip to content

Commit

Permalink
Add listener and event handler registration (close #130)
Browse files Browse the repository at this point in the history
This introduces registerListener and registerEventHandler to the support
code helper. Those two methods let clients register custom listeners for
AST events.

Relates to #91.
  • Loading branch information
devpaul authored and jbpros committed Nov 27, 2013
1 parent 127a7c9 commit 4be20a5
Show file tree
Hide file tree
Showing 9 changed files with 469 additions and 231 deletions.
16 changes: 11 additions & 5 deletions lib/cucumber/listener.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@ var Listener = function () {
},

buildHandlerNameForEvent: function buildHandlerNameForEvent(event) {
var handlerName =
Listener.EVENT_HANDLER_NAME_PREFIX +
event.getName() +
Listener.EVENT_HANDLER_NAME_SUFFIX;
return handlerName;
return self.buildHandlerName(event.getName());
},

getHandlerForEvent: function getHandlerForEvent(event) {
var eventHandlerName = self.buildHandlerNameForEvent(event);
return self[eventHandlerName];
},

buildHandlerName: function buildHandler(shortName) {
return Listener.EVENT_HANDLER_NAME_PREFIX + shortName + Listener.EVENT_HANDLER_NAME_SUFFIX;
},

setHandlerForEvent: function setHandlerForEvent(shortname, handler) {
var eventName = self.buildHandlerName(shortname);
self[eventName] = handler;
}
};
return self;
Expand All @@ -33,6 +38,7 @@ var Listener = function () {
Listener.EVENT_HANDLER_NAME_PREFIX = 'handle';
Listener.EVENT_HANDLER_NAME_SUFFIX = 'Event';

Listener.Events = require('./listener/events');
Listener.Formatter = require('./listener/formatter');
Listener.PrettyFormatter = require('./listener/pretty_formatter');
Listener.ProgressFormatter = require('./listener/progress_formatter');
Expand Down
10 changes: 10 additions & 0 deletions lib/cucumber/listener/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
exports['BeforeFeatures'] = 'BeforeFeatures';
exports['BeforeFeature'] = 'BeforeFeature';
exports['Background'] = 'Background';
exports['BeforeScenario'] = 'BeforeScenario';
exports['BeforeStep'] = 'BeforeStep';
exports['StepResult'] = 'StepResult';
exports['AfterStep'] = 'AfterStep';
exports['AfterScenario'] = 'AfterScenario';
exports['AfterFeature'] = 'AfterFeature';
exports['AfterFeatures'] = 'AfterFeatures';
18 changes: 13 additions & 5 deletions lib/cucumber/runtime/ast_tree_walker.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
var Cucumber = require('../../cucumber');

var listeners;
var world;
var allFeaturesSucceded = true;
var skippingSteps = false;
Expand Down Expand Up @@ -107,10 +106,19 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
},

broadcastEvent: function broadcastEvent(event, callback) {
listeners.forEach(
function(listener, callback) { listener.hear(event, callback); },
callback
);
broadcastToListners(listeners, onRuntimeListenersComplete);

function onRuntimeListenersComplete() {
var listeners = supportCodeLibrary.getListeners();
broadcastToListners(listeners, callback);
}

function broadcastToListners(listeners, callback) {
listeners.forEach(
function(listener, callback) { listener.hear(event, callback); },
callback
);
}
},

lookupStepDefinitionByName: function lookupStepDefinitionByName(stepName) {
Expand Down
54 changes: 46 additions & 8 deletions lib/cucumber/support_code/library.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var Library = function(supportCodeDefinition) {
var Cucumber = require('../../cucumber');

var listeners = Cucumber.Type.Collection();
var stepDefinitions = Cucumber.Type.Collection();
var hooker = Cucumber.SupportCode.Library.Hooker();
var worldConstructor = Cucumber.SupportCode.WorldConstructor();
Expand Down Expand Up @@ -50,6 +51,20 @@ var Library = function(supportCodeDefinition) {
stepDefinitions.add(stepDefinition);
},

registerListener: function registerListener(listener) {
listeners.add(listener);
},

registerHandler: function registerHandler(eventName, handler) {
var listener = Cucumber.Listener();
listener.setHandlerForEvent(eventName, handler);
self.registerListener(listener);
},

getListeners: function getListeners() {
return listeners;
},

instantiateNewWorld: function instantiateNewWorld(callback) {
var world = new worldConstructor(function(explicitWorld) {
process.nextTick(function() { // release the constructor
Expand All @@ -60,19 +75,42 @@ var Library = function(supportCodeDefinition) {
};

var supportCodeHelper = {
Around : self.defineAroundHook,
Before : self.defineBeforeHook,
After : self.defineAfterHook,
Given : self.defineStep,
When : self.defineStep,
Then : self.defineStep,
defineStep : self.defineStep,
World : worldConstructor
Around : self.defineAroundHook,
Before : self.defineBeforeHook,
After : self.defineAfterHook,
Given : self.defineStep,
When : self.defineStep,
Then : self.defineStep,
defineStep : self.defineStep,
registerListener : self.registerListener,
registerHandler : self.registerHandler,
World : worldConstructor
};

appendEventHandlers(supportCodeHelper, self);
supportCodeDefinition.call(supportCodeHelper);
worldConstructor = supportCodeHelper.World;

return self;
};

function appendEventHandlers(supportCodeHelper, library) {
var Cucumber = require('../../cucumber');
var events = Cucumber.Listener.Events;
var eventName;

for (eventName in events) {
if (events.hasOwnProperty(eventName)) {
supportCodeHelper[eventName] = createEventListenerMethod(library, eventName);
}
}
}

function createEventListenerMethod(library, eventName) {
return function(handler) {
library.registerHandler(eventName, handler);
};
}

Library.Hooker = require('./library/hooker');
module.exports = Library;
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"Kim, Jang-hwan <[email protected]>",
"Michael Zedeler <[email protected]>",
"Tom V <[email protected]>",
"David Godfrey <[email protected]>"
"David Godfrey <[email protected]>",
"Paul Shannon (http://devpaul.com)"
],
"repository": {
"type": "git",
Expand Down
20 changes: 20 additions & 0 deletions spec/cucumber/listener/events_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require('../../support/spec_helper');

describe('Cucumber.Listener.Events', function () {
var Cucumber = requireLib('cucumber');
var events = Cucumber.Listener.Events;

describe('construction', function () {
it("contains a list of event names", function () {
for(var name in events) {
if(events.hasOwnProperty(name)) {
expect(events[name]).toEqual(name);
}
}
});

it("is defined", function() {
expect(events).toBeDefined();
});
});
});
39 changes: 36 additions & 3 deletions spec/cucumber/listener_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require('../support/spec_helper');

describe("Cucumber.Listener", function() {
var Cucumber = requireLib('cucumber');
var listener;

beforeEach(function() {
listener = Cucumber.Listener();
Expand Down Expand Up @@ -96,20 +97,23 @@ describe("Cucumber.Listener", function() {
});

describe("buildHandlerNameForEvent", function () {
var event, eventName;
var event, eventName, buildHandlerName;

beforeEach(function () {
eventName = "SomeEventName";
event = createSpyWithStubs("Event", {getName: eventName});
buildHandlerName = spyOn(listener, "buildHandlerName");

});

it("gets the name of the event", function () {
listener.buildHandlerNameForEvent(event);
expect(event.getName).toHaveBeenCalled();
});

it("returns the name of the event with prefix 'handle' and suffix 'Event'", function () {
expect(listener.buildHandlerNameForEvent(event)).toBe("handle" + eventName + "Event");
it("calls buildHandlerName", function() {
listener.buildHandlerNameForEvent(event);
expect(buildHandlerName).toHaveBeenCalled();
});
});

Expand Down Expand Up @@ -145,4 +149,33 @@ describe("Cucumber.Listener", function() {
});
});
});

describe("buildHandlerName", function() {
it("returns the name of the event with prefix 'handle' and suffix 'Event'", function () {
var eventName = "shortName";
var expected = "handle" + eventName + "Event";

expect(listener.buildHandlerName(eventName)).toBe(expected);
});
});

describe("setHandlerForEvent", function() {
var shortName = "anEventName";
var handler = function(){};
var buildHandlerName;

beforeEach(function() {
buildHandlerName = spyOn(listener, "buildHandlerName").andCallThrough();
listener.setHandlerForEvent(shortName, handler);
});

it("attaches the function as a property to itself", function() {
var expectedKey = Cucumber.Listener.EVENT_HANDLER_NAME_PREFIX + shortName + Cucumber.Listener.EVENT_HANDLER_NAME_SUFFIX;
expect(listener[expectedKey]).toBe(handler);
});

it("calls buildHandlerName", function() {
expect(buildHandlerName).toHaveBeenCalled();
});
});
});
30 changes: 23 additions & 7 deletions spec/cucumber/runtime/ast_tree_walker_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ require('../../support/spec_helper');

describe("Cucumber.Runtime.AstTreeWalker", function() {
var Cucumber = requireLib('cucumber');
var treeWalker, features, supportCodeLibrary, listeners;
var treeWalker, features, supportCodeLibrary, listeners, supportListeners;

beforeEach(function() {
features = createSpyWithStubs("Features AST element", {acceptVisitor: null});
supportCodeLibrary = createSpy("Support code library");
listeners = [createSpy("First listener"), createSpy("Second listener")];
supportListeners = [createSpy("First support listener"), createSpy("Second support listener")];
spyOnStub(listeners, 'syncForEach').andCallFake(function(cb) { listeners.forEach(cb); });
spyOnStub(supportListeners, 'syncForEach').andCallFake(function(cb) { supportListeners.forEach(cb); });
spyOnStub(supportCodeLibrary, 'getListeners').andCallFake(function() { return supportListeners; });
treeWalker = Cucumber.Runtime.AstTreeWalker(features, supportCodeLibrary, listeners);
});

Expand Down Expand Up @@ -447,7 +450,7 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {

beforeEach(function() {
wrapper = treeWalker.wrapAfterEventBroadcast(event, callback);
spyOn(treeWalker, 'broadcastAfterEvent');;
spyOn(treeWalker, 'broadcastAfterEvent');
});

it("broadcasts an after event with the received callback as callback", function() {
Expand Down Expand Up @@ -501,21 +504,34 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {


describe("broadcastEvent()", function() {
var event, eventName, callback;
var event, callback;

beforeEach(function() {
event = createSpy("Event");
callback = createSpy("Callback");
spyOn(listeners, 'forEach');
spyOnListeners(listeners);
spyOnListeners(supportListeners);
});

function spyOnListeners(listeners) {
spyOn(listeners, 'forEach').andCallFake(function() {
var callback = listeners.forEach.mostRecentCall.args[1];
callback();
});
}

it("iterates over the listeners", function() {
treeWalker.broadcastEvent(event, callback);
expect(listeners.forEach).toHaveBeenCalled();
expect(listeners.forEach).toHaveBeenCalledWithAFunctionAsNthParameter(1);
expect(listeners.forEach).toHaveBeenCalledWithValueAsNthParameter(callback, 2);
assertListenerCollectionCalled(listeners.forEach);
assertListenerCollectionCalled(supportListeners.forEach);
expect(supportListeners.forEach).toHaveBeenCalledWithValueAsNthParameter(callback, 2);
});

function assertListenerCollectionCalled(forEachSpy) {
expect(forEachSpy).toHaveBeenCalled();
expect(forEachSpy).toHaveBeenCalledWithAFunctionAsNthParameter(1);
}

describe("for each listener", function() {
var userFunction, listener, forEachCallback;

Expand Down
Loading

0 comments on commit 4be20a5

Please sign in to comment.