Skip to content

Commit

Permalink
events other then success
Browse files Browse the repository at this point in the history
1. added .isEvent property (to distinguish between legacy 'success' and any sub-events)
2. added property .event that is either 'success' or the named sub-event
3. the api for sub-events is a context named either 'on' or 'events' ('events' is transformed into 'on')

1. updated addVow to look at the vow.binding.context.event instead of 'success'
2. if the sub-event is 'error' there is no need to listen for the topics error event again
3. Hooked EventEmitter.prototype.emit so I can capture events that happen before addVow is run.
4. Updated the vow calling params so if we have a known emitted event I don't change the calling structure (i.e. add a null error)

1. Throw if a sub-event or 'on' context has a topic
2. Sub-events can inherit from EventEmitter
3. changed 'success' to ctx.event
4. execute new context even if the event has already fired

Added basic tests to demonstrate "Vows with sub events"

Fixes #109
  • Loading branch information
seebees authored and indexzero committed Nov 25, 2011
1 parent d081d49 commit a781c45
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 158 deletions.
66 changes: 57 additions & 9 deletions lib/vows.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,29 +56,59 @@ var Suite = require('./vows/suite').Suite;
// goodness.
//
function addVow(vow) {
var batch = vow.batch;
var batch = vow.batch,
event = vow.binding.context.event || 'success';

batch.total ++;
batch.vows.push(vow);

return this.on("success", function () {
// always set a listener on the event
this.on(event, function () {
var args = Array.prototype.slice.call(arguments);
// If the vow is a sub-event then we know it is an
// emited event. So I don't muck with the arguments
// However the legacey behavior:
// If the callback is expecting two or more arguments,
// pass the error as the first (null) and the result after.
if (vow.callback.length >= 2 && batch.suite.options.error) {
if (!(this.ctx && this.ctx.isEvent) &&
vow.callback.length >= 2 && batch.suite.options.error) {
args.unshift(null);
}
runTest(args, this.ctx);
vows.tryEnd(batch);
});

}).on("error", function (err) {
if (vow.callback.length >= 2 || !batch.suite.options.error) {
runTest(arguments, this.ctx);
if (event !== 'error') {
this.on("error", function (err) {
if (vow.callback.length >= 2 || !batch.suite.options.error) {
runTest(arguments, this.ctx);
} else {
output('errored', { type: 'promise', error: err.stack ||
err.message || JSON.stringify(err) });
}
vows.tryEnd(batch);
});
}

// in case an event fired before we could listen
if (this._vowsEmitedEvents &&
this._vowsEmitedEvents.hasOwnProperty(event)) {
// make sure no one is messing with me
if (Array.isArray(this._vowsEmitedEvents[event])) {
var self = this;
// I don't think I need to optimize for one event,
// I think it is more important to make sure I check the vow n times
self._vowsEmitedEvents[event].forEach(function(args) {
runTest(args, self.ctx);
});
} else {
output('errored', { type: 'promise', error: err.stack || err.message || JSON.stringify(err) });
// initial conditions problem
throw new Error('_vowsEmitedEvents[' + event + '] is not an Array')
}
vows.tryEnd(batch);
});
}

return this;

function runTest(args, ctx) {
if (vow.callback instanceof String) {
Expand Down Expand Up @@ -121,7 +151,8 @@ function addVow(vow) {
// If not, report an error message.
//
process.on('exit', function () {
var results = { honored: 0, broken: 0, errored: 0, pending: 0, total: 0 }, failure;
var results = { honored: 0, broken: 0, errored: 0, pending: 0, total: 0 },
failure;

vows.suites.forEach(function (s) {
if ((s.results.total > 0) && (s.results.time === null)) {
Expand Down Expand Up @@ -165,13 +196,30 @@ process.on('exit', function () {

vows.suites = [];

// We need the old emit function so we can hook it
// and do magic to deal with events that have fired
var oldEmit = vows.options.Emitter.prototype.emit;

//
// Create a new test suite
//
vows.describe = function (subject) {
var suite = new(Suite)(subject);

this.options.Emitter.prototype.addVow = addVow;
// just in case someone emit's before I get to it
this.options.Emitter.prototype.emit = function (event) {
this._vowsEmitedEvents = this._vowsEmitedEvents || {};
// slice off the event
var args = Array.prototype.slice.call(arguments, 1);
// if multiple events are fired, add or push
if (this._vowsEmitedEvents.hasOwnProperty(event)) {
this._vowsEmitedEvents[event].push(args);
} else {
this._vowsEmitedEvents[event] = [args];
}
return oldEmit.apply(this, arguments);
}
this.suites.push(suite);

//
Expand Down
13 changes: 13 additions & 0 deletions lib/vows/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ this.Context = function (vow, ctx, env) {
else { process.nextTick(emit) }
};
this.name = vow.description;
// events is an alias for on
if (this.name === 'events') {
this.name = vow.description = 'on';
}

if (ctx.name === 'on') {
this.isEvent = true;
this.event = this.name;
} else {
this.isEvent = false;
this.event = 'success';
}

this.title = [
ctx.title || '',
vow.description || ''
Expand Down
50 changes: 35 additions & 15 deletions lib/vows/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ this.Suite.prototype = new(function () {
topic = ctx.tests.topic;

if (typeof(topic) === 'function') {
if (ctx.isEvent || ctx.name === 'on') {
throw new Error('Event context cannot contain a topic');
}

// Run the topic, passing the previous context topics
topic = topic.apply(ctx.env, ctx.topics);

Expand All @@ -142,28 +146,39 @@ this.Suite.prototype = new(function () {

// If the topic doesn't return an event emitter (such as a promise),
// we create it ourselves, and emit the value on the next tick.
if (! (topic && topic.constructor === events.EventEmitter)) {
ctx.emitter = new(events.EventEmitter);

if (! ctx._callback) {
process.nextTick(function (val) {
return function () { ctx.emitter.emit("success", val) };
}(topic));
if (! (topic &&
topic.constructor === events.EventEmitter)) {
// If the context is a traditional vow, then a topic can ONLY
// be an EventEmitter. However if the context is a sub-event
// then the topic may be an instanceof EventEmitter
if (!ctx.isEvent ||
(ctx.isEvent && !(topic instanceof events.EventEmitter))) {

ctx.emitter = new(events.EventEmitter);

if (! ctx._callback) {
process.nextTick(function (val) {
return function () {
ctx.emitter.emit("success", val)
};
}(topic));
}
topic = ctx.emitter;
}
topic = ctx.emitter;
}

topic.on('success', function (val) {
topic.on(ctx.event, function (val) {
// Once the topic fires, add the return value
// to the beginning of the topics list, so it
// becomes the first argument for the next topic.
// If we're using the parent topic, no need to
// prepend it to the topics list, or we'll get
// duplicates.
if (! old) Array.prototype.unshift.apply(ctx.topics, arguments);
if (!old || ctx.isEvent) {
Array.prototype.unshift.apply(ctx.topics, arguments)
};
});
if (topic.setMaxListeners) { topic.setMaxListeners(Infinity) }

// Now run the tests, or sub-contexts
Object.keys(ctx.tests).filter(function (k) {
return ctx.tests[k] && k !== 'topic' &&
Expand All @@ -189,14 +204,19 @@ this.Suite.prototype = new(function () {
// topic fires.
// If we encounter an object literal, we recurse, sending it
// our current context.
if ((typeof(vow.callback) === 'function') || (vow.callback instanceof String)) {
if ((typeof(vow.callback) === 'function') ||
(vow.callback instanceof String)) {
topic.addVow(vow);
} else if (typeof(vow.callback) === 'object') {
// If there's a setup stage, we have to wait for it to fire,
// before calling the inner context. Else, just run the inner context
// before calling the inner context.
// If the event has already fired, the context is 'on' or
// there is no setup stage, just run the inner context
// synchronously.
if (topic) {
topic.on("success", function (ctx) {
if (topic &&
ctx.name !== 'on' &&
!topic._vowsEmitedEvents.hasOwnProperty(ctx.event)) {
topic.on(ctx.event, function (ctx) {
return function (val) {
return run(new(Context)(vow, ctx, env), lastTopic);
};
Expand Down
134 changes: 0 additions & 134 deletions test/testInherit.js

This file was deleted.

Loading

0 comments on commit a781c45

Please sign in to comment.