From ea2434bd9757bce0c327a03d1a8ffa80937a74d1 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 5 Jan 2010 20:58:07 -0500 Subject: [PATCH 001/409] init --- LICENSE | 20 +++++++++ README.md | 18 ++++++++ lib/vows.js | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 lib/vows.js diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..a1edd93b --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2009 cloudhead + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..0ac9d0a2 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ + +Vows.js +======= + +asynchronous promise-based testing for node.js + +usage +----- + + var vows = require('vows'), + assert = require('assert'); + + vows.tell('Deep Thought', -> () { + question('what is the answer to the universe?').addVow(-> (answer) { + assert.equals(answer, 42); + }, 'it should know the answer to the ultimate question of life'); + }); + diff --git a/lib/vows.js b/lib/vows.js new file mode 100644 index 00000000..fc437cb3 --- /dev/null +++ b/lib/vows.js @@ -0,0 +1,124 @@ +// +// Vows.js - asynchronous promise-based testing for node.js +// +// usage: +// +// var vows = require('vows'), +// assert = require('assert'); +// +// vows.tell('Deep Thought', function () { +// question('what is the answer to the universe?').addVow(function (answer) { +// assert.equals(answer, 42); +// }, 'it should know the answer to the ultimate question of life'); +// }); +// +var sys = require('sys'); +var vows = exports; + +// Keeps track of the outcome of vows. +var total = 0, honored = 0, + broken = 0, errored = 0, + start, end; + +// +// This function gets added to process.Promise.prototype, by default. +// It's essentially a wrapper around `addCallback`, which adds all the specification +// goodness. +// +function addVow(/* description & callback */) { + var desc, callback; + + if (arguments.length < 2) throw "A vow is comprised of a description and a proof"; + + total++; + + // Sometimes it might be nicer to pass the proof first, + // and the description second, so we let the user choose + // the order. + if (arguments[0] instanceof Function) { + callback = arguments[0]; + desc = arguments[1]; + } else { + desc = arguments[0]; + callback = arguments[1]; + } + + return this.addCallback(function () { + var vow = "- ", exception; + + // Run the test, and try to catch `AssertionError`s and other exceptions; + // increment counters accordingly. + try { + callback.apply(null, arguments); + vow += stylize(desc, 'green'); + honored++; + } catch (e) { + if (e.name.match(/AssertionError/)) { + vow += stylize(desc, 'yellow'); + exception = ' ~ ' + stylize("expected " + + stylize(sys.inspect(e.expected), 'bold') + ", got " + + stylize(sys.inspect(e.actual), 'bold') + "\n", 'yellow'); + broken++; + } else { + vow += stylize(desc, 'red'); + exception = ' ! ' + stylize(e.stack, 'red') + "\n"; + errored++; + } + } + sys.puts(vow); + + if (exception) process.stdio.writeError(exception); + + // Output results once all the vows have been checked + if (honored + broken + errored === total) { + var result = honored + " honored, " + + broken + " broken, " + + errored + " errored", + + style = honored === total ? + ('green') : (errored === 0 ? 'yellow' : 'red'); + + sys.puts("\nVerified " + total + " vows in " + + (((new Date) - start) / 1000) + " seconds."); + + sys.puts("\n" + stylize(result, style)); + } + }); +}; + +// Options +vows.options = { + emitter: process.Promise +}; + +// Run all vows/tests +vows.tell = function (topic, tests) { + this.options.emitter.prototype.addVow = addVow; + + if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); + else if (topic instanceof Function) tests = topic; + else throw "tell() takes a topic and a function"; + + start = new Date(); + tests.call(this); +}; + +// Return the `vows` object after setting some options +vows.config = function (opts) { + process.mixin(this.options, opts); + return this; +}; + +// Stylize a string +function stylize(str, style) { + var styles = { + 'bold' : [1, 22], + 'underline' : [4, 24], + 'yellow' : [33, 39], + 'green' : [32, 39], + 'red' : [31, 39] + }; + return '\033[' + styles[style][0] + 'm' + str + + '\033[' + styles[style][1] + 'm'; +} + From 6e4ac1042637c9daa84c9ac26478ba6151edd43c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 5 Jan 2010 19:32:32 -0700 Subject: [PATCH 002/409] --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0ac9d0a2..5a5803fe 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ - -Vows.js -======= +Vows +==== asynchronous promise-based testing for node.js From 8f1b15728e05f2003009102cdf78dec8658c2d37 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 5 Jan 2010 21:33:29 -0500 Subject: [PATCH 003/409] fixed readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5a5803fe..c0c98a7f 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ usage var vows = require('vows'), assert = require('assert'); - vows.tell('Deep Thought', -> () { - question('what is the answer to the universe?').addVow(-> (answer) { + vows.tell('Deep Thought', function () { + question('what is the answer to the universe?').addVow(function (answer) { assert.equals(answer, 42); }, 'it should know the answer to the ultimate question of life'); }); From abdd87b36d97ecbb14912f2322d29fb05db1caad Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 5 Jan 2010 21:46:27 -0500 Subject: [PATCH 004/409] some explanations to the readme --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0c98a7f..cb0ac2f7 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ Vows asynchronous promise-based testing for node.js -usage ------ +synopsis +-------- var vows = require('vows'), assert = require('assert'); @@ -15,3 +15,9 @@ usage }, 'it should know the answer to the ultimate question of life'); }); +In the example above, `question()` would be a function which returns a _promise_. +When the `"success"` event is emitted, the function passed to `addVow` is run, +and the results output to the console. + +Vows are run as soon as the promise completes, so the order in which they are run is undefined. + From 06fd971a4cb414fc3d0ea462c1b0a265cd7a8f12 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 13 Jan 2010 09:21:44 -0500 Subject: [PATCH 005/409] added contexts and `should` --- lib/vows.js | 75 ++++++++++++++++++++++++++++++++++++++++------- test/vows-test.js | 33 +++++++++++++++++++++ 2 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 test/vows-test.js diff --git a/lib/vows.js b/lib/vows.js index fc437cb3..421fb597 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -12,8 +12,47 @@ // }, 'it should know the answer to the ultimate question of life'); // }); // -var sys = require('sys'); -var vows = exports; +var sys = require('sys'), + assert = require('assert'), + vows = exports; + +function augment(obj) { + obj = new(obj.constructor)(obj); + + // Recursively augment any Objects + if (obj instanceof Object) { + for (key in obj) { + if (obj.hasOwnProperty(key)) obj[key] = augment(obj[key]); + } + } + + obj.should = { + equal: function (expected) { assert.equal(obj, expected) }, + match: function (expected) { assert.ok(obj.match(expected)) }, + include: function (item) { assert.ok(obj.match(new(RegExp)(item))) }, + beA: function (type) { + if (typeof(obj) === 'object') { assert.ok(obj.constructor === type) } + else { + if (typeof(type) === 'function') type = type.name.toLowerCase(); + assert.ok(typeof(obj) === type); + } + } + }; + return obj; +}; + +// A better `typeof` +function typeOf(value) { + var s = typeof value; + + if (s === 'object' || s === 'function') { + if (value) { + if (value instanceof Array) { s = 'array' } + else if (value instanceof RegExp) { s = 'regexp' } + } else { s = 'null' } + } + return s; +} // Keeps track of the outcome of vows. var total = 0, honored = 0, @@ -44,12 +83,16 @@ function addVow(/* description & callback */) { } return this.addCallback(function () { - var vow = "- ", exception; + var vow = "- ", exception, topics = [], topic; + + for (var i = 0; i < arguments.length; i++) { + topics[i] = augment(arguments[i]); + } // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { - callback.apply(null, arguments); + callback.apply(null, topics); vow += stylize(desc, 'green'); honored++; } catch (e) { @@ -79,7 +122,7 @@ function addVow(/* description & callback */) { ('green') : (errored === 0 ? 'yellow' : 'red'); sys.puts("\nVerified " + total + " vows in " + - (((new Date) - start) / 1000) + " seconds."); + (((new(Date)) - start) / 1000) + " seconds."); sys.puts("\n" + stylize(result, style)); } @@ -93,14 +136,26 @@ vows.options = { // Run all vows/tests vows.tell = function (topic, tests) { + var promise; this.options.emitter.prototype.addVow = addVow; if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); - else if (topic instanceof Function) tests = topic; - else throw "tell() takes a topic and a function"; - - start = new Date(); - tests.call(this); + else if (topic instanceof Object) tests = topic; + else throw "tell() takes a topic and an Object"; + + start = new(Date); + + for (batch in tests) { + if (tests.hasOwnProperty(batch)) { + promise = tests[batch].setup(this); + for (item in tests[batch]) { + if (tests[batch].hasOwnProperty(item) && item !== 'setup') { + promise.addVow(tests[batch][item], item); + } + } + } + } + return; }; // Return the `vows` object after setting some options diff --git a/test/vows-test.js b/test/vows-test.js new file mode 100644 index 00000000..ea2ac36f --- /dev/null +++ b/test/vows-test.js @@ -0,0 +1,33 @@ +var vows = require('../lib/vows'); + +vows.tell("Vows", { + "let's start with some basics": { + setup: function () { + var promise = new(process.Promise); + setTimeout(function () { promise.emitSuccess("hello world.") }, 100); + return promise; + }, + "testing equality": function (it) { + it.should.equal("hello world."); + it.should.beA(String); + }, + "testing match": function (it) { + it.should.match(/[a-z]+ [a-z]+/); + }, + "testing inclusion": function (it) { + it.should.include("world"); + } + }, + "and now something a little more complex": { + setup: function () { + var promise = new(process.Promise); + setTimeout(function () { promise.emitSuccess({f: 1, z: 2}) }, 100); + return promise; + }, + "testing member equality": function (it) { + it.should.beA(Object); + it['f'].should.equal(1); + it['z'].should.equal(2); + } + }, +}); From b2dcc083f28c7934fba28be4324f4db459d5a04f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 13 Jan 2010 18:25:31 -0500 Subject: [PATCH 006/409] moved some code around | should stuff is in macros object now --- lib/vows.js | 65 +++++++++++++++++++++++------------------------------ 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 421fb597..a2985a36 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -16,43 +16,6 @@ var sys = require('sys'), assert = require('assert'), vows = exports; -function augment(obj) { - obj = new(obj.constructor)(obj); - - // Recursively augment any Objects - if (obj instanceof Object) { - for (key in obj) { - if (obj.hasOwnProperty(key)) obj[key] = augment(obj[key]); - } - } - - obj.should = { - equal: function (expected) { assert.equal(obj, expected) }, - match: function (expected) { assert.ok(obj.match(expected)) }, - include: function (item) { assert.ok(obj.match(new(RegExp)(item))) }, - beA: function (type) { - if (typeof(obj) === 'object') { assert.ok(obj.constructor === type) } - else { - if (typeof(type) === 'function') type = type.name.toLowerCase(); - assert.ok(typeof(obj) === type); - } - } - }; - return obj; -}; - -// A better `typeof` -function typeOf(value) { - var s = typeof value; - - if (s === 'object' || s === 'function') { - if (value) { - if (value instanceof Array) { s = 'array' } - else if (value instanceof RegExp) { s = 'regexp' } - } else { s = 'null' } - } - return s; -} // Keeps track of the outcome of vows. var total = 0, honored = 0, @@ -67,6 +30,21 @@ var total = 0, honored = 0, function addVow(/* description & callback */) { var desc, callback; + function augment(obj) { + obj = new(obj.constructor)(obj); + + // Recursively augment any Objects + if (obj instanceof Object) { + for (key in obj) { + if (obj.hasOwnProperty(key)) obj[key] = augment(obj[key]); + } + } + for (macro in vows.macros) { + obj.should[macro] = vows.macros[macro]; + } + return obj; + }; + if (arguments.length < 2) throw "A vow is comprised of a description and a proof"; total++; @@ -129,6 +107,19 @@ function addVow(/* description & callback */) { }); }; +vows.macros = { + equal: function (expected) { assert.equal(obj, expected) }, + match: function (expected) { assert.ok(obj.match(expected)) }, + include: function (item) { assert.ok(obj.match(new(RegExp)(item))) }, + beA: function (type) { + if (typeof(obj) === 'object') { assert.ok(obj.constructor === type) } + else { + if (typeof(type) === 'function') type = type.name.toLowerCase(); + assert.ok(typeof(obj) === type); + } + } +}; + // Options vows.options = { emitter: process.Promise From e6040566d33fdf1e5c9bc07e0eae54e51e3fed08 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 15 Jan 2010 09:33:32 -0500 Subject: [PATCH 007/409] moved macros out, added BrokenVow exception --- lib/vows.js | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index a2985a36..96fd630a 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -12,16 +12,45 @@ // }, 'it should know the answer to the ultimate question of life'); // }); // +require.paths.unshift('lib'); + var sys = require('sys'), assert = require('assert'), + inspect = require('eyes').inspector({writer: function () {}}), vows = exports; - // Keeps track of the outcome of vows. var total = 0, honored = 0, broken = 0, errored = 0, start, end; +vows.macros = { + equal: function (actual, expected) { + if (actual == expected) { return true } + else { fail("expected " + inspect(expected) + ", got " + inspect(actual)) } + }, + match: function (actual, expected) { assert.ok(actual.match(expected)) }, + include: function (actual, item) { assert.ok(actual.match(new(RegExp)(item))) }, + beA: function (actual, type) { + if (typeof(actual) === 'object') { assert.ok(actual.constructor === type) } + else { + if (typeof(type) === 'function') type = type.name.toLowerCase(); + assert.ok(typeof(actual) === type); + } + } +}; + +var BrokenVow = function (message) { + return { + name: 'BrokenVow', + message: message + }; +}; + +function fail(message) { + throw new(BrokenVow)(message); +} + // // This function gets added to process.Promise.prototype, by default. // It's essentially a wrapper around `addCallback`, which adds all the specification From e88842b26948da522c49cda05af0a32da48cb051 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 15 Jan 2010 09:34:20 -0500 Subject: [PATCH 008/409] reworked augment() and fixed some code --- lib/vows.js | 58 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 96fd630a..9b705aa4 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -60,19 +60,38 @@ function addVow(/* description & callback */) { var desc, callback; function augment(obj) { - obj = new(obj.constructor)(obj); - - // Recursively augment any Objects + // Recursively call `augment()` on any Objects or Arrays, + // wrap Functions to return augmented values, + // reconstruct native types by calling their constructor. if (obj instanceof Object) { - for (key in obj) { - if (obj.hasOwnProperty(key)) obj[key] = augment(obj[key]); + if (typeof(obj) === "object") { + for (var key in obj) { + if (obj.hasOwnProperty(key) && key !== 'should') obj[key] = augment(obj[key]); + } + } else if (obj instanceof Function) { + obj = (function (fun) { + return function () { + return augment(fun.apply(null, arguments)); + }; + })(obj); } + } else { + obj = new(obj.constructor)(obj); } - for (macro in vows.macros) { - obj.should[macro] = vows.macros[macro]; + + obj.should = {}; + + // Copy macros into `obj.should`, + // closing on the current value of `obj` + for (var macro in vows.macros) { + obj.should[macro] = (function (macro) { + return function (expected) { + return vows.macros[macro](obj, expected); + }; + })(macro); } return obj; - }; + } if (arguments.length < 2) throw "A vow is comprised of a description and a proof"; @@ -105,10 +124,14 @@ function addVow(/* description & callback */) { } catch (e) { if (e.name.match(/AssertionError/)) { vow += stylize(desc, 'yellow'); - exception = ' ~ ' + stylize("expected " + + exception = ' ~ ' + stylize("expected " + stylize(sys.inspect(e.expected), 'bold') + ", got " + stylize(sys.inspect(e.actual), 'bold') + "\n", 'yellow'); broken++; + } else if (e.name.match(/BrokenVow/)) { + vow += stylize(desc, 'yellow'); + exception = ' ~ ' + stylize(e.message + "\n", 'yellow'); + broken++; } else { vow += stylize(desc, 'red'); exception = ' ! ' + stylize(e.stack, 'red') + "\n"; @@ -136,19 +159,6 @@ function addVow(/* description & callback */) { }); }; -vows.macros = { - equal: function (expected) { assert.equal(obj, expected) }, - match: function (expected) { assert.ok(obj.match(expected)) }, - include: function (item) { assert.ok(obj.match(new(RegExp)(item))) }, - beA: function (type) { - if (typeof(obj) === 'object') { assert.ok(obj.constructor === type) } - else { - if (typeof(type) === 'function') type = type.name.toLowerCase(); - assert.ok(typeof(obj) === type); - } - } -}; - // Options vows.options = { emitter: process.Promise @@ -165,10 +175,10 @@ vows.tell = function (topic, tests) { start = new(Date); - for (batch in tests) { + for (var batch in tests) { if (tests.hasOwnProperty(batch)) { promise = tests[batch].setup(this); - for (item in tests[batch]) { + for (var item in tests[batch]) { if (tests[batch].hasOwnProperty(item) && item !== 'setup') { promise.addVow(tests[batch][item], item); } From a04987eabaf9dbbef32029784d078360262e817c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 15 Jan 2010 09:34:47 -0500 Subject: [PATCH 009/409] added some tests --- test/vows-test.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/vows-test.js b/test/vows-test.js index ea2ac36f..8c822ddc 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -21,13 +21,19 @@ vows.tell("Vows", { "and now something a little more complex": { setup: function () { var promise = new(process.Promise); - setTimeout(function () { promise.emitSuccess({f: 1, z: 2}) }, 100); + setTimeout(function () { + promise.emitSuccess({ + f: function (a) { return a; }, + z: 2 + }) + }, 100); return promise; }, "testing member equality": function (it) { - it.should.beA(Object); - it['f'].should.equal(1); - it['z'].should.equal(2); + it.f({a:function(){return {j:11}}}).a().j.should.equal(11); + it.f([1,2,3])[1].should.equal(2) + //it['f'].should.equal(1); + //it['z'].should.equal(2); } }, }); From fc25adc2688f1384118363f4e01207b05d6d1735 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 18 Jan 2010 01:12:36 -0500 Subject: [PATCH 010/409] added a bunch of macros --- lib/vows.js | 60 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 9b705aa4..8fe37847 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -27,17 +27,63 @@ var total = 0, honored = 0, vows.macros = { equal: function (actual, expected) { if (actual == expected) { return true } - else { fail("expected " + inspect(expected) + ", got " + inspect(actual)) } + else { fail("expected {expected}, got {actual}", actual, expected) } }, - match: function (actual, expected) { assert.ok(actual.match(expected)) }, - include: function (actual, item) { assert.ok(actual.match(new(RegExp)(item))) }, + be: function (actual, expected) { + if (Boolean(expected) === Boolean(actual)) { + return true; + } else { + fail("expected {actual} to evaluate to {expected}", actual, expected); + } + }, + beTrue: function (actual) { return be(actual, true) }, + beFalse: function (actual) { return be(actual, false) }, + + match: function (actual, expected) { + if (actual.match(expected)) { + return true; + } else { + fail("expected {actual} to match {expected}", actual, expected); + } + }, + include: function (actual, item) { + if ((function (obj) { + if (isString(obj) && obj.match(new(RegExp)(item))) { return true } + else if (isArray(obj)) { + for (var i = 0; i < obj.length; i++) { + if (obj[i] === item) return true; + } + } else if (isObject(actual)) { + return obj.hasOwnProperty(item); + } + return false; + })(actual)) { return true } + else { + fail("expected {actual} to include {expected}", actual, expected); + } + }, + have: function () { return this.include.apply(this, arguments) }, beA: function (actual, type) { - if (typeof(actual) === 'object') { assert.ok(actual.constructor === type) } + if (function (obj) { + if (typeof(obj) === 'object') { return obj.constructor === type } + else { + if (typeof(type) === 'function') type = type.name.toLowerCase(); + return typeof(obj) === type; + } + }(actual)) { return true } else { - if (typeof(type) === 'function') type = type.name.toLowerCase(); - assert.ok(typeof(actual) === type); + fail("expected {actual} to be of type {expected}", actual, type); } - } + }, + throwAn: function (actual, expected) { + try { + actual.call(null); + } catch (e) { + if (e === expected) { return true } + } + fail("expected {actual} to throw a {expected}", actual, expected); + }, + throwA: function () { return this.throwAn.apply(this, arguments) } }; var BrokenVow = function (message) { From 81967189f9fb8527976358565fe69ac9eb66d128 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 18 Jan 2010 01:12:49 -0500 Subject: [PATCH 011/409] eyes writer = null --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 8fe37847..2499b958 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -16,7 +16,7 @@ require.paths.unshift('lib'); var sys = require('sys'), assert = require('assert'), - inspect = require('eyes').inspector({writer: function () {}}), + inspect = require('eyes').inspector({writer: null}), vows = exports; // Keeps track of the outcome of vows. From de030170d3d9ba058e34772e92fd7ac80cfac377 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 18 Jan 2010 01:13:08 -0500 Subject: [PATCH 012/409] fixed the failure function, and added some type checkers --- lib/vows.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 2499b958..e145434c 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -93,10 +93,24 @@ var BrokenVow = function (message) { }; }; -function fail(message) { +function fail(message, actual, expected) { + message = message.replace(/{actual}/g, inspect(actual)). + replace(/{expected}/g, inspect(expected)); throw new(BrokenVow)(message); } +function isArray (obj) { + return (obj instanceof Array); +} + +function isString (obj) { + return typeof(obj) === 'string' || obj instanceof String; +} + +function isObject (obj) { + return typeof(obj) === 'object' && obj instanceof Object && !isArray(obj); +} + // // This function gets added to process.Promise.prototype, by default. // It's essentially a wrapper around `addCallback`, which adds all the specification From 7800c8cf7c192e3d217f384ca17fd65fefe8201f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 18 Jan 2010 01:13:34 -0500 Subject: [PATCH 013/409] pass the augment function to vows --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index e145434c..ba0696e3 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -178,7 +178,7 @@ function addVow(/* description & callback */) { // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { - callback.apply(null, topics); + callback.apply(null, topics.concat(augment)); vow += stylize(desc, 'green'); honored++; } catch (e) { From bcfd2e3b58c0e13ea861609fad0c5f3e578a6bf3 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 18 Jan 2010 01:14:03 -0500 Subject: [PATCH 014/409] recursively call nested tests --- lib/vows.js | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index ba0696e3..03f9aa68 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -226,7 +226,7 @@ vows.options = { // Run all vows/tests vows.tell = function (topic, tests) { - var promise; + var promise, value; this.options.emitter.prototype.addVow = addVow; if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); @@ -235,17 +235,25 @@ vows.tell = function (topic, tests) { start = new(Date); - for (var batch in tests) { - if (tests.hasOwnProperty(batch)) { - promise = tests[batch].setup(this); - for (var item in tests[batch]) { - if (tests[batch].hasOwnProperty(item) && item !== 'setup') { - promise.addVow(tests[batch][item], item); - } + return (function run(tests, context) { + for (var item in tests) { + value = tests[item]; + + if (typeof(tests["setup"]) === 'function') { + promise = tests.setup(this); + } + + // Skip setup & prototype attributes + if (item === 'setup' || ! tests.hasOwnProperty(item)) continue; + + if (typeof(value) === 'function' && value instanceof Function) { + promise.addVow(value, context + ' ' + item); + } + else if (typeof(value) === 'object' && value instanceof Object) { + run(value, item); } } - } - return; + })(tests); }; // Return the `vows` object after setting some options From 9d35920a9ced6da8a946fbca28163d1315aa3478 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 19 Jan 2010 00:30:18 -0500 Subject: [PATCH 015/409] made require stuff more reliable --- lib/vows.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 03f9aa68..062795bc 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -12,13 +12,15 @@ // }, 'it should know the answer to the ultimate question of life'); // }); // -require.paths.unshift('lib'); +require.paths.unshift( + __filename.split('/').slice(0, -1).concat('vendor').join('/')); var sys = require('sys'), assert = require('assert'), - inspect = require('eyes').inspector({writer: null}), + eyes = require('eyes').inspector({writer: null}), vows = exports; + // Keeps track of the outcome of vows. var total = 0, honored = 0, broken = 0, errored = 0, From 722f08f6a6d099a7f694cb45b247fd5f2d2132d2 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 19 Jan 2010 00:30:56 -0500 Subject: [PATCH 016/409] moved some things around, and added comments --- lib/vows.js | 41 +++++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 062795bc..0cb28a94 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -26,6 +26,9 @@ var total = 0, honored = 0, broken = 0, errored = 0, start, end; +// +// Assertion Macros +// vows.macros = { equal: function (actual, expected) { if (actual == expected) { return true } @@ -88,6 +91,9 @@ vows.macros = { throwA: function () { return this.throwAn.apply(this, arguments) } }; +// +// Exception raised when a test doesn't pass +// var BrokenVow = function (message) { return { name: 'BrokenVow', @@ -95,24 +101,15 @@ var BrokenVow = function (message) { }; }; +// +// Function called when a test failed +// function fail(message, actual, expected) { message = message.replace(/{actual}/g, inspect(actual)). replace(/{expected}/g, inspect(expected)); throw new(BrokenVow)(message); } -function isArray (obj) { - return (obj instanceof Array); -} - -function isString (obj) { - return typeof(obj) === 'string' || obj instanceof String; -} - -function isObject (obj) { - return typeof(obj) === 'object' && obj instanceof Object && !isArray(obj); -} - // // This function gets added to process.Promise.prototype, by default. // It's essentially a wrapper around `addCallback`, which adds all the specification @@ -264,6 +261,14 @@ vows.config = function (opts) { return this; }; +// +// Utility functions +// + +function inspect(val) { + return '\033[1m' + eyes(val) + '\033[22m'; +} + // Stylize a string function stylize(str, style) { var styles = { @@ -277,3 +282,15 @@ function stylize(str, style) { '\033[' + styles[style][1] + 'm'; } +function isArray (obj) { + return (obj instanceof Array); +} + +function isString (obj) { + return typeof(obj) === 'string' || obj instanceof String; +} + +function isObject (obj) { + return typeof(obj) === 'object' && obj instanceof Object && !isArray(obj); +} + From 2a88b0059a955075b7d97b450b80f3da7b90dee3 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 19 Jan 2010 00:31:36 -0500 Subject: [PATCH 017/409] use our own inspect function --- lib/vows.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 0cb28a94..a3433428 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -184,8 +184,8 @@ function addVow(/* description & callback */) { if (e.name.match(/AssertionError/)) { vow += stylize(desc, 'yellow'); exception = ' ~ ' + stylize("expected " + - stylize(sys.inspect(e.expected), 'bold') + ", got " + - stylize(sys.inspect(e.actual), 'bold') + "\n", 'yellow'); + stylize(inspect(e.expected), 'bold') + ", got " + + stylize(inspect(e.actual), 'bold') + "\n", 'yellow'); broken++; } else if (e.name.match(/BrokenVow/)) { vow += stylize(desc, 'yellow'); From f866d0c3c0becd792eebfb2cd392521830fa8c71 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 19 Jan 2010 00:32:36 -0500 Subject: [PATCH 018/409] moved setup outside of loop, so it doesnt run on every vow, also pass the parent setup return value --- lib/vows.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index a3433428..3e99dc5e 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -235,13 +235,12 @@ vows.tell = function (topic, tests) { start = new(Date); return (function run(tests, context) { + if (typeof(tests["setup"]) === 'function') { + promise = tests.setup(promise); + } for (var item in tests) { value = tests[item]; - if (typeof(tests["setup"]) === 'function') { - promise = tests.setup(this); - } - // Skip setup & prototype attributes if (item === 'setup' || ! tests.hasOwnProperty(item)) continue; From b6185ba9562898e1f56e3c611d925959904baf9e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 19 Jan 2010 00:37:52 -0500 Subject: [PATCH 019/409] vendored eyes.js --- lib/vendor/eyes.js | 166 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 lib/vendor/eyes.js diff --git a/lib/vendor/eyes.js b/lib/vendor/eyes.js new file mode 100644 index 00000000..753f6a98 --- /dev/null +++ b/lib/vendor/eyes.js @@ -0,0 +1,166 @@ +// +// Eyes.js - a customizable value inspector for Node.js +// +// usage: +// +// var inspect = require('eyes').inspector({styles: {all: 'magenta'}}); +// inspect(something); // inspect with the settings passed to `inspector` +// +// or +// +// var eyes = require('eyes'); +// eyes.inspect(something); // inspect with the default settings +// +var eyes = { + defaults: { + styles: { // Styles applied to stdout + all: 'cyan', // Overall style applied to everything + label: 'underline', // Inspection labels, like 'array' in `array: [1, 2, 3]` + other: 'inverted', // Objects which don't have a literal representation, such as functions + key: 'bold', // The keys in object literals, like 'a' in `{a: 1}` + special: null, // null, undefined... + string: null, + number: null, + bool: null + }, + hideFunctions: false, + writer: process.stdio.write, + maxLength: 2048 // Truncate output if longer + }, + + // Return a curried inspect() function, with the `options` argument filled in. + inspector: function (options) { + var that = this; + return function (obj, label, opts) { + return that.inspect(obj, label, + process.mixin(true, {}, options || {}, opts || {})); + }; + }, + + // If we have a `writer` defined, use it to print a styled string, + // if not, we just return the stringified object with no styling. + inspect: function (obj, label, options) { + options = process.mixin(true, {}, this.defaults, options || {}); + if (options.writer) { + return this.print(this.stringify(obj, options), label, options); + } else { + options.styles = {}; + return this.stringify(obj, options); + } + }, + + // Output using the 'writer', and an optional label + // Loop through `str`, and truncate it after `options.maxLength` has been reached. + // Because escape sequences are, at this point embeded within + // the output string, we can't measure the length of the string + // in a useful way, without separating what is an escape sequence, + // versus a printable character (`c`). So we resort to counting the + // length manually. + print: function (str, label, options) { + for (var c = 0, i = 0; i < str.length; i++) { + if (str.charAt(i) === '\033') { i += 4 } // `4` because '\033[25m'.length + 1 == 5 + else if (c === options.maxLength) { + str = str.slice(0, i - 1) + '…'; + break; + } else { c++ } + } + return options.writer((label ? + this.stylize(label, options.styles.label, options.styles) + ': ' : '') + + this.stylize(str, options.styles.all, options.styles) + '\033[0m' + "\n"); + }, + + // Convert any object to a string, ready for output. + // When an 'array' or an 'object' are encountered, they are + // passed to specialized functions, which can then recursively call + // stringify(). + stringify: function (obj, options) { + var that = this, stylize = function (str, style) { + return that.stylize(str, options.styles[style], options.styles) + }; + + switch (typeOf(obj)) { + case "string": + obj = (obj.length === 1 ? "'" + obj + "'" : '"' + obj + '"') + .replace(/\n/g, '\\n'); + return stylize(obj, 'string'); + case "regexp" : return stylize('/' + obj.source + '/', 'regexp'); + case "number" : return stylize(obj + '', 'number'); + case "function" : return options.writer ? stylize("Function", 'other') : '[Function]'; + case "null" : return stylize("null", 'special'); + case "undefined": return stylize("undefined", 'special'); + case "boolean" : return stylize(obj + '', 'bool'); + case "date" : return stylize(obj.toString()); + case "array" : return this.stringifyArray(obj, options); + case "object" : return this.stringifyObject(obj, options); + } + }, + + // Convert an array to a string, such as [1, 2, 3]. + // This function calls stringify() for each of the elements + // in the array. + stringifyArray: function (ary, options) { + var out = []; + + for (var i = 0; i < ary.length; i++) { + out.push(this.stringify(ary[i], options)); + } + return '[' + out.join(', ') + ']'; + }, + + // Convert an object to a string, such as {a: 1}. + // This function calls stringify() for each of its values, + // and does not output functions or prototype values. + stringifyObject: function (obj, options) { + var out = []; + + for (var k in obj) { + if (obj.hasOwnProperty(k) && !(obj[k] instanceof Function && options.hideFunctions)) { + out.push(this.stylize(k, options.styles.key, options.styles) + ': ' + + this.stringify(obj[k], options)); + } + } + return "{" + out.join(', ') + "}"; + }, + + // Apply a style to a string, eventually, + // I'd like this to support passing multiple + // styles. + stylize: function (str, style, styles) { + var codes = { + 'bold' : [1, 22], + 'underline' : [4, 24], + 'inverse' : [7, 27], + 'cyan' : [36, 39], + 'magenta' : [35, 39], + 'yellow' : [33, 39], + 'green' : [32, 39], + 'red' : [31, 39] + }, endCode; + + if (style && codes[style]) { + endCode = (codes[style][1] === 39 && styles.all) ? codes[styles.all][0] + : codes[style][1]; + return '\033[' + codes[style][0] + 'm' + str + + '\033[' + endCode + 'm'; + } else { return str } + } +}; + +// CommonJS module support +process.mixin(exports, eyes); + +// A better `typeof` +function typeOf(value) { + var s = typeof(value), + types = [Object, Array, String, RegExp, Number, Function, Boolean, Date]; + + if (s === 'object' || s === 'function') { + if (value) { + types.forEach(function (t) { + if (value instanceof t) { s = t.name.toLowerCase() } + }); + } else { s = 'null' } + } + return s; +} + From f2ff3230b3532474e4e4ae2026ae63c9883a38f8 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 19 Jan 2010 17:50:15 -0500 Subject: [PATCH 020/409] simplified require --- lib/vows.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 3e99dc5e..e829ba3e 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -12,15 +12,16 @@ // }, 'it should know the answer to the ultimate question of life'); // }); // -require.paths.unshift( - __filename.split('/').slice(0, -1).concat('vendor').join('/')); +var path = require('path'); + +require.paths.unshift(path.join(path.dirname(__filename), 'vendor'), + path.dirname(__filename)); var sys = require('sys'), assert = require('assert'), - eyes = require('eyes').inspector({writer: null}), + eyes = require('eyes').inspector({ writer: null }), vows = exports; - // Keeps track of the outcome of vows. var total = 0, honored = 0, broken = 0, errored = 0, From 82d821e82a6937946919d2ebd7b8e7a76997130a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 19 Jan 2010 17:55:39 -0500 Subject: [PATCH 021/409] moved macros to vows/macros --- lib/vows.js | 74 +---------------------------------------- lib/vows/macros.js | 83 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 73 deletions(-) create mode 100644 lib/vows/macros.js diff --git a/lib/vows.js b/lib/vows.js index e829ba3e..9e8f4ccc 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -30,67 +30,7 @@ var total = 0, honored = 0, // // Assertion Macros // -vows.macros = { - equal: function (actual, expected) { - if (actual == expected) { return true } - else { fail("expected {expected}, got {actual}", actual, expected) } - }, - be: function (actual, expected) { - if (Boolean(expected) === Boolean(actual)) { - return true; - } else { - fail("expected {actual} to evaluate to {expected}", actual, expected); - } - }, - beTrue: function (actual) { return be(actual, true) }, - beFalse: function (actual) { return be(actual, false) }, - - match: function (actual, expected) { - if (actual.match(expected)) { - return true; - } else { - fail("expected {actual} to match {expected}", actual, expected); - } - }, - include: function (actual, item) { - if ((function (obj) { - if (isString(obj) && obj.match(new(RegExp)(item))) { return true } - else if (isArray(obj)) { - for (var i = 0; i < obj.length; i++) { - if (obj[i] === item) return true; - } - } else if (isObject(actual)) { - return obj.hasOwnProperty(item); - } - return false; - })(actual)) { return true } - else { - fail("expected {actual} to include {expected}", actual, expected); - } - }, - have: function () { return this.include.apply(this, arguments) }, - beA: function (actual, type) { - if (function (obj) { - if (typeof(obj) === 'object') { return obj.constructor === type } - else { - if (typeof(type) === 'function') type = type.name.toLowerCase(); - return typeof(obj) === type; - } - }(actual)) { return true } - else { - fail("expected {actual} to be of type {expected}", actual, type); - } - }, - throwAn: function (actual, expected) { - try { - actual.call(null); - } catch (e) { - if (e === expected) { return true } - } - fail("expected {actual} to throw a {expected}", actual, expected); - }, - throwA: function () { return this.throwAn.apply(this, arguments) } -}; +vows.macros = require('vows/macros'); // // Exception raised when a test doesn't pass @@ -282,15 +222,3 @@ function stylize(str, style) { '\033[' + styles[style][1] + 'm'; } -function isArray (obj) { - return (obj instanceof Array); -} - -function isString (obj) { - return typeof(obj) === 'string' || obj instanceof String; -} - -function isObject (obj) { - return typeof(obj) === 'object' && obj instanceof Object && !isArray(obj); -} - diff --git a/lib/vows/macros.js b/lib/vows/macros.js new file mode 100644 index 00000000..5495571e --- /dev/null +++ b/lib/vows/macros.js @@ -0,0 +1,83 @@ + +var macros = { + equal: function (actual, expected) { + if (actual == expected) { return true } + else { fail("expected {expected}, got {actual}", actual, expected) } + }, + be: function (actual, expected) { + if (Boolean(expected) === Boolean(actual)) { + return true; + } else { + fail("expected {actual} to evaluate to {expected}", actual, expected); + } + }, + beTrue: function (actual) { return be(actual, true) }, + beFalse: function (actual) { return be(actual, false) }, + + match: function (actual, expected) { + if (actual.match(expected)) { + return true; + } else { + fail("expected {actual} to match {expected}", actual, expected); + } + }, + include: function (actual, item) { + if ((function (obj) { + if (isString(obj) && obj.match(new(RegExp)(item))) { return true } + else if (isArray(obj)) { + for (var i = 0; i < obj.length; i++) { + if (obj[i] === item) return true; + } + } else if (isObject(actual)) { + return obj.hasOwnProperty(item); + } + return false; + })(actual)) { return true } + else { + fail("expected {actual} to include {expected}", actual, expected); + } + }, + have: function () { return this.include.apply(this, arguments) }, + beA: function (actual, type) { + if (function (obj) { + if (typeof(obj) === 'object') { return obj.constructor === type } + else { + if (typeof(type) === 'function') type = type.name.toLowerCase(); + return typeof(obj) === type; + } + }(actual)) { return true } + else { + fail("expected {actual} to be of type {expected}", actual, type); + } + }, + throwAn: function (actual, expected) { + try { + actual.call(null); + } catch (e) { + if (e === expected) { return true } + } + fail("expected {actual} to throw a {expected}", actual, expected); + }, + throwA: function () { return this.throwAn.apply(this, arguments) } +}; + +// +// CommonJS Export +// +process.mixin(exports, macros); + +// +// Utility functions +// +function isArray (obj) { + return (obj instanceof Array); +} + +function isString (obj) { + return typeof(obj) === 'string' || obj instanceof String; +} + +function isObject (obj) { + return typeof(obj) === 'object' && obj instanceof Object && !isArray(obj); +} + From 3edf29d04d4c6ccc4293053f7a9f29e9934335f6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 21 Jan 2010 13:37:07 -0500 Subject: [PATCH 022/409] compatibility with assert -- moved message generation to AssertionError.toString --- lib/vows.js | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 9e8f4ccc..660297b4 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -32,23 +32,19 @@ var total = 0, honored = 0, // vows.macros = require('vows/macros'); -// -// Exception raised when a test doesn't pass -// -var BrokenVow = function (message) { - return { - name: 'BrokenVow', - message: message - }; -}; +assert.AssertionError.prototype.toString = function () { + var that = this; + function parse(str) { + return str.replace(/{actual}/g, inspect(that.actual)). + replace(/{expected}/g, inspect(that.expected)). + replace(/{operator}/g, stylize(that.operator, 'bold')); + } -// -// Function called when a test failed -// -function fail(message, actual, expected) { - message = message.replace(/{actual}/g, inspect(actual)). - replace(/{expected}/g, inspect(expected)); - throw new(BrokenVow)(message); + if (this.message) { + return stylize(parse(this.message), 'yellow'); + } else { + return stylize(parse(assert.messages[this.operator]), 'yellow'); + } } // @@ -122,15 +118,9 @@ function addVow(/* description & callback */) { vow += stylize(desc, 'green'); honored++; } catch (e) { - if (e.name.match(/AssertionError/)) { - vow += stylize(desc, 'yellow'); - exception = ' ~ ' + stylize("expected " + - stylize(inspect(e.expected), 'bold') + ", got " + - stylize(inspect(e.actual), 'bold') + "\n", 'yellow'); - broken++; - } else if (e.name.match(/BrokenVow/)) { + if (e.name && e.name.match(/AssertionError/)) { vow += stylize(desc, 'yellow'); - exception = ' ~ ' + stylize(e.message + "\n", 'yellow'); + exception = ' ~ ' + e.toString() + "\n"; broken++; } else { vow += stylize(desc, 'red'); From 2c0bd50153a679e115b11d11c34be98fa2c71ca8 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 21 Jan 2010 13:37:38 -0500 Subject: [PATCH 023/409] no more augmenting, just asserts --- lib/vows.js | 42 ++---------------------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 660297b4..07e744e1 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -55,40 +55,6 @@ assert.AssertionError.prototype.toString = function () { function addVow(/* description & callback */) { var desc, callback; - function augment(obj) { - // Recursively call `augment()` on any Objects or Arrays, - // wrap Functions to return augmented values, - // reconstruct native types by calling their constructor. - if (obj instanceof Object) { - if (typeof(obj) === "object") { - for (var key in obj) { - if (obj.hasOwnProperty(key) && key !== 'should') obj[key] = augment(obj[key]); - } - } else if (obj instanceof Function) { - obj = (function (fun) { - return function () { - return augment(fun.apply(null, arguments)); - }; - })(obj); - } - } else { - obj = new(obj.constructor)(obj); - } - - obj.should = {}; - - // Copy macros into `obj.should`, - // closing on the current value of `obj` - for (var macro in vows.macros) { - obj.should[macro] = (function (macro) { - return function (expected) { - return vows.macros[macro](obj, expected); - }; - })(macro); - } - return obj; - } - if (arguments.length < 2) throw "A vow is comprised of a description and a proof"; total++; @@ -105,16 +71,12 @@ function addVow(/* description & callback */) { } return this.addCallback(function () { - var vow = "- ", exception, topics = [], topic; - - for (var i = 0; i < arguments.length; i++) { - topics[i] = augment(arguments[i]); - } + var vow = "- ", exception, topic, msg; // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { - callback.apply(null, topics.concat(augment)); + callback.apply(null, arguments); vow += stylize(desc, 'green'); honored++; } catch (e) { From e1c80eef3a3c864e22b63c68000a7f702f8db6ba Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 21 Jan 2010 13:37:53 -0500 Subject: [PATCH 024/409] more robust error messages --- lib/vows.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 07e744e1..d542c56f 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -86,7 +86,8 @@ function addVow(/* description & callback */) { broken++; } else { vow += stylize(desc, 'red'); - exception = ' ! ' + stylize(e.stack, 'red') + "\n"; + msg = e.stack || e.message || e.toString() || e; + exception = ' ! ' + stylize(msg, 'red') + "\n"; errored++; } } From 19d933481b0161f711422bc6fae8527459dae510 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 21 Jan 2010 13:38:21 -0500 Subject: [PATCH 025/409] new assert macros --- lib/vows/macros.js | 96 +++++++++++++++++----------------------------- 1 file changed, 35 insertions(+), 61 deletions(-) diff --git a/lib/vows/macros.js b/lib/vows/macros.js index 5495571e..bb3109bc 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -1,70 +1,44 @@ +var assert = require('assert'); -var macros = { - equal: function (actual, expected) { - if (actual == expected) { return true } - else { fail("expected {expected}, got {actual}", actual, expected) } - }, - be: function (actual, expected) { - if (Boolean(expected) === Boolean(actual)) { - return true; - } else { - fail("expected {actual} to evaluate to {expected}", actual, expected); - } - }, - beTrue: function (actual) { return be(actual, true) }, - beFalse: function (actual) { return be(actual, false) }, +var messages = { + 'equal' : "expected {expected}, got {actual} ({operator})", + 'notEqual' : "didn't expect {actual} ({operator})", + 'throws' : "expected {expected} to be thrown", + 'doesNotThrow': "didn't expect {actual} to be thrown", + 'ok' : "expected expression to evaluate to {expected}, but was {actual}" +}; +messages['strictEqual'] = messages['deepEqual'] = messages['equal']; +messages['notStrictEqual'] = messages['notDeepEqual'] = messages['notEqual']; - match: function (actual, expected) { - if (actual.match(expected)) { - return true; - } else { - fail("expected {actual} to match {expected}", actual, expected); - } - }, - include: function (actual, item) { - if ((function (obj) { - if (isString(obj) && obj.match(new(RegExp)(item))) { return true } - else if (isArray(obj)) { - for (var i = 0; i < obj.length; i++) { - if (obj[i] === item) return true; - } - } else if (isObject(actual)) { - return obj.hasOwnProperty(item); - } - return false; - })(actual)) { return true } - else { - fail("expected {actual} to include {expected}", actual, expected); - } - }, - have: function () { return this.include.apply(this, arguments) }, - beA: function (actual, type) { - if (function (obj) { - if (typeof(obj) === 'object') { return obj.constructor === type } - else { - if (typeof(type) === 'function') type = type.name.toLowerCase(); - return typeof(obj) === type; - } - }(actual)) { return true } - else { - fail("expected {actual} to be of type {expected}", actual, type); - } - }, - throwAn: function (actual, expected) { - try { - actual.call(null); - } catch (e) { - if (e === expected) { return true } +for (var key in messages) { + assert[key] = (function (key, callback) { + return function (actual, message) { + callback(actual, message || messages[key]); + }; + })(key, assert[key]); +} + +assert.match = function (actual, expected, message) { + if (! actual.match(expected)) assert.fail(actual, expected, message || "expected {actual} to match {expected}", "match", assert.match); +}; +assert.matches = assert.match; + +assert.include = function (actual, expected, message) { + if ((function (obj) { + if (isArray(obj) || isString(obj)) { + return obj.indexOf(expected) === -1 + } else if (isObject(actual)) { + return ! obj.hasOwnProperty(item); } - fail("expected {actual} to throw a {expected}", actual, expected); - }, - throwA: function () { return this.throwAn.apply(this, arguments) } + return false; + })(actual)) { + assert.fail(actual, expected, message || "expected {actual} to include {expected}", "include", assert.include); + } }; +assert.includes = assert.include; -// // CommonJS Export -// -process.mixin(exports, macros); +process.mixin(exports, assert); // // Utility functions From fba1d1ba95235fbef8f293f9a7c9e9cd344548a0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 21 Jan 2010 20:41:13 -0500 Subject: [PATCH 026/409] use isArray --- lib/vows/macros.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/macros.js b/lib/vows/macros.js index bb3109bc..3070a034 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -44,7 +44,7 @@ process.mixin(exports, assert); // Utility functions // function isArray (obj) { - return (obj instanceof Array); + return Array.isArray(obj); } function isString (obj) { From bf99a55d6d144b01861e071d6f3607827a2db924 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 23 Jan 2010 00:25:53 -0500 Subject: [PATCH 027/409] Promise is now in events module | wrapped vows.tell in nextTick --- lib/vows.js | 60 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index d542c56f..d1a7f117 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -19,6 +19,7 @@ require.paths.unshift(path.join(path.dirname(__filename), 'vendor'), var sys = require('sys'), assert = require('assert'), + events = require('events'), eyes = require('eyes').inspector({ writer: null }), vows = exports; @@ -48,7 +49,7 @@ assert.AssertionError.prototype.toString = function () { } // -// This function gets added to process.Promise.prototype, by default. +// This function gets added to events.Promise.prototype, by default. // It's essentially a wrapper around `addCallback`, which adds all the specification // goodness. // @@ -114,38 +115,45 @@ function addVow(/* description & callback */) { // Options vows.options = { - emitter: process.Promise + emitter: events.Promise }; // Run all vows/tests vows.tell = function (topic, tests) { - var promise, value; this.options.emitter.prototype.addVow = addVow; - if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); - else if (topic instanceof Object) tests = topic; - else throw "tell() takes a topic and an Object"; - - start = new(Date); - - return (function run(tests, context) { - if (typeof(tests["setup"]) === 'function') { - promise = tests.setup(promise); - } - for (var item in tests) { - value = tests[item]; - - // Skip setup & prototype attributes - if (item === 'setup' || ! tests.hasOwnProperty(item)) continue; - - if (typeof(value) === 'function' && value instanceof Function) { - promise.addVow(value, context + ' ' + item); - } - else if (typeof(value) === 'object' && value instanceof Object) { - run(value, item); - } + return process.nextTick(function () { + var promise, value; + + if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); + else if (topic instanceof Object) tests = topic; + else throw "tell() takes a topic and an Object"; + + start = new(Date); + + if (typeof(tests) === 'function') { + return tests.call(null); + } else { + (function run(tests, context) { + if (typeof(tests["setup"]) === 'function') { + promise = tests.setup(promise); + } + for (var item in tests) { + value = tests[item]; + + // Skip setup & prototype attributes + if (item === 'setup' || ! tests.hasOwnProperty(item)) continue; + + if (typeof(value) === 'function' && value instanceof Function) { + promise.addVow(value, context + ' ' + item); + } + else if (typeof(value) === 'object' && value instanceof Object) { + run(value, item); + } + } + })(tests); } - })(tests); + }); }; // Return the `vows` object after setting some options From 9bc98beb5cb09871d9de8744af34be3182c19bc8 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 23 Jan 2010 00:26:18 -0500 Subject: [PATCH 028/409] if somehow no message is available... --- lib/vows.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index d1a7f117..0453b121 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -44,7 +44,11 @@ assert.AssertionError.prototype.toString = function () { if (this.message) { return stylize(parse(this.message), 'yellow'); } else { - return stylize(parse(assert.messages[this.operator]), 'yellow'); + return stylize([ + this.expected, + this.operator, + this.actual + ].join(' '), 'yellow'); } } From f20adc4e54c3b0e5c58865932561d3b307a59112 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 23 Jan 2010 00:27:25 -0500 Subject: [PATCH 029/409] fixed macros; assert.ok needs to be handled separately because it has less arguments --- lib/vows/macros.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/vows/macros.js b/lib/vows/macros.js index 3070a034..43dfdfce 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -5,19 +5,24 @@ var messages = { 'notEqual' : "didn't expect {actual} ({operator})", 'throws' : "expected {expected} to be thrown", 'doesNotThrow': "didn't expect {actual} to be thrown", - 'ok' : "expected expression to evaluate to {expected}, but was {actual}" }; messages['strictEqual'] = messages['deepEqual'] = messages['equal']; messages['notStrictEqual'] = messages['notDeepEqual'] = messages['notEqual']; for (var key in messages) { assert[key] = (function (key, callback) { - return function (actual, message) { - callback(actual, message || messages[key]); + return function (actual, expected, message) { + callback(actual, expected, message || messages[key]); }; })(key, assert[key]); } +assert.ok = (function (callback) { + return function (actual, message) { + callback(actual, message || "expected expression to evaluate to {expected}, but was {actual}"); + }; +})(assert.ok); + assert.match = function (actual, expected, message) { if (! actual.match(expected)) assert.fail(actual, expected, message || "expected {actual} to match {expected}", "match", assert.match); }; From 2bb38a233dfb2a5892bdaf588a09225d7ba372e6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 23 Jan 2010 00:49:39 -0500 Subject: [PATCH 030/409] simplified tests until I have time to do something more in depth --- test/vows-test.js | 58 +++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/test/vows-test.js b/test/vows-test.js index 8c822ddc..61496ee5 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -1,39 +1,39 @@ -var vows = require('../lib/vows'); +var path = require('path'); + +require.paths.unshift(path.join(path.dirname(__filename), '..', 'lib')); + +var events = require('events'), + assert = require('assert'); +var vows = require('vows'); + +var promiser = function (val) { + return function () { + var promise = new(events.Promise); + process.nextTick(function () { promise.emitSuccess(val) }); + return promise; + } +}; vows.tell("Vows", { - "let's start with some basics": { - setup: function () { - var promise = new(process.Promise); - setTimeout(function () { promise.emitSuccess("hello world.") }, 100); - return promise; - }, + "a context": { + setup: promiser("hello world"), + "testing equality": function (it) { - it.should.equal("hello world."); - it.should.beA(String); + assert.equal(it, "hello world"); }, "testing match": function (it) { - it.should.match(/[a-z]+ [a-z]+/); + assert.match(it, /[a-z]+ [a-z]+/); }, "testing inclusion": function (it) { - it.should.include("world"); - } - }, - "and now something a little more complex": { - setup: function () { - var promise = new(process.Promise); - setTimeout(function () { - promise.emitSuccess({ - f: function (a) { return a; }, - z: 2 - }) - }, 100); - return promise; + assert.include(it, "world"); }, - "testing member equality": function (it) { - it.f({a:function(){return {j:11}}}).a().j.should.equal(11); - it.f([1,2,3])[1].should.equal(2) - //it['f'].should.equal(1); - //it['z'].should.equal(2); + "a nested context": { + setup: function (parent) { + return parent; + }, + "with equality": function (it) { + assert.equal(it, "hello world"); + } } - }, + } }); From f079239a9f0f421777ca15d9d5eceded451f63cb Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 28 Jan 2010 21:27:24 -0500 Subject: [PATCH 031/409] fixed context sentences not building properly --- lib/vows.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 0453b121..89f54b80 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -127,7 +127,7 @@ vows.tell = function (topic, tests) { this.options.emitter.prototype.addVow = addVow; return process.nextTick(function () { - var promise, value; + var promise, value, sentence; if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); else if (topic instanceof Object) tests = topic; @@ -144,15 +144,15 @@ vows.tell = function (topic, tests) { } for (var item in tests) { value = tests[item]; + sentence = context ? context + ' ' + item : item; // Skip setup & prototype attributes if (item === 'setup' || ! tests.hasOwnProperty(item)) continue; if (typeof(value) === 'function' && value instanceof Function) { - promise.addVow(value, context + ' ' + item); - } - else if (typeof(value) === 'object' && value instanceof Object) { - run(value, item); + promise.addVow(value, sentence); + } else if (typeof(value) === 'object' && value instanceof Object) { + run(value, sentence); } } })(tests); From a550da9d833ed31d6838173b04ea1b316bd0317a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 28 Jan 2010 21:27:55 -0500 Subject: [PATCH 032/409] parse and show file and line number on assertion error --- lib/vows.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 89f54b80..5e20b277 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -34,7 +34,9 @@ var total = 0, honored = 0, vows.macros = require('vows/macros'); assert.AssertionError.prototype.toString = function () { - var that = this; + var that = this, + source = this.stack.match(/(\w+\.js)(:\d+):\d+/); + function parse(str) { return str.replace(/{actual}/g, inspect(that.actual)). replace(/{expected}/g, inspect(that.expected)). @@ -42,7 +44,8 @@ assert.AssertionError.prototype.toString = function () { } if (this.message) { - return stylize(parse(this.message), 'yellow'); + return stylize(parse(this.message) + " @ " + + stylize(source[1], 'bold') + source[2], 'yellow'); } else { return stylize([ this.expected, From 78be6c3fbd0c43b7a7a1785bb77a836aa86d878e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 28 Jan 2010 21:28:19 -0500 Subject: [PATCH 033/409] lets keep it asynch, although Id love to write to stderr --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 5e20b277..f6d80cfa 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -101,7 +101,7 @@ function addVow(/* description & callback */) { } sys.puts(vow); - if (exception) process.stdio.writeError(exception); + if (exception) sys.puts(exception); // Output results once all the vows have been checked if (honored + broken + errored === total) { From c1a3f59349b8f3067459e164bdb231e9b13e0871 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 29 Jan 2010 21:40:38 -0500 Subject: [PATCH 034/409] tests for nested context passing --- test/vows-test.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/test/vows-test.js b/test/vows-test.js index 61496ee5..1bc8bad7 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -15,7 +15,7 @@ var promiser = function (val) { }; vows.tell("Vows", { - "a context": { + "A context": { setup: promiser("hello world"), "testing equality": function (it) { @@ -27,13 +27,37 @@ vows.tell("Vows", { "testing inclusion": function (it) { assert.include(it, "world"); }, - "a nested context": { + "with a nested context": { setup: function (parent) { - return parent; + return promiser(parent)(); }, - "with equality": function (it) { + "testing equality": function (it) { assert.equal(it, "hello world"); } } + }, + "Nested contexts": { + setup: promiser(1), + ".": { + setup: function (a) { + assert.equal(a, 1); + return promiser(2)(); + }, + ".": { + setup: function (b, a) { + assert.equal(b, 2); + assert.equal(a, 1); + return promiser(3)(); + }, + ".": { + setup: function (c, b, a) { + assert.equal(c, 3); + assert.equal(b, 2); + assert.equal(a, 1); + return promiser(4)(); + }, + } + } + } } }); From e5d897a34029f831ad77e51824ff738e7b19851b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 00:45:15 -0500 Subject: [PATCH 035/409] added possibility to pass a Vow object directly to addVow() --- lib/vows.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index f6d80cfa..2fbe974b 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -63,19 +63,24 @@ assert.AssertionError.prototype.toString = function () { function addVow(/* description & callback */) { var desc, callback; - if (arguments.length < 2) throw "A vow is comprised of a description and a proof"; - total++; - // Sometimes it might be nicer to pass the proof first, - // and the description second, so we let the user choose - // the order. - if (arguments[0] instanceof Function) { - callback = arguments[0]; - desc = arguments[1]; + if (arguments.length > 1) { + // Sometimes it might be nicer to pass the proof first, + // and the description second, so we let the user choose + // the order. + if (arguments[0] instanceof Function) { + callback = arguments[0]; + desc = arguments[1]; + } else { + desc = arguments[0]; + callback = arguments[1]; + } + } else if (arguments[0].constructor.name === 'Vow') { + callback = arguments[0].callback; + desc = arguments[0].description; } else { - desc = arguments[0]; - callback = arguments[1]; + throw new(Error)("wrong argument type for addVow()"); } return this.addCallback(function () { From e551b24ce53833282a428d1c3edc18396308ae2f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 00:46:11 -0500 Subject: [PATCH 036/409] tryFinish() which checks if everything was run, and if so outputs the results and exists --- lib/vows.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 2fbe974b..17c20367 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -108,22 +108,26 @@ function addVow(/* description & callback */) { if (exception) sys.puts(exception); - // Output results once all the vows have been checked - if (honored + broken + errored === total) { - var result = honored + " honored, " + - broken + " broken, " + - errored + " errored", + tryFinish(vows.remaining); + }); +}; - style = honored === total ? - ('green') : (errored === 0 ? 'yellow' : 'red'); +function tryFinish(remaining) { + // Output results once all the vows have been checked + if (honored + broken + errored === total && remaining === 0) { + var result = honored + " honored, " + + broken + " broken, " + + errored + " errored", - sys.puts("\nVerified " + total + " vows in " + - (((new(Date)) - start) / 1000) + " seconds."); + style = honored === total ? + ('green') : (errored === 0 ? 'yellow' : 'red'); - sys.puts("\n" + stylize(result, style)); - } - }); -}; + sys.puts("\nVerified " + total + " vows in " + + (((new(Date)) - start) / 1000) + " seconds."); + + sys.puts("\n" + stylize(result, style)); + } +} // Options vows.options = { From f19ff442aabdfe95a306accbe7c0d8b3578c30ae Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 00:47:26 -0500 Subject: [PATCH 037/409] completely revamped the run loop, fixing lots of problems, and the ability to pass topics down the chain --- lib/vows.js | 62 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 17c20367..dc5225b0 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -134,12 +134,26 @@ vows.options = { emitter: events.Promise }; +// Run all vows/tests // Run all vows/tests vows.tell = function (topic, tests) { this.options.emitter.prototype.addVow = addVow; + this.tests = tests; + this.remaining = 0; + + function Context(vow, topics) { + this.tests = vow.callback; + this.topics = topics || []; + this.name = vow.description || null; + }; + + function Vow(callback, description) { + this.callback = callback; + this.description = description; + }; return process.nextTick(function () { - var promise, value, sentence; + var setup, vow; if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); else if (topic instanceof Object) tests = topic; @@ -147,31 +161,49 @@ vows.tell = function (topic, tests) { start = new(Date); - if (typeof(tests) === 'function') { - return tests.call(null); + if (typeof(vows.tests) === 'function') { + return vows.tests.call(null); } else { - (function run(tests, context) { - if (typeof(tests["setup"]) === 'function') { - promise = tests.setup(promise); + (function count(tests) { + vows.remaining++; + Object.keys(tests).forEach(function (key) { + if (! (tests[key] instanceof Function)) count(tests[key]); + }); + })(vows.tests); + + (function run(ctx) { + if (typeof(ctx.tests["setup"]) === 'function') { + setup = ctx.tests.setup.apply(this, ctx.topics); } - for (var item in tests) { - value = tests[item]; - sentence = context ? context + ' ' + item : item; + for (var item in ctx.tests) { + vow = new(Vow)(ctx.tests[item], ctx.name ? ctx.name + ' ' + item : item); // Skip setup & prototype attributes - if (item === 'setup' || ! tests.hasOwnProperty(item)) continue; + if (! ctx.tests.hasOwnProperty(item) || + ! vow.callback || item === 'setup') continue; - if (typeof(value) === 'function' && value instanceof Function) { - promise.addVow(value, sentence); - } else if (typeof(value) === 'object' && value instanceof Object) { - run(value, sentence); + if (vow.callback instanceof Function) { + setup.addVow(vow); + } else if (isObject(vow.callback)) { + if (setup) { + setup.addCallback(function (vow, ctx) { + return function (val) { + ctx.topics.unshift(val); + return run(new(Context)(vow, ctx.topics)); + }; + }(vow, ctx)); + } else { + run(new(Context)(vow, ctx.topics)); + } } } - })(tests); + tryFinish(--vows.remaining); + })(new(Context)(new(Vow)(vows.tests, null), [])); } }); }; + // Return the `vows` object after setting some options vows.config = function (opts) { process.mixin(this.options, opts); From 90bf0a2a0820ccdd6f5c6a379ff2ec4fb74a81ce Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 00:48:31 -0500 Subject: [PATCH 038/409] no need for helper isObject anymore --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index dc5225b0..4fdb9167 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -184,7 +184,7 @@ vows.tell = function (topic, tests) { if (vow.callback instanceof Function) { setup.addVow(vow); - } else if (isObject(vow.callback)) { + } else if (typeof(val) === 'object' && ! Array.isArray(val)) { if (setup) { setup.addCallback(function (vow, ctx) { return function (val) { From 58020a5e2e58f05b3b33308b778377dd8e02083a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 00:48:50 -0500 Subject: [PATCH 039/409] more accurate tests for topic passing --- test/vows-test.js | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/test/vows-test.js b/test/vows-test.js index 1bc8bad7..8a557249 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -38,24 +38,19 @@ vows.tell("Vows", { }, "Nested contexts": { setup: promiser(1), - ".": { - setup: function (a) { - assert.equal(a, 1); - return promiser(2)(); - }, - ".": { - setup: function (b, a) { - assert.equal(b, 2); - assert.equal(a, 1); - return promiser(3)(); - }, - ".": { - setup: function (c, b, a) { - assert.equal(c, 3); - assert.equal(b, 2); - assert.equal(a, 1); - return promiser(4)(); - }, + + "have": { + setup: function (a) { return promiser(2)() }, + + "access": { + setup: function (b, a) { return promiser(3)() }, + + "to": { + setup: function (c, b, a) { return promiser([4, c, b, a])() }, + + "the parent topics": function (topics) { + assert.equal(topics.join(), [4, 3, 2, 1].join()); + } } } } From 54b50ff182fc06d09cb3777087b3b2b1b99c27ef Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 00:50:21 -0500 Subject: [PATCH 040/409] woops, fix var name --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 4fdb9167..f3dd50fa 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -184,7 +184,7 @@ vows.tell = function (topic, tests) { if (vow.callback instanceof Function) { setup.addVow(vow); - } else if (typeof(val) === 'object' && ! Array.isArray(val)) { + } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { if (setup) { setup.addCallback(function (vow, ctx) { return function (val) { From 03d8b71c4d36b1a5e06fe7d1f05bcc10906efe9c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 01:04:06 -0500 Subject: [PATCH 041/409] set setup var to null if no setup --- lib/vows.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index f3dd50fa..cf732fd3 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -174,7 +174,8 @@ vows.tell = function (topic, tests) { (function run(ctx) { if (typeof(ctx.tests["setup"]) === 'function') { setup = ctx.tests.setup.apply(this, ctx.topics); - } + } else { setup = null } + for (var item in ctx.tests) { vow = new(Vow)(ctx.tests[item], ctx.name ? ctx.name + ' ' + item : item); From 6ce224c7e257185a5df3b1b4e04764e5fdba8cd9 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 01:04:21 -0500 Subject: [PATCH 042/409] minor style/comment fixes --- lib/vows.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index cf732fd3..abcd70fa 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -134,7 +134,6 @@ vows.options = { emitter: events.Promise }; -// Run all vows/tests // Run all vows/tests vows.tell = function (topic, tests) { this.options.emitter.prototype.addVow = addVow; @@ -145,12 +144,12 @@ vows.tell = function (topic, tests) { this.tests = vow.callback; this.topics = topics || []; this.name = vow.description || null; - }; + } function Vow(callback, description) { this.callback = callback; this.description = description; - }; + } return process.nextTick(function () { var setup, vow; From dd5d357e8629cf09db53d62ac6eb27d74371ba0f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 01:25:45 -0500 Subject: [PATCH 043/409] refactored runloop --- lib/vows.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index abcd70fa..1cfa9434 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -175,13 +175,11 @@ vows.tell = function (topic, tests) { setup = ctx.tests.setup.apply(this, ctx.topics); } else { setup = null } - for (var item in ctx.tests) { + Object.keys(ctx.tests).filter(function (k) { + return ctx.tests[k] && k !== 'setup'; + }).forEach(function (item) { vow = new(Vow)(ctx.tests[item], ctx.name ? ctx.name + ' ' + item : item); - // Skip setup & prototype attributes - if (! ctx.tests.hasOwnProperty(item) || - ! vow.callback || item === 'setup') continue; - if (vow.callback instanceof Function) { setup.addVow(vow); } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { @@ -196,7 +194,7 @@ vows.tell = function (topic, tests) { run(new(Context)(vow, ctx.topics)); } } - } + }); tryFinish(--vows.remaining); })(new(Context)(new(Vow)(vows.tests, null), [])); } From 8e3df95f10163a7b44400c9ee33536249a4ca5ac Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 17:35:28 -0500 Subject: [PATCH 044/409] support for non-promise return values, by wrapping them in promises --- lib/vows.js | 9 +++++++++ test/vows-test.js | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/lib/vows.js b/lib/vows.js index 1cfa9434..8d1e0467 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -175,6 +175,15 @@ vows.tell = function (topic, tests) { setup = ctx.tests.setup.apply(this, ctx.topics); } else { setup = null } + if (! (setup instanceof vows.options.emitter)) { + var emitter = new(vows.options.emitter); + + process.nextTick(function (val) { + return function () { emitter.emitSuccess(val) }; + }(setup)); + setup = emitter; + } + Object.keys(ctx.tests).filter(function (k) { return ctx.tests[k] && k !== 'setup'; }).forEach(function (item) { diff --git a/test/vows-test.js b/test/vows-test.js index 8a557249..e084e7e9 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -54,5 +54,11 @@ vows.tell("Vows", { } } } + }, + "Non-promise return value": { + setup: function () { return 1 }, + "should be converted to a promise": function (val) { + assert.equal(val, 1); + } } }); From 1e302b1263c5f0c2913cf3c7761eb5583625ceb4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 17:36:29 -0500 Subject: [PATCH 045/409] capitalize emitter, as its a constructor --- lib/vows.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 8d1e0467..38eef1a6 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -131,12 +131,12 @@ function tryFinish(remaining) { // Options vows.options = { - emitter: events.Promise + Emitter: events.Promise }; // Run all vows/tests vows.tell = function (topic, tests) { - this.options.emitter.prototype.addVow = addVow; + this.options.Emitter.prototype.addVow = addVow; this.tests = tests; this.remaining = 0; @@ -175,8 +175,8 @@ vows.tell = function (topic, tests) { setup = ctx.tests.setup.apply(this, ctx.topics); } else { setup = null } - if (! (setup instanceof vows.options.emitter)) { - var emitter = new(vows.options.emitter); + if (! (setup instanceof vows.options.Emitter)) { + var emitter = new(vows.options.Emitter); process.nextTick(function (val) { return function () { emitter.emitSuccess(val) }; From e956d51407a443f75a8aecde778aca7683393131 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 30 Jan 2010 18:51:19 -0500 Subject: [PATCH 046/409] moved the promise creat0r inside the setup check conditional --- lib/vows.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 38eef1a6..05dfb119 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -173,16 +173,15 @@ vows.tell = function (topic, tests) { (function run(ctx) { if (typeof(ctx.tests["setup"]) === 'function') { setup = ctx.tests.setup.apply(this, ctx.topics); - } else { setup = null } - if (! (setup instanceof vows.options.Emitter)) { - var emitter = new(vows.options.Emitter); + if (! (setup instanceof vows.options.Emitter)) { + var emitter = new(vows.options.Emitter); - process.nextTick(function (val) { - return function () { emitter.emitSuccess(val) }; - }(setup)); - setup = emitter; - } + process.nextTick(function (val) { + return function () { emitter.emitSuccess(val) }; + }(setup)); setup = emitter; + } + } else { setup = null } Object.keys(ctx.tests).filter(function (k) { return ctx.tests[k] && k !== 'setup'; From acb4b31dc514eabbaf6dbdd138a911efab3146b4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 31 Jan 2010 13:48:42 -0500 Subject: [PATCH 047/409] style changes --- lib/vows.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 05dfb119..a5b883b1 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -70,15 +70,13 @@ function addVow(/* description & callback */) { // and the description second, so we let the user choose // the order. if (arguments[0] instanceof Function) { - callback = arguments[0]; - desc = arguments[1]; + callback = arguments[0], desc = arguments[1]; } else { - desc = arguments[0]; - callback = arguments[1]; + callback = arguments[1], desc = arguments[0]; } } else if (arguments[0].constructor.name === 'Vow') { - callback = arguments[0].callback; - desc = arguments[0].description; + callback = arguments[0].callback, + desc = arguments[0].description; } else { throw new(Error)("wrong argument type for addVow()"); } From 78c893d9cb8f3a4b433581edccc2e1e5b6b16513 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 31 Jan 2010 13:50:13 -0500 Subject: [PATCH 048/409] pass the previous context to new Contexts, instead of just the list of topics --- lib/vows.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index a5b883b1..fe860bb6 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -138,9 +138,9 @@ vows.tell = function (topic, tests) { this.tests = tests; this.remaining = 0; - function Context(vow, topics) { + function Context(vow, ctx) { this.tests = vow.callback; - this.topics = topics || []; + this.topics = ctx.topics || []; this.name = vow.description || null; } @@ -193,16 +193,16 @@ vows.tell = function (topic, tests) { setup.addCallback(function (vow, ctx) { return function (val) { ctx.topics.unshift(val); - return run(new(Context)(vow, ctx.topics)); + return run(new(Context)(vow, ctx)); }; }(vow, ctx)); } else { - run(new(Context)(vow, ctx.topics)); + run(new(Context)(vow, ctx)); } } }); tryFinish(--vows.remaining); - })(new(Context)(new(Vow)(vows.tests, null), [])); + })(new(Context)(new(Vow)(vows.tests, null), {})); } }); }; From 676a016e0c776d20c380762ced7978e0650d5d0b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 31 Jan 2010 14:38:48 -0500 Subject: [PATCH 049/409] separate contexts from tests, in output --- lib/vows.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index fe860bb6..14dfffb9 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -61,7 +61,7 @@ assert.AssertionError.prototype.toString = function () { // goodness. // function addVow(/* description & callback */) { - var desc, callback; + var desc, callback, context = ''; total++; @@ -76,13 +76,15 @@ function addVow(/* description & callback */) { } } else if (arguments[0].constructor.name === 'Vow') { callback = arguments[0].callback, + context = arguments[0].context, desc = arguments[0].description; } else { throw new(Error)("wrong argument type for addVow()"); } return this.addCallback(function () { - var vow = "- ", exception, topic, msg; + var vow = '- ' + (context ? context + ': ' : ''), + exception, topic, msg; // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. @@ -141,11 +143,13 @@ vows.tell = function (topic, tests) { function Context(vow, ctx) { this.tests = vow.callback; this.topics = ctx.topics || []; - this.name = vow.description || null; + this.name = (ctx.name ? ctx.name + ' ' : '') + + (vow.description || ''); } - function Vow(callback, description) { + function Vow(callback, context, description) { this.callback = callback; + this.context = context; this.description = description; } @@ -184,7 +188,7 @@ vows.tell = function (topic, tests) { Object.keys(ctx.tests).filter(function (k) { return ctx.tests[k] && k !== 'setup'; }).forEach(function (item) { - vow = new(Vow)(ctx.tests[item], ctx.name ? ctx.name + ' ' + item : item); + vow = new(Vow)(ctx.tests[item], ctx.name, item); if (vow.callback instanceof Function) { setup.addVow(vow); @@ -202,7 +206,7 @@ vows.tell = function (topic, tests) { } }); tryFinish(--vows.remaining); - })(new(Context)(new(Vow)(vows.tests, null), {})); + })(new(Context)(new(Vow)(vows.tests, null, null), {})); } }); }; From d8513b2bf67e4cf1a355be42aad9c69d701e4e1d Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 31 Jan 2010 14:48:26 -0500 Subject: [PATCH 050/409] some refactoring to make things cleaner/shorter --- lib/vows.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 14dfffb9..663580ec 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -61,50 +61,48 @@ assert.AssertionError.prototype.toString = function () { // goodness. // function addVow(/* description & callback */) { - var desc, callback, context = ''; + var desc, callback, context = '', vow = {}, args = arguments; total++; - if (arguments.length > 1) { + if (args.length > 1) { // Sometimes it might be nicer to pass the proof first, // and the description second, so we let the user choose // the order. - if (arguments[0] instanceof Function) { - callback = arguments[0], desc = arguments[1]; + if (args[0] instanceof Function) { + vow.callback = args[0], vow.description = args[1]; } else { - callback = arguments[1], desc = arguments[0]; + vow.callback = args[1], vow.description = args[0]; } - } else if (arguments[0].constructor.name === 'Vow') { - callback = arguments[0].callback, - context = arguments[0].context, - desc = arguments[0].description; + } else if (args[0].constructor.name === 'Vow') { + vow = args[0]; } else { throw new(Error)("wrong argument type for addVow()"); } return this.addCallback(function () { - var vow = '- ' + (context ? context + ': ' : ''), + var title = '- ' + (vow.context ? vow.context + ': ' : ''), exception, topic, msg; // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { - callback.apply(null, arguments); - vow += stylize(desc, 'green'); + vow.callback.apply(null, arguments); + title += stylize(vow.description, 'green'); honored++; } catch (e) { if (e.name && e.name.match(/AssertionError/)) { - vow += stylize(desc, 'yellow'); + title += stylize(vow.description, 'yellow'); exception = ' ~ ' + e.toString() + "\n"; broken++; } else { - vow += stylize(desc, 'red'); + title += stylize(desc, 'red'); msg = e.stack || e.message || e.toString() || e; exception = ' ! ' + stylize(msg, 'red') + "\n"; errored++; } } - sys.puts(vow); + sys.puts(title); if (exception) sys.puts(exception); From b3a0dff99123c2509a0a291d327b50bd95a2319a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 2 Feb 2010 11:28:10 -0500 Subject: [PATCH 051/409] catch more file names in stack regex --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 663580ec..d7fe384c 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -35,7 +35,7 @@ vows.macros = require('vows/macros'); assert.AssertionError.prototype.toString = function () { var that = this, - source = this.stack.match(/(\w+\.js)(:\d+):\d+/); + source = this.stack.match(/([a-zA-Z0-9_-]+\.js)(:\d+):\d+/); function parse(str) { return str.replace(/{actual}/g, inspect(that.actual)). From 82eaa81842a8a89b521696e4a5a4a2d1014eab3e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 2 Feb 2010 11:28:49 -0500 Subject: [PATCH 052/409] make a copy of topics, we dont want to share that list, the world is async! --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index d7fe384c..6e9eb03c 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -140,7 +140,7 @@ vows.tell = function (topic, tests) { function Context(vow, ctx) { this.tests = vow.callback; - this.topics = ctx.topics || []; + this.topics = (ctx.topics || []).slice(0); this.name = (ctx.name ? ctx.name + ' ' : '') + (vow.description || ''); } From 93d61f61bd4f9c4abe2fee4ac3badb66bef4e1da Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 4 Feb 2010 18:21:23 -0500 Subject: [PATCH 053/409] added errback support, changed formatting of output to be clearer --- lib/vows.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 6e9eb03c..f8c0a8ac 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -28,6 +28,9 @@ var total = 0, honored = 0, broken = 0, errored = 0, start, end; +// Context stack, used in addVow() to keep track +var lastContext; + // // Assertion Macros // @@ -81,8 +84,7 @@ function addVow(/* description & callback */) { } return this.addCallback(function () { - var title = '- ' + (vow.context ? vow.context + ': ' : ''), - exception, topic, msg; + var title = '- ', exception, topic, msg; // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. @@ -102,12 +104,23 @@ function addVow(/* description & callback */) { errored++; } } - sys.puts(title); + output(title, exception); + }).addErrback(function (err) { + var exception = " * " + stylize('The promise returned an error: ' + + stylize(err, 'bold'), 'red'); + errored++; + output('- ' + stylize(vow.description, 'red'), exception + "\n"); + }); + function output(title, exception) { + if (vow.context && lastContext !== vow.context) { + lastContext = vow.context; + sys.puts(vow.context); + } + sys.puts(title); if (exception) sys.puts(exception); - tryFinish(vows.remaining); - }); + } }; function tryFinish(remaining) { From 9e4b6249aa91971ca6350f194710f90e5ec3778b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 5 Feb 2010 21:58:46 -0500 Subject: [PATCH 054/409] comments! --- lib/vows.js | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index f8c0a8ac..81380c63 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -145,7 +145,9 @@ vows.options = { Emitter: events.Promise }; -// Run all vows/tests +// Run all vows/tests. +// It can take either a function as `tests`, +// or an object literal. vows.tell = function (topic, tests) { this.options.Emitter.prototype.addVow = addVow; this.tests = tests; @@ -164,6 +166,7 @@ vows.tell = function (topic, tests) { this.description = description; } + // We run the tests asynchronously, for added flexibility return process.nextTick(function () { var setup, vow; @@ -176,6 +179,8 @@ vows.tell = function (topic, tests) { if (typeof(vows.tests) === 'function') { return vows.tests.call(null); } else { + // Count the number of vows/promises expected to fire, + // so we know when the tests are over. (function count(tests) { vows.remaining++; Object.keys(tests).forEach(function (key) { @@ -183,10 +188,21 @@ vows.tell = function (topic, tests) { }); })(vows.tests); + // The test runner, it calls itself recursively, passing the + // previous context to the inner contexts. This is so the `setup` + // functions have access to all the previous context topics in their + // arguments list. + // It is defined and invoked at the same time. + // If it encouters a `setup` function, it waits for the returned + // promise to emit (the topic), at which point it runs the functions under it, + // passing the topic as an argument. (function run(ctx) { if (typeof(ctx.tests["setup"]) === 'function') { + // Run the setup, passing the previous context topics setup = ctx.tests.setup.apply(this, ctx.topics); + // If the setup doesn't return an event emitter (such as a promise), + // we create it ourselves, and emit the value on the next tick. if (! (setup instanceof vows.options.Emitter)) { var emitter = new(vows.options.Emitter); @@ -196,17 +212,30 @@ vows.tell = function (topic, tests) { } } else { setup = null } + // Now run the tests, or sub-contexts Object.keys(ctx.tests).filter(function (k) { return ctx.tests[k] && k !== 'setup'; }).forEach(function (item) { + // Holds the current test or context vow = new(Vow)(ctx.tests[item], ctx.name, item); + // If we encounter a function, add it to the callbacks + // of the `setup` function, so it'll get called once the + // setup fires. + // If we encounter an object literal, we recurse, sending it + // our current context. if (vow.callback instanceof Function) { setup.addVow(vow); } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { + // 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 + // synchronously. if (setup) { setup.addCallback(function (vow, ctx) { return function (val) { + // Once the setup fires, add the return value + // to the beginning of the topics list, so it + // becomes the first argument for the next setup. ctx.topics.unshift(val); return run(new(Context)(vow, ctx)); }; @@ -216,7 +245,9 @@ vows.tell = function (topic, tests) { } } }); + // Check if we're done running the tests tryFinish(--vows.remaining); + // This is our initial, empty context })(new(Context)(new(Vow)(vows.tests, null, null), {})); } }); From 9ff22f05abde25e1aadef9513a7e5b04f7f6d34b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 5 Feb 2010 22:07:43 -0500 Subject: [PATCH 055/409] fix to tell()s argument processing --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 81380c63..9ad2f50c 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -171,7 +171,7 @@ vows.tell = function (topic, tests) { var setup, vow; if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); - else if (topic instanceof Object) tests = topic; + else if (topic instanceof Object || topic instanceof Function) vows.tests = topic; else throw "tell() takes a topic and an Object"; start = new(Date); From 8faa84f8bf52ee435ebce32b31cca4e2056d719a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 12 Feb 2010 16:36:43 -0500 Subject: [PATCH 056/409] duck type the Emitter, instead of checking the instance, for added flexibility --- lib/vows.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 9ad2f50c..1603cab3 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -149,7 +149,8 @@ vows.options = { // It can take either a function as `tests`, // or an object literal. vows.tell = function (topic, tests) { - this.options.Emitter.prototype.addVow = addVow; + this.options.Emitter.prototype.addVow = + events.Promise.prototype.addVow = addVow; this.tests = tests; this.remaining = 0; @@ -203,8 +204,8 @@ vows.tell = function (topic, tests) { // If the setup doesn't return an event emitter (such as a promise), // we create it ourselves, and emit the value on the next tick. - if (! (setup instanceof vows.options.Emitter)) { - var emitter = new(vows.options.Emitter); + if (setup === undefined || typeof(setup.addCallback) !== 'function') { + var emitter = new(events.Promise); process.nextTick(function (val) { return function () { emitter.emitSuccess(val) }; From f4cca0517a13cf0581daccaf34f8a9b456bb6af7 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 16 Feb 2010 14:12:13 -0500 Subject: [PATCH 057/409] makefile init --- makefile | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 makefile diff --git a/makefile b/makefile new file mode 100644 index 00000000..a4168158 --- /dev/null +++ b/makefile @@ -0,0 +1,8 @@ +# +# Run all tests +# +test: + node test/vows-test.js + node test/addvow-test.js + +.PHONY: test From d74919155abee6cfd7fd97a502a2808e1b580f52 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 16 Feb 2010 14:12:22 -0500 Subject: [PATCH 058/409] test for addVow() --- test/addvow-test.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 test/addvow-test.js diff --git a/test/addvow-test.js b/test/addvow-test.js new file mode 100644 index 00000000..7bf75a34 --- /dev/null +++ b/test/addvow-test.js @@ -0,0 +1,22 @@ +var path = require('path'); + +require.paths.unshift(path.join(path.dirname(__filename), '..', 'lib')); + +var events = require('events'), + assert = require('assert'); +var vows = require('vows'); + +var promiser = function (val) { + return function () { + var promise = new(events.Promise); + process.nextTick(function () { promise.emitSuccess(val) }); + return promise; + } +}; + +vows.tell("Vows:unit", function () { + promiser("hello world")().addVow(function (val) { + assert.equal(val, "hello world"); + }, "addVow()"); +}); + From e51aeb1c0ad551ab050bbde796f641c3552d13af Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 21 Feb 2010 20:53:26 -0500 Subject: [PATCH 059/409] move from Promises to EventEmitter --- lib/vows.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 9ad2f50c..3fddbcc7 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -83,7 +83,7 @@ function addVow(/* description & callback */) { throw new(Error)("wrong argument type for addVow()"); } - return this.addCallback(function () { + return this.addListener("success", function () { var title = '- ', exception, topic, msg; // Run the test, and try to catch `AssertionError`s and other exceptions; @@ -105,7 +105,7 @@ function addVow(/* description & callback */) { } } output(title, exception); - }).addErrback(function (err) { + }).addListener("error", function (err) { var exception = " * " + stylize('The promise returned an error: ' + stylize(err, 'bold'), 'red'); errored++; @@ -207,7 +207,7 @@ vows.tell = function (topic, tests) { var emitter = new(vows.options.Emitter); process.nextTick(function (val) { - return function () { emitter.emitSuccess(val) }; + return function () { emitter.emit("success", val) }; }(setup)); setup = emitter; } } else { setup = null } @@ -231,7 +231,7 @@ vows.tell = function (topic, tests) { // before calling the inner context. Else, just run the inner context // synchronously. if (setup) { - setup.addCallback(function (vow, ctx) { + setup.addListener("success", function (vow, ctx) { return function (val) { // Once the setup fires, add the return value // to the beginning of the topics list, so it From 50f55a164e20bd50a814491c78f76914907db3e7 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 21 Feb 2010 20:54:19 -0500 Subject: [PATCH 060/409] fixed exceptions not showing the vow title they occured in --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 3fddbcc7..d3ae1c83 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -98,7 +98,7 @@ function addVow(/* description & callback */) { exception = ' ~ ' + e.toString() + "\n"; broken++; } else { - title += stylize(desc, 'red'); + title += stylize(vow.description, 'red'); msg = e.stack || e.message || e.toString() || e; exception = ' ! ' + stylize(msg, 'red') + "\n"; errored++; From 6d6133e27c7abddb7c318cbe1f80fff90244ecdc Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 21 Feb 2010 20:54:41 -0500 Subject: [PATCH 061/409] Emitter is now events.EventEmitter --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index d3ae1c83..26f0e2e4 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -142,7 +142,7 @@ function tryFinish(remaining) { // Options vows.options = { - Emitter: events.Promise + Emitter: events.EventEmitter }; // Run all vows/tests. From ce6bfb15f8317f82dedd23f3655a3f61bf596f27 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 21 Feb 2010 20:55:39 -0500 Subject: [PATCH 062/409] fixed tests to use EventEmitter --- test/vows-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/vows-test.js b/test/vows-test.js index e084e7e9..e6aae96c 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -8,8 +8,8 @@ var vows = require('vows'); var promiser = function (val) { return function () { - var promise = new(events.Promise); - process.nextTick(function () { promise.emitSuccess(val) }); + var promise = new(events.EventEmitter); + process.nextTick(function () { promise.emit('success', val) }); return promise; } }; From 96a1ee6ab07516bc996fb23ce8608f47efe55196 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 21 Feb 2010 20:57:41 -0500 Subject: [PATCH 063/409] remove Promise --- lib/vows.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 80e15c85..b08df1ff 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -149,8 +149,7 @@ vows.options = { // It can take either a function as `tests`, // or an object literal. vows.tell = function (topic, tests) { - this.options.Emitter.prototype.addVow = - events.Promise.prototype.addVow = addVow; + this.options.Emitter.prototype.addVow = addVow; this.tests = tests; this.remaining = 0; From 9bd0c84055695e4d1e45fb42e6e98ff5053d2d4c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 21 Feb 2010 20:59:17 -0500 Subject: [PATCH 064/409] reverted to HEAD^ --- lib/vows.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index b08df1ff..26f0e2e4 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -203,8 +203,8 @@ vows.tell = function (topic, tests) { // If the setup doesn't return an event emitter (such as a promise), // we create it ourselves, and emit the value on the next tick. - if (setup === undefined || typeof(setup.addCallback) !== 'function') { - var emitter = new(events.Promise); + if (! (setup instanceof vows.options.Emitter)) { + var emitter = new(vows.options.Emitter); process.nextTick(function (val) { return function () { emitter.emit("success", val) }; From ef65176d0fa6b4ec9dc4656cb00ecc1e95eb80d9 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 22 Feb 2010 18:02:07 -0500 Subject: [PATCH 065/409] started working on readme --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cb0ac2f7..0b386c23 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,17 @@ Vows ==== -asynchronous promise-based testing for node.js +asynchronous testing for node.js + +introduction +------------ +There are two reasons why we might want asynchronous testing. The first, and obvious reason is that node.js is asynchronous, and therefore our tests need to be. The second reason is to make test suites which target I/O libraries run much faster. + +_Vows_ is an experiment in making this possible, while adding a minimum of overhead. + +philosophy +---------- + synopsis -------- @@ -15,9 +25,43 @@ synopsis }, 'it should know the answer to the ultimate question of life'); }); -In the example above, `question()` would be a function which returns a _promise_. +In the example above, `question()` would be a function which returns an `EventEmitter`. When the `"success"` event is emitted, the function passed to `addVow` is run, and the results output to the console. Vows are run as soon as the promise completes, so the order in which they are run is undefined. +writing specs +------------- + + vows.tell('A Database library', { + // run this once, and execute the following tests when it completes + setup: function () { return new(DB) }, + + 'set() should store a k/v pair': { + // the inner context gets the return values of the outer contexts + // passed as arguments. Here, `db` is new(DB). + setup: function (db) { return db.set('pulp', 'orange') }, + + // `res` is the value emitted by the above `db.set` + 'and return OK': function (res) { + assert.equal(res, "OK"); + }, + 'and when checked for existence': { + // here, we need to access `db`, from the parent context. + // It's passed as the 2nd argument to `setup`, we discard the first, + // which would have been the above `res`. + setup: function (_, db) { return db.exists('pulp') }, + + 'return true': function (re) { + assert.equal(re, true); + } + } + }, + 'get()': { + setup: function (db) { return db.get('dream') }, + 'should return the stored value': function (res) { + assert.equal(res, 'catcher'); + } + } + }); From d44f3e9a86edccc62f56d9225b399ac96e543c96 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 25 Feb 2010 10:57:20 -0500 Subject: [PATCH 066/409] use __dirname --- test/addvow-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/addvow-test.js b/test/addvow-test.js index 7bf75a34..405d33ca 100644 --- a/test/addvow-test.js +++ b/test/addvow-test.js @@ -1,6 +1,6 @@ var path = require('path'); -require.paths.unshift(path.join(path.dirname(__filename), '..', 'lib')); +require.paths.unshift(path.join(__dirname, '..', 'lib')); var events = require('events'), assert = require('assert'); From 3db6aa8edfb075bef62faaa86d2975ea2426f173 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 25 Feb 2010 10:57:29 -0500 Subject: [PATCH 067/409] use EventEmitter instead of Promise --- test/addvow-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/addvow-test.js b/test/addvow-test.js index 405d33ca..539dd88d 100644 --- a/test/addvow-test.js +++ b/test/addvow-test.js @@ -8,8 +8,8 @@ var vows = require('vows'); var promiser = function (val) { return function () { - var promise = new(events.Promise); - process.nextTick(function () { promise.emitSuccess(val) }); + var promise = new(events.EventEmitter); + process.nextTick(function () { promise.emit('success', val) }); return promise; } }; From bb9a6a8b04cecc5cbea582daf2d2a740261bee4b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 1 Mar 2010 22:06:10 -0500 Subject: [PATCH 068/409] screenshot --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0b386c23..f2b07045 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ There are two reasons why we might want asynchronous testing. The first, and obv _Vows_ is an experiment in making this possible, while adding a minimum of overhead. +![vows-ss](http://dl.dropbox.com/u/251849/vows-ss.gif) + philosophy ---------- From 12eead62c86c6e31e94005d7873db8686ad575f8 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 3 Mar 2010 22:15:22 -0500 Subject: [PATCH 069/409] cleaned up readme --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index f2b07045..4939dc51 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,6 @@ _Vows_ is an experiment in making this possible, while adding a minimum of overh ![vows-ss](http://dl.dropbox.com/u/251849/vows-ss.gif) -philosophy ----------- - - synopsis -------- From b6f245c2295b8058044816657abf4b1994f515d6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 3 Apr 2010 14:55:06 -0400 Subject: [PATCH 070/409] remove process.mixin --- lib/vows.js | 2 +- lib/vows/macros.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 26f0e2e4..534a3bd4 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -256,7 +256,7 @@ vows.tell = function (topic, tests) { // Return the `vows` object after setting some options vows.config = function (opts) { - process.mixin(this.options, opts); + for (var k in opts) { this.options[k] = opts[k] } return this; }; diff --git a/lib/vows/macros.js b/lib/vows/macros.js index 43dfdfce..7f89aabe 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -42,9 +42,6 @@ assert.include = function (actual, expected, message) { }; assert.includes = assert.include; -// CommonJS Export -process.mixin(exports, assert); - // // Utility functions // From a3261c11d37f2bc08b1b63a83f7795c4cfd9e4ca Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 3 Apr 2010 16:43:20 -0400 Subject: [PATCH 071/409] fix makefile, so it doesn't output commands --- makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/makefile b/makefile index a4168158..8fdbaf04 100644 --- a/makefile +++ b/makefile @@ -2,7 +2,7 @@ # Run all tests # test: - node test/vows-test.js - node test/addvow-test.js + @@node test/vows-test.js + @@node test/addvow-test.js .PHONY: test From a1552791d0b37ed76f8bd6efadb009d1e5c6f9f1 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 3 Apr 2010 16:43:51 -0400 Subject: [PATCH 072/409] vows.prepare() -- wrap a Node.js style async function in an EventEmitter --- lib/vows.js | 27 +++++++++++++++++++++++++++ test/vows-test.js | 21 +++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/lib/vows.js b/lib/vows.js index 534a3bd4..8272b411 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -140,6 +140,33 @@ function tryFinish(remaining) { } } +// +// Wrap a Node.js style async function into an EventEmmitter +// +vows.prepare = function (obj, targets) { + targets.forEach(function (target) { + if (target in obj) { + obj[target] = (function (fun) { + return function () { + var args = Array.prototype.slice.call(arguments); + var ee = new(events.EventEmitter); + + args.push(function (err /* [, data] */) { + var args = Array.prototype.slice.call(arguments, 1); + + if (err) { ee.emit('error', err) } + else { ee.emit.apply(ee, ['success'].concat(args)) } + }); + fun.apply(obj, args); + + return ee; + }; + })(obj[target]); + } + }); + return obj; +}; + // Options vows.options = { Emitter: events.EventEmitter diff --git a/test/vows-test.js b/test/vows-test.js index e6aae96c..6fe8ede7 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -6,6 +6,13 @@ var events = require('events'), assert = require('assert'); var vows = require('vows'); +var api = vows.prepare({ + get: function (id, callback) { + process.nextTick(function () { callback(null, id) }); + }, + version: function () { return '1.0' } +}, ['get']); + var promiser = function (val) { return function () { var promise = new(events.EventEmitter); @@ -60,5 +67,19 @@ vows.tell("Vows", { "should be converted to a promise": function (val) { assert.equal(val, 1); } + }, + "A 'prepared' interface": { + "with a wrapped function": { + setup: function () { return api.get(42) }, + "should work as expected": function (val) { + assert.equal(val, 42); + } + }, + "with a non-wrapped function": { + setup: function () { return api.version() }, + "should work as expected": function (val) { + assert.equal(val, '1.0'); + } + } } }); From 56bc4168fb43c927b4908b638559bdbbf3e6f190 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 3 Apr 2010 16:44:21 -0400 Subject: [PATCH 073/409] use the new __dirname --- test/vows-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/vows-test.js b/test/vows-test.js index 6fe8ede7..3582a14e 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -1,6 +1,6 @@ var path = require('path'); -require.paths.unshift(path.join(path.dirname(__filename), '..', 'lib')); +require.paths.unshift(path.join(__dirname, '..', 'lib')); var events = require('events'), assert = require('assert'); From dd26b48dce60d2cc5c60bcfdbf8487b3cc9a54f1 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 14 Apr 2010 16:11:53 -0400 Subject: [PATCH 074/409] update eyes.js --- lib/vendor/eyes | 1 + lib/vendor/eyes.js | 166 --------------------------------------------- 2 files changed, 1 insertion(+), 166 deletions(-) create mode 160000 lib/vendor/eyes delete mode 100644 lib/vendor/eyes.js diff --git a/lib/vendor/eyes b/lib/vendor/eyes new file mode 160000 index 00000000..5faabc10 --- /dev/null +++ b/lib/vendor/eyes @@ -0,0 +1 @@ +Subproject commit 5faabc1072605d0434f9c46443535cdb41268f7b diff --git a/lib/vendor/eyes.js b/lib/vendor/eyes.js deleted file mode 100644 index 753f6a98..00000000 --- a/lib/vendor/eyes.js +++ /dev/null @@ -1,166 +0,0 @@ -// -// Eyes.js - a customizable value inspector for Node.js -// -// usage: -// -// var inspect = require('eyes').inspector({styles: {all: 'magenta'}}); -// inspect(something); // inspect with the settings passed to `inspector` -// -// or -// -// var eyes = require('eyes'); -// eyes.inspect(something); // inspect with the default settings -// -var eyes = { - defaults: { - styles: { // Styles applied to stdout - all: 'cyan', // Overall style applied to everything - label: 'underline', // Inspection labels, like 'array' in `array: [1, 2, 3]` - other: 'inverted', // Objects which don't have a literal representation, such as functions - key: 'bold', // The keys in object literals, like 'a' in `{a: 1}` - special: null, // null, undefined... - string: null, - number: null, - bool: null - }, - hideFunctions: false, - writer: process.stdio.write, - maxLength: 2048 // Truncate output if longer - }, - - // Return a curried inspect() function, with the `options` argument filled in. - inspector: function (options) { - var that = this; - return function (obj, label, opts) { - return that.inspect(obj, label, - process.mixin(true, {}, options || {}, opts || {})); - }; - }, - - // If we have a `writer` defined, use it to print a styled string, - // if not, we just return the stringified object with no styling. - inspect: function (obj, label, options) { - options = process.mixin(true, {}, this.defaults, options || {}); - if (options.writer) { - return this.print(this.stringify(obj, options), label, options); - } else { - options.styles = {}; - return this.stringify(obj, options); - } - }, - - // Output using the 'writer', and an optional label - // Loop through `str`, and truncate it after `options.maxLength` has been reached. - // Because escape sequences are, at this point embeded within - // the output string, we can't measure the length of the string - // in a useful way, without separating what is an escape sequence, - // versus a printable character (`c`). So we resort to counting the - // length manually. - print: function (str, label, options) { - for (var c = 0, i = 0; i < str.length; i++) { - if (str.charAt(i) === '\033') { i += 4 } // `4` because '\033[25m'.length + 1 == 5 - else if (c === options.maxLength) { - str = str.slice(0, i - 1) + '…'; - break; - } else { c++ } - } - return options.writer((label ? - this.stylize(label, options.styles.label, options.styles) + ': ' : '') + - this.stylize(str, options.styles.all, options.styles) + '\033[0m' + "\n"); - }, - - // Convert any object to a string, ready for output. - // When an 'array' or an 'object' are encountered, they are - // passed to specialized functions, which can then recursively call - // stringify(). - stringify: function (obj, options) { - var that = this, stylize = function (str, style) { - return that.stylize(str, options.styles[style], options.styles) - }; - - switch (typeOf(obj)) { - case "string": - obj = (obj.length === 1 ? "'" + obj + "'" : '"' + obj + '"') - .replace(/\n/g, '\\n'); - return stylize(obj, 'string'); - case "regexp" : return stylize('/' + obj.source + '/', 'regexp'); - case "number" : return stylize(obj + '', 'number'); - case "function" : return options.writer ? stylize("Function", 'other') : '[Function]'; - case "null" : return stylize("null", 'special'); - case "undefined": return stylize("undefined", 'special'); - case "boolean" : return stylize(obj + '', 'bool'); - case "date" : return stylize(obj.toString()); - case "array" : return this.stringifyArray(obj, options); - case "object" : return this.stringifyObject(obj, options); - } - }, - - // Convert an array to a string, such as [1, 2, 3]. - // This function calls stringify() for each of the elements - // in the array. - stringifyArray: function (ary, options) { - var out = []; - - for (var i = 0; i < ary.length; i++) { - out.push(this.stringify(ary[i], options)); - } - return '[' + out.join(', ') + ']'; - }, - - // Convert an object to a string, such as {a: 1}. - // This function calls stringify() for each of its values, - // and does not output functions or prototype values. - stringifyObject: function (obj, options) { - var out = []; - - for (var k in obj) { - if (obj.hasOwnProperty(k) && !(obj[k] instanceof Function && options.hideFunctions)) { - out.push(this.stylize(k, options.styles.key, options.styles) + ': ' + - this.stringify(obj[k], options)); - } - } - return "{" + out.join(', ') + "}"; - }, - - // Apply a style to a string, eventually, - // I'd like this to support passing multiple - // styles. - stylize: function (str, style, styles) { - var codes = { - 'bold' : [1, 22], - 'underline' : [4, 24], - 'inverse' : [7, 27], - 'cyan' : [36, 39], - 'magenta' : [35, 39], - 'yellow' : [33, 39], - 'green' : [32, 39], - 'red' : [31, 39] - }, endCode; - - if (style && codes[style]) { - endCode = (codes[style][1] === 39 && styles.all) ? codes[styles.all][0] - : codes[style][1]; - return '\033[' + codes[style][0] + 'm' + str + - '\033[' + endCode + 'm'; - } else { return str } - } -}; - -// CommonJS module support -process.mixin(exports, eyes); - -// A better `typeof` -function typeOf(value) { - var s = typeof(value), - types = [Object, Array, String, RegExp, Number, Function, Boolean, Date]; - - if (s === 'object' || s === 'function') { - if (value) { - types.forEach(function (t) { - if (value instanceof t) { s = t.name.toLowerCase() } - }); - } else { s = 'null' } - } - return s; -} - From ba90a71ba7a13b571de2c823d7323c22171078f4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 14 Apr 2010 19:17:30 -0400 Subject: [PATCH 075/409] update eyes --- lib/vendor/eyes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vendor/eyes b/lib/vendor/eyes index 5faabc10..7aed7165 160000 --- a/lib/vendor/eyes +++ b/lib/vendor/eyes @@ -1 +1 @@ -Subproject commit 5faabc1072605d0434f9c46443535cdb41268f7b +Subproject commit 7aed7165cbe591310c6b00b48111018cf66c00d6 From d83b203159c54ee6ece1b9f9cab1354a77e13d95 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 16 Apr 2010 00:03:52 -0400 Subject: [PATCH 076/409] new eyes.js --- lib/vendor/eyes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vendor/eyes b/lib/vendor/eyes index 7aed7165..d13426e1 160000 --- a/lib/vendor/eyes +++ b/lib/vendor/eyes @@ -1 +1 @@ -Subproject commit 7aed7165cbe591310c6b00b48111018cf66c00d6 +Subproject commit d13426e1794a49f9042296327f7ded9bfb540575 From 1b02c0acf7ba3a2c90d6b9d87fe9ee147e7ba946 Mon Sep 17 00:00:00 2001 From: Matt Lyon Date: Sat, 10 Apr 2010 16:05:00 +0800 Subject: [PATCH 077/409] bugfix: only add setup vals to context once see test case, this was failing before because it was overwriting the previous context value. --- lib/vows.js | 14 +++++++++----- test/vows-test.js | 8 ++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 8272b411..ea206a76 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -220,10 +220,11 @@ vows.tell = function (topic, tests) { // functions have access to all the previous context topics in their // arguments list. // It is defined and invoked at the same time. - // If it encouters a `setup` function, it waits for the returned + // If it encounters a `setup` function, it waits for the returned // promise to emit (the topic), at which point it runs the functions under it, // passing the topic as an argument. (function run(ctx) { + var ctxAdded = false; if (typeof(ctx.tests["setup"]) === 'function') { // Run the setup, passing the previous context topics setup = ctx.tests.setup.apply(this, ctx.topics); @@ -237,6 +238,13 @@ vows.tell = function (topic, tests) { return function () { emitter.emit("success", val) }; }(setup)); setup = emitter; } + + setup.addListener('success', function (val) { + // Once the setup fires, add the return value + // to the beginning of the topics list, so it + // becomes the first argument for the next setup. + ctx.topics.unshift(val); + }); } else { setup = null } // Now run the tests, or sub-contexts @@ -260,10 +268,6 @@ vows.tell = function (topic, tests) { if (setup) { setup.addListener("success", function (vow, ctx) { return function (val) { - // Once the setup fires, add the return value - // to the beginning of the topics list, so it - // becomes the first argument for the next setup. - ctx.topics.unshift(val); return run(new(Context)(vow, ctx)); }; }(vow, ctx)); diff --git a/test/vows-test.js b/test/vows-test.js index 3582a14e..9f73a7da 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -58,6 +58,14 @@ vows.tell("Vows", { "the parent topics": function (topics) { assert.equal(topics.join(), [4, 3, 2, 1].join()); } + }, + + "from": { + setup: function (c, b, a) { return promiser([4, c, b, a])() }, + + "the parent topics": function(topics) { + assert.equal(topics.join(), [4, 3, 2, 1].join()); + } } } } From 50f76f817e92e5ea5918cd9ab406844f1e301db7 Mon Sep 17 00:00:00 2001 From: Matt Lyon Date: Fri, 16 Apr 2010 10:48:28 +0800 Subject: [PATCH 078/409] other repos should be submodules --- .gitmodules | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..72f4057b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/vendor/eyes"] + path = lib/vendor/eyes + url = git://github.com/cloudhead/eyes.js From a4915ef9596e9aaa63ac846ede53f745e52730a2 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 29 Apr 2010 13:37:33 -0400 Subject: [PATCH 079/409] Change failed test line number information Use grey, comment style. Also a minor tweak in output. --- lib/vows.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 8272b411..290cd41e 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -47,8 +47,8 @@ assert.AssertionError.prototype.toString = function () { } if (this.message) { - return stylize(parse(this.message) + " @ " + - stylize(source[1], 'bold') + source[2], 'yellow'); + return stylize(parse(this.message), 'yellow') + + stylize(' // ' + source[1] + source[2], 'grey'); } else { return stylize([ this.expected, @@ -84,7 +84,7 @@ function addVow(/* description & callback */) { } return this.addListener("success", function () { - var title = '- ', exception, topic, msg; + var title = ' - ', exception, topic, msg; // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. @@ -302,7 +302,8 @@ function stylize(str, style) { 'underline' : [4, 24], 'yellow' : [33, 39], 'green' : [32, 39], - 'red' : [31, 39] + 'red' : [31, 39], + 'grey' : [90, 39] }; return '\033[' + styles[style][0] + 'm' + str + '\033[' + styles[style][1] + 'm'; From e69c16c0440680505cf01a2b236ce2f2f2b95f79 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 29 Apr 2010 13:40:08 -0400 Subject: [PATCH 080/409] Ability to only run tests matching a given string --- lib/vows.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 290cd41e..3c3e7630 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -180,6 +180,8 @@ vows.tell = function (topic, tests) { this.tests = tests; this.remaining = 0; + var matcher = new(RegExp)(process.argv[2] || '.*'); + function Context(vow, ctx) { this.tests = vow.callback; this.topics = (ctx.topics || []).slice(0); @@ -208,11 +210,21 @@ vows.tell = function (topic, tests) { } else { // Count the number of vows/promises expected to fire, // so we know when the tests are over. + // We match the keys against `matcher`, to decide + // whether or not they should be included in the test. (function count(tests) { + var match = false; vows.remaining++; Object.keys(tests).forEach(function (key) { - if (! (tests[key] instanceof Function)) count(tests[key]); + if (typeof(tests[key]) !== "function") { + if (! (match = count(tests[key]) || + match || matcher.test(key))) { + delete tests[key]; + vows.remaining--; + } + } }); + return match; })(vows.tests); // The test runner, it calls itself recursively, passing the From 1d14683b1a76daf30d88bb4ec649cdebcdb466a7 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:37:03 -0400 Subject: [PATCH 081/409] Buffer test output, return EventEmitter. --- lib/vows.js | 22 ++++++++++++++++++++-- test/vows-test.js | 4 +++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 49f0c33d..be6d8322 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -31,6 +31,9 @@ var total = 0, honored = 0, // Context stack, used in addVow() to keep track var lastContext; +// Output buffer +var buffer; + // // Assertion Macros // @@ -123,6 +126,15 @@ function addVow(/* description & callback */) { } }; +function puts() { + var args = Array.prototype.slice.call(arguments); + if (vows.promise.listeners('end').length > 0) { + buffer.push(args.join('\n')); + } else { + sys.puts.apply(null, args); + } +} + function tryFinish(remaining) { // Output results once all the vows have been checked if (honored + broken + errored === total && remaining === 0) { @@ -180,7 +192,12 @@ vows.tell = function (topic, tests) { this.tests = tests; this.remaining = 0; - var matcher = new(RegExp)(process.argv[2] || '.*'); + // Reset values + total = 0, honored = 0, + broken = 0, errored = 0; + buffer = []; + + this.promise = new(events.EventEmitter); function Context(vow, ctx) { this.tests = vow.callback; @@ -196,7 +213,7 @@ vows.tell = function (topic, tests) { } // We run the tests asynchronously, for added flexibility - return process.nextTick(function () { + process.nextTick(function () { var setup, vow; if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); @@ -294,6 +311,7 @@ vows.tell = function (topic, tests) { })(new(Context)(new(Vow)(vows.tests, null, null), {})); } }); + return this.promise; }; diff --git a/test/vows-test.js b/test/vows-test.js index 9f73a7da..b31b938e 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -16,12 +16,14 @@ var api = vows.prepare({ var promiser = function (val) { return function () { var promise = new(events.EventEmitter); + setTimeout(function () { process.nextTick(function () { promise.emit('success', val) }); + }, 200); return promise; } }; -vows.tell("Vows", { +return vows.tell("Vows", { "A context": { setup: promiser("hello world"), From 70ef3a6e83bf29729a2f6a9f537ce397dafe2f7c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:38:37 -0400 Subject: [PATCH 082/409] whitespace --- lib/vows.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index be6d8322..7bc98805 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -2,7 +2,7 @@ // Vows.js - asynchronous promise-based testing for node.js // // usage: -// +// // var vows = require('vows'), // assert = require('assert'); // @@ -14,7 +14,7 @@ // var path = require('path'); -require.paths.unshift(path.join(path.dirname(__filename), 'vendor'), +require.paths.unshift(path.join(path.dirname(__filename), 'vendor'), path.dirname(__filename)); var sys = require('sys'), @@ -28,7 +28,7 @@ var total = 0, honored = 0, broken = 0, errored = 0, start, end; -// Context stack, used in addVow() to keep track +// Context stack, used in addVow() to keep track var lastContext; // Output buffer @@ -78,7 +78,7 @@ function addVow(/* description & callback */) { if (args[0] instanceof Function) { vow.callback = args[0], vow.description = args[1]; } else { - vow.callback = args[1], vow.description = args[0]; + vow.callback = args[1], vow.description = args[0]; } } else if (args[0].constructor.name === 'Vow') { vow = args[0]; @@ -300,7 +300,7 @@ vows.tell = function (topic, tests) { return run(new(Context)(vow, ctx)); }; }(vow, ctx)); - } else { + } else { run(new(Context)(vow, ctx)); } } From 8b2afda439099d32463d9b5977465bffe481bd96 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:39:36 -0400 Subject: [PATCH 083/409] don't use a Vow object, just use Object.create() --- lib/vows.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 7bc98805..a8b64af8 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -80,7 +80,7 @@ function addVow(/* description & callback */) { } else { vow.callback = args[1], vow.description = args[0]; } - } else if (args[0].constructor.name === 'Vow') { + } else if (args[0].callback && args[0].context) { vow = args[0]; } else { throw new(Error)("wrong argument type for addVow()"); @@ -281,7 +281,7 @@ vows.tell = function (topic, tests) { return ctx.tests[k] && k !== 'setup'; }).forEach(function (item) { // Holds the current test or context - vow = new(Vow)(ctx.tests[item], ctx.name, item); + vow = Object.create({callback:ctx.tests[item], context:ctx.name, description:item}); // If we encounter a function, add it to the callbacks // of the `setup` function, so it'll get called once the @@ -308,7 +308,7 @@ vows.tell = function (topic, tests) { // Check if we're done running the tests tryFinish(--vows.remaining); // This is our initial, empty context - })(new(Context)(new(Vow)(vows.tests, null, null), {})); + })(new(Context)({callback:vows.tests, context:null, description:null}, {})); } }); return this.promise; From 6d6c950f10b5903775ac51ff619bad23b38196ae Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:41:08 -0400 Subject: [PATCH 084/409] changed spacing in test output --- lib/vows.js | 6 +++--- lib/vows/macros.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index a8b64af8..c70b509a 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -98,12 +98,12 @@ function addVow(/* description & callback */) { } catch (e) { if (e.name && e.name.match(/AssertionError/)) { title += stylize(vow.description, 'yellow'); - exception = ' ~ ' + e.toString() + "\n"; + exception = ' ~ ' + e.toString(); broken++; } else { title += stylize(vow.description, 'red'); - msg = e.stack || e.message || e.toString() || e; - exception = ' ! ' + stylize(msg, 'red') + "\n"; + msg = e.stack || e.message || e.toString() || e; + exception = ' ! ' + stylize(msg, 'red'); errored++; } } diff --git a/lib/vows/macros.js b/lib/vows/macros.js index 7f89aabe..46af8089 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -1,7 +1,7 @@ var assert = require('assert'); var messages = { - 'equal' : "expected {expected}, got {actual} ({operator})", + 'equal' : "expected {expected},\n got {actual} ({operator})", 'notEqual' : "didn't expect {actual} ({operator})", 'throws' : "expected {expected} to be thrown", 'doesNotThrow': "didn't expect {actual} to be thrown", From d4d7e3ee7d2555d54af196e8444bc8a30674416a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:42:00 -0400 Subject: [PATCH 085/409] the --brief option. Also fixed various buffering problems --- lib/vows.js | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index c70b509a..32d3e1b3 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -116,12 +116,14 @@ function addVow(/* description & callback */) { }); function output(title, exception) { - if (vow.context && lastContext !== vow.context) { - lastContext = vow.context; - sys.puts(vow.context); + if (exception || !vows.options.brief) { + if (vow.context && lastContext !== vow.context) { + lastContext = vow.context; + puts(vow.context); + } + puts(title); + if (exception) puts(exception); } - sys.puts(title); - if (exception) sys.puts(exception); tryFinish(vows.remaining); } }; @@ -145,10 +147,18 @@ function tryFinish(remaining) { style = honored === total ? ('green') : (errored === 0 ? 'yellow' : 'red'); - sys.puts("\nVerified " + total + " vows in " + - (((new(Date)) - start) / 1000) + " seconds."); + if (!vows.options.brief) { + puts("\nVerified " + total + " vows in " + + (((new(Date)) - start) / 1000) + " seconds."); + puts("\n" + stylize(result, style)); + } + + vows.promise.emit("end", honored, broken, errored); + if (broken || errored) { sys.puts(buffer.join('\n') + '\n') } - sys.puts("\n" + stylize(result, style)); + process.stdout.addListener('drain', function () { + process.exit(broken || errored ? 1 : 0); + }); } } @@ -181,7 +191,8 @@ vows.prepare = function (obj, targets) { // Options vows.options = { - Emitter: events.EventEmitter + Emitter: events.EventEmitter, + brief: false }; // Run all vows/tests. @@ -206,19 +217,17 @@ vows.tell = function (topic, tests) { (vow.description || ''); } - function Vow(callback, context, description) { - this.callback = callback; - this.context = context; - this.description = description; - } - // We run the tests asynchronously, for added flexibility process.nextTick(function () { var setup, vow; - if (typeof topic === 'string' && tests) sys.puts('\n' + stylize(topic, 'underline') + '\n'); - else if (topic instanceof Object || topic instanceof Function) vows.tests = topic; - else throw "tell() takes a topic and an Object"; + if (typeof(topic) === 'string' && tests) { + if (!vows.options.brief) { + puts('\n' + stylize(topic, 'underline') + '\n'); + } + } else if (topic instanceof Object || topic instanceof Function) { + vows.tests = topic; + } else { throw "tell() takes a topic and an Object" } start = new(Date); From 3db9c6ac8317ada69167a780940944f3c57fbf45 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:42:27 -0400 Subject: [PATCH 086/409] the matcher is an option now. -R'match string' --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 32d3e1b3..46382321 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -244,7 +244,7 @@ vows.tell = function (topic, tests) { Object.keys(tests).forEach(function (key) { if (typeof(tests[key]) !== "function") { if (! (match = count(tests[key]) || - match || matcher.test(key))) { + match || vows.options.matcher.test(key))) { delete tests[key]; vows.remaining--; } From 051bb401598f218bc506f2544164ca81f28dc665 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:42:40 -0400 Subject: [PATCH 087/409] formatting --- lib/vows.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 46382321..d7f53eb8 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -290,7 +290,11 @@ vows.tell = function (topic, tests) { return ctx.tests[k] && k !== 'setup'; }).forEach(function (item) { // Holds the current test or context - vow = Object.create({callback:ctx.tests[item], context:ctx.name, description:item}); + vow = Object.create({ + callback: ctx.tests[item], + context: ctx.name, + description: item + }); // If we encounter a function, add it to the callbacks // of the `setup` function, so it'll get called once the @@ -317,7 +321,7 @@ vows.tell = function (topic, tests) { // Check if we're done running the tests tryFinish(--vows.remaining); // This is our initial, empty context - })(new(Context)({callback:vows.tests, context:null, description:null}, {})); + })(new(Context)({ callback: vows.tests, context: null, description: null }, {})); } }); return this.promise; From 1a6400968b10e4aef54438aaf1f35658f8f7ce94 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:43:08 -0400 Subject: [PATCH 088/409] bin/vows, autotesting utility --- bin/vows | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100755 bin/vows diff --git a/bin/vows b/bin/vows new file mode 100755 index 00000000..fe95f85b --- /dev/null +++ b/bin/vows @@ -0,0 +1,180 @@ +#!/usr/bin/env node + +var path = require('path'), + sys = require('sys'), + fs = require('fs'), + exec = require('child_process').exec, + Script = process.binding('evals').Script; + +require.paths.unshift(path.join(__dirname, '..', 'lib')); + +var argv = [], options = { + matcher: /.*/, + brief: false +}; + +// +// Parse command-line parameters +// +for (var i = 0, arg; i < process.argv.length; i++) { + arg = process.argv[i]; + + if (arg === __filename) { continue } + + if (arg[0] !== '-') { + argv.push(arg); + } else { + arg = arg.match(/^--?(.*)/)[1]; + + if (arg[0] === 'R') { + options.matcher = new(RegExp)(arg.slice(1)); + } else if (arg in options) { + options[arg] = true; + } + } +} + +// Get rid of process runner +// ('node' in most cases) +argv = argv.slice(1); + +var vows = require('vows').config(options); + +if (argv.length > 0) { + argv.forEach(function (arg) { + runTest(arg); + }); +} else { + // + // Watch mode + // + vows.options.brief = true; + + (function () { + var clock = [ + '. ', '.. ', '... ', ' ...', + ' ..', ' .', ' .', ' ..', + '... ', '.. ', '. ' + ]; + var current = 0, + status = 0, + runningTests = false, + statusText = '', + colors = ['\033[32m', '\033[33m', '\033[91m'], + timer = setInterval(tick, 100); + + cursorHide(); + + // Run every 100ms + function tick() { + cursorSave(); + eraseLine(); + print(colors[status]); + print(clock[current]); + + if (runningTests) { + print(' \033[39mrunning tests...'); + } else { + print(' ' + statusText); + } + + print('\033[39m'); + cursorRestore(); + current = (current == clock.length - 1) ? 0 : current + 1; + } + + // + // Utility functions + // + function print(str) { process.stdout.write(str) } + function eraseLine() { print("\033[2K") } + function cursorSave() { print("\033[s") } + function cursorRestore() { print("\033[u") } + function cursorHide() { print("\033[?25l") } + function cursorShow() { print("\033[?25h") } + function cleanup() { cursorShow(), print('\n') } + + // + // Called when a file has been modified. + // Run the matching tests and change the status. + // + function changed(file) { + runningTests = true; + runTest("test/" + file + "-test.js").addListener('end', function (h, b, e) { + runningTests = false; + statusText = h + " honored, " + + b + " broken, " + + e + " errored"; + + if (b || e) { + eraseLine(); + status = e ? 2 : 1; + } else { + status = 0; + } + }); + } + // + // Recursively traverse a hierarchy, returning + // a list of all relevant .js files. + // + function paths(dir) { + var paths = []; + + try { fs.statSync(dir) } + catch (e) { return [] } + + (function traverse(dir, stack) { + stack.push(dir); + fs.readdirSync(stack.join('/')).forEach(function (file) { + var path = stack.concat([file]).join('/'), + stat = fs.statSync(path); + + if (file[0] == '.' || file === 'vendor') { + return; + } else if (stat.isFile() && /\.js$/.test(file)) { + paths.push(path); + } else if (stat.isDirectory()) { + traverse(file, stack); + } + }); + stack.pop(); + })(dir || '.', []); + + return paths; + } + + // + // Watch all relevant files in lib/ and src/, + // and call `changed()` on change. + // + paths('lib').concat(paths('src')).forEach(function (p) { + fs.watchFile(p, function (current, previous) { + if (new(Date)(current.mtime).valueOf() === + new(Date)(previous.mtime).valueOf()) { return } + else { + changed(path.basename(p, '.js')); + } + }); + }); + + process.addListener('exit', cleanup); + process.addListener('SIGINT', function () { + cleanup(); + process.exit(); + }); + process.addListener('SIGQUIT', function () { + changed(); + }); + })(); +} + +function runTest(file) { + var code = (function (require, __filename, __dirname) { + /* content */ + }).toString().replace('/* content */', fs.readFileSync(file)); + + return Script.runInThisContext('(' + code + ')', file) + .call(global, require, file, path.dirname(file)); +} + From 5bba9c3d69a8d66b32964cf968abe8b7d563c36e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:44:52 -0400 Subject: [PATCH 089/409] default value for matcher --- lib/vows.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index d7f53eb8..b8390aec 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -192,7 +192,8 @@ vows.prepare = function (obj, targets) { // Options vows.options = { Emitter: events.EventEmitter, - brief: false + brief: false, + matcher: /.*/ }; // Run all vows/tests. From 547d478e4cd9c29731ac26b6d6605adaf7562967 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 22:45:01 -0400 Subject: [PATCH 090/409] forgot to remove some test code --- test/vows-test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/vows-test.js b/test/vows-test.js index b31b938e..7519c54f 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -16,9 +16,7 @@ var api = vows.prepare({ var promiser = function (val) { return function () { var promise = new(events.EventEmitter); - setTimeout(function () { process.nextTick(function () { promise.emit('success', val) }); - }, 200); return promise; } }; From ba08ca38dbf91faa8f8c189a097acfde8b75d644 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 23:00:07 -0400 Subject: [PATCH 091/409] use typeof instead of instanceof --- lib/vows.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index b8390aec..e68d5ec0 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -75,7 +75,7 @@ function addVow(/* description & callback */) { // Sometimes it might be nicer to pass the proof first, // and the description second, so we let the user choose // the order. - if (args[0] instanceof Function) { + if (typeof(args[0]) === "function") { vow.callback = args[0], vow.description = args[1]; } else { vow.callback = args[1], vow.description = args[0]; @@ -302,7 +302,7 @@ vows.tell = function (topic, tests) { // setup fires. // If we encounter an object literal, we recurse, sending it // our current context. - if (vow.callback instanceof Function) { + if (typeof(vow.callback) === 'function') { setup.addVow(vow); } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { // If there's a setup stage, we have to wait for it to fire, From 1348def5666feb040a53a45da672d069f1a3a9d4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 1 May 2010 23:00:19 -0400 Subject: [PATCH 092/409] describe is an alias of tell --- lib/vows.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/vows.js b/lib/vows.js index e68d5ec0..c482299b 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -328,6 +328,7 @@ vows.tell = function (topic, tests) { return this.promise; }; +vows.describe = vows.tell; // Return the `vows` object after setting some options vows.config = function (opts) { From b78539e4c5080ae431ff479926faa343b94512bc Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 2 May 2010 03:46:38 -0400 Subject: [PATCH 093/409] allow access to Context object, from tests --- lib/vows.js | 2 +- test/vows-test.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index c482299b..99af1d40 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -266,7 +266,7 @@ vows.tell = function (topic, tests) { var ctxAdded = false; if (typeof(ctx.tests["setup"]) === 'function') { // Run the setup, passing the previous context topics - setup = ctx.tests.setup.apply(this, ctx.topics); + setup = ctx.tests.setup.apply(ctx, ctx.topics); // If the setup doesn't return an event emitter (such as a promise), // we create it ourselves, and emit the value on the next tick. diff --git a/test/vows-test.js b/test/vows-test.js index 7519c54f..42ae52b5 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -36,10 +36,20 @@ return vows.tell("Vows", { }, "with a nested context": { setup: function (parent) { + this.state = 42; return promiser(parent)(); }, "testing equality": function (it) { assert.equal(it, "hello world"); + }, + "a sub context": { + setup: function () { + return this.state; + }, + "has access to the parent context object": function (r) { + assert.equal(r, 42); + assert.equal(this.state, 42); + } } } }, From 3cfd5a56b04c22a9ff25e72d70b676a117d27f99 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 2 May 2010 03:47:09 -0400 Subject: [PATCH 094/409] explicitly return vows.promise from test runner --- bin/vows | 1 + test/vows-test.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/vows b/bin/vows index fe95f85b..0e4b070f 100755 --- a/bin/vows +++ b/bin/vows @@ -172,6 +172,7 @@ if (argv.length > 0) { function runTest(file) { var code = (function (require, __filename, __dirname) { /* content */ + return vows.promise; }).toString().replace('/* content */', fs.readFileSync(file)); return Script.runInThisContext('(' + code + ')', file) diff --git a/test/vows-test.js b/test/vows-test.js index 42ae52b5..ba7d14a0 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -21,7 +21,7 @@ var promiser = function (val) { } }; -return vows.tell("Vows", { +vows.tell("Vows", { "A context": { setup: promiser("hello world"), From 333a7f2a4df2c42affd3c738536968b254a7dc42 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 2 May 2010 14:07:40 -0400 Subject: [PATCH 095/409] attempt to detect the name of the test folder --- bin/vows | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/bin/vows b/bin/vows index 0e4b070f..2c9f738f 100755 --- a/bin/vows +++ b/bin/vows @@ -60,8 +60,18 @@ if (argv.length > 0) { status = 0, runningTests = false, statusText = '', + testFolder, colors = ['\033[32m', '\033[33m', '\033[91m'], - timer = setInterval(tick, 100); + timer = setInterval(tick, 100), + root = fs.readdirSync('.'); + + if (root.indexOf('test') !== -1) { + testFolder = 'test'; + } else if (root.indexOf('spec') !== -1) { + testFolder = 'spec'; + } else { + throw new(Error)("Couldn't find test folder"); + } cursorHide(); @@ -100,7 +110,10 @@ if (argv.length > 0) { // function changed(file) { runningTests = true; - runTest("test/" + file + "-test.js").addListener('end', function (h, b, e) { + file = /-(test|spec)$/.test(file) ? path.join(testFolder, file + '.js') + : path.join(testFolder, file + '-' + testFolder + '.js'); + + runTest(file).addListener('end', function (h, b, e) { runningTests = false; statusText = h + " honored, " + b + " broken, " + @@ -148,7 +161,7 @@ if (argv.length > 0) { // Watch all relevant files in lib/ and src/, // and call `changed()` on change. // - paths('lib').concat(paths('src')).forEach(function (p) { + [].concat(paths('lib'), paths('src'), paths(testFolder)).forEach(function (p) { fs.watchFile(p, function (current, previous) { if (new(Date)(current.mtime).valueOf() === new(Date)(previous.mtime).valueOf()) { return } From d15c5383ee4151998aa1d3459ec4999586a26ad5 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 2 May 2010 14:10:14 -0400 Subject: [PATCH 096/409] rename makefile to Makefile --- makefile => Makefile | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename makefile => Makefile (100%) diff --git a/makefile b/Makefile similarity index 100% rename from makefile rename to Makefile From c1167bde95758be7da767bc1eb38bb90358eeb4c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 2 May 2010 14:15:07 -0400 Subject: [PATCH 097/409] package.json --- package.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 00000000..772a6a64 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name" : "vows", + "description" : "Asynchronous BDD & continuous integration for node.js", + "url" : "http://cloudhead.io/vows", + "keywords" : ["testing", "spec", "test", "BDD"], + "author" : "Alexis Sellier ", + "contributors" : [], + "dependencies" : ["eyes"], + "lib" : "lib", + "version" : "0.1.1" +} From 531d4bf9d7f707e91323a15a2de3cb91b8da1b34 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 2 May 2010 14:25:52 -0400 Subject: [PATCH 098/409] refactored escape code printing --- bin/vows | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/bin/vows b/bin/vows index 2c9f738f..02edaa11 100755 --- a/bin/vows +++ b/bin/vows @@ -61,7 +61,7 @@ if (argv.length > 0) { runningTests = false, statusText = '', testFolder, - colors = ['\033[32m', '\033[33m', '\033[91m'], + colors = ['32m', '33m', '91m'], timer = setInterval(tick, 100), root = fs.readdirSync('.'); @@ -79,16 +79,16 @@ if (argv.length > 0) { function tick() { cursorSave(); eraseLine(); - print(colors[status]); + esc(colors[status]); print(clock[current]); if (runningTests) { - print(' \033[39mrunning tests...'); + print(' ' + esc('39m') + 'running tests...'); } else { print(' ' + statusText); } - print('\033[39m'); + esc('39m'); cursorRestore(); current = (current == clock.length - 1) ? 0 : current + 1; } @@ -97,11 +97,12 @@ if (argv.length > 0) { // Utility functions // function print(str) { process.stdout.write(str) } - function eraseLine() { print("\033[2K") } - function cursorSave() { print("\033[s") } - function cursorRestore() { print("\033[u") } - function cursorHide() { print("\033[?25l") } - function cursorShow() { print("\033[?25h") } + function esc(str) { print("\033[" + str) } + function eraseLine() { esc("2K") } + function cursorSave() { esc("s") } + function cursorRestore() { esc("u") } + function cursorHide() { esc("?25l") } + function cursorShow() { esc("?25h") } function cleanup() { cursorShow(), print('\n') } // From 1198d468a99f50b7f2603313daaebaa4dc90711e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 2 May 2010 14:54:38 -0400 Subject: [PATCH 099/409] evaluate everything within an 'environment', which is passed down --- lib/vows.js | 18 +++++++++++------- test/vows-test.js | 9 ++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 99af1d40..18bfe897 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -92,7 +92,7 @@ function addVow(/* description & callback */) { // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { - vow.callback.apply(null, arguments); + vow.callback.apply(args[0].binding || null, arguments); title += stylize(vow.description, 'green'); honored++; } catch (e) { @@ -211,16 +211,18 @@ vows.tell = function (topic, tests) { this.promise = new(events.EventEmitter); - function Context(vow, ctx) { + function Context(vow, ctx, env) { this.tests = vow.callback; this.topics = (ctx.topics || []).slice(0); + this.env = env || {}; + this.env.context = this; this.name = (ctx.name ? ctx.name + ' ' : '') + (vow.description || ''); } // We run the tests asynchronously, for added flexibility process.nextTick(function () { - var setup, vow; + var setup, vow, env; if (typeof(topic) === 'string' && tests) { if (!vows.options.brief) { @@ -266,7 +268,7 @@ vows.tell = function (topic, tests) { var ctxAdded = false; if (typeof(ctx.tests["setup"]) === 'function') { // Run the setup, passing the previous context topics - setup = ctx.tests.setup.apply(ctx, ctx.topics); + setup = ctx.tests.setup.apply(ctx.env, ctx.topics); // If the setup doesn't return an event emitter (such as a promise), // we create it ourselves, and emit the value on the next tick. @@ -294,8 +296,10 @@ vows.tell = function (topic, tests) { vow = Object.create({ callback: ctx.tests[item], context: ctx.name, - description: item + description: item, + binding: ctx.env }); + env = Object.create(ctx.env); // If we encounter a function, add it to the callbacks // of the `setup` function, so it'll get called once the @@ -311,11 +315,11 @@ vows.tell = function (topic, tests) { if (setup) { setup.addListener("success", function (vow, ctx) { return function (val) { - return run(new(Context)(vow, ctx)); + return run(new(Context)(vow, ctx, env)); }; }(vow, ctx)); } else { - run(new(Context)(vow, ctx)); + run(new(Context)(vow, ctx, env)); } } }); diff --git a/test/vows-test.js b/test/vows-test.js index ba7d14a0..d5dcfd5d 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -42,13 +42,20 @@ vows.tell("Vows", { "testing equality": function (it) { assert.equal(it, "hello world"); }, + "has access to the environment": function () { + assert.equal(this.state, 42); + }, "a sub context": { setup: function () { return this.state; }, - "has access to the parent context object": function (r) { + "has access to the parent environment": function (r) { assert.equal(r, 42); assert.equal(this.state, 42); + }, + "has access to the parent context object": function (r) { + assert.ok(Array.isArray(this.context.topics)); + assert.include(this.context.topics, "hello world"); } } } From abadd5df523abb55fdb8d40ef73f5cf6f92d7b57 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 2 May 2010 14:55:41 -0400 Subject: [PATCH 100/409] updated eyes --- lib/vendor/eyes | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vendor/eyes b/lib/vendor/eyes index d13426e1..77666781 160000 --- a/lib/vendor/eyes +++ b/lib/vendor/eyes @@ -1 +1 @@ -Subproject commit d13426e1794a49f9042296327f7ded9bfb540575 +Subproject commit 7766678142d73892957843eeaafc6f2db240b549 From 11d2e8fe7381da629f98702173baaeb2c950bf85 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 2 May 2010 16:49:34 -0400 Subject: [PATCH 101/409] added isEmpty and typeOf assertion macros --- lib/vows/macros.js | 46 +++++++++++++++++++++++++++++++++++++++++++++- test/vows-test.js | 10 ++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/lib/vows/macros.js b/lib/vows/macros.js index 46af8089..18b4aa4e 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -24,7 +24,9 @@ assert.ok = (function (callback) { })(assert.ok); assert.match = function (actual, expected, message) { - if (! actual.match(expected)) assert.fail(actual, expected, message || "expected {actual} to match {expected}", "match", assert.match); + if (! expected.test(actual)) { + assert.fail(actual, expected, message || "expected {actual} to match {expected}", "match", assert.match); + } }; assert.matches = assert.match; @@ -42,9 +44,37 @@ assert.include = function (actual, expected, message) { }; assert.includes = assert.include; +assert.isEmpty = function (actual, message) { + if ((isObject(actual) && Object.keys(actual).length > 0) || actual.length > 0) { + assert.fail(actual, 0, message || "expected {actual} to be empty", "length", assert.isEmpty); + } +}; + +assert.isArray = function (actual, message) { + assertTypeOf(actual, 'array', message || "expected {actual} to be an Array", assert.isArray); +}; +assert.isObject = function (actual, message) { + assertTypeOf(actual, 'object', message || "expected {actual} to be an Object", assert.isObject); +}; +assert.isNumber = function (actual, message) { + assertTypeOf(actual, 'number', message || "expected {actual} to be a Number", assert.isNumber); +}; +assert.isString = function (actual, message) { + assertTypeOf(actual, 'string', message || "expected {actual} to be a String", assert.isString); +}; +assert.typeOf = function (actual, expected, message) { + assertTypeOf(actual, expected, message, assert.typeOf); +}; + // // Utility functions // +function assertTypeOf(actual, expected, message, caller) { + if (typeOf(actual) !== expected) { + assert.fail(actual, expected, message || "expected {actual} to be of type {expected}", "typeOf", caller); + } +}; + function isArray (obj) { return Array.isArray(obj); } @@ -57,3 +87,17 @@ function isObject (obj) { return typeof(obj) === 'object' && obj instanceof Object && !isArray(obj); } +// A better `typeof` +function typeOf(value) { + var s = typeof(value), + types = [Object, Array, String, RegExp, Number, Function, Boolean, Date]; + + if (s === 'object' || s === 'function') { + if (value) { + types.forEach(function (t) { + if (value instanceof t) { s = t.name.toLowerCase() } + }); + } else { s = 'null' } + } + return s; +} diff --git a/test/vows-test.js b/test/vows-test.js index d5dcfd5d..c905f09e 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -34,6 +34,16 @@ vows.tell("Vows", { "testing inclusion": function (it) { assert.include(it, "world"); }, + "testing type": function (it) { + assert.typeOf(it, 'string'); + assert.isArray([]); + assert.isObject({}); + }, + "testing emptiness": function (it) { + assert.isEmpty({}); + assert.isEmpty([]); + assert.isEmpty(""); + }, "with a nested context": { setup: function (parent) { this.state = 42; From a7b5857b57d489651959cd548be1cc880ff0b4c2 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 3 May 2010 20:38:41 -0400 Subject: [PATCH 102/409] allow non-function subjects --- lib/vows.js | 13 +++++++++++-- test/vows-test.js | 7 +++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 18bfe897..41261f4e 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -245,7 +245,7 @@ vows.tell = function (topic, tests) { var match = false; vows.remaining++; Object.keys(tests).forEach(function (key) { - if (typeof(tests[key]) !== "function") { + if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) { if (! (match = count(tests[key]) || match || vows.options.matcher.test(key))) { delete tests[key]; @@ -266,7 +266,16 @@ vows.tell = function (topic, tests) { // passing the topic as an argument. (function run(ctx) { var ctxAdded = false; - if (typeof(ctx.tests["setup"]) === 'function') { + + if ('setup' in ctx.tests) { + + // Setup isn't a function, wrap it into one. + if (typeof(ctx.tests.setup) !== 'function') { + ctx.tests.setup = (function (topic) { + return function () { return topic }; + })(ctx.tests.setup); + } + // Run the setup, passing the previous context topics setup = ctx.tests.setup.apply(ctx.env, ctx.topics); diff --git a/test/vows-test.js b/test/vows-test.js index c905f09e..78716568 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -116,5 +116,12 @@ vows.tell("Vows", { assert.equal(val, '1.0'); } } + }, + "Non-functions as subjects": { + setup: 45, + + "should work as expected": function (subject) { + assert.equal(subject, 45); + } } }); From 8a03c2a3af6e08a5cdea1cf07f1c325b34e57d9a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 7 May 2010 14:59:24 -0700 Subject: [PATCH 103/409] 'setup' is now called 'topic' --- lib/vows.js | 54 +++++++++++++++++++++++------------------------ test/vows-test.js | 26 +++++++++++------------ 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 41261f4e..6570282c 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -199,7 +199,7 @@ vows.options = { // Run all vows/tests. // It can take either a function as `tests`, // or an object literal. -vows.tell = function (topic, tests) { +vows.tell = function (subject, tests) { this.options.Emitter.prototype.addVow = addVow; this.tests = tests; this.remaining = 0; @@ -222,14 +222,14 @@ vows.tell = function (topic, tests) { // We run the tests asynchronously, for added flexibility process.nextTick(function () { - var setup, vow, env; + var topic, vow, env; - if (typeof(topic) === 'string' && tests) { + if (typeof(subject) === 'string' && tests) { if (!vows.options.brief) { puts('\n' + stylize(topic, 'underline') + '\n'); } - } else if (topic instanceof Object || topic instanceof Function) { - vows.tests = topic; + } else if (subject instanceof Object || subject instanceof Function) { + vows.tests = subject; } else { throw "tell() takes a topic and an Object" } start = new(Date); @@ -257,49 +257,49 @@ vows.tell = function (topic, tests) { })(vows.tests); // The test runner, it calls itself recursively, passing the - // previous context to the inner contexts. This is so the `setup` + // previous context to the inner contexts. This is so the `topic` // functions have access to all the previous context topics in their // arguments list. // It is defined and invoked at the same time. - // If it encounters a `setup` function, it waits for the returned + // If it encounters a `topic` function, it waits for the returned // promise to emit (the topic), at which point it runs the functions under it, // passing the topic as an argument. (function run(ctx) { var ctxAdded = false; - if ('setup' in ctx.tests) { + if ('topic' in ctx.tests) { - // Setup isn't a function, wrap it into one. - if (typeof(ctx.tests.setup) !== 'function') { - ctx.tests.setup = (function (topic) { + // Topic isn't a function, wrap it into one. + if (typeof(ctx.tests.topic) !== 'function') { + ctx.tests.topic = (function (topic) { return function () { return topic }; - })(ctx.tests.setup); + })(ctx.tests.topic); } - // Run the setup, passing the previous context topics - setup = ctx.tests.setup.apply(ctx.env, ctx.topics); + // Run the topic, passing the previous context topics + topic = ctx.tests.topic.apply(ctx.env, ctx.topics); - // If the setup doesn't return an event emitter (such as a promise), + // 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 (! (setup instanceof vows.options.Emitter)) { + if (! (topic instanceof vows.options.Emitter)) { var emitter = new(vows.options.Emitter); process.nextTick(function (val) { return function () { emitter.emit("success", val) }; - }(setup)); setup = emitter; + }(topic)); topic = emitter; } - setup.addListener('success', function (val) { - // Once the setup fires, add the return value + topic.addListener('success', 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 setup. + // becomes the first argument for the next topic. ctx.topics.unshift(val); }); - } else { setup = null } + } else { topic = null } // Now run the tests, or sub-contexts Object.keys(ctx.tests).filter(function (k) { - return ctx.tests[k] && k !== 'setup'; + return ctx.tests[k] && k !== 'topic'; }).forEach(function (item) { // Holds the current test or context vow = Object.create({ @@ -311,18 +311,18 @@ vows.tell = function (topic, tests) { env = Object.create(ctx.env); // If we encounter a function, add it to the callbacks - // of the `setup` function, so it'll get called once the - // setup fires. + // of the `topic` function, so it'll get called once the + // topic fires. // If we encounter an object literal, we recurse, sending it // our current context. if (typeof(vow.callback) === 'function') { - setup.addVow(vow); + topic.addVow(vow); } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { // 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 // synchronously. - if (setup) { - setup.addListener("success", function (vow, ctx) { + if (topic) { + topic.addListener("success", function (vow, ctx) { return function (val) { return run(new(Context)(vow, ctx, env)); }; diff --git a/test/vows-test.js b/test/vows-test.js index 78716568..e3548b01 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -21,9 +21,9 @@ var promiser = function (val) { } }; -vows.tell("Vows", { +vows.describe("Vows", { "A context": { - setup: promiser("hello world"), + topic: promiser("hello world"), "testing equality": function (it) { assert.equal(it, "hello world"); @@ -45,7 +45,7 @@ vows.tell("Vows", { assert.isEmpty(""); }, "with a nested context": { - setup: function (parent) { + topic: function (parent) { this.state = 42; return promiser(parent)(); }, @@ -56,7 +56,7 @@ vows.tell("Vows", { assert.equal(this.state, 42); }, "a sub context": { - setup: function () { + topic: function () { return this.state; }, "has access to the parent environment": function (r) { @@ -71,16 +71,16 @@ vows.tell("Vows", { } }, "Nested contexts": { - setup: promiser(1), + topic: promiser(1), "have": { - setup: function (a) { return promiser(2)() }, + topic: function (a) { return promiser(2)() }, "access": { - setup: function (b, a) { return promiser(3)() }, + topic: function (b, a) { return promiser(3)() }, "to": { - setup: function (c, b, a) { return promiser([4, c, b, a])() }, + topic: function (c, b, a) { return promiser([4, c, b, a])() }, "the parent topics": function (topics) { assert.equal(topics.join(), [4, 3, 2, 1].join()); @@ -88,7 +88,7 @@ vows.tell("Vows", { }, "from": { - setup: function (c, b, a) { return promiser([4, c, b, a])() }, + topic: function (c, b, a) { return promiser([4, c, b, a])() }, "the parent topics": function(topics) { assert.equal(topics.join(), [4, 3, 2, 1].join()); @@ -98,27 +98,27 @@ vows.tell("Vows", { } }, "Non-promise return value": { - setup: function () { return 1 }, + topic: function () { return 1 }, "should be converted to a promise": function (val) { assert.equal(val, 1); } }, "A 'prepared' interface": { "with a wrapped function": { - setup: function () { return api.get(42) }, + topic: function () { return api.get(42) }, "should work as expected": function (val) { assert.equal(val, 42); } }, "with a non-wrapped function": { - setup: function () { return api.version() }, + topic: function () { return api.version() }, "should work as expected": function (val) { assert.equal(val, '1.0'); } } }, "Non-functions as subjects": { - setup: 45, + topic: 45, "should work as expected": function (subject) { assert.equal(subject, 45); From 8260e38ea08efd9b192bde74c45a315e6db79915 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 7 May 2010 15:00:45 -0700 Subject: [PATCH 104/409] updated READMe --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4939dc51..ad5a5b41 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,12 @@ writing specs vows.tell('A Database library', { // run this once, and execute the following tests when it completes - setup: function () { return new(DB) }, + topic: function () { return new(DB) }, 'set() should store a k/v pair': { // the inner context gets the return values of the outer contexts // passed as arguments. Here, `db` is new(DB). - setup: function (db) { return db.set('pulp', 'orange') }, + topic: function (db) { return db.set('pulp', 'orange') }, // `res` is the value emitted by the above `db.set` 'and return OK': function (res) { @@ -47,9 +47,9 @@ writing specs }, 'and when checked for existence': { // here, we need to access `db`, from the parent context. - // It's passed as the 2nd argument to `setup`, we discard the first, + // It's passed as the 2nd argument to `topic`, we discard the first, // which would have been the above `res`. - setup: function (_, db) { return db.exists('pulp') }, + topic: function (_, db) { return db.exists('pulp') }, 'return true': function (re) { assert.equal(re, true); @@ -57,7 +57,7 @@ writing specs } }, 'get()': { - setup: function (db) { return db.get('dream') }, + topic: function (db) { return db.get('dream') }, 'should return the stored value': function (res) { assert.equal(res, 'catcher'); } From 06485b8444413031702347c8964cd416ef136e15 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 7 May 2010 15:01:10 -0700 Subject: [PATCH 105/409] added spinning wheel --- bin/vows | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/bin/vows b/bin/vows index 02edaa11..b6472a1e 100755 --- a/bin/vows +++ b/bin/vows @@ -55,13 +55,17 @@ if (argv.length > 0) { '. ', '.. ', '... ', ' ...', ' ..', ' .', ' .', ' ..', '... ', '.. ', '. ' + ], wheel = [ + '-', '\\', '|', '/' ]; var current = 0, status = 0, runningTests = false, + gracePeriod = 0, statusText = '', + lastRun, testFolder, - colors = ['32m', '33m', '91m'], + colors = ['32m', '33m', '31m'], timer = setInterval(tick, 100), root = fs.readdirSync('.'); @@ -80,17 +84,26 @@ if (argv.length > 0) { cursorSave(); eraseLine(); esc(colors[status]); - print(clock[current]); - if (runningTests) { - print(' ' + esc('39m') + 'running tests...'); + if (runningTests || gracePeriod) { + gracePeriod--; + print(wheel[current]); + esc('39m'); + print(' working...'); + if (current == wheel.length - 1) { current = -1 } } else { + print(clock[current]); print(' ' + statusText); + if (lastRun) { + esc('90m'); + print( ' (' + lastRun.valueOf() + ')'); + } + if (current == clock.length - 1) { current = -1 } } + current++; esc('39m'); cursorRestore(); - current = (current == clock.length - 1) ? 0 : current + 1; } // @@ -111,6 +124,9 @@ if (argv.length > 0) { // function changed(file) { runningTests = true; + gracePeriod = 5; + current = 0; + file = /-(test|spec)$/.test(file) ? path.join(testFolder, file + '.js') : path.join(testFolder, file + '-' + testFolder + '.js'); @@ -119,6 +135,7 @@ if (argv.length > 0) { statusText = h + " honored, " + b + " broken, " + e + " errored"; + lastRun = new(Date); if (b || e) { eraseLine(); From 6e49a11ebd2cbbd0a64b71853cf1e56debdaa2f7 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 10 May 2010 15:29:53 -0400 Subject: [PATCH 106/409] write an error if an EventEmitter hasn't fired --- lib/vows.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/vows.js b/lib/vows.js index 6570282c..9549b0b7 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -162,6 +162,16 @@ function tryFinish(remaining) { } } +// +// On exit, check that all promises have been fired. +// If not, print an error message. +// +process.addListener('exit', function () { + if (honored + broken + errored < total) { + sys.puts('\n* ' + stylize("An EventEmitter has failed to fire.", 'red')); + } +}); + // // Wrap a Node.js style async function into an EventEmmitter // From 0b891d64d5222d9f452f48c4ba4e9b1823c11e98 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 10 May 2010 15:30:19 -0400 Subject: [PATCH 107/409] topic/subject ref fix --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 9549b0b7..6a4aa793 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -236,7 +236,7 @@ vows.tell = function (subject, tests) { if (typeof(subject) === 'string' && tests) { if (!vows.options.brief) { - puts('\n' + stylize(topic, 'underline') + '\n'); + puts('\n' + stylize(subject, 'underline') + '\n'); } } else if (subject instanceof Object || subject instanceof Function) { vows.tests = subject; From fa5194913a77f08429974bc0a1594c21c0b0f9bd Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 10 May 2010 17:35:46 -0400 Subject: [PATCH 108/409] allow nested contexts with no topics --- lib/vows.js | 19 ++++++++++--------- test/vows-test.js | 10 ++++++++++ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 6a4aa793..ec42399f 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -274,20 +274,21 @@ vows.tell = function (subject, tests) { // If it encounters a `topic` function, it waits for the returned // promise to emit (the topic), at which point it runs the functions under it, // passing the topic as an argument. - (function run(ctx) { + (function run(ctx, lastTopic) { var ctxAdded = false; - if ('topic' in ctx.tests) { - + topic = lastTopic = ('topic' in ctx.tests) ? ctx.tests.topic + : lastTopic; + if (topic) { // Topic isn't a function, wrap it into one. - if (typeof(ctx.tests.topic) !== 'function') { - ctx.tests.topic = (function (topic) { + if (typeof(topic) !== 'function') { + topic = (function (topic) { return function () { return topic }; - })(ctx.tests.topic); + })(topic); } // Run the topic, passing the previous context topics - topic = ctx.tests.topic.apply(ctx.env, ctx.topics); + topic = topic.apply(ctx.env, ctx.topics); // 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. @@ -334,11 +335,11 @@ vows.tell = function (subject, tests) { if (topic) { topic.addListener("success", function (vow, ctx) { return function (val) { - return run(new(Context)(vow, ctx, env)); + return run(new(Context)(vow, ctx, env), lastTopic); }; }(vow, ctx)); } else { - run(new(Context)(vow, ctx, env)); + run(new(Context)(vow, ctx, env), lastTopic); } } }); diff --git a/test/vows-test.js b/test/vows-test.js index e3548b01..086fb36c 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -97,6 +97,16 @@ vows.describe("Vows", { } } }, + "Nested contexts with no topics": { + topic: 45, + "should": { + "pass": { + "the value down": function (topic) { + assert.equal(topic, 45); + } + } + } + }, "Non-promise return value": { topic: function () { return 1 }, "should be converted to a promise": function (val) { From b95d282f14e07566ea28f263658b007df694a991 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 10 May 2010 22:10:12 -0400 Subject: [PATCH 109/409] API change, ability to run serial test suites --- lib/vows.js | 270 +++++++++++++++++++++++--------------------- test/addvow-test.js | 2 +- test/vows-test.js | 2 +- 3 files changed, 141 insertions(+), 133 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 6a4aa793..3d214db7 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -62,7 +62,7 @@ assert.AssertionError.prototype.toString = function () { } // -// This function gets added to events.Promise.prototype, by default. +// This function gets added to events.EventEmitter.prototype, by default. // It's essentially a wrapper around `addCallback`, which adds all the specification // goodness. // @@ -128,9 +128,135 @@ function addVow(/* description & callback */) { } }; +function Context(vow, ctx, env) { + this.tests = vow.callback; + this.topics = (ctx.topics || []).slice(0); + this.env = env || {}; + this.env.context = this; + this.name = (ctx.name ? ctx.name + ' ' : '') + + (vow.description || ''); +} + +function addVows(tests) { + var promise = new(events.EventEmitter); + vows.promises.push(promise); + + this.addListener("success", function () { + // We run the tests asynchronously, for added flexibility + process.nextTick(function () { + var topic, vow, env; + + if (typeof(tests) === 'function') { + return tests.call(null); + } else { + // Count the number of vows/promises expected to fire, + // so we know when the tests are over. + // We match the keys against `matcher`, to decide + // whether or not they should be included in the test. + (function count(tests) { + var match = false; + vows.remaining++; + Object.keys(tests).forEach(function (key) { + if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) { + if (! (match = count(tests[key]) || + match || vows.options.matcher.test(key))) { + delete tests[key]; + vows.remaining--; + } + } + }); + return match; + })(tests); + + // The test runner, it calls itself recursively, passing the + // previous context to the inner contexts. This is so the `topic` + // functions have access to all the previous context topics in their + // arguments list. + // It is defined and invoked at the same time. + // If it encounters a `topic` function, it waits for the returned + // promise to emit (the topic), at which point it runs the functions under it, + // passing the topic as an argument. + (function run(ctx) { + var ctxAdded = false; + + if ('topic' in ctx.tests) { + + // Topic isn't a function, wrap it into one. + if (typeof(ctx.tests.topic) !== 'function') { + ctx.tests.topic = (function (topic) { + return function () { return topic }; + })(ctx.tests.topic); + } + + // Run the topic, passing the previous context topics + topic = ctx.tests.topic.apply(ctx.env, ctx.topics); + + // 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 instanceof vows.options.Emitter)) { + var emitter = new(vows.options.Emitter); + + process.nextTick(function (val) { + return function () { emitter.emit("success", val) }; + }(topic)); topic = emitter; + } + + topic.addListener('success', 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. + ctx.topics.unshift(val); + }); + } else { topic = null } + + // Now run the tests, or sub-contexts + Object.keys(ctx.tests).filter(function (k) { + return ctx.tests[k] && k !== 'topic'; + }).forEach(function (item) { + // Holds the current test or context + vow = Object.create({ + callback: ctx.tests[item], + context: ctx.name, + description: item, + binding: ctx.env + }); + env = Object.create(ctx.env); + + // If we encounter a function, add it to the callbacks + // of the `topic` function, so it'll get called once the + // topic fires. + // If we encounter an object literal, we recurse, sending it + // our current context. + if (typeof(vow.callback) === 'function') { + topic.addVow(vow); + } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { + // 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 + // synchronously. + if (topic) { + topic.addListener("success", function (vow, ctx) { + return function (val) { + return run(new(Context)(vow, ctx, env)); + }; + }(vow, ctx)); + } else { + run(new(Context)(vow, ctx, env)); + } + } + }); + // Check if we're done running the tests + tryFinish(--vows.remaining); + // This is our initial, empty context + })(new(Context)({ callback: tests, context: null, description: null }, {})); + } + }); + }); + return promise; +} + function puts() { var args = Array.prototype.slice.call(arguments); - if (vows.promise.listeners('end').length > 0) { + if (vows.promises[vows.promises.length - 1].listeners('end').length > 0) { buffer.push(args.join('\n')); } else { sys.puts.apply(null, args); @@ -153,7 +279,7 @@ function tryFinish(remaining) { puts("\n" + stylize(result, style)); } - vows.promise.emit("end", honored, broken, errored); + vows.promises[vows.promises.length - 1].emit("end", honored, broken, errored); if (broken || errored) { sys.puts(buffer.join('\n') + '\n') } process.stdout.addListener('drain', function () { @@ -209,146 +335,28 @@ vows.options = { // Run all vows/tests. // It can take either a function as `tests`, // or an object literal. -vows.tell = function (subject, tests) { +vows.tell = function (subject) { this.options.Emitter.prototype.addVow = addVow; - this.tests = tests; + this.options.Emitter.prototype.addVows = addVows; this.remaining = 0; + this.promises = []; // Reset values total = 0, honored = 0, broken = 0, errored = 0; buffer = []; - this.promise = new(events.EventEmitter); - - function Context(vow, ctx, env) { - this.tests = vow.callback; - this.topics = (ctx.topics || []).slice(0); - this.env = env || {}; - this.env.context = this; - this.name = (ctx.name ? ctx.name + ' ' : '') + - (vow.description || ''); - } + var promise = new(events.EventEmitter); - // We run the tests asynchronously, for added flexibility process.nextTick(function () { - var topic, vow, env; - - if (typeof(subject) === 'string' && tests) { - if (!vows.options.brief) { - puts('\n' + stylize(subject, 'underline') + '\n'); - } - } else if (subject instanceof Object || subject instanceof Function) { - vows.tests = subject; - } else { throw "tell() takes a topic and an Object" } - - start = new(Date); - - if (typeof(vows.tests) === 'function') { - return vows.tests.call(null); - } else { - // Count the number of vows/promises expected to fire, - // so we know when the tests are over. - // We match the keys against `matcher`, to decide - // whether or not they should be included in the test. - (function count(tests) { - var match = false; - vows.remaining++; - Object.keys(tests).forEach(function (key) { - if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) { - if (! (match = count(tests[key]) || - match || vows.options.matcher.test(key))) { - delete tests[key]; - vows.remaining--; - } - } - }); - return match; - })(vows.tests); - - // The test runner, it calls itself recursively, passing the - // previous context to the inner contexts. This is so the `topic` - // functions have access to all the previous context topics in their - // arguments list. - // It is defined and invoked at the same time. - // If it encounters a `topic` function, it waits for the returned - // promise to emit (the topic), at which point it runs the functions under it, - // passing the topic as an argument. - (function run(ctx) { - var ctxAdded = false; - - if ('topic' in ctx.tests) { - - // Topic isn't a function, wrap it into one. - if (typeof(ctx.tests.topic) !== 'function') { - ctx.tests.topic = (function (topic) { - return function () { return topic }; - })(ctx.tests.topic); - } - - // Run the topic, passing the previous context topics - topic = ctx.tests.topic.apply(ctx.env, ctx.topics); - - // 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 instanceof vows.options.Emitter)) { - var emitter = new(vows.options.Emitter); - - process.nextTick(function (val) { - return function () { emitter.emit("success", val) }; - }(topic)); topic = emitter; - } - - topic.addListener('success', 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. - ctx.topics.unshift(val); - }); - } else { topic = null } - - // Now run the tests, or sub-contexts - Object.keys(ctx.tests).filter(function (k) { - return ctx.tests[k] && k !== 'topic'; - }).forEach(function (item) { - // Holds the current test or context - vow = Object.create({ - callback: ctx.tests[item], - context: ctx.name, - description: item, - binding: ctx.env - }); - env = Object.create(ctx.env); - - // If we encounter a function, add it to the callbacks - // of the `topic` function, so it'll get called once the - // topic fires. - // If we encounter an object literal, we recurse, sending it - // our current context. - if (typeof(vow.callback) === 'function') { - topic.addVow(vow); - } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { - // 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 - // synchronously. - if (topic) { - topic.addListener("success", function (vow, ctx) { - return function (val) { - return run(new(Context)(vow, ctx, env)); - }; - }(vow, ctx)); - } else { - run(new(Context)(vow, ctx, env)); - } - } - }); - // Check if we're done running the tests - tryFinish(--vows.remaining); - // This is our initial, empty context - })(new(Context)({ callback: vows.tests, context: null, description: null }, {})); + if (!vows.options.brief) { + puts('\n' + stylize(subject, 'underline') + '\n'); } + promise.emit("success"); }); - return this.promise; + start = new(Date); + + return promise; }; vows.describe = vows.tell; diff --git a/test/addvow-test.js b/test/addvow-test.js index 539dd88d..72b8a5bc 100644 --- a/test/addvow-test.js +++ b/test/addvow-test.js @@ -14,7 +14,7 @@ var promiser = function (val) { } }; -vows.tell("Vows:unit", function () { +vows.tell("Vows:unit").addVows(function () { promiser("hello world")().addVow(function (val) { assert.equal(val, "hello world"); }, "addVow()"); diff --git a/test/vows-test.js b/test/vows-test.js index e3548b01..f6c35035 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -21,7 +21,7 @@ var promiser = function (val) { } }; -vows.describe("Vows", { +vows.describe("Vows").addVows({ "A context": { topic: promiser("hello world"), From b7a65c0d1d852b4fd9460cf6a0f3d040632c0ee7 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 10 May 2010 22:29:00 -0400 Subject: [PATCH 110/409] vow counting is sync. emit success when local remaining == 0 --- lib/vows.js | 212 ++++++++++++++++++++++++++-------------------------- 1 file changed, 107 insertions(+), 105 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 3d214db7..e2696699 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -139,117 +139,119 @@ function Context(vow, ctx, env) { function addVows(tests) { var promise = new(events.EventEmitter); - vows.promises.push(promise); - - this.addListener("success", function () { - // We run the tests asynchronously, for added flexibility - process.nextTick(function () { - var topic, vow, env; + var remaining = 0; - if (typeof(tests) === 'function') { - return tests.call(null); - } else { - // Count the number of vows/promises expected to fire, - // so we know when the tests are over. - // We match the keys against `matcher`, to decide - // whether or not they should be included in the test. - (function count(tests) { - var match = false; - vows.remaining++; - Object.keys(tests).forEach(function (key) { - if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) { - if (! (match = count(tests[key]) || - match || vows.options.matcher.test(key))) { - delete tests[key]; - vows.remaining--; - } - } - }); - return match; - })(tests); - - // The test runner, it calls itself recursively, passing the - // previous context to the inner contexts. This is so the `topic` - // functions have access to all the previous context topics in their - // arguments list. - // It is defined and invoked at the same time. - // If it encounters a `topic` function, it waits for the returned - // promise to emit (the topic), at which point it runs the functions under it, - // passing the topic as an argument. - (function run(ctx) { - var ctxAdded = false; - - if ('topic' in ctx.tests) { - - // Topic isn't a function, wrap it into one. - if (typeof(ctx.tests.topic) !== 'function') { - ctx.tests.topic = (function (topic) { - return function () { return topic }; - })(ctx.tests.topic); - } + vows.promises.push(promise); - // Run the topic, passing the previous context topics - topic = ctx.tests.topic.apply(ctx.env, ctx.topics); + // Count the number of vows/promises expected to fire, + // so we know when the tests are over. + // We match the keys against `matcher`, to decide + // whether or not they should be included in the test. + (function count(tests) { + var match = false; + remaining++; + Object.keys(tests).forEach(function (key) { + if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) { + if (! (match = count(tests[key]) || + match || vows.options.matcher.test(key))) { + delete tests[key]; + remaining--; + } + } + }); + return match; + })(tests); - // 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 instanceof vows.options.Emitter)) { - var emitter = new(vows.options.Emitter); + vows.remaining += remaining; - process.nextTick(function (val) { - return function () { emitter.emit("success", val) }; - }(topic)); topic = emitter; - } + this.addListener("success", function () { + var topic, vow, env; - topic.addListener('success', 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. - ctx.topics.unshift(val); - }); - } else { topic = null } - - // Now run the tests, or sub-contexts - Object.keys(ctx.tests).filter(function (k) { - return ctx.tests[k] && k !== 'topic'; - }).forEach(function (item) { - // Holds the current test or context - vow = Object.create({ - callback: ctx.tests[item], - context: ctx.name, - description: item, - binding: ctx.env - }); - env = Object.create(ctx.env); - - // If we encounter a function, add it to the callbacks - // of the `topic` function, so it'll get called once the - // topic fires. - // If we encounter an object literal, we recurse, sending it - // our current context. - if (typeof(vow.callback) === 'function') { - topic.addVow(vow); - } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { - // 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 - // synchronously. - if (topic) { - topic.addListener("success", function (vow, ctx) { - return function (val) { - return run(new(Context)(vow, ctx, env)); - }; - }(vow, ctx)); - } else { - run(new(Context)(vow, ctx, env)); - } - } + if (typeof(tests) === 'function') { + return tests.call(null); + } else { + // The test runner, it calls itself recursively, passing the + // previous context to the inner contexts. This is so the `topic` + // functions have access to all the previous context topics in their + // arguments list. + // It is defined and invoked at the same time. + // If it encounters a `topic` function, it waits for the returned + // promise to emit (the topic), at which point it runs the functions under it, + // passing the topic as an argument. + (function run(ctx) { + var ctxAdded = false; + + if ('topic' in ctx.tests) { + + // Topic isn't a function, wrap it into one. + if (typeof(ctx.tests.topic) !== 'function') { + ctx.tests.topic = (function (topic) { + return function () { return topic }; + })(ctx.tests.topic); + } + + // Run the topic, passing the previous context topics + topic = ctx.tests.topic.apply(ctx.env, ctx.topics); + + // 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 instanceof vows.options.Emitter)) { + var emitter = new(vows.options.Emitter); + + process.nextTick(function (val) { + return function () { emitter.emit("success", val) }; + }(topic)); topic = emitter; + } + + topic.addListener('success', 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. + ctx.topics.unshift(val); }); - // Check if we're done running the tests - tryFinish(--vows.remaining); - // This is our initial, empty context - })(new(Context)({ callback: tests, context: null, description: null }, {})); - } - }); + } else { topic = null } + + // Now run the tests, or sub-contexts + Object.keys(ctx.tests).filter(function (k) { + return ctx.tests[k] && k !== 'topic'; + }).forEach(function (item) { + // Holds the current test or context + vow = Object.create({ + callback: ctx.tests[item], + context: ctx.name, + description: item, + binding: ctx.env + }); + env = Object.create(ctx.env); + + // If we encounter a function, add it to the callbacks + // of the `topic` function, so it'll get called once the + // topic fires. + // If we encounter an object literal, we recurse, sending it + // our current context. + if (typeof(vow.callback) === 'function') { + topic.addVow(vow); + } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { + // 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 + // synchronously. + if (topic) { + topic.addListener("success", function (vow, ctx) { + return function (val) { + return run(new(Context)(vow, ctx, env)); + }; + }(vow, ctx)); + } else { + run(new(Context)(vow, ctx, env)); + } + } + }); + if (--remaining === 0) { promise.emit("success") } + // Check if we're done running the tests + tryFinish(--vows.remaining); + // This is our initial, empty context + })(new(Context)({ callback: tests, context: null, description: null }, {})); + } }); return promise; } From f394e799fce2ba6016f1a9470b9ef8251375406d Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 10 May 2010 22:29:22 -0400 Subject: [PATCH 111/409] test for chained vows --- test/vows-test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/vows-test.js b/test/vows-test.js index f6c35035..45339d8c 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -124,4 +124,8 @@ vows.describe("Vows").addVows({ assert.equal(subject, 45); } } +}).addVows({ + "A 2nd test suite": { + "should run after the first": function () {} + } }); From bfa2a26cafc289d64f2ca4399a3a3195c7d022fc Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 00:32:35 -0400 Subject: [PATCH 112/409] keep track of the number of test suites --- lib/vows.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 962406cc..ba2eba64 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -26,7 +26,10 @@ var sys = require('sys'), // Keeps track of the outcome of vows. var total = 0, honored = 0, broken = 0, errored = 0, - start, end; + + start, // Start time + end, // End time + suites; // Number of test suites added by `addVows()` // Context stack, used in addVow() to keep track var lastContext; @@ -141,6 +144,8 @@ function addVows(tests) { var promise = new(events.EventEmitter); var remaining = 0; + suites++; + vows.promises.push(promise); // Count the number of vows/promises expected to fire, @@ -346,7 +351,7 @@ vows.tell = function (subject) { // Reset values total = 0, honored = 0, broken = 0, errored = 0; - buffer = []; + buffer = [], suites = 0; var promise = new(events.EventEmitter); From 68e147d7b55ce8a651e72f4fcf6471e2678b0e64 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 00:33:58 -0400 Subject: [PATCH 113/409] pass the test-suite promises to tryFinish() --- lib/vows.js | 61 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index ba2eba64..5afcdf11 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -127,7 +127,7 @@ function addVow(/* description & callback */) { puts(title); if (exception) puts(exception); } - tryFinish(vows.remaining); + tryFinish(vows.remaining, vow.promise); } }; @@ -226,7 +226,8 @@ function addVows(tests) { callback: ctx.tests[item], context: ctx.name, description: item, - binding: ctx.env + binding: ctx.env, + promise: promise }); env = Object.create(ctx.env); @@ -253,7 +254,7 @@ function addVows(tests) { } }); // Check if we're done running the tests - tryFinish(--vows.remaining); + tryFinish(--vows.remaining, promise); // This is our initial, empty context })(new(Context)({ callback: tests, context: null, description: null }, {})); } @@ -263,35 +264,51 @@ function addVows(tests) { function puts() { var args = Array.prototype.slice.call(arguments); - if (vows.promises[vows.promises.length - 1].listeners('end').length > 0) { + if (vows.promises[suites - 1].listeners('finish').length > 0) { buffer.push(args.join('\n')); } else { - sys.puts.apply(null, args); + sys.puts.apply(null, args); } } -function tryFinish(remaining) { +// +// Checks if all the required tests have been run, +// and either triggers the next test suite, if any, +// or exits the process. +// +function tryFinish(remaining, promise) { + var result, style; + // Output results once all the vows have been checked if (honored + broken + errored === total && remaining === 0) { - var result = honored + " honored, " + - broken + " broken, " + - errored + " errored", - - style = honored === total ? - ('green') : (errored === 0 ? 'yellow' : 'red'); + result = honored + " honored, " + + broken + " broken, " + + errored + " errored", + style = honored === total ? ('green') + : (errored === 0 ? 'yellow' : 'red'); + + // If this isn't the last test suite in the chain, + // emit 'end', to trigger the next test suite. + if (promise.listeners('end').length > 0) { + promise.emit('end', honored, broken, errored); + } else { + if (!vows.options.brief) { + puts("\nVerified " + total + " vows in " + + (((new(Date)) - start) / 1000) + " seconds."); + puts("\n" + stylize(result, style)); + } - if (!vows.options.brief) { - puts("\nVerified " + total + " vows in " + - (((new(Date)) - start) / 1000) + " seconds."); - puts("\n" + stylize(result, style)); - } + // The 'finish' event is triggered once all the tests have been run. + // It's used by bin/vows + vows.promises[suites - 1].emit("finish", honored, broken, errored); - vows.promises[vows.promises.length - 1].emit("end", honored, broken, errored); - if (broken || errored) { sys.puts(buffer.join('\n') + '\n') } + if ((broken || errored) && buffer.length) { sys.puts(buffer.join('\n') + '\n') } - process.stdout.addListener('drain', function () { - process.exit(broken || errored ? 1 : 0); - }); + // Don't exit until stdout is empty + process.stdout.addListener('drain', function () { + process.exit(broken || errored ? 1 : 0); + }); + } } } From 57fa14b5e74fa4eb471955672008271bc69eb6b8 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 00:34:37 -0400 Subject: [PATCH 114/409] use 'end' as a completion event for test-suites --- lib/vows.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 5afcdf11..4ecab593 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -167,11 +167,11 @@ function addVows(tests) { return match; })(tests); - vows.remaining += remaining; - - this.addListener("success", function () { + this.addListener("end", function () { var topic, vow, env; + vows.remaining += remaining; + if (typeof(tests) === 'function') { return tests.call(null); } else { @@ -376,7 +376,7 @@ vows.tell = function (subject) { if (!vows.options.brief) { puts('\n' + stylize(subject, 'underline') + '\n'); } - promise.emit("success"); + promise.emit("end"); }); start = new(Date); From cb3ab7e570f6f4de614de4114c359a73bb964f8a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 00:34:52 -0400 Subject: [PATCH 115/409] don't require a topic at all --- lib/vows.js | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 4ecab593..334e1b6d 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -188,34 +188,32 @@ function addVows(tests) { topic = lastTopic = ('topic' in ctx.tests) ? ctx.tests.topic : lastTopic; - if (topic) { - // Topic isn't a function, wrap it into one. - if (typeof(topic) !== 'function') { - topic = (function (topic) { - return function () { return topic }; - })(topic); - } + // Topic isn't a function, wrap it into one. + if (typeof(topic) !== 'function') { + topic = (function (topic) { + return function () { return topic }; + })(topic); + } - // Run the topic, passing the previous context topics - topic = topic.apply(ctx.env, ctx.topics); + // Run the topic, passing the previous context topics + topic = topic.apply(ctx.env, ctx.topics); - // 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 instanceof vows.options.Emitter)) { - var emitter = new(vows.options.Emitter); + // 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 instanceof vows.options.Emitter)) { + var emitter = new(vows.options.Emitter); - process.nextTick(function (val) { - return function () { emitter.emit("success", val) }; - }(topic)); topic = emitter; - } + process.nextTick(function (val) { + return function () { emitter.emit("success", val) }; + }(topic)); topic = emitter; + } - topic.addListener('success', 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. - ctx.topics.unshift(val); - }); - } else { topic = null } + topic.addListener('success', 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. + ctx.topics.unshift(val); + }); // Now run the tests, or sub-contexts Object.keys(ctx.tests).filter(function (k) { From 0dd8387999755976460c13fb94c7f1f6ae3965fd Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 00:34:59 -0400 Subject: [PATCH 116/409] tests. --- test/addvow-test.js | 2 +- test/vows-test.js | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/test/addvow-test.js b/test/addvow-test.js index 72b8a5bc..41a0af55 100644 --- a/test/addvow-test.js +++ b/test/addvow-test.js @@ -14,7 +14,7 @@ var promiser = function (val) { } }; -vows.tell("Vows:unit").addVows(function () { +vows.describe("Vows/unit").addVows(function () { promiser("hello world")().addVow(function (val) { assert.equal(val, "hello world"); }, "addVow()"); diff --git a/test/vows-test.js b/test/vows-test.js index dc939a80..315eee04 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -136,6 +136,17 @@ vows.describe("Vows").addVows({ } }).addVows({ "A 2nd test suite": { + topic: function () { + var p = new(events.EventEmitter); + setTimeout(function () { + p.emit("success"); + }, 100); + return p; + }, "should run after the first": function () {} } +}).addVows({ + "A 3rd test suite": { + "should run last": function () {} + } }); From 4fc5b9c3d29d1629d603adaa465ccdde70fa0b84 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 01:59:21 -0400 Subject: [PATCH 117/409] only count vows if passing an object to addVows() --- lib/vows.js | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 334e1b6d..46a0127f 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -148,24 +148,26 @@ function addVows(tests) { vows.promises.push(promise); - // Count the number of vows/promises expected to fire, - // so we know when the tests are over. - // We match the keys against `matcher`, to decide - // whether or not they should be included in the test. - (function count(tests) { - var match = false; - remaining++; - Object.keys(tests).forEach(function (key) { - if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) { - if (! (match = count(tests[key]) || - match || vows.options.matcher.test(key))) { - delete tests[key]; - remaining--; + if (typeof(tests) === 'object') { + // Count the number of vows/promises expected to fire, + // so we know when the tests are over. + // We match the keys against `matcher`, to decide + // whether or not they should be included in the test. + (function count(tests) { + var match = false; + remaining++; + Object.keys(tests).forEach(function (key) { + if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) { + if (! (match = count(tests[key]) || + match || vows.options.matcher.test(key))) { + delete tests[key]; + remaining--; + } } - } - }); - return match; - })(tests); + }); + return match; + })(tests); + } this.addListener("end", function () { var topic, vow, env; From a54c07665a0e922996a7b843a231a4ba57b5587f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 01:59:52 -0400 Subject: [PATCH 118/409] when passing a function, there is no promise, also print an nl --- lib/vows.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 46a0127f..be77bbcc 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -289,7 +289,8 @@ function tryFinish(remaining, promise) { // If this isn't the last test suite in the chain, // emit 'end', to trigger the next test suite. - if (promise.listeners('end').length > 0) { + if (promise && promise.listeners('end').length > 0) { + sys.print('\n'); promise.emit('end', honored, broken, errored); } else { if (!vows.options.brief) { From 15648b13fcb6bf2e61909d0c72d874dac590cb2b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 02:00:12 -0400 Subject: [PATCH 119/409] 'end' takes some parameters --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index be77bbcc..b461bc95 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -169,7 +169,7 @@ function addVows(tests) { })(tests); } - this.addListener("end", function () { + this.addListener("end", function (honored, broken, errored) { var topic, vow, env; vows.remaining += remaining; From fee2e78a5eac73d7d91e8b470bc25c072d01d382 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 02:01:50 -0400 Subject: [PATCH 120/409] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 772a6a64..0f6dc41e 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,5 @@ "contributors" : [], "dependencies" : ["eyes"], "lib" : "lib", - "version" : "0.1.1" + "version" : "0.1.2" } From 06290eec4b32371b38cb8031a6f97283b96317f4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 02:04:02 -0400 Subject: [PATCH 121/409] updated readme/comments to new API --- README.md | 6 +++--- lib/vows.js | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ad5a5b41..920e2d60 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Vows ==== -asynchronous testing for node.js +> Asynchronous BDD & continuous integration for node.js introduction ------------ @@ -17,7 +17,7 @@ synopsis var vows = require('vows'), assert = require('assert'); - vows.tell('Deep Thought', function () { + vows.describe('Deep Thought').addVows(function () { question('what is the answer to the universe?').addVow(function (answer) { assert.equals(answer, 42); }, 'it should know the answer to the ultimate question of life'); @@ -32,7 +32,7 @@ Vows are run as soon as the promise completes, so the order in which they are ru writing specs ------------- - vows.tell('A Database library', { + vows.describe('A Database library').addVows({ // run this once, and execute the following tests when it completes topic: function () { return new(DB) }, diff --git a/lib/vows.js b/lib/vows.js index b461bc95..8db67ccc 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -1,12 +1,11 @@ // -// Vows.js - asynchronous promise-based testing for node.js +// Vows.js - asynchronous event-based BDD for node.js // // usage: // -// var vows = require('vows'), -// assert = require('assert'); +// var vows = require('vows'); // -// vows.tell('Deep Thought', function () { +// vows.describe('Deep Thought').addVows(function () { // question('what is the answer to the universe?').addVow(function (answer) { // assert.equals(answer, 42); // }, 'it should know the answer to the ultimate question of life'); From 8a4f76d70ce7142ec3b44c074425fcc8e1a06be6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 14:04:53 -0400 Subject: [PATCH 122/409] vows.describe is the default now --- lib/vows.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 8db67ccc..9f31009f 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -359,7 +359,7 @@ vows.options = { // Run all vows/tests. // It can take either a function as `tests`, // or an object literal. -vows.tell = function (subject) { +vows.describe = function (subject) { this.options.Emitter.prototype.addVow = addVow; this.options.Emitter.prototype.addVows = addVows; this.remaining = 0; @@ -383,7 +383,7 @@ vows.tell = function (subject) { return promise; }; -vows.describe = vows.tell; +vows.tell = vows.describe; // Return the `vows` object after setting some options vows.config = function (opts) { From 99eb7debcee0bc0fecb936dadfc718f2d18fbbe0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 14:05:19 -0400 Subject: [PATCH 123/409] improved emitter code in describe() --- lib/vows.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 9f31009f..11d27a13 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -370,17 +370,18 @@ vows.describe = function (subject) { broken = 0, errored = 0; buffer = [], suites = 0; - var promise = new(events.EventEmitter); - process.nextTick(function () { if (!vows.options.brief) { puts('\n' + stylize(subject, 'underline') + '\n'); } - promise.emit("end"); }); - start = new(Date); - - return promise; + return new(events.EventEmitter)().addListener('newListener', function (e, listener) { + if (e === 'end') { + this.removeListener(e, listener); + start = new(Date); + listener.call(this); + } + }); }; vows.tell = vows.describe; From e43aa0edf300ed5db9f73ce867990c5a1e241fb0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 14:28:30 -0400 Subject: [PATCH 124/409] added assert.length & assert.isFunction --- lib/vows/macros.js | 9 +++++++++ test/vows-test.js | 3 +++ 2 files changed, 12 insertions(+) diff --git a/lib/vows/macros.js b/lib/vows/macros.js index 18b4aa4e..fd18abc6 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -50,6 +50,12 @@ assert.isEmpty = function (actual, message) { } }; +assert.length = function (actual, expected, message) { + if (actual.length !== expected) { + assert.fail(actual, expected, message || "expected {actual} to have {expected} elements", "length", assert.length); + } +}; + assert.isArray = function (actual, message) { assertTypeOf(actual, 'array', message || "expected {actual} to be an Array", assert.isArray); }; @@ -62,6 +68,9 @@ assert.isNumber = function (actual, message) { assert.isString = function (actual, message) { assertTypeOf(actual, 'string', message || "expected {actual} to be a String", assert.isString); }; +assert.isFunction = function (actual, message) { + assertTypeOf(actual, 'function', message || "expected {actual} to be a Function", assert.isFunction); +}; assert.typeOf = function (actual, expected, message) { assertTypeOf(actual, expected, message, assert.typeOf); }; diff --git a/test/vows-test.js b/test/vows-test.js index 315eee04..c2571c8f 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -31,6 +31,9 @@ vows.describe("Vows").addVows({ "testing match": function (it) { assert.match(it, /[a-z]+ [a-z]+/); }, + "testing length": function (it) { + assert.length(it, 11); + }, "testing inclusion": function (it) { assert.include(it, "world"); }, From d79b6d5a44266c1a0d2dc833e59772b796dde7f5 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 11 May 2010 20:17:58 -0400 Subject: [PATCH 125/409] fixed assert.include on objects --- lib/vows/macros.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vows/macros.js b/lib/vows/macros.js index fd18abc6..f66fda19 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -33,9 +33,9 @@ assert.matches = assert.match; assert.include = function (actual, expected, message) { if ((function (obj) { if (isArray(obj) || isString(obj)) { - return obj.indexOf(expected) === -1 + return obj.indexOf(expected) === -1; } else if (isObject(actual)) { - return ! obj.hasOwnProperty(item); + return ! obj.hasOwnProperty(expected); } return false; })(actual)) { From e97b946deeaae1f25d0b710369e2179ac53f450e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 12 May 2010 23:58:50 -0400 Subject: [PATCH 126/409] pass emitted errors if test is expecting it --- lib/vows.js | 31 ++++++++++++++++++++++++------- test/vows-test.js | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 11d27a13..42054b8e 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -89,12 +89,34 @@ function addVow(/* description & callback */) { } return this.addListener("success", function () { + var args = Array.prototype.slice.call(arguments); + // 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) { + args.unshift(null); + } + runTest(args); + + }).addListener("error", function (err) { + var exception; + + if (vow.callback.length >= 2) { + runTest([err]); + } else { + exception = " * " + stylize('The promise returned an error: ' + + stylize(err, 'bold'), 'red'); + errored++; + output('- ' + stylize(vow.description, 'red'), exception + "\n"); + } + }); + + function runTest(args) { var title = ' - ', exception, topic, msg; // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { - vow.callback.apply(args[0].binding || null, arguments); + vow.callback.apply(vow.binding || null, args); title += stylize(vow.description, 'green'); honored++; } catch (e) { @@ -110,12 +132,7 @@ function addVow(/* description & callback */) { } } output(title, exception); - }).addListener("error", function (err) { - var exception = " * " + stylize('The promise returned an error: ' + - stylize(err, 'bold'), 'red'); - errored++; - output('- ' + stylize(vow.description, 'red'), exception + "\n"); - }); + } function output(title, exception) { if (exception || !vows.options.brief) { diff --git a/test/vows-test.js b/test/vows-test.js index c2571c8f..7117750f 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -130,13 +130,42 @@ vows.describe("Vows").addVows({ } } }, - "Non-functions as subjects": { + "Non-functions as topics": { topic: 45, - "should work as expected": function (subject) { - assert.equal(subject, 45); + "should work as expected": function (topic) { + assert.equal(topic, 45); } - } + }, + "A topic emitting an error": { + topic: function () { + var promise = new(events.EventEmitter); + process.nextTick(function () { + promise.emit("error", 404); + }); + return promise; + }, + "shouldn't raise an exception if the test expects it": function (e, res) { + assert.equal(e, 404); + assert.ok(! res); + } + }, + "A topic not emitting an error": { + topic: function () { + var promise = new(events.EventEmitter); + process.nextTick(function () { + promise.emit("success", true); + }); + return promise; + }, + "should pass `null` as first argument, if the test is expecting an error": function (e, res) { + assert.strictEqual(e, null); + assert.equal(res, true); + }, + "should pass the result as first argument if the test isn't expecting an error": function (res) { + assert.equal(res, true); + } + }, }).addVows({ "A 2nd test suite": { topic: function () { From 0cefa915b6887bf900bacd14391c1efa89abbab6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 12 May 2010 23:59:38 -0400 Subject: [PATCH 127/409] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0f6dc41e..2198c6cd 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,5 @@ "contributors" : [], "dependencies" : ["eyes"], "lib" : "lib", - "version" : "0.1.2" + "version" : "0.1.3" } From 219ea8152914812981916061f215719a0504c321 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 14 May 2010 16:48:17 -0400 Subject: [PATCH 128/409] fix lastTopic not being set properly --- lib/vows.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 42054b8e..94a83879 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -204,8 +204,8 @@ function addVows(tests) { (function run(ctx, lastTopic) { var ctxAdded = false; - topic = lastTopic = ('topic' in ctx.tests) ? ctx.tests.topic - : lastTopic; + topic = ('topic' in ctx.tests) ? ctx.tests.topic + : lastTopic; // Topic isn't a function, wrap it into one. if (typeof(topic) !== 'function') { topic = (function (topic) { @@ -214,7 +214,7 @@ function addVows(tests) { } // Run the topic, passing the previous context topics - topic = topic.apply(ctx.env, ctx.topics); + topic = lastTopic = topic.apply(ctx.env, ctx.topics); // 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. From 28f23cac7d9995d9f14f2fdb275876ddd97b5bb5 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 14 May 2010 19:35:09 -0400 Subject: [PATCH 129/409] make sure result doesn't precede title --- lib/vows.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 94a83879..614dc6f5 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -393,10 +393,13 @@ vows.describe = function (subject) { } }); return new(events.EventEmitter)().addListener('newListener', function (e, listener) { + var that = this; if (e === 'end') { this.removeListener(e, listener); - start = new(Date); - listener.call(this); + process.nextTick(function () { + start = new(Date); + listener.call(that); + }); } }); }; From 053d7de53666dc6c2a97ad00190cec1e1f8c77d1 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 14 May 2010 23:39:33 -0400 Subject: [PATCH 130/409] (test) topics returning functions --- test/vows-test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/vows-test.js b/test/vows-test.js index 7117750f..1056e0cf 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -137,6 +137,22 @@ vows.describe("Vows").addVows({ assert.equal(topic, 45); } }, + "topics returning functions": { + topic: function () { + return function () { return 42 }; + }, + + "should work as expected": function (topic) { + assert.isFunction(topic); + assert.equal(topic(), 42); + }, + "in a sub-context": { + "should work as expected": function (topic) { + assert.isFunction(topic); + assert.equal(topic(), 42); + }, + } + }, "A topic emitting an error": { topic: function () { var promise = new(events.EventEmitter); From 6454351045b03b48d9231da583d6274c149d7d04 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 14 May 2010 23:45:42 -0400 Subject: [PATCH 131/409] fixed bug with function returning topics --- lib/vows.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 614dc6f5..e415f6bf 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -202,19 +202,18 @@ function addVows(tests) { // promise to emit (the topic), at which point it runs the functions under it, // passing the topic as an argument. (function run(ctx, lastTopic) { - var ctxAdded = false; - - topic = ('topic' in ctx.tests) ? ctx.tests.topic - : lastTopic; - // Topic isn't a function, wrap it into one. - if (typeof(topic) !== 'function') { - topic = (function (topic) { - return function () { return topic }; - })(topic); + topic = ctx.tests.topic; + + if (typeof(topic) === 'function') { + // Run the topic, passing the previous context topics + topic = topic.apply(ctx.env, ctx.topics); } - // Run the topic, passing the previous context topics - topic = lastTopic = topic.apply(ctx.env, ctx.topics); + if (topic) { + lastTopic = topic; + } else { + topic = lastTopic; + } // 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. From 1cb886c327a401e292098605de620678ccfdee58 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 16 May 2010 01:12:43 -0400 Subject: [PATCH 132/409] (fix) count vows properly, by skipping 'topic' keys --- lib/vows.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index e415f6bf..50662618 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -172,8 +172,10 @@ function addVows(tests) { (function count(tests) { var match = false; remaining++; - Object.keys(tests).forEach(function (key) { - if (typeof(tests[key]) === "object" && !Array.isArray(tests[key])) { + Object.keys(tests).filter(function (k) { + return k !== 'topic'; + }).forEach(function (key) { + if (typeof(tests[key]) === "object") { if (! (match = count(tests[key]) || match || vows.options.matcher.test(key))) { delete tests[key]; From f8872064cce4357a5ba502b2a63789d1057b6263 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 16 May 2010 01:13:24 -0400 Subject: [PATCH 133/409] (fix) output the subjects without need for nextTick --- lib/vows.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 50662618..6661591c 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -281,10 +281,10 @@ function addVows(tests) { function puts() { var args = Array.prototype.slice.call(arguments); - if (vows.promises[suites - 1].listeners('finish').length > 0) { + if (vows.promises.length && vows.promises[suites - 1].listeners('finish').length > 0) { buffer.push(args.join('\n')); } else { - sys.puts.apply(null, args); + sys.puts.apply(null, args); } } @@ -388,11 +388,10 @@ vows.describe = function (subject) { broken = 0, errored = 0; buffer = [], suites = 0; - process.nextTick(function () { - if (!vows.options.brief) { - puts('\n' + stylize(subject, 'underline') + '\n'); - } - }); + if (!vows.options.brief) { + puts('\n' + stylize(subject, 'underline') + '\n'); + } + return new(events.EventEmitter)().addListener('newListener', function (e, listener) { var that = this; if (e === 'end') { From f59fb55dc05f73d8a8a55a982163ed52915f5ed0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 16 May 2010 01:14:50 -0400 Subject: [PATCH 134/409] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2198c6cd..3d016269 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,5 @@ "contributors" : [], "dependencies" : ["eyes"], "lib" : "lib", - "version" : "0.1.3" + "version" : "0.1.4" } From 1882f196ad569f646de07c29adf2007071d824d1 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 16 May 2010 02:31:53 -0400 Subject: [PATCH 135/409] (fix) topics getting added multiple times --- lib/vows.js | 10 +++++++++- test/vows-test.js | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 6661591c..df56385a 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -204,6 +204,7 @@ function addVows(tests) { // promise to emit (the topic), at which point it runs the functions under it, // passing the topic as an argument. (function run(ctx, lastTopic) { + var old = false; topic = ctx.tests.topic; if (typeof(topic) === 'function') { @@ -211,9 +212,13 @@ function addVows(tests) { topic = topic.apply(ctx.env, ctx.topics); } + // If this context has a topic, store it in `lastTopic`, + // if not, use the last topic, passed down by a parent + // context. if (topic) { lastTopic = topic; } else { + old = true; topic = lastTopic; } @@ -231,7 +236,10 @@ function addVows(tests) { // 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. - ctx.topics.unshift(val); + // If we're using the parent topic, no need to + // prepend it to the topics list, or we'll get + // duplicates. + if (! old) ctx.topics.unshift(val); }); // Now run the tests, or sub-contexts diff --git a/test/vows-test.js b/test/vows-test.js index 1056e0cf..1d767073 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -110,6 +110,26 @@ vows.describe("Vows").addVows({ } } }, + "Nested contexts with topic gaps": { + topic: 45, + "should": { + "pass": { + topic: 101, + "the": { + "values": { + topic: function (prev, prev2) { + return this.context.topics.slice(0); + }, + "down": function (topics) { + assert.length(topics, 2); + assert.equal(topics[0], 101); + assert.equal(topics[1], 45); + } + } + } + } + } + }, "Non-promise return value": { topic: function () { return 1 }, "should be converted to a promise": function (val) { From aba0f57fb665bb73ca60c2e517f3d897b772a571 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sun, 16 May 2010 01:07:39 -0700 Subject: [PATCH 136/409] updated SS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 920e2d60..c8ad8e0a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ There are two reasons why we might want asynchronous testing. The first, and obv _Vows_ is an experiment in making this possible, while adding a minimum of overhead. -![vows-ss](http://dl.dropbox.com/u/251849/vows-ss.gif) +![vows-ss](http://files.droplr.com/files/36156834/ZfmbC.Screen%20shot%202010-05-11%20at%2020:19:25.png) synopsis -------- From dc9a7461d54c23e2965c5a8af683a5666ea4b1c6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 17 May 2010 01:42:44 -0400 Subject: [PATCH 137/409] Decouple the reporting system. --- lib/vows.js | 140 +++++++++++++++++++---------------- lib/vows/printers/console.js | 73 ++++++++++++++++++ lib/vows/printers/json.js | 3 + 3 files changed, 151 insertions(+), 65 deletions(-) create mode 100644 lib/vows/printers/console.js create mode 100644 lib/vows/printers/json.js diff --git a/lib/vows.js b/lib/vows.js index df56385a..9a547379 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -22,6 +22,21 @@ var sys = require('sys'), eyes = require('eyes').inspector({ writer: null }), vows = exports; +// Options +vows.options = { + Emitter: events.EventEmitter, + brief: false, + json: false, + matcher: /.*/, + printer: require('vows/printers/console') +}; + +vows.__defineGetter__('printer', function () { + return vows.options.printer; +}); + +var stylize = require('vows/printers/console').stylize; + // Keeps track of the outcome of vows. var total = 0, honored = 0, broken = 0, errored = 0, @@ -36,6 +51,32 @@ var lastContext; // Output buffer var buffer; +var argv = []; +// +// Parse command-line parameters +// +for (var i = 0, arg; i < process.argv.length; i++) { + arg = process.argv[i]; + + if (arg === __filename) { continue } + + if (arg[0] !== '-') { + argv.push(arg); + } else { + arg = arg.match(/^--?(.*)/)[1]; + + if (arg[0] === 'R') { + vows.options.matcher = new(RegExp)(arg.slice(1)); + } else if (arg in vows.options) { + vows.options[arg] = true; + } + } +} + +// Get rid of process runner +// ('node' in most cases) +argv = argv.slice(1); + // // Assertion Macros // @@ -96,6 +137,7 @@ function addVow(/* description & callback */) { args.unshift(null); } runTest(args); + tryFinish(vows.remaining, vow.promise); }).addListener("error", function (err) { var exception; @@ -103,47 +145,47 @@ function addVow(/* description & callback */) { if (vow.callback.length >= 2) { runTest([err]); } else { - exception = " * " + stylize('The promise returned an error: ' + - stylize(err, 'bold'), 'red'); + exception = { type: 'promise', error: err }; errored++; - output('- ' + stylize(vow.description, 'red'), exception + "\n"); + output('errored', exception); } + tryFinish(vows.remaining, vow.promise); }); function runTest(args) { - var title = ' - ', exception, topic, msg; + var exception, topic, status; // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { vow.callback.apply(vow.binding || null, args); - title += stylize(vow.description, 'green'); + output('honored', exception); honored++; } catch (e) { if (e.name && e.name.match(/AssertionError/)) { - title += stylize(vow.description, 'yellow'); - exception = ' ~ ' + e.toString(); + exception = e.toString(); + output('broken', exception); broken++; } else { - title += stylize(vow.description, 'red'); - msg = e.stack || e.message || e.toString() || e; - exception = ' ! ' + stylize(msg, 'red'); + exception = e.stack || e.message || e.toString() || e; errored++; + output('errored', exception); } } - output(title, exception); } - function output(title, exception) { + function output(status, exception) { if (exception || !vows.options.brief) { if (vow.context && lastContext !== vow.context) { lastContext = vow.context; - puts(vow.context); + vows.printer.print(['context', vow.context]); } - puts(title); - if (exception) puts(exception); + vows.printer.print(['vow', { + title: vow.description, + status: status, + exception: exception || null + }]); } - tryFinish(vows.remaining, vow.promise); } }; @@ -287,49 +329,40 @@ function addVows(tests) { return promise; } -function puts() { - var args = Array.prototype.slice.call(arguments); - if (vows.promises.length && vows.promises[suites - 1].listeners('finish').length > 0) { - buffer.push(args.join('\n')); - } else { - sys.puts.apply(null, args); - } -} - // // Checks if all the required tests have been run, // and either triggers the next test suite, if any, // or exits the process. // function tryFinish(remaining, promise) { - var result, style; + var result, style, time; // Output results once all the vows have been checked if (honored + broken + errored === total && remaining === 0) { - result = honored + " honored, " + - broken + " broken, " + - errored + " errored", - style = honored === total ? ('green') - : (errored === 0 ? 'yellow' : 'red'); - // If this isn't the last test suite in the chain, // emit 'end', to trigger the next test suite. if (promise && promise.listeners('end').length > 0) { - sys.print('\n'); + if (vows.options.json) { + puts(['end']); + } else { + sys.print('\n'); + } promise.emit('end', honored, broken, errored); } else { - if (!vows.options.brief) { - puts("\nVerified " + total + " vows in " + - (((new(Date)) - start) / 1000) + " seconds."); - puts("\n" + stylize(result, style)); - } - + time = (new(Date) - start) / 1000; // The 'finish' event is triggered once all the tests have been run. // It's used by bin/vows vows.promises[suites - 1].emit("finish", honored, broken, errored); - if ((broken || errored) && buffer.length) { sys.puts(buffer.join('\n') + '\n') } + vows.printer.print([ 'finish', { + honored: honored, + broken: broken, + errored: errored, + total: total, + time: time + }]); + if ((broken || errored) && buffer.length) { sys.puts(buffer.join('\n') + '\n') } // Don't exit until stdout is empty process.stdout.addListener('drain', function () { process.exit(broken || errored ? 1 : 0); @@ -344,7 +377,7 @@ function tryFinish(remaining, promise) { // process.addListener('exit', function () { if (honored + broken + errored < total) { - sys.puts('\n* ' + stylize("An EventEmitter has failed to fire.", 'red')); + vows.printer.print(['error', { error: "An EventEmitter has failed to fire.", type: 'promise' }]); } }); @@ -375,13 +408,6 @@ vows.prepare = function (obj, targets) { return obj; }; -// Options -vows.options = { - Emitter: events.EventEmitter, - brief: false, - matcher: /.*/ -}; - // Run all vows/tests. // It can take either a function as `tests`, // or an object literal. @@ -397,7 +423,7 @@ vows.describe = function (subject) { buffer = [], suites = 0; if (!vows.options.brief) { - puts('\n' + stylize(subject, 'underline') + '\n'); + this.printer.print(['subject', subject]); } return new(events.EventEmitter)().addListener('newListener', function (e, listener) { @@ -412,8 +438,6 @@ vows.describe = function (subject) { }); }; -vows.tell = vows.describe; - // Return the `vows` object after setting some options vows.config = function (opts) { for (var k in opts) { this.options[k] = opts[k] } @@ -428,17 +452,3 @@ function inspect(val) { return '\033[1m' + eyes(val) + '\033[22m'; } -// Stylize a string -function stylize(str, style) { - var styles = { - 'bold' : [1, 22], - 'underline' : [4, 24], - 'yellow' : [33, 39], - 'green' : [32, 39], - 'red' : [31, 39], - 'grey' : [90, 39] - }; - return '\033[' + styles[style][0] + 'm' + str + - '\033[' + styles[style][1] + 'm'; -} - diff --git a/lib/vows/printers/console.js b/lib/vows/printers/console.js new file mode 100644 index 00000000..2b19a7d2 --- /dev/null +++ b/lib/vows/printers/console.js @@ -0,0 +1,73 @@ +var sys = require('sys'); +// +// Console printer +// +this.print = function (data) { + var event = data[1]; + + switch (data[0]) { + case 'subject': + puts('\n' + stylize(event, 'underline') + '\n'); + break; + case 'context': + puts(event); + break; + case 'vow': + puts(' - ' + stylize(event.title, ({ + honored: 'green', broken: 'yellow', errored: 'red' + })[event.status])); + if (event.status === 'broken') { + puts(' ~ ' + event.exception); + } else if (event.status === 'errored') { + if (event.exception.type === 'promise') { + puts(' * ' + stylize("An 'error' event was caught: " + + stylize(event.exception.error, 'bold'), 'red')); + } else { + puts(' ! ' + stylize(event.exception, 'red')); + } + } + break; + case 'end': + sys.print('\n'); + break; + case 'finish': + var result = event.honored + " honored, " + + event.broken + " broken, " + + event.errored + " errored", + style = event.honored === event.total ? ('green') + : (event.errored === 0 ? 'yellow' : 'red'); + + puts("\nVerified " + event.total + " vows in " + + (event.time + " seconds.")); + puts("\n" + stylize(result, style)); + break; + case 'error': + puts('\n * ' + stylize(event.error, 'red')); + break; + } +}; + +function puts(args) { + args = Array.prototype.slice.call(arguments).map(function (a) { + return a.replace(/`([^`]+)`/g, function (_, capture) { return stylize(capture, 'italic') }) + .replace(/\*([^*]+)\*/g, function (_, capture) { return stylize(capture, 'bold') }); + }); + sys.puts.apply(null, args); +} + +// Stylize a string +function stylize(str, style) { + var styles = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'yellow' : [33, 39], + 'green' : [32, 39], + 'red' : [31, 39], + 'grey' : [90, 39], + 'green-hi' : [92, 32], + }; + return '\033[' + styles[style][0] + 'm' + str + + '\033[' + styles[style][1] + 'm'; +} +this.stylize = stylize; diff --git a/lib/vows/printers/json.js b/lib/vows/printers/json.js new file mode 100644 index 00000000..896400d5 --- /dev/null +++ b/lib/vows/printers/json.js @@ -0,0 +1,3 @@ +this.print = function (obj) { + sys.puts(JSON.stringify(obj)); +}; From a604161c2388801db50e712d4d07014dacfbdc79 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 17 May 2010 01:53:06 -0400 Subject: [PATCH 138/409] renamed 'printer' -> 'reporter' --- lib/vows.js | 26 +++++++++------------ lib/vows/{printers => reporters}/console.js | 4 ++-- lib/vows/{printers => reporters}/json.js | 0 3 files changed, 13 insertions(+), 17 deletions(-) rename lib/vows/{printers => reporters}/console.js (97%) rename lib/vows/{printers => reporters}/json.js (100%) diff --git a/lib/vows.js b/lib/vows.js index 9a547379..d20983c7 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -28,14 +28,14 @@ vows.options = { brief: false, json: false, matcher: /.*/, - printer: require('vows/printers/console') + reporter: require('vows/reporters/console') }; -vows.__defineGetter__('printer', function () { - return vows.options.printer; +vows.__defineGetter__('reporter', function () { + return vows.options.reporter; }); -var stylize = require('vows/printers/console').stylize; +var stylize = require('vows/reporters/console').stylize; // Keeps track of the outcome of vows. var total = 0, honored = 0, @@ -178,9 +178,9 @@ function addVow(/* description & callback */) { if (exception || !vows.options.brief) { if (vow.context && lastContext !== vow.context) { lastContext = vow.context; - vows.printer.print(['context', vow.context]); + vows.reporter.report(['context', vow.context]); } - vows.printer.print(['vow', { + vows.reporter.report(['vow', { title: vow.description, status: status, exception: exception || null @@ -342,11 +342,7 @@ function tryFinish(remaining, promise) { // If this isn't the last test suite in the chain, // emit 'end', to trigger the next test suite. if (promise && promise.listeners('end').length > 0) { - if (vows.options.json) { - puts(['end']); - } else { - sys.print('\n'); - } + vows.reporter.report(['end']); promise.emit('end', honored, broken, errored); } else { time = (new(Date) - start) / 1000; @@ -354,7 +350,7 @@ function tryFinish(remaining, promise) { // It's used by bin/vows vows.promises[suites - 1].emit("finish", honored, broken, errored); - vows.printer.print([ 'finish', { + vows.reporter.report([ 'finish', { honored: honored, broken: broken, errored: errored, @@ -373,11 +369,11 @@ function tryFinish(remaining, promise) { // // On exit, check that all promises have been fired. -// If not, print an error message. +// If not, report an error message. // process.addListener('exit', function () { if (honored + broken + errored < total) { - vows.printer.print(['error', { error: "An EventEmitter has failed to fire.", type: 'promise' }]); + vows.reporter.report(['error', { error: "An EventEmitter has failed to fire.", type: 'promise' }]); } }); @@ -423,7 +419,7 @@ vows.describe = function (subject) { buffer = [], suites = 0; if (!vows.options.brief) { - this.printer.print(['subject', subject]); + this.reporter.report(['subject', subject]); } return new(events.EventEmitter)().addListener('newListener', function (e, listener) { diff --git a/lib/vows/printers/console.js b/lib/vows/reporters/console.js similarity index 97% rename from lib/vows/printers/console.js rename to lib/vows/reporters/console.js index 2b19a7d2..bc61665e 100644 --- a/lib/vows/printers/console.js +++ b/lib/vows/reporters/console.js @@ -1,8 +1,8 @@ var sys = require('sys'); // -// Console printer +// Console reporter // -this.print = function (data) { +this.report = function (data) { var event = data[1]; switch (data[0]) { diff --git a/lib/vows/printers/json.js b/lib/vows/reporters/json.js similarity index 100% rename from lib/vows/printers/json.js rename to lib/vows/reporters/json.js From cc041eecf7dbf4dede654bf7512b05373061e662 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 17 May 2010 02:05:37 -0400 Subject: [PATCH 139/409] fix JSON reporter --- lib/vows/reporters/json.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/vows/reporters/json.js b/lib/vows/reporters/json.js index 896400d5..15359904 100644 --- a/lib/vows/reporters/json.js +++ b/lib/vows/reporters/json.js @@ -1,3 +1,7 @@ -this.print = function (obj) { +var sys = require('sys'); +// +// Console JSON reporter +// +this.report = function (obj) { sys.puts(JSON.stringify(obj)); }; From 8a1d4478a204d61ae123fde13f8214ea5dcaf750 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 17 May 2010 14:09:08 -0400 Subject: [PATCH 140/409] overhaul of continuous testing functionality, to use json backend --- bin/vows | 114 ++++++++++++++++++++++++++----------------------------- 1 file changed, 53 insertions(+), 61 deletions(-) diff --git a/bin/vows b/bin/vows index b6472a1e..740df87a 100755 --- a/bin/vows +++ b/bin/vows @@ -3,53 +3,32 @@ var path = require('path'), sys = require('sys'), fs = require('fs'), - exec = require('child_process').exec, - Script = process.binding('evals').Script; + events = require('events'), + spawn = require('child_process').spawn; require.paths.unshift(path.join(__dirname, '..', 'lib')); -var argv = [], options = { - matcher: /.*/, - brief: false -}; +var console = require('vows/reporters/console'); -// -// Parse command-line parameters -// -for (var i = 0, arg; i < process.argv.length; i++) { - arg = process.argv[i]; +var options = []; - if (arg === __filename) { continue } - - if (arg[0] !== '-') { - argv.push(arg); - } else { - arg = arg.match(/^--?(.*)/)[1]; - - if (arg[0] === 'R') { - options.matcher = new(RegExp)(arg.slice(1)); - } else if (arg in options) { - options[arg] = true; - } +var argv = process.argv.slice(1).filter(function (a) { + if (a !== __filename) { + if (a[0] === '-') { options.push(a) } + return true; } -} - -// Get rid of process runner -// ('node' in most cases) -argv = argv.slice(1); - -var vows = require('vows').config(options); +}); if (argv.length > 0) { argv.forEach(function (arg) { - runTest(arg); + runTest(arg, function (result) { + console.report(result); + }); }); } else { // // Watch mode // - vows.options.brief = true; - (function () { var clock = [ '. ', '.. ', '... ', ' ...', @@ -59,10 +38,10 @@ if (argv.length > 0) { '-', '\\', '|', '/' ]; var current = 0, - status = 0, runningTests = false, + currentFile, gracePeriod = 0, - statusText = '', + statusText, lastRun, testFolder, colors = ['32m', '33m', '31m'], @@ -83,17 +62,18 @@ if (argv.length > 0) { function tick() { cursorSave(); eraseLine(); - esc(colors[status]); + lastRun && esc(colors[statusText.errored ? 2 : (statusText.broken ? 1 : 0)]); - if (runningTests || gracePeriod) { + if (runningTests > 0 || gracePeriod) { gracePeriod--; print(wheel[current]); esc('39m'); - print(' working...'); + print(' detected change in ' + currentFile + '...'); if (current == wheel.length - 1) { current = -1 } } else { print(clock[current]); - print(' ' + statusText); + print(' '); + statusText && print(console.report(['finish', statusText], null)); if (lastRun) { esc('90m'); print( ' (' + lastRun.valueOf() + ')'); @@ -123,26 +103,25 @@ if (argv.length > 0) { // Run the matching tests and change the status. // function changed(file) { - runningTests = true; - gracePeriod = 5; + statusText = { honored: 0, broken: 0, errored: 0 }; + gracePeriod = 10; current = 0; + currentFile = file + '.js'; + file = /-(test|spec)$/.test(file) ? path.join(testFolder, file + '.js') : path.join(testFolder, file + '-' + testFolder + '.js'); - runTest(file).addListener('end', function (h, b, e) { - runningTests = false; - statusText = h + " honored, " + - b + " broken, " + - e + " errored"; - lastRun = new(Date); - - if (b || e) { - eraseLine(); - status = e ? 2 : 1; - } else { - status = 0; - } + paths(testFolder).forEach(function (p) { + runningTests ++; + runTest(p).addListener('finish', function (obj) { + runningTests --; + delete obj.time; + statusText.honored += obj.honored; + statusText.broken += obj.broken; + statusText.errored += obj.errored; + lastRun = new(Date); + }); }); } // @@ -200,13 +179,26 @@ if (argv.length > 0) { })(); } -function runTest(file) { - var code = (function (require, __filename, __dirname) { - /* content */ - return vows.promise; - }).toString().replace('/* content */', fs.readFileSync(file)); +function runTest(file, callback) { + var test = spawn('node', options.concat([file, '--json'])), obj; + var promise = new(events.EventEmitter); + + test.stdout.addListener('data', function (data) { + data.toString('utf8').trim().split('\n').forEach(function (chunk) { + try { + obj = JSON.parse(chunk); + promise.emit(obj[0], obj[1]); + if (callback) { callback(obj) } + } catch (e) { + sys.puts(e, chunk); + } + }); + }); - return Script.runInThisContext('(' + code + ')', file) - .call(global, require, file, path.dirname(file)); + test.stderr.addListener('data', function (data) { + sys.debug(data.stack); + }); + test.addListener('exit', function (code) {}); + return promise; } From 1606341ebff6bc233895661aa973bb091953528d Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 17 May 2010 14:09:31 -0400 Subject: [PATCH 141/409] use json reporter if --json is passed --- lib/vows.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index d20983c7..784192c6 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -27,12 +27,15 @@ vows.options = { Emitter: events.EventEmitter, brief: false, json: false, - matcher: /.*/, - reporter: require('vows/reporters/console') + matcher: /.*/ }; vows.__defineGetter__('reporter', function () { - return vows.options.reporter; + if (vows.options.json) { + return require('vows/reporters/json'); + } else { + return require('vows/reporters/console'); + } }); var stylize = require('vows/reporters/console').stylize; From 98c70ad001635462111b999c38ca0b934a202104 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 17 May 2010 14:10:03 -0400 Subject: [PATCH 142/409] make console reporter a little more powerful --- lib/vows/reporters/console.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/vows/reporters/console.js b/lib/vows/reporters/console.js index bc61665e..c22aed9b 100644 --- a/lib/vows/reporters/console.js +++ b/lib/vows/reporters/console.js @@ -2,9 +2,14 @@ var sys = require('sys'); // // Console reporter // -this.report = function (data) { +var stream, buffer; + +this.report = function (data, s) { var event = data[1]; + stream = typeof(s) === 'object' ? s : process.stdout; + buffer = []; + switch (data[0]) { case 'subject': puts('\n' + stylize(event, 'underline') + '\n'); @@ -37,14 +42,17 @@ this.report = function (data) { style = event.honored === event.total ? ('green') : (event.errored === 0 ? 'yellow' : 'red'); - puts("\nVerified " + event.total + " vows in " + - (event.time + " seconds.")); - puts("\n" + stylize(result, style)); + if (event.time) { + puts("\nVerified " + event.total + " vows in " + + (event.time + " seconds.\n")); + } + puts(stylize(result, style)); break; case 'error': puts('\n * ' + stylize(event.error, 'red')); break; } + return buffer.join(''); }; function puts(args) { @@ -52,7 +60,7 @@ function puts(args) { return a.replace(/`([^`]+)`/g, function (_, capture) { return stylize(capture, 'italic') }) .replace(/\*([^*]+)\*/g, function (_, capture) { return stylize(capture, 'bold') }); }); - sys.puts.apply(null, args); + return stream ? stream.write(args.join('\n') + '\n') : buffer.push(args); } // Stylize a string From bf80becdaa97f17c00012f273a2448b74b945919 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 17 May 2010 14:12:20 -0400 Subject: [PATCH 143/409] time can equal 0, check more reliably --- lib/vows/reporters/console.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/reporters/console.js b/lib/vows/reporters/console.js index c22aed9b..ebf5d076 100644 --- a/lib/vows/reporters/console.js +++ b/lib/vows/reporters/console.js @@ -42,7 +42,7 @@ this.report = function (data, s) { style = event.honored === event.total ? ('green') : (event.errored === 0 ? 'yellow' : 'red'); - if (event.time) { + if ('time' in event) { puts("\nVerified " + event.total + " vows in " + (event.time + " seconds.\n")); } From fba631c40794b0edda6fb1521944371e87a65218 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 17 May 2010 14:13:45 -0400 Subject: [PATCH 144/409] (minor) renamed statusText to status --- bin/vows | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bin/vows b/bin/vows index 740df87a..d5d9b5c3 100755 --- a/bin/vows +++ b/bin/vows @@ -38,10 +38,10 @@ if (argv.length > 0) { '-', '\\', '|', '/' ]; var current = 0, - runningTests = false, + runningTests = 0, currentFile, gracePeriod = 0, - statusText, + status, lastRun, testFolder, colors = ['32m', '33m', '31m'], @@ -62,7 +62,7 @@ if (argv.length > 0) { function tick() { cursorSave(); eraseLine(); - lastRun && esc(colors[statusText.errored ? 2 : (statusText.broken ? 1 : 0)]); + lastRun && esc(colors[status.errored ? 2 : (status.broken ? 1 : 0)]); if (runningTests > 0 || gracePeriod) { gracePeriod--; @@ -73,7 +73,7 @@ if (argv.length > 0) { } else { print(clock[current]); print(' '); - statusText && print(console.report(['finish', statusText], null)); + status && print(console.report(['finish', status], null)); if (lastRun) { esc('90m'); print( ' (' + lastRun.valueOf() + ')'); @@ -103,7 +103,7 @@ if (argv.length > 0) { // Run the matching tests and change the status. // function changed(file) { - statusText = { honored: 0, broken: 0, errored: 0 }; + status = { honored: 0, broken: 0, errored: 0 }; gracePeriod = 10; current = 0; @@ -117,9 +117,9 @@ if (argv.length > 0) { runTest(p).addListener('finish', function (obj) { runningTests --; delete obj.time; - statusText.honored += obj.honored; - statusText.broken += obj.broken; - statusText.errored += obj.errored; + status.honored += obj.honored; + status.broken += obj.broken; + status.errored += obj.errored; lastRun = new(Date); }); }); From babc2e63240c1fa84f93e83074282b3742b44e63 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 17 May 2010 14:15:32 -0400 Subject: [PATCH 145/409] version bump to 0.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d016269..afaa377e 100644 --- a/package.json +++ b/package.json @@ -7,5 +7,5 @@ "contributors" : [], "dependencies" : ["eyes"], "lib" : "lib", - "version" : "0.1.4" + "version" : "0.2.0" } From 748e1ee8fffeb9d14732b32df78fa2d01290be57 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 18 May 2010 20:41:13 -0400 Subject: [PATCH 146/409] added 'install' task --- Makefile | 7 ++++++- README.md | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8fdbaf04..1ecf00ee 100644 --- a/Makefile +++ b/Makefile @@ -5,4 +5,9 @@ test: @@node test/vows-test.js @@node test/addvow-test.js -.PHONY: test +install: + @@echo "vows: updating submodules..." + @@git submodule update --init + @@echo "vows: done." + +.PHONY: test install diff --git a/README.md b/README.md index 920e2d60..8dfb32a5 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ and the results output to the console. Vows are run as soon as the promise completes, so the order in which they are run is undefined. +installaiton +------------ + + $ make install + writing specs ------------- From 3f4bbecff38129a633c0280f51c0ec5f5d41a6db Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 18 May 2010 21:06:19 -0400 Subject: [PATCH 147/409] throw Error if missing top-level context --- README.md | 56 +++++++++++++++++++++++++++-------------------------- lib/vows.js | 3 +++ 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 409c80e0..12fe1b80 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ _Vows_ is an experiment in making this possible, while adding a minimum of overh synopsis -------- - + var vows = require('vows'), assert = require('assert'); @@ -25,7 +25,7 @@ synopsis In the example above, `question()` would be a function which returns an `EventEmitter`. When the `"success"` event is emitted, the function passed to `addVow` is run, -and the results output to the console. +and the results output to the console. Vows are run as soon as the promise completes, so the order in which they are run is undefined. @@ -38,33 +38,35 @@ writing specs ------------- vows.describe('A Database library').addVows({ - // run this once, and execute the following tests when it completes - topic: function () { return new(DB) }, - - 'set() should store a k/v pair': { - // the inner context gets the return values of the outer contexts - // passed as arguments. Here, `db` is new(DB). - topic: function (db) { return db.set('pulp', 'orange') }, - - // `res` is the value emitted by the above `db.set` - 'and return OK': function (res) { - assert.equal(res, "OK"); + 'A DB object': { + // run this once, and execute the following tests when it completes + topic: function () { return new(DB) }, + + 'set() should store a k/v pair': { + // the inner context gets the return values of the outer contexts + // passed as arguments. Here, `db` is new(DB). + topic: function (db) { return db.set('pulp', 'orange') }, + + // `res` is the value emitted by the above `db.set` + 'and return OK': function (res) { + assert.equal(res, "OK"); + }, + 'and when checked for existence': { + // here, we need to access `db`, from the parent context. + // It's passed as the 2nd argument to `topic`, we discard the first, + // which would have been the above `res`. + topic: function (_, db) { return db.exists('pulp') }, + + 'return true': function (re) { + assert.equal(re, true); + } + } }, - 'and when checked for existence': { - // here, we need to access `db`, from the parent context. - // It's passed as the 2nd argument to `topic`, we discard the first, - // which would have been the above `res`. - topic: function (_, db) { return db.exists('pulp') }, - - 'return true': function (re) { - assert.equal(re, true); + 'get()': { + topic: function (db) { return db.get('dream') }, + 'should return the stored value': function (res) { + assert.equal(res, 'catcher'); } } - }, - 'get()': { - topic: function (db) { return db.get('dream') }, - 'should return the stored value': function (res) { - assert.equal(res, 'catcher'); - } } }); diff --git a/lib/vows.js b/lib/vows.js index 784192c6..c0b18561 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -210,6 +210,9 @@ function addVows(tests) { vows.promises.push(promise); if (typeof(tests) === 'object') { + if ('topic' in tests) { + throw new(Error)("Missing top-level context."); + } // Count the number of vows/promises expected to fire, // so we know when the tests are over. // We match the keys against `matcher`, to decide From 03a417135a3f2552b344ec7eb21689e56020c83a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 18 May 2010 23:13:42 -0400 Subject: [PATCH 148/409] (new) test for NaN, and added assert.isNaN --- lib/vows/macros.js | 11 ++++++++++- test/vows-test.js | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/vows/macros.js b/lib/vows/macros.js index f66fda19..d20e00d9 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -63,7 +63,16 @@ assert.isObject = function (actual, message) { assertTypeOf(actual, 'object', message || "expected {actual} to be an Object", assert.isObject); }; assert.isNumber = function (actual, message) { - assertTypeOf(actual, 'number', message || "expected {actual} to be a Number", assert.isNumber); + if (isNaN(actual)) { + assert.fail(actual, 'number', message || "expected {actual} to be of type {expected}", "isNaN", assert.isNumber); + } else { + assertTypeOf(actual, 'number', message || "expected {actual} to be a Number", assert.isNumber); + } +}; +assert.isNaN = function (actual, message) { + if (! isNaN(actual)) { + assert.fail(actual, 'NaN', message || "expected {actual} to be NaN", "isNaN", assert.isNaN); + } }; assert.isString = function (actual, message) { assertTypeOf(actual, 'string', message || "expected {actual} to be a String", assert.isString); diff --git a/test/vows-test.js b/test/vows-test.js index 1d767073..c29e0062 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -41,6 +41,8 @@ vows.describe("Vows").addVows({ assert.typeOf(it, 'string'); assert.isArray([]); assert.isObject({}); + assert.isNumber(0); + assert.isNaN(0/0); }, "testing emptiness": function (it) { assert.isEmpty({}); From 94a58be50f656a8813c727be611bc1a8093974bf Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 20 May 2010 21:30:46 -0400 Subject: [PATCH 149/409] (dist) updated paths and package.json --- lib/vendor/eyes | 1 - lib/vows.js | 3 +-- package.json | 3 ++- 3 files changed, 3 insertions(+), 4 deletions(-) delete mode 160000 lib/vendor/eyes diff --git a/lib/vendor/eyes b/lib/vendor/eyes deleted file mode 160000 index 77666781..00000000 --- a/lib/vendor/eyes +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7766678142d73892957843eeaafc6f2db240b549 diff --git a/lib/vows.js b/lib/vows.js index c0b18561..0de7cc8a 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -13,8 +13,7 @@ // var path = require('path'); -require.paths.unshift(path.join(path.dirname(__filename), 'vendor'), - path.dirname(__filename)); +require.paths.unshift(__dirname); var sys = require('sys'), assert = require('assert'), diff --git a/package.json b/package.json index afaa377e..9c977e30 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "author" : "Alexis Sellier ", "contributors" : [], "dependencies" : ["eyes"], - "lib" : "lib", + "directories" : {"lib": "./lib"}, + "main" : "./lib/vows", "version" : "0.2.0" } From 0083f0aa3918eb57033307ed2e76c8fe3741b9e6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 20 May 2010 21:31:29 -0400 Subject: [PATCH 150/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c977e30..c15489c2 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,5 @@ "dependencies" : ["eyes"], "directories" : {"lib": "./lib"}, "main" : "./lib/vows", - "version" : "0.2.0" + "version" : "0.2.1" } From 473e2156f2fff4314677be3c2428a3b2fc01f14c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 20 May 2010 21:39:56 -0400 Subject: [PATCH 151/409] (dist) fixed dependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c15489c2..053a39d9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "keywords" : ["testing", "spec", "test", "BDD"], "author" : "Alexis Sellier ", "contributors" : [], - "dependencies" : ["eyes"], + "dependencies" : {"eyes": ">=0.1.0"}, "directories" : {"lib": "./lib"}, "main" : "./lib/vows", "version" : "0.2.1" From c600238f28d5fd2f9f69c7db1a5bec5c0ac0f246 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 20 May 2010 21:41:36 -0400 Subject: [PATCH 152/409] (doc) new install instructions --- .gitmodules | 3 --- README.md | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 72f4057b..00000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "lib/vendor/eyes"] - path = lib/vendor/eyes - url = git://github.com/cloudhead/eyes.js diff --git a/README.md b/README.md index 12fe1b80..45742cda 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Vows are run as soon as the promise completes, so the order in which they are ru installaiton ------------ - $ make install + $ npm install vows writing specs ------------- From a214eb83953f217ce2378098cb25d911334cb7ea Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 22 May 2010 23:08:15 -0400 Subject: [PATCH 153/409] (new) Support for callback-style async testing In a topic, call the async function like so: `write(this.callback)` --- lib/vows.js | 22 ++++++++++++++++++---- test/vows-test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 0de7cc8a..5bf7d1c8 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -192,10 +192,21 @@ function addVow(/* description & callback */) { }; function Context(vow, ctx, env) { + var that = this; + this.tests = vow.callback; this.topics = (ctx.topics || []).slice(0); + this.emitter = null; this.env = env || {}; this.env.context = this; + this.env.__defineGetter__('callback', function () { + that._callback = true; + return function (e, res) { + var args = Array.prototype.slice.call(arguments, 1); + if (e) { that.emitter.emit('error', e) } + else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } + }; + }); this.name = (ctx.name ? ctx.name + ' ' : '') + (vow.description || ''); } @@ -272,11 +283,14 @@ function addVows(tests) { // 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 instanceof vows.options.Emitter)) { - var emitter = new(vows.options.Emitter); + ctx.emitter = new(vows.options.Emitter); - process.nextTick(function (val) { - return function () { emitter.emit("success", val) }; - }(topic)); topic = emitter; + if (! ctx._callback) { + process.nextTick(function (val) { + return function () { ctx.emitter.emit("success", val) }; + }(topic)); + } + topic = ctx.emitter; } topic.addListener('success', function (val) { diff --git a/test/vows-test.js b/test/vows-test.js index c29e0062..7d355f05 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -204,6 +204,39 @@ vows.describe("Vows").addVows({ assert.equal(res, true); } }, + "a topic with callback-style async": { + "when successful": { + topic: function () { + function async(callback) { + process.nextTick(function () { + callback(null, "OK"); + }); + } + async(this.callback); + }, + "should work like an event-emitter": function (res) { + assert.equal(res, "OK"); + }, + "should assign `null` to the error argument": function (e, res) { + assert.strictEqual(e, null); + assert.equal(res, "OK"); + } + }, + "when unsuccessful": { + topic: function () { + function async(callback) { + process.nextTick(function () { + callback("ERROR"); + }); + } + async(this.callback); + }, + "should work like an event-emitter": function (e, res) { + assert.equal(e, "ERROR"); + assert.equal(res, undefined); + } + } + } }).addVows({ "A 2nd test suite": { topic: function () { From 70cf79ec8f2f7074ebf61e50b5008d3fa3d80eb5 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 22 May 2010 23:13:24 -0400 Subject: [PATCH 154/409] (minor) standardized error messages --- lib/vows.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 5bf7d1c8..19ed4c1c 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -128,7 +128,7 @@ function addVow(/* description & callback */) { } else if (args[0].callback && args[0].context) { vow = args[0]; } else { - throw new(Error)("wrong argument type for addVow()"); + throw new(Error)("wrong argument type for addVow()."); } return this.addListener("success", function () { @@ -221,7 +221,7 @@ function addVows(tests) { if (typeof(tests) === 'object') { if ('topic' in tests) { - throw new(Error)("Missing top-level context."); + throw new(Error)("missing top-level context."); } // Count the number of vows/promises expected to fire, // so we know when the tests are over. From 8092bb307df850c8926ba79de44434eb208e085b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 22 May 2010 23:14:15 -0400 Subject: [PATCH 155/409] throw error when this.callback with a return value --- lib/vows.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/vows.js b/lib/vows.js index 19ed4c1c..77f08dff 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -289,6 +289,8 @@ function addVows(tests) { process.nextTick(function (val) { return function () { ctx.emitter.emit("success", val) }; }(topic)); + } else if (typeof(topic) !== "undefined") { + throw new(Error)("topic must not return anything when using `this.callback`."); } topic = ctx.emitter; } From c741f7bff4c3160719c0cc13afb58d31505dd44a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 22 May 2010 23:23:21 -0400 Subject: [PATCH 156/409] (minor doc) typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45742cda..91a55ef1 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ and the results output to the console. Vows are run as soon as the promise completes, so the order in which they are run is undefined. -installaiton +installation ------------ $ npm install vows From 5df28a5852f313b1ccb4f151c7c8633e77ad4432 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 22 May 2010 23:24:23 -0400 Subject: [PATCH 157/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 053a39d9..3e31da2d 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,5 @@ "dependencies" : {"eyes": ">=0.1.0"}, "directories" : {"lib": "./lib"}, "main" : "./lib/vows", - "version" : "0.2.1" + "version" : "0.2.2" } From cf459bc1aa20999e710e0a00362c98f4d18eb6a7 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 22 May 2010 23:52:36 -0400 Subject: [PATCH 158/409] fixed inspector doing weird shit. --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 77f08dff..4f7c1607 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -18,7 +18,7 @@ require.paths.unshift(__dirname); var sys = require('sys'), assert = require('assert'), events = require('events'), - eyes = require('eyes').inspector({ writer: null }), + eyes = require('eyes').inspector({ stream: null }), vows = exports; // Options From cb9e66ed61088e019859e3550521547f64d29e31 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 22 May 2010 23:52:52 -0400 Subject: [PATCH 159/409] (new) added assert.isNull, and made isObject more robust --- lib/vows/macros.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/vows/macros.js b/lib/vows/macros.js index d20e00d9..d51f0199 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -74,6 +74,11 @@ assert.isNaN = function (actual, message) { assert.fail(actual, 'NaN', message || "expected {actual} to be NaN", "isNaN", assert.isNaN); } }; +assert.isNull = function (actual, message) { + if (actual !== null) { + assert.fail(actual, 'null', message || "expected {actual} to be null", "===", assert.isNull); + } +}; assert.isString = function (actual, message) { assertTypeOf(actual, 'string', message || "expected {actual} to be a String", assert.isString); }; @@ -102,7 +107,7 @@ function isString (obj) { } function isObject (obj) { - return typeof(obj) === 'object' && obj instanceof Object && !isArray(obj); + return typeof(obj) === 'object' && obj && !isArray(obj); } // A better `typeof` From f867791e0e5cd97a0524095e7f67b55e3332adc9 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 22 May 2010 23:53:10 -0400 Subject: [PATCH 160/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3e31da2d..5294e870 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,5 @@ "dependencies" : {"eyes": ">=0.1.0"}, "directories" : {"lib": "./lib"}, "main" : "./lib/vows", - "version" : "0.2.2" + "version" : "0.2.3" } From 87afe4c425a2426d4e0752f529a15cc1388fdb00 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 23 May 2010 15:07:43 -0400 Subject: [PATCH 161/409] don't complain about return value in topic if old --- lib/vows.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 4f7c1607..3c5cbe42 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -289,7 +289,7 @@ function addVows(tests) { process.nextTick(function (val) { return function () { ctx.emitter.emit("success", val) }; }(topic)); - } else if (typeof(topic) !== "undefined") { + } else if (typeof(topic) !== "undefined" && !old) { throw new(Error)("topic must not return anything when using `this.callback`."); } topic = ctx.emitter; From 179f85462058b4b7f347bfa913646e683ee4ce53 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 23 May 2010 15:07:59 -0400 Subject: [PATCH 162/409] (new) assert.instanceOf assert.isUndefined --- lib/vows/macros.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/vows/macros.js b/lib/vows/macros.js index d51f0199..024ee9d4 100644 --- a/lib/vows/macros.js +++ b/lib/vows/macros.js @@ -79,6 +79,11 @@ assert.isNull = function (actual, message) { assert.fail(actual, 'null', message || "expected {actual} to be null", "===", assert.isNull); } }; +assert.isUndefined = function (actual, message) { + if (actual !== undefined) { + assert.fail(actual, 'undefined', message || "expected {actual} to be undefined", "===", assert.isUndefined); + } +}; assert.isString = function (actual, message) { assertTypeOf(actual, 'string', message || "expected {actual} to be a String", assert.isString); }; @@ -88,6 +93,11 @@ assert.isFunction = function (actual, message) { assert.typeOf = function (actual, expected, message) { assertTypeOf(actual, expected, message, assert.typeOf); }; +assert.instanceOf = function (actual, expected, message) { + if (! (actual instanceof expected)) { + assert.fail(actual, expected, message || "expected {actual} to be an instance of {expected}", "instanceof", assert.instanceOf); + } +}; // // Utility functions From 2f231b114b535814dabf1cd4f1a92e433a9623cb Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 23 May 2010 15:14:37 -0400 Subject: [PATCH 163/409] (doc) updated README with assertion macros --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 91a55ef1..ac89fdf0 100644 --- a/README.md +++ b/README.md @@ -70,3 +70,39 @@ writing specs } } }); + +assertion macros +---------------- + +### equality # + +- assert.equal +- assert.notEqual +- assert.strictEqual +- assert.strictNotEqual + +### type # + +- assert.isFunction +- assert.isObject +- assert.isNaN +- assert.isString +- assert.isArray +- assert.isBoolean +- assert.isNumber +- assert.isNull +- assert.isUndefined +- assert.typeOf +- assert.instanceOf + +### properties # + +- assert.include +- assert.match +- assert.length +- assert.isEmpty + +### exceptions # + +- assert.throws +- assert.doesNotThrow From b10a30a74338b5eb57a0b32da5c9bd34b13a813e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 23 May 2010 15:15:34 -0400 Subject: [PATCH 164/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5294e870..e7f13d31 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,5 @@ "dependencies" : {"eyes": ">=0.1.0"}, "directories" : {"lib": "./lib"}, "main" : "./lib/vows", - "version" : "0.2.3" + "version" : "0.2.4" } From 9fa313cad47b81c2eaa96cddf292cdc0c5192e64 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 24 May 2010 02:34:53 -0400 Subject: [PATCH 165/409] Fix incorrect binding in test functions. `env` wasn't being closed over properly. --- lib/vows.js | 4 ++-- test/vows-test.js | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 3c5cbe42..3e54d9e5 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -331,11 +331,11 @@ function addVows(tests) { // before calling the inner context. Else, just run the inner context // synchronously. if (topic) { - topic.addListener("success", function (vow, ctx) { + topic.addListener("success", function (vow, ctx, env) { return function (val) { return run(new(Context)(vow, ctx, env), lastTopic); }; - }(vow, ctx)); + }(vow, ctx, env)); } else { run(new(Context)(vow, ctx, env), lastTopic); } diff --git a/test/vows-test.js b/test/vows-test.js index 7d355f05..3595fa83 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -237,6 +237,26 @@ vows.describe("Vows").addVows({ } } } +}).addVows({ + "Sibling contexts": { + "'A', with `this.foo = true`": { + topic: function () { + this.foo = true; + return this.foo; + }, + "should have `this.foo` set to true": function (res) { + assert.equal(res, true); + } + }, + "'B', with nothing set": { + topic: function () { + return this.foo; + }, + "should have `this.foo` be undefined": function (res) { + assert.isUndefined(res); + } + } + } }).addVows({ "A 2nd test suite": { topic: function () { From ce73ecd7fc7b8c032ed983aa4a4e16b96fc5e9b9 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 24 May 2010 02:44:48 -0400 Subject: [PATCH 166/409] Cleaned up the inner loop a little --- lib/vows.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 3e54d9e5..03f9ce45 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -246,7 +246,7 @@ function addVows(tests) { } this.addListener("end", function (honored, broken, errored) { - var topic, vow, env; + var topic; vows.remaining += remaining; @@ -309,15 +309,18 @@ function addVows(tests) { Object.keys(ctx.tests).filter(function (k) { return ctx.tests[k] && k !== 'topic'; }).forEach(function (item) { + // Create a new evaluation context, + // inheriting from the parent one. + var env = Object.create(ctx.env); + // Holds the current test or context - vow = Object.create({ + var vow = Object.create({ callback: ctx.tests[item], context: ctx.name, description: item, binding: ctx.env, promise: promise }); - env = Object.create(ctx.env); // If we encounter a function, add it to the callbacks // of the `topic` function, so it'll get called once the @@ -331,11 +334,11 @@ function addVows(tests) { // before calling the inner context. Else, just run the inner context // synchronously. if (topic) { - topic.addListener("success", function (vow, ctx, env) { + topic.addListener("success", function (ctx) { return function (val) { return run(new(Context)(vow, ctx, env), lastTopic); }; - }(vow, ctx, env)); + }(ctx)); } else { run(new(Context)(vow, ctx, env), lastTopic); } From 0a53b7051a760275ae1350537fc9e6b19ebbad48 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 24 May 2010 02:45:17 -0400 Subject: [PATCH 167/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e7f13d31..88be48f0 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,5 @@ "dependencies" : {"eyes": ">=0.1.0"}, "directories" : {"lib": "./lib"}, "main" : "./lib/vows", - "version" : "0.2.4" + "version" : "0.2.5" } From ba8c46e32224b1cd3e050c2ce2dea9c8ad675611 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 30 May 2010 02:56:41 -0400 Subject: [PATCH 168/409] (new) dot-matrix reporter - dot-matrix is new default reporter - spec reporter can be accessed via `--spec` option - console.js renamed to spec.js --- lib/vows.js | 7 +- lib/vows/reporters/dot-matrix.js | 94 ++++++++++++++++++++++ lib/vows/reporters/{console.js => spec.js} | 0 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 lib/vows/reporters/dot-matrix.js rename lib/vows/reporters/{console.js => spec.js} (100%) diff --git a/lib/vows.js b/lib/vows.js index 03f9ce45..bdba5d7b 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -26,18 +26,21 @@ vows.options = { Emitter: events.EventEmitter, brief: false, json: false, + spec: false, matcher: /.*/ }; vows.__defineGetter__('reporter', function () { if (vows.options.json) { return require('vows/reporters/json'); + } else if (vows.options.spec) { + return require('vows/reporters/spec'); } else { - return require('vows/reporters/console'); + return require('vows/reporters/dot-matrix'); } }); -var stylize = require('vows/reporters/console').stylize; +var stylize = require('vows/reporters/spec').stylize; // Keeps track of the outcome of vows. var total = 0, honored = 0, diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js new file mode 100644 index 00000000..d27c9b9f --- /dev/null +++ b/lib/vows/reporters/dot-matrix.js @@ -0,0 +1,94 @@ + +var sys = require('sys'); +// +// Console reporter +// +var stream, buffer, messages = []; + +this.report = function (data, s) { + var event = data[1]; + + stream = typeof(s) === 'object' ? s : process.stdout; + buffer = []; + + switch (data[0]) { + case 'subject': + puts('\n' + stylize(event, 'underline') + '\n'); + sys.print(' '); + break; + case 'context': + break; + case 'vow': + if (event.status === 'honored') { + sys.print(stylize('.', 'green')); + } else if (event.status === 'broken') { + sys.print(stylize('B', 'yellow')); + messages.push(' - ' + stylize(event.title, 'yellow')); + messages.push(' ~ ' + event.exception); + messages.push(''); + } else if (event.status === 'errored') { + sys.print(stylize('E', 'red')); + messages.push(' - ' + stylize(event.title, 'red')); + if (event.exception.type === 'promise') { + messages.push(' * ' + stylize("An 'error' event was caught: " + + stylize(event.exception.error, 'bold'), 'red')); + } else { + messages.push(' ! ' + stylize(event.exception, 'red')); + } + messages.push(''); + } + break; + case 'end': + sys.print(' '); + break; + case 'finish': + var result = event.honored + " honored, " + + event.broken + " broken, " + + event.errored + " errored", + style = event.honored === event.total ? ('green') + : (event.errored === 0 ? 'yellow' : 'red'); + + if (messages.length) { + messages.pop(); // drop trailing blank message + puts('\n\n' + messages.join('\n')); + } else { + sys.print('\n'); + } + + if ('time' in event) { + puts("\nVerified " + event.total + " vows in " + + (event.time + " seconds.\n")); + } + puts(stylize(result, style)); + break; + case 'error': + messages.push('\n * ' + stylize(event.error, 'red')); + break; + } + return buffer.join(''); +}; + +function puts(args) { + args = Array.prototype.slice.call(arguments).map(function (a) { + return a.replace(/`([^`]+)`/g, function (_, capture) { return stylize(capture, 'italic') }) + .replace(/\*([^*]+)\*/g, function (_, capture) { return stylize(capture, 'bold') }); + }); + return stream ? stream.write(args.join('\n') + '\n') : buffer.push(args); +} + +// Stylize a string +function stylize(str, style) { + var styles = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'yellow' : [33, 39], + 'green' : [32, 39], + 'red' : [31, 39], + 'grey' : [90, 39], + 'green-hi' : [92, 32], + }; + return '\033[' + styles[style][0] + 'm' + str + + '\033[' + styles[style][1] + 'm'; +} +this.stylize = stylize; diff --git a/lib/vows/reporters/console.js b/lib/vows/reporters/spec.js similarity index 100% rename from lib/vows/reporters/console.js rename to lib/vows/reporters/spec.js From fb7d8a9d8e8c382bc8aef277455552ccc39d25e0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 1 Jun 2010 00:32:08 -0400 Subject: [PATCH 169/409] extracted console utils out of spec/dot-matrix reporters --- lib/vows.js | 2 +- lib/vows/console.js | 27 +++++++++++++++++++++++++++ lib/vows/reporters/dot-matrix.js | 31 +++++-------------------------- lib/vows/reporters/spec.js | 32 +++++--------------------------- 4 files changed, 38 insertions(+), 54 deletions(-) create mode 100644 lib/vows/console.js diff --git a/lib/vows.js b/lib/vows.js index bdba5d7b..854768a4 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -40,7 +40,7 @@ vows.__defineGetter__('reporter', function () { } }); -var stylize = require('vows/reporters/spec').stylize; +var stylize = require('vows/console').stylize; // Keeps track of the outcome of vows. var total = 0, honored = 0, diff --git a/lib/vows/console.js b/lib/vows/console.js new file mode 100644 index 00000000..56fd8fb6 --- /dev/null +++ b/lib/vows/console.js @@ -0,0 +1,27 @@ + +// Stylize a string +this.stylize = function stylize(str, style) { + var styles = { + 'bold' : [1, 22], + 'italic' : [3, 23], + 'underline' : [4, 24], + 'yellow' : [33, 39], + 'green' : [32, 39], + 'red' : [31, 39], + 'grey' : [90, 39], + 'green-hi' : [92, 32], + }; + return '\033[' + styles[style][0] + 'm' + str + + '\033[' + styles[style][1] + 'm'; +}; + +this.puts = function (options) { + var stylize = exports.stylize; + return function (args) { + args = Array.prototype.slice.call(arguments).map(function (a) { + return a.replace(/`([^`]+)`/g, function (_, capture) { return stylize(capture, 'italic') }) + .replace(/\*([^*]+)\*/g, function (_, capture) { return stylize(capture, 'bold') }); + }); + return options.stream ? options.stream.write(args.join('\n') + '\n') : buffer.push(args); + }; +}; diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index d27c9b9f..2887a23e 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -1,5 +1,9 @@ var sys = require('sys'); + +var options = {}; +var stylize = require('vows/console').stylize, + puts = require('vows/console').puts(options); // // Console reporter // @@ -8,7 +12,7 @@ var stream, buffer, messages = []; this.report = function (data, s) { var event = data[1]; - stream = typeof(s) === 'object' ? s : process.stdout; + options.stream = typeof(s) === 'object' ? s : process.stdout; buffer = []; switch (data[0]) { @@ -67,28 +71,3 @@ this.report = function (data, s) { } return buffer.join(''); }; - -function puts(args) { - args = Array.prototype.slice.call(arguments).map(function (a) { - return a.replace(/`([^`]+)`/g, function (_, capture) { return stylize(capture, 'italic') }) - .replace(/\*([^*]+)\*/g, function (_, capture) { return stylize(capture, 'bold') }); - }); - return stream ? stream.write(args.join('\n') + '\n') : buffer.push(args); -} - -// Stylize a string -function stylize(str, style) { - var styles = { - 'bold' : [1, 22], - 'italic' : [3, 23], - 'underline' : [4, 24], - 'yellow' : [33, 39], - 'green' : [32, 39], - 'red' : [31, 39], - 'grey' : [90, 39], - 'green-hi' : [92, 32], - }; - return '\033[' + styles[style][0] + 'm' + str + - '\033[' + styles[style][1] + 'm'; -} -this.stylize = stylize; diff --git a/lib/vows/reporters/spec.js b/lib/vows/reporters/spec.js index ebf5d076..2fc8b27b 100644 --- a/lib/vows/reporters/spec.js +++ b/lib/vows/reporters/spec.js @@ -1,13 +1,16 @@ var sys = require('sys'); + +var options = {}; +var stylize = require('vows/console').stylize, + puts = require('vows/console').puts(options); // // Console reporter // -var stream, buffer; this.report = function (data, s) { var event = data[1]; - stream = typeof(s) === 'object' ? s : process.stdout; + options.stream = typeof(s) === 'object' ? s : process.stdout; buffer = []; switch (data[0]) { @@ -52,30 +55,5 @@ this.report = function (data, s) { puts('\n * ' + stylize(event.error, 'red')); break; } - return buffer.join(''); }; -function puts(args) { - args = Array.prototype.slice.call(arguments).map(function (a) { - return a.replace(/`([^`]+)`/g, function (_, capture) { return stylize(capture, 'italic') }) - .replace(/\*([^*]+)\*/g, function (_, capture) { return stylize(capture, 'bold') }); - }); - return stream ? stream.write(args.join('\n') + '\n') : buffer.push(args); -} - -// Stylize a string -function stylize(str, style) { - var styles = { - 'bold' : [1, 22], - 'italic' : [3, 23], - 'underline' : [4, 24], - 'yellow' : [33, 39], - 'green' : [32, 39], - 'red' : [31, 39], - 'grey' : [90, 39], - 'green-hi' : [92, 32], - }; - return '\033[' + styles[style][0] + 'm' + str + - '\033[' + styles[style][1] + 'm'; -} -this.stylize = stylize; From 14278d0cd834341fc500afdb27157f895f5f99e2 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 1 Jun 2010 03:02:52 -0400 Subject: [PATCH 170/409] cleaned up project structure a little --- lib/assert/error.js | 25 +++++++++++++ lib/{vows => assert}/macros.js | 0 lib/vows.js | 67 +++++++--------------------------- lib/vows/context.js | 21 +++++++++++ 4 files changed, 59 insertions(+), 54 deletions(-) create mode 100644 lib/assert/error.js rename lib/{vows => assert}/macros.js (100%) create mode 100644 lib/vows/context.js diff --git a/lib/assert/error.js b/lib/assert/error.js new file mode 100644 index 00000000..8239493f --- /dev/null +++ b/lib/assert/error.js @@ -0,0 +1,25 @@ +var stylize = require('vows/console').stylize; +var inspect = require('vows').inspect; + +require('assert').AssertionError.prototype.toString = function () { + var that = this, + source = this.stack.match(/([a-zA-Z0-9_-]+\.js)(:\d+):\d+/); + + function parse(str) { + return str.replace(/{actual}/g, inspect(that.actual)). + replace(/{expected}/g, inspect(that.expected)). + replace(/{operator}/g, stylize(that.operator, 'bold')); + } + + if (this.message) { + return stylize(parse(this.message), 'yellow') + + stylize(' // ' + source[1] + source[2], 'grey'); + } else { + return stylize([ + this.expected, + this.operator, + this.actual + ].join(' '), 'yellow'); + } +}; + diff --git a/lib/vows/macros.js b/lib/assert/macros.js similarity index 100% rename from lib/vows/macros.js rename to lib/assert/macros.js diff --git a/lib/vows.js b/lib/vows.js index 854768a4..b312fea9 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -13,14 +13,13 @@ // var path = require('path'); -require.paths.unshift(__dirname); - var sys = require('sys'), - assert = require('assert'), events = require('events'), eyes = require('eyes').inspector({ stream: null }), vows = exports; +require.paths.unshift(__dirname); + // Options vows.options = { Emitter: events.EventEmitter, @@ -82,32 +81,20 @@ for (var i = 0, arg; i < process.argv.length; i++) { // ('node' in most cases) argv = argv.slice(1); +vows.inspect = function inspect(val) { + return '\033[1m' + eyes(val) + '\033[22m'; +}; + // -// Assertion Macros +// Assertion Macros & Extensions // -vows.macros = require('vows/macros'); - -assert.AssertionError.prototype.toString = function () { - var that = this, - source = this.stack.match(/([a-zA-Z0-9_-]+\.js)(:\d+):\d+/); - - function parse(str) { - return str.replace(/{actual}/g, inspect(that.actual)). - replace(/{expected}/g, inspect(that.expected)). - replace(/{operator}/g, stylize(that.operator, 'bold')); - } +require('./assert/error'); +require('./assert/macros'); - if (this.message) { - return stylize(parse(this.message), 'yellow') + - stylize(' // ' + source[1] + source[2], 'grey'); - } else { - return stylize([ - this.expected, - this.operator, - this.actual - ].join(' '), 'yellow'); - } -} +// +// Context constructor +// +var Context = require('vows/context').Context; // // This function gets added to events.EventEmitter.prototype, by default. @@ -194,26 +181,6 @@ function addVow(/* description & callback */) { } }; -function Context(vow, ctx, env) { - var that = this; - - this.tests = vow.callback; - this.topics = (ctx.topics || []).slice(0); - this.emitter = null; - this.env = env || {}; - this.env.context = this; - this.env.__defineGetter__('callback', function () { - that._callback = true; - return function (e, res) { - var args = Array.prototype.slice.call(arguments, 1); - if (e) { that.emitter.emit('error', e) } - else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } - }; - }); - this.name = (ctx.name ? ctx.name + ' ' : '') + - (vow.description || ''); -} - function addVows(tests) { var promise = new(events.EventEmitter); var remaining = 0; @@ -467,11 +434,3 @@ vows.config = function (opts) { return this; }; -// -// Utility functions -// - -function inspect(val) { - return '\033[1m' + eyes(val) + '\033[22m'; -} - diff --git a/lib/vows/context.js b/lib/vows/context.js new file mode 100644 index 00000000..5fdfa090 --- /dev/null +++ b/lib/vows/context.js @@ -0,0 +1,21 @@ + +this.Context = function (vow, ctx, env) { + var that = this; + + this.tests = vow.callback; + this.topics = (ctx.topics || []).slice(0); + this.emitter = null; + this.env = env || {}; + this.env.context = this; + this.env.__defineGetter__('callback', function () { + that._callback = true; + return function (e, res) { + var args = Array.prototype.slice.call(arguments, 1); + if (e) { that.emitter.emit('error', e) } + else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } + }; + }); + this.name = (ctx.name ? ctx.name + ' ' : '') + + (vow.description || ''); +}; + From 7200208aea9d11b6dd417151d74363910cf4229b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 1 Jun 2010 03:06:13 -0400 Subject: [PATCH 171/409] moved vows.prepare to extras.js --- lib/vows.js | 29 ++--------------------------- lib/vows/extras.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 27 deletions(-) create mode 100644 lib/vows/extras.js diff --git a/lib/vows.js b/lib/vows.js index b312fea9..663e299b 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -85,6 +85,8 @@ vows.inspect = function inspect(val) { return '\033[1m' + eyes(val) + '\033[22m'; }; +vows.prepare = require('vows/extras').prepare; + // // Assertion Macros & Extensions // @@ -371,33 +373,6 @@ process.addListener('exit', function () { } }); -// -// Wrap a Node.js style async function into an EventEmmitter -// -vows.prepare = function (obj, targets) { - targets.forEach(function (target) { - if (target in obj) { - obj[target] = (function (fun) { - return function () { - var args = Array.prototype.slice.call(arguments); - var ee = new(events.EventEmitter); - - args.push(function (err /* [, data] */) { - var args = Array.prototype.slice.call(arguments, 1); - - if (err) { ee.emit('error', err) } - else { ee.emit.apply(ee, ['success'].concat(args)) } - }); - fun.apply(obj, args); - - return ee; - }; - })(obj[target]); - } - }); - return obj; -}; - // Run all vows/tests. // It can take either a function as `tests`, // or an object literal. diff --git a/lib/vows/extras.js b/lib/vows/extras.js new file mode 100644 index 00000000..3912c527 --- /dev/null +++ b/lib/vows/extras.js @@ -0,0 +1,28 @@ +var events = require('events'); +// +// Wrap a Node.js style async function into an EventEmmitter +// +this.prepare = function (obj, targets) { + targets.forEach(function (target) { + if (target in obj) { + obj[target] = (function (fun) { + return function () { + var args = Array.prototype.slice.call(arguments); + var ee = new(events.EventEmitter); + + args.push(function (err /* [, data] */) { + var args = Array.prototype.slice.call(arguments, 1); + + if (err) { ee.emit('error', err) } + else { ee.emit.apply(ee, ['success'].concat(args)) } + }); + fun.apply(obj, args); + + return ee; + }; + })(obj[target]); + } + }); + return obj; +}; + From e16cadf1dda35f70cd4ba6d89ab310ac058cab06 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 3 Jun 2010 15:59:33 -0400 Subject: [PATCH 172/409] (dist) cleanup Makefile --- Makefile | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Makefile b/Makefile index 1ecf00ee..1a1cb539 100644 --- a/Makefile +++ b/Makefile @@ -3,11 +3,5 @@ # test: @@node test/vows-test.js - @@node test/addvow-test.js - -install: - @@echo "vows: updating submodules..." - @@git submodule update --init - @@echo "vows: done." .PHONY: test install From 4adab800aaa2393d046eb07cb2d7dad35f171e3b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 3 Jun 2010 15:59:52 -0400 Subject: [PATCH 173/409] dot-matrix is the default reporter --- bin/vows | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/vows b/bin/vows index d5d9b5c3..3d1b74a2 100755 --- a/bin/vows +++ b/bin/vows @@ -8,7 +8,7 @@ var path = require('path'), require.paths.unshift(path.join(__dirname, '..', 'lib')); -var console = require('vows/reporters/console'); +var console = require('vows/reporters/dot-matrix'); var options = []; From b9c032936fe90f40b747d973164b2cf2cb72c9d6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 3 Jun 2010 16:01:04 -0400 Subject: [PATCH 174/409] Complete re-architecturing of vows. - Suite object, created by vows.describe - Exportable batches/suites - No global state, except for vows.suites --- lib/vows.js | 316 +++++++--------------------------------------------- 1 file changed, 43 insertions(+), 273 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 663e299b..f4595692 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -11,8 +11,6 @@ // }, 'it should know the answer to the ultimate question of life'); // }); // -var path = require('path'); - var sys = require('sys'), events = require('events'), eyes = require('eyes').inspector({ stream: null }), @@ -41,46 +39,6 @@ vows.__defineGetter__('reporter', function () { var stylize = require('vows/console').stylize; -// Keeps track of the outcome of vows. -var total = 0, honored = 0, - broken = 0, errored = 0, - - start, // Start time - end, // End time - suites; // Number of test suites added by `addVows()` - -// Context stack, used in addVow() to keep track -var lastContext; - -// Output buffer -var buffer; - -var argv = []; -// -// Parse command-line parameters -// -for (var i = 0, arg; i < process.argv.length; i++) { - arg = process.argv[i]; - - if (arg === __filename) { continue } - - if (arg[0] !== '-') { - argv.push(arg); - } else { - arg = arg.match(/^--?(.*)/)[1]; - - if (arg[0] === 'R') { - vows.options.matcher = new(RegExp)(arg.slice(1)); - } else if (arg in vows.options) { - vows.options[arg] = true; - } - } -} - -// Get rid of process runner -// ('node' in most cases) -argv = argv.slice(1); - vows.inspect = function inspect(val) { return '\033[1m' + eyes(val) + '\033[22m'; }; @@ -94,34 +52,38 @@ require('./assert/error'); require('./assert/macros'); // -// Context constructor +// Suite constructor // -var Context = require('vows/context').Context; +var Suite = require('vows/suite').Suite; // -// This function gets added to events.EventEmitter.prototype, by default. -// It's essentially a wrapper around `addCallback`, which adds all the specification -// goodness. +// Checks if all the tests in the batch have been run, +// and triggers the next batch (if any), by emitting the 'end' event. // -function addVow(/* description & callback */) { - var desc, callback, context = '', vow = {}, args = arguments; +vows.tryEnd = function (batch) { + var result, style, time; - total++; + if (batch.honored + batch.broken + batch.errored === batch.total && + batch.remaining === 0) { - if (args.length > 1) { - // Sometimes it might be nicer to pass the proof first, - // and the description second, so we let the user choose - // the order. - if (typeof(args[0]) === "function") { - vow.callback = args[0], vow.description = args[1]; - } else { - vow.callback = args[1], vow.description = args[0]; - } - } else if (args[0].callback && args[0].context) { - vow = args[0]; - } else { - throw new(Error)("wrong argument type for addVow()."); + Object.keys(batch).forEach(function (k) { + (k in batch.suite.results) && (batch.suite.results[k] += batch[k]); + }); + + vows.reporter.report(['end']); + batch.promise.emit('end', batch.honored, batch.broken, batch.errored); } +} + +// +// This function gets added to events.EventEmitter.prototype, by default. +// It's essentially a wrapper around `addListener`, which adds all the specification +// goodness. +// +function addVow(vow) { + var batch = vow.batch; + + batch.total ++; return this.addListener("success", function () { var args = Array.prototype.slice.call(arguments); @@ -131,7 +93,7 @@ function addVow(/* description & callback */) { args.unshift(null); } runTest(args); - tryFinish(vows.remaining, vow.promise); + vows.tryEnd(batch); }).addListener("error", function (err) { var exception; @@ -140,10 +102,10 @@ function addVow(/* description & callback */) { runTest([err]); } else { exception = { type: 'promise', error: err }; - errored++; + batch.errored ++; output('errored', exception); } - tryFinish(vows.remaining, vow.promise); + vows.tryEnd(batch); }); function runTest(args) { @@ -154,15 +116,15 @@ function addVow(/* description & callback */) { try { vow.callback.apply(vow.binding || null, args); output('honored', exception); - honored++; + batch.honored ++; } catch (e) { if (e.name && e.name.match(/AssertionError/)) { exception = e.toString(); output('broken', exception); - broken++; + batch.broken ++; } else { exception = e.stack || e.message || e.toString() || e; - errored++; + batch.errored ++; output('errored', exception); } } @@ -170,8 +132,8 @@ function addVow(/* description & callback */) { function output(status, exception) { if (exception || !vows.options.brief) { - if (vow.context && lastContext !== vow.context) { - lastContext = vow.context; + if (vow.context && batch.lastContext !== vow.context) { + batch.lastContext = vow.context; vows.reporter.report(['context', vow.context]); } vows.reporter.report(['vow', { @@ -183,224 +145,32 @@ function addVow(/* description & callback */) { } }; -function addVows(tests) { - var promise = new(events.EventEmitter); - var remaining = 0; - - suites++; - - vows.promises.push(promise); - - if (typeof(tests) === 'object') { - if ('topic' in tests) { - throw new(Error)("missing top-level context."); - } - // Count the number of vows/promises expected to fire, - // so we know when the tests are over. - // We match the keys against `matcher`, to decide - // whether or not they should be included in the test. - (function count(tests) { - var match = false; - remaining++; - Object.keys(tests).filter(function (k) { - return k !== 'topic'; - }).forEach(function (key) { - if (typeof(tests[key]) === "object") { - if (! (match = count(tests[key]) || - match || vows.options.matcher.test(key))) { - delete tests[key]; - remaining--; - } - } - }); - return match; - })(tests); - } - - this.addListener("end", function (honored, broken, errored) { - var topic; - - vows.remaining += remaining; - - if (typeof(tests) === 'function') { - return tests.call(null); - } else { - // The test runner, it calls itself recursively, passing the - // previous context to the inner contexts. This is so the `topic` - // functions have access to all the previous context topics in their - // arguments list. - // It is defined and invoked at the same time. - // If it encounters a `topic` function, it waits for the returned - // promise to emit (the topic), at which point it runs the functions under it, - // passing the topic as an argument. - (function run(ctx, lastTopic) { - var old = false; - topic = ctx.tests.topic; - - if (typeof(topic) === 'function') { - // Run the topic, passing the previous context topics - topic = topic.apply(ctx.env, ctx.topics); - } - - // If this context has a topic, store it in `lastTopic`, - // if not, use the last topic, passed down by a parent - // context. - if (topic) { - lastTopic = topic; - } else { - old = true; - topic = lastTopic; - } - - // 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 instanceof vows.options.Emitter)) { - ctx.emitter = new(vows.options.Emitter); - - if (! ctx._callback) { - process.nextTick(function (val) { - return function () { ctx.emitter.emit("success", val) }; - }(topic)); - } else if (typeof(topic) !== "undefined" && !old) { - throw new(Error)("topic must not return anything when using `this.callback`."); - } - topic = ctx.emitter; - } - - topic.addListener('success', 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) ctx.topics.unshift(val); - }); - - // Now run the tests, or sub-contexts - Object.keys(ctx.tests).filter(function (k) { - return ctx.tests[k] && k !== 'topic'; - }).forEach(function (item) { - // Create a new evaluation context, - // inheriting from the parent one. - var env = Object.create(ctx.env); - - // Holds the current test or context - var vow = Object.create({ - callback: ctx.tests[item], - context: ctx.name, - description: item, - binding: ctx.env, - promise: promise - }); - - // If we encounter a function, add it to the callbacks - // of the `topic` function, so it'll get called once the - // topic fires. - // If we encounter an object literal, we recurse, sending it - // our current context. - if (typeof(vow.callback) === 'function') { - topic.addVow(vow); - } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { - // 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 - // synchronously. - if (topic) { - topic.addListener("success", function (ctx) { - return function (val) { - return run(new(Context)(vow, ctx, env), lastTopic); - }; - }(ctx)); - } else { - run(new(Context)(vow, ctx, env), lastTopic); - } - } - }); - // Check if we're done running the tests - tryFinish(--vows.remaining, promise); - // This is our initial, empty context - })(new(Context)({ callback: tests, context: null, description: null }, {})); - } - }); - return promise; -} - -// -// Checks if all the required tests have been run, -// and either triggers the next test suite, if any, -// or exits the process. -// -function tryFinish(remaining, promise) { - var result, style, time; - - // Output results once all the vows have been checked - if (honored + broken + errored === total && remaining === 0) { - // If this isn't the last test suite in the chain, - // emit 'end', to trigger the next test suite. - if (promise && promise.listeners('end').length > 0) { - vows.reporter.report(['end']); - promise.emit('end', honored, broken, errored); - } else { - time = (new(Date) - start) / 1000; - // The 'finish' event is triggered once all the tests have been run. - // It's used by bin/vows - vows.promises[suites - 1].emit("finish", honored, broken, errored); - - vows.reporter.report([ 'finish', { - honored: honored, - broken: broken, - errored: errored, - total: total, - time: time - }]); - - if ((broken || errored) && buffer.length) { sys.puts(buffer.join('\n') + '\n') } - // Don't exit until stdout is empty - process.stdout.addListener('drain', function () { - process.exit(broken || errored ? 1 : 0); - }); - } - } -} - // // On exit, check that all promises have been fired. // If not, report an error message. // process.addListener('exit', function () { - if (honored + broken + errored < total) { + if (vows.suites.filter(function (s) { return s.results.time === null }).length > 0) { vows.reporter.report(['error', { error: "An EventEmitter has failed to fire.", type: 'promise' }]); } }); -// Run all vows/tests. -// It can take either a function as `tests`, -// or an object literal. +vows.suites = []; + +// +// Create a new test suite +// vows.describe = function (subject) { - this.options.Emitter.prototype.addVow = addVow; - this.options.Emitter.prototype.addVows = addVows; - this.remaining = 0; - this.promises = []; + var suite = new(Suite); - // Reset values - total = 0, honored = 0, - broken = 0, errored = 0; - buffer = [], suites = 0; + this.options.Emitter.prototype.addVow = addVow; + this.suites.push(suite); if (!vows.options.brief) { this.reporter.report(['subject', subject]); } - return new(events.EventEmitter)().addListener('newListener', function (e, listener) { - var that = this; - if (e === 'end') { - this.removeListener(e, listener); - process.nextTick(function () { - start = new(Date); - listener.call(that); - }); - } - }); + return suite; }; // Return the `vows` object after setting some options From 6fe14ec1c8f723d127865baf7d6c017aca357078 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 3 Jun 2010 16:02:39 -0400 Subject: [PATCH 175/409] suite.js init --- lib/vows/suite.js | 197 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 lib/vows/suite.js diff --git a/lib/vows/suite.js b/lib/vows/suite.js new file mode 100644 index 00000000..6bc41612 --- /dev/null +++ b/lib/vows/suite.js @@ -0,0 +1,197 @@ +var events = require('events'), + path = require('path'); + +var vows = require(path.join(__dirname, '..', 'vows')); +var Context = require('vows/context').Context; +var tryEnd = vows.tryEnd; + +this.Suite = function () { + this.results = { + honored: 0, + broken: 0, + errored: 0, + total: 0, + time: null + }; + this.batches = []; +}; + +this.Suite.prototype = new(function () { + this.addVows = function (tests) { + var batch = { + tests: tests, + suite: this, + remaining: 0, + honored: 0, + broken: 0, + errored: 0, + total: 0 + }; + + this.batches.push(batch); + + if (typeof(tests) === 'object') { + if ('topic' in tests) { + throw new(Error)("missing top-level context."); + } + // Count the number of vows/promises expected to fire, + // so we know when the tests are over. + // We match the keys against `matcher`, to decide + // whether or not they should be included in the test. + (function count(tests) { + var match = false; + batch.remaining ++; + Object.keys(tests).filter(function (k) { + return k !== 'topic'; + }).forEach(function (key) { + if (typeof(tests[key]) === "object") { + if (! (match = count(tests[key]) || + match || vows.options.matcher.test(key))) { + delete tests[key]; + batch.remaining --; + } + } + }); + return match; + })(tests); + } + + return this; + }; + + this.runVows = function (batch) { + var topic, + tests = batch.tests, + promise = batch.promise = new(events.EventEmitter); + + if (typeof(tests) === 'function') { + return tests.call(null); + } else { + // The test runner, it calls itself recursively, passing the + // previous context to the inner contexts. This is so the `topic` + // functions have access to all the previous context topics in their + // arguments list. + // It is defined and invoked at the same time. + // If it encounters a `topic` function, it waits for the returned + // promise to emit (the topic), at which point it runs the functions under it, + // passing the topic as an argument. + (function run(ctx, lastTopic) { + var old = false; + topic = ctx.tests.topic; + + if (typeof(topic) === 'function') { + // Run the topic, passing the previous context topics + topic = topic.apply(ctx.env, ctx.topics); + } + + // If this context has a topic, store it in `lastTopic`, + // if not, use the last topic, passed down by a parent + // context. + if (topic) { + lastTopic = topic; + } else { + old = true; + topic = lastTopic; + } + + // 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 instanceof vows.options.Emitter)) { + ctx.emitter = new(vows.options.Emitter); + + if (! ctx._callback) { + process.nextTick(function (val) { + return function () { ctx.emitter.emit("success", val) }; + }(topic)); + } else if (typeof(topic) !== "undefined" && !old) { + throw new(Error)("topic must not return anything when using `this.callback`."); + } + topic = ctx.emitter; + } + + topic.addListener('success', 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) ctx.topics.unshift(val); + }); + + // Now run the tests, or sub-contexts + Object.keys(ctx.tests).filter(function (k) { + return ctx.tests[k] && k !== 'topic'; + }).forEach(function (item) { + // Create a new evaluation context, + // inheriting from the parent one. + var env = Object.create(ctx.env); + + // Holds the current test or context + var vow = Object.create({ + callback: ctx.tests[item], + context: ctx.name, + description: item, + binding: ctx.env, + batch: batch + }); + + // If we encounter a function, add it to the callbacks + // of the `topic` function, so it'll get called once the + // topic fires. + // If we encounter an object literal, we recurse, sending it + // our current context. + if (typeof(vow.callback) === 'function') { + topic.addVow(vow); + } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { + // 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 + // synchronously. + if (topic) { + topic.addListener("success", function (ctx) { + return function (val) { + return run(new(Context)(vow, ctx, env), lastTopic); + }; + }(ctx)); + } else { + run(new(Context)(vow, ctx, env), lastTopic); + } + } + }); + // Check if we're done running the tests + batch.remaining --; + tryEnd(batch); + // This is our initial, empty context + })(new(Context)({ callback: tests, context: null, description: null }, {})); + } + return promise; + }; + + this.run = function (callback) { + var that = this; + var start = new(Date); + + (function run(batches) { + var batch; + + if (batch = batches.shift()) { + that.runVows(batch).addListener('end', function () { + run(batches); + }); + } else { + that.results.time = (new(Date) - start) / 1000; + + vows.reporter.report(['finish', that.results]); + + if (callback) { callback(that.results) } + + // Don't exit until stdout is empty + process.stdout.addListener('drain', function () { + process.exit(that.results.broken || that.results.errored ? 1 : 0); + }); + } + })(this.batches.slice(0)); + }; + + this.runParallel = function () {}; +}); From e5855a23e48752f186c2bf0ab3e05e3db4696d8e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 3 Jun 2010 16:03:09 -0400 Subject: [PATCH 176/409] fix dot-matrix reporter not reporting errors --- lib/vows/reporters/dot-matrix.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index 2887a23e..ce9face0 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -66,7 +66,7 @@ this.report = function (data, s) { puts(stylize(result, style)); break; case 'error': - messages.push('\n * ' + stylize(event.error, 'red')); + puts('\n\n * ' + stylize(event.error, 'red')); break; } return buffer.join(''); From e2d19518998a49a9c9ec4b926636a8fabea01787 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 3 Jun 2010 16:03:29 -0400 Subject: [PATCH 177/409] bye bye addVow --- test/addvow-test.js | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 test/addvow-test.js diff --git a/test/addvow-test.js b/test/addvow-test.js deleted file mode 100644 index 41a0af55..00000000 --- a/test/addvow-test.js +++ /dev/null @@ -1,22 +0,0 @@ -var path = require('path'); - -require.paths.unshift(path.join(__dirname, '..', 'lib')); - -var events = require('events'), - assert = require('assert'); -var vows = require('vows'); - -var promiser = function (val) { - return function () { - var promise = new(events.EventEmitter); - process.nextTick(function () { promise.emit('success', val) }); - return promise; - } -}; - -vows.describe("Vows/unit").addVows(function () { - promiser("hello world")().addVow(function (val) { - assert.equal(val, "hello world"); - }, "addVow()"); -}); - From 8cd49ba1d040676e5ee31b92f7368fba2ec6f4da Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 01:25:26 -0400 Subject: [PATCH 178/409] 'reporter' option instead of boolean flags. Also pass subject to Suite. --- lib/vows.js | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index f4595692..64917ca6 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -22,19 +22,12 @@ require.paths.unshift(__dirname); vows.options = { Emitter: events.EventEmitter, brief: false, - json: false, - spec: false, + reporter: require('vows/reporters/dot-matrix'), matcher: /.*/ }; vows.__defineGetter__('reporter', function () { - if (vows.options.json) { - return require('vows/reporters/json'); - } else if (vows.options.spec) { - return require('vows/reporters/spec'); - } else { - return require('vows/reporters/dot-matrix'); - } + return vows.options.reporter; }); var stylize = require('vows/console').stylize; @@ -51,11 +44,6 @@ vows.prepare = require('vows/extras').prepare; require('./assert/error'); require('./assert/macros'); -// -// Suite constructor -// -var Suite = require('vows/suite').Suite; - // // Checks if all the tests in the batch have been run, // and triggers the next batch (if any), by emitting the 'end' event. @@ -75,6 +63,11 @@ vows.tryEnd = function (batch) { } } +// +// Suite constructor +// +var Suite = require('vows/suite').Suite; + // // This function gets added to events.EventEmitter.prototype, by default. // It's essentially a wrapper around `addListener`, which adds all the specification @@ -161,7 +154,7 @@ vows.suites = []; // Create a new test suite // vows.describe = function (subject) { - var suite = new(Suite); + var suite = new(Suite)(subject); this.options.Emitter.prototype.addVow = addVow; this.suites.push(suite); From bd8a4f8f4c76f1e68662a7749b94bc19c4d66ac2 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 01:26:24 -0400 Subject: [PATCH 179/409] refactor reporters, share more. --- lib/vows/console.js | 18 ++++++++++++++++++ lib/vows/reporters/dot-matrix.js | 19 +++++-------------- lib/vows/reporters/spec.js | 18 +++++------------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/lib/vows/console.js b/lib/vows/console.js index 56fd8fb6..cea4c476 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -25,3 +25,21 @@ this.puts = function (options) { return options.stream ? options.stream.write(args.join('\n') + '\n') : buffer.push(args); }; }; + +this.result = function (event) { + var result = event.honored + " honored, " + + event.broken + " broken, " + + event.errored + " errored", + style = event.honored === event.total ? ('green') + : (event.errored === 0 ? 'yellow' : 'red'), + buffer = []; + + + if ('time' in event) { + buffer.push("\nVerified " + event.total + " vows in " + + (event.time + " seconds.\n")); + } + buffer.push(this.stylize(result, style)); + + return buffer; +}; diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index ce9face0..24eaa0e2 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -2,13 +2,15 @@ var sys = require('sys'); var options = {}; -var stylize = require('vows/console').stylize, - puts = require('vows/console').puts(options); +var console = require('vows/console'); +var stylize = console.stylize, + puts = console.puts(options); // // Console reporter // var stream, buffer, messages = []; +this.name = 'dot-matrix'; this.report = function (data, s) { var event = data[1]; @@ -46,24 +48,13 @@ this.report = function (data, s) { sys.print(' '); break; case 'finish': - var result = event.honored + " honored, " + - event.broken + " broken, " + - event.errored + " errored", - style = event.honored === event.total ? ('green') - : (event.errored === 0 ? 'yellow' : 'red'); - if (messages.length) { messages.pop(); // drop trailing blank message puts('\n\n' + messages.join('\n')); } else { sys.print('\n'); } - - if ('time' in event) { - puts("\nVerified " + event.total + " vows in " + - (event.time + " seconds.\n")); - } - puts(stylize(result, style)); + puts(console.result(event).join('\n')); break; case 'error': puts('\n\n * ' + stylize(event.error, 'red')); diff --git a/lib/vows/reporters/spec.js b/lib/vows/reporters/spec.js index 2fc8b27b..7872cc41 100644 --- a/lib/vows/reporters/spec.js +++ b/lib/vows/reporters/spec.js @@ -1,12 +1,14 @@ var sys = require('sys'); var options = {}; -var stylize = require('vows/console').stylize, - puts = require('vows/console').puts(options); +var console = require('vows/console'); +var stylize = console.stylize, + puts = console.puts(options); // // Console reporter // +this.name = 'spec'; this.report = function (data, s) { var event = data[1]; @@ -39,17 +41,7 @@ this.report = function (data, s) { sys.print('\n'); break; case 'finish': - var result = event.honored + " honored, " + - event.broken + " broken, " + - event.errored + " errored", - style = event.honored === event.total ? ('green') - : (event.errored === 0 ? 'yellow' : 'red'); - - if ('time' in event) { - puts("\nVerified " + event.total + " vows in " + - (event.time + " seconds.\n")); - } - puts(stylize(result, style)); + puts(console.result(event).join('\n')); break; case 'error': puts('\n * ' + stylize(event.error, 'red')); From 073e8754d8ac93f80286d6dba245b6aab009d08f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 01:27:03 -0400 Subject: [PATCH 180/409] ability to reset batch/suite --- lib/vows/suite.js | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 6bc41612..fd13a570 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -1,27 +1,40 @@ var events = require('events'), path = require('path'); -var vows = require(path.join(__dirname, '..', 'vows')); +require.paths.unshift(path.join(__dirname, '..')); + +var vows = require('vows'); var Context = require('vows/context').Context; var tryEnd = vows.tryEnd; -this.Suite = function () { - this.results = { - honored: 0, - broken: 0, - errored: 0, - total: 0, - time: null - }; +this.Suite = function (subject) { + this.subject = subject; this.batches = []; + this.reset(); }; this.Suite.prototype = new(function () { + this.reset = function () { + this.results = { + honored: 0, + broken: 0, + errored: 0, + total: 0, + time: null + }; + this.batches.forEach(function (b) { + b.lastContext = null; + b.remaining = b._remaining; + b.honored = b.broken = b.errored = b.total = 0; + }); + }; + this.addVows = function (tests) { var batch = { tests: tests, suite: this, remaining: 0, + _remaining: 0, honored: 0, broken: 0, errored: 0, @@ -55,6 +68,7 @@ this.Suite.prototype = new(function () { return match; })(tests); } + batch._remaining = batch.remaining; return this; }; @@ -189,6 +203,7 @@ this.Suite.prototype = new(function () { process.stdout.addListener('drain', function () { process.exit(that.results.broken || that.results.errored ? 1 : 0); }); + that.reset(); } })(this.batches.slice(0)); }; From 0e3b661d089faf2560b2aefbeb4e87aa3cb6e0e2 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 01:27:26 -0400 Subject: [PATCH 181/409] ability to export batch/suite --- lib/vows/suite.js | 4 ++++ test/vows-test.js | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index fd13a570..42cb3a01 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -209,4 +209,8 @@ this.Suite.prototype = new(function () { }; this.runParallel = function () {}; + + this.export = function (exports) { + return exports.vows = this; + }; }); diff --git a/test/vows-test.js b/test/vows-test.js index 3595fa83..01fde4d4 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -272,4 +272,4 @@ vows.describe("Vows").addVows({ "A 3rd test suite": { "should run last": function () {} } -}); +}).export(this); From 7ce45791dc59ea8b0dac7be8800d6e71bf3b2818 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 01:27:46 -0400 Subject: [PATCH 182/409] another test, just to test runner --- test/other-test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 test/other-test.js diff --git a/test/other-test.js b/test/other-test.js new file mode 100644 index 00000000..a4d0d943 --- /dev/null +++ b/test/other-test.js @@ -0,0 +1,15 @@ +var path = require('path'); + +require.paths.unshift(path.join(__dirname, '..', 'lib')); + +var assert = require('assert'); +var vows = require('vows'); + +vows.describe("Vows/other").addVows({ + "Another test": { + topic: true, + "exists in the test/ folder!": function (topic) { + assert.ok (topic); + } + } +}).export(this); From 244cd012183f65dbd872d71ce5185f6a86f647d9 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 01:58:20 -0400 Subject: [PATCH 183/409] reset Suite before running it, instead of after, so we don't upset the exit check --- lib/vows/suite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 42cb3a01..cc2cfd39 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -184,6 +184,7 @@ this.Suite.prototype = new(function () { this.run = function (callback) { var that = this; var start = new(Date); + this.reset(); (function run(batches) { var batch; @@ -203,7 +204,6 @@ this.Suite.prototype = new(function () { process.stdout.addListener('drain', function () { process.exit(that.results.broken || that.results.errored ? 1 : 0); }); - that.reset(); } })(this.batches.slice(0)); }; From af04a10c90120c7fa9348bf17d07e20c11fcf5d8 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 02:00:02 -0400 Subject: [PATCH 184/409] exported Suites also run automatically when file is run directly --- lib/vows/suite.js | 8 ++++++-- test/other-test.js | 2 +- test/vows-test.js | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index cc2cfd39..403894f7 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -210,7 +210,11 @@ this.Suite.prototype = new(function () { this.runParallel = function () {}; - this.export = function (exports) { - return exports.vows = this; + this.export = function (module) { + if (require.main === module) { + return this.run(); + } else { + return module.exports.vows = this; + } }; }); diff --git a/test/other-test.js b/test/other-test.js index a4d0d943..013a50cf 100644 --- a/test/other-test.js +++ b/test/other-test.js @@ -12,4 +12,4 @@ vows.describe("Vows/other").addVows({ assert.ok (topic); } } -}).export(this); +}).export(module); diff --git a/test/vows-test.js b/test/vows-test.js index 01fde4d4..99a62153 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -272,4 +272,4 @@ vows.describe("Vows").addVows({ "A 3rd test suite": { "should run last": function () {} } -}).export(this); +}).export(module); From 88b5ade8201c0c7b0e6cc4fa6fa5b4d66a0addf7 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:32:42 -0400 Subject: [PATCH 185/409] complete rewrite of bin/vows --- bin/vows | 255 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 168 insertions(+), 87 deletions(-) diff --git a/bin/vows b/bin/vows index 3d1b74a2..15ec2ac7 100755 --- a/bin/vows +++ b/bin/vows @@ -3,29 +3,104 @@ var path = require('path'), sys = require('sys'), fs = require('fs'), - events = require('events'), - spawn = require('child_process').spawn; + events = require('events'); + +var inspect = require('eyes').inspector({ stream: null }); require.paths.unshift(path.join(__dirname, '..', 'lib')); -var console = require('vows/reporters/dot-matrix'); +var console = require('vows/console'); +var _reporter = require('vows/reporters/dot-matrix'), reporter = { + name: _reporter.name, +}; + +var options = { + reporter: reporter, + brief: false, + matcher: /.*/ +}; + +var suites = []; + +// Get rid of process runner +// ('node' in most cases) +var arg, args = [], argv = process.argv.slice(2); -var options = []; +// +// Parse command-line parameters +// +while (arg = argv.shift()) { + if (arg === __filename) { continue } -var argv = process.argv.slice(1).filter(function (a) { - if (a !== __filename) { - if (a[0] === '-') { options.push(a) } - return true; + if (arg[0] !== '-') { + args.push(arg); + } else { + arg = arg.match(/^--?(.+)/)[1]; + + if (arg[0] === 'R') { + options.matcher = new(RegExp)(arg.slice(1)); + } else if (arg in options) { + options[arg] = true; + } else { + switch (arg) { + case 'json': + _reporter = require('vows/reporters/json'); + break; + case 'spec': + _reporter = require('vows/reporters/spec'); + break; + case 'verbose': + case 'v': + options.verbose = true; + break; + } + } } -}); +} + +if (args.length === 0) { + options.reporter = reporter = require('vows/reporters/watch'); +} + +var vows = require('vows').config(options); +var stylize = require('vows/console').stylize; -if (argv.length > 0) { - argv.forEach(function (arg) { - runTest(arg, function (result) { - console.report(result); +msg('bin', 'argv', args); +msg('bin', 'options', { reporter: options.reporter.name, matcher: options.matcher }); + +if (args.length > 0) { + reporter.report = function (data) { + switch (data[0]) { + case 'subject': + _reporter.report(data); + break; + case 'vow': + case 'context': + _reporter.report(data); + break; + case 'end': + (options.verbose || _reporter.name === 'json') && _reporter.report(data); + break; + case 'finish': + options.verbose ? _reporter.print('\n') : _reporter.print(' '); + } + }; + suites = args.map(function (a) { + a = path.join(process.cwd(), a.replace(/\.js$/, '')); + msg('runner', "loading", a); + return require(a).vows; + }); + runSuites(suites, function (results) { + !options.verbose && _reporter.print('\n'); + msg('runner', 'finish'); + _reporter.report(['finish', results], { + write: function (str) { + sys.print(str.replace(/^\n\n/, '\n')); + } }); }); } else { + msg('watcher', 'watching files in', process.cwd() + '/'); // // Watch mode // @@ -34,16 +109,12 @@ if (argv.length > 0) { '. ', '.. ', '... ', ' ...', ' ..', ' .', ' .', ' ..', '... ', '.. ', '. ' - ], wheel = [ - '-', '\\', '|', '/' ]; - var current = 0, - runningTests = 0, - currentFile, - gracePeriod = 0, - status, - lastRun, + var status, + current = 0, testFolder, + running = 0, + lastRun, colors = ['32m', '33m', '31m'], timer = setInterval(tick, 100), root = fs.readdirSync('.'); @@ -56,32 +127,30 @@ if (argv.length > 0) { throw new(Error)("Couldn't find test folder"); } + process.addListener('uncaughtException', cleanup); + process.addListener('exit', cleanup); + process.addListener('SIGINT', function () { + cleanup(); + process.exit(); + }); + process.addListener('SIGQUIT', function () { + changed(); + }); + cursorHide(); // Run every 100ms function tick() { + if (running) { return } + cursorSave(); eraseLine(); lastRun && esc(colors[status.errored ? 2 : (status.broken ? 1 : 0)]); + print(clock[current]); - if (runningTests > 0 || gracePeriod) { - gracePeriod--; - print(wheel[current]); - esc('39m'); - print(' detected change in ' + currentFile + '...'); - if (current == wheel.length - 1) { current = -1 } - } else { - print(clock[current]); - print(' '); - status && print(console.report(['finish', status], null)); - if (lastRun) { - esc('90m'); - print( ' (' + lastRun.valueOf() + ')'); - } - if (current == clock.length - 1) { current = -1 } - } + if (current == clock.length - 1) { current = -1 } - current++; + current ++; esc('39m'); cursorRestore(); } @@ -89,7 +158,7 @@ if (argv.length > 0) { // // Utility functions // - function print(str) { process.stdout.write(str) } + function print(str) { sys.print(str) } function esc(str) { print("\033[" + str) } function eraseLine() { esc("2K") } function cursorSave() { esc("s") } @@ -104,24 +173,35 @@ if (argv.length > 0) { // function changed(file) { status = { honored: 0, broken: 0, errored: 0 }; - gracePeriod = 10; - current = 0; - - currentFile = file + '.js'; - - file = /-(test|spec)$/.test(file) ? path.join(testFolder, file + '.js') - : path.join(testFolder, file + '-' + testFolder + '.js'); - - paths(testFolder).forEach(function (p) { - runningTests ++; - runTest(p).addListener('finish', function (obj) { - runningTests --; - delete obj.time; - status.honored += obj.honored; - status.broken += obj.broken; - status.errored += obj.errored; - lastRun = new(Date); - }); + + msg('watcher', 'detected change in', file + '.js'); + + file = (/-(test|spec)$/.test(file) ? path.join(testFolder, file) + : path.join(testFolder, file + '-' + testFolder)) + '.js'; + + try { + fs.statSync(file); + } catch (e) { + msg('watcher', 'no equivalence found, running all tests.'); + file = null; + } + + var suites = (/-(test|spec)\.js$/.test(file) ? [file] : paths(testFolder)).map(function (p) { + return path.join(process.cwd(), p.replace(/\.js$/, '')); + }).map(function (p) { + delete(require.main.moduleCache[p]); + return p; + }); + + msg('watcher', 'loading', suites); + running ++; + + runSuites(suites.map(function (s) { return require(s).vows }), function (results) { + delete(results.time); + print(console.result(results).join('') + '\n\n'); + lastRun = new(Date); + status = results; + running --; }); } // @@ -167,38 +247,39 @@ if (argv.length > 0) { } }); }); - - process.addListener('exit', cleanup); - process.addListener('SIGINT', function () { - cleanup(); - process.exit(); - }); - process.addListener('SIGQUIT', function () { - changed(); - }); })(); } -function runTest(file, callback) { - var test = spawn('node', options.concat([file, '--json'])), obj; - var promise = new(events.EventEmitter); - - test.stdout.addListener('data', function (data) { - data.toString('utf8').trim().split('\n').forEach(function (chunk) { - try { - obj = JSON.parse(chunk); - promise.emit(obj[0], obj[1]); - if (callback) { callback(obj) } - } catch (e) { - sys.puts(e, chunk); - } - }); - }); - - test.stderr.addListener('data', function (data) { - sys.debug(data.stack); - }); - test.addListener('exit', function (code) {}); - return promise; +function runSuites(suites, callback) { + var results = { + honored: 0, + broken: 0, + errored: 0, + total: 0, + time: 0 + }; + (function run(suites, callback) { + var suite = suites.shift(); + if (suite) { + msg('runner', "running", suite.subject); + suite.run(function (result) { + Object.keys(result).forEach(function (k) { + results[k] += result[k]; + }); + run(suites, callback); + }); + } else { + callback(results); + } + })(suites, callback); } +function msg(cmd, subject, str) { + if (options.verbose) { + sys.puts( stylize('vows ', 'green') + + stylize(cmd, 'bold') + + ' ' + subject + ' ' + + (str ? (str[0] === '/' ? str : inspect(str)) : '') + ); + } +} From 1ab43bdea440e6cf988da9aa4c9c5516b2bbd836 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:33:20 -0400 Subject: [PATCH 186/409] report subject on run() --- lib/vows.js | 4 ---- lib/vows/suite.js | 3 +++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 64917ca6..cb725b0b 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -159,10 +159,6 @@ vows.describe = function (subject) { this.options.Emitter.prototype.addVow = addVow; this.suites.push(suite); - if (!vows.options.brief) { - this.reporter.report(['subject', subject]); - } - return suite; }; diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 403894f7..645c1e3f 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -184,8 +184,11 @@ this.Suite.prototype = new(function () { this.run = function (callback) { var that = this; var start = new(Date); + this.reset(); + vows.reporter.report(['subject', this.subject]); + (function run(batches) { var batch; From 151b76c91211d42537f71f46678408568ac4eea0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:34:11 -0400 Subject: [PATCH 187/409] fuck the buffer --- lib/vows/console.js | 2 +- lib/vows/reporters/dot-matrix.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/vows/console.js b/lib/vows/console.js index cea4c476..35cddbc2 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -22,7 +22,7 @@ this.puts = function (options) { return a.replace(/`([^`]+)`/g, function (_, capture) { return stylize(capture, 'italic') }) .replace(/\*([^*]+)\*/g, function (_, capture) { return stylize(capture, 'bold') }); }); - return options.stream ? options.stream.write(args.join('\n') + '\n') : buffer.push(args); + return options.stream.write(args.join('\n') + '\n'); }; }; diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index 24eaa0e2..6e0545b5 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -8,14 +8,13 @@ var stylize = console.stylize, // // Console reporter // -var stream, buffer, messages = []; +var stream, messages = []; this.name = 'dot-matrix'; this.report = function (data, s) { var event = data[1]; options.stream = typeof(s) === 'object' ? s : process.stdout; - buffer = []; switch (data[0]) { case 'subject': @@ -49,7 +48,6 @@ this.report = function (data, s) { break; case 'finish': if (messages.length) { - messages.pop(); // drop trailing blank message puts('\n\n' + messages.join('\n')); } else { sys.print('\n'); From b9882a50f16edd4da1eb18a06eb1b3cb35d70d21 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:34:59 -0400 Subject: [PATCH 188/409] add print() function to reporters --- lib/vows/reporters/dot-matrix.js | 5 ++++- lib/vows/reporters/json.js | 3 +++ lib/vows/reporters/spec.js | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index 6e0545b5..850d84ce 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -58,5 +58,8 @@ this.report = function (data, s) { puts('\n\n * ' + stylize(event.error, 'red')); break; } - return buffer.join(''); +}; + +this.print = function (str) { + sys.print(str); }; diff --git a/lib/vows/reporters/json.js b/lib/vows/reporters/json.js index 15359904..449a68a4 100644 --- a/lib/vows/reporters/json.js +++ b/lib/vows/reporters/json.js @@ -2,6 +2,9 @@ var sys = require('sys'); // // Console JSON reporter // +this.name = 'json'; this.report = function (obj) { sys.puts(JSON.stringify(obj)); }; + +this.print = function (str) {}; diff --git a/lib/vows/reporters/spec.js b/lib/vows/reporters/spec.js index 7872cc41..405b3e4f 100644 --- a/lib/vows/reporters/spec.js +++ b/lib/vows/reporters/spec.js @@ -49,3 +49,6 @@ this.report = function (data, s) { } }; +this.print = function (str) { + sys.print(str); +}; From 1f2abe5fe09b49fa122d094598dde6ccf6b00814 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:35:54 -0400 Subject: [PATCH 189/409] (new) watch reporter --- lib/vows/reporters/watch.js | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 lib/vows/reporters/watch.js diff --git a/lib/vows/reporters/watch.js b/lib/vows/reporters/watch.js new file mode 100644 index 00000000..bb69c791 --- /dev/null +++ b/lib/vows/reporters/watch.js @@ -0,0 +1,43 @@ +var sys = require('sys'); + +var options = {}; +var console = require('vows/console'); +var stylize = console.stylize, + puts = console.puts(options); +// +// Console reporter +// +this.name = 'watch'; +this.report = function (data) { + var event = data[1]; + + options.stream = process.stdout; + + switch (data[0]) { + case 'subject': + puts(stylize(event, 'underline') + '\n'); + break; + case 'vow': + if (event.status !== 'honored') { + if (event.status === 'broken') { + puts(' - ' + stylize(event.title, 'yellow')); + puts(' ~ ' + event.exception); + puts(''); + } else if (event.status === 'errored') { + puts(' - ' + stylize(event.title, 'red')); + if (event.exception.type === 'promise') { + puts(' * ' + stylize("An 'error' event was caught: " + + stylize(event.exception.error, 'bold'), 'red')); + } else { + puts(' ! ' + stylize(event.exception, 'red')); + } + puts(''); + } + } + break; + case 'error': + puts('\n\n * ' + stylize(event.error, 'red')); + break; + } +}; +this.print = function (str) {}; From ec867b64e50aeee673470e27908c9da60eb0e002 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:36:25 -0400 Subject: [PATCH 190/409] output fixes --- lib/vows/console.js | 2 +- lib/vows/reporters/dot-matrix.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/vows/console.js b/lib/vows/console.js index 35cddbc2..c8b0f8f0 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -36,7 +36,7 @@ this.result = function (event) { if ('time' in event) { - buffer.push("\nVerified " + event.total + " vows in " + + buffer.push("Verified " + event.total + " vows in " + (event.time + " seconds.\n")); } buffer.push(this.stylize(result, style)); diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index 850d84ce..412d2455 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -18,8 +18,7 @@ this.report = function (data, s) { switch (data[0]) { case 'subject': - puts('\n' + stylize(event, 'underline') + '\n'); - sys.print(' '); + messages.push(stylize(event, 'underline') + '\n'); break; case 'context': break; From c3afbbc873fa69e226e322069e12eb42ee7a77d5 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:42:51 -0400 Subject: [PATCH 191/409] revised vows.js header --- lib/vows.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index cb725b0b..d166222e 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -5,11 +5,15 @@ // // var vows = require('vows'); // -// vows.describe('Deep Thought').addVows(function () { -// question('what is the answer to the universe?').addVow(function (answer) { -// assert.equals(answer, 42); -// }, 'it should know the answer to the ultimate question of life'); -// }); +// vows.describe('Deep Thought').addVows({ +// "An instance of DeepThought": { +// topic: new DeepThought, +// +// "should know the answer to the ultimate question of life": function (deepThought) { +// assert.equal (deepThought.question('what is the answer to the universe?'), 42); +// } +// } +// }).run(); // var sys = require('sys'), events = require('events'), From 7d93078ed5a3cfd450c7e13e6d99cd56120ff61b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:43:29 -0400 Subject: [PATCH 192/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 88be48f0..086696b2 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,5 @@ "dependencies" : {"eyes": ">=0.1.0"}, "directories" : {"lib": "./lib"}, "main" : "./lib/vows", - "version" : "0.2.5" + "version" : "0.3.0" } From 575d1a519b4c9a584d6886011d7ec4a7cb3ae008 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:44:49 -0400 Subject: [PATCH 193/409] updated Makefile to use test runner --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1a1cb539..b2fc4b8a 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,6 @@ # Run all tests # test: - @@node test/vows-test.js + @@bin/vows test/vows-test.js test/other-test.js .PHONY: test install From 2f9bb00781b007d43bbe89a6ab411037d6144651 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 4 Jun 2010 23:47:17 -0400 Subject: [PATCH 194/409] (dist) added bin to package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 086696b2..82703d31 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,6 @@ "dependencies" : {"eyes": ">=0.1.0"}, "directories" : {"lib": "./lib"}, "main" : "./lib/vows", + "bin" : { "vows": "./bin/vows" }, "version" : "0.3.0" } From b9d856e3b19944da1135a60bea95a914b70af972 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 01:42:16 -0400 Subject: [PATCH 195/409] (dist) lib is lib/vows --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82703d31..ad2426af 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "author" : "Alexis Sellier ", "contributors" : [], "dependencies" : {"eyes": ">=0.1.0"}, - "directories" : {"lib": "./lib"}, + "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "version" : "0.3.0" From f825f7f2a5389de59d0869d7087e71e2de139b9e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 02:21:33 -0400 Subject: [PATCH 196/409] tidy up the requires in bin/vows --- bin/vows | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/vows b/bin/vows index 15ec2ac7..5cff10db 100755 --- a/bin/vows +++ b/bin/vows @@ -9,7 +9,9 @@ var inspect = require('eyes').inspector({ stream: null }); require.paths.unshift(path.join(__dirname, '..', 'lib')); +var vows = require('vows'); var console = require('vows/console'); +var stylize = require('vows/console').stylize; var _reporter = require('vows/reporters/dot-matrix'), reporter = { name: _reporter.name, }; @@ -62,8 +64,7 @@ if (args.length === 0) { options.reporter = reporter = require('vows/reporters/watch'); } -var vows = require('vows').config(options); -var stylize = require('vows/console').stylize; +vows.config(options); msg('bin', 'argv', args); msg('bin', 'options', { reporter: options.reporter.name, matcher: options.matcher }); From aae87c23ab83b7a964a56f04e03194b54ca1d42d Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 03:01:27 -0400 Subject: [PATCH 197/409] no more global module state --- bin/vows | 4 +--- lib/vows.js | 31 +++---------------------------- lib/vows/suite.js | 38 +++++++++++++++++++++++++++++++------- 3 files changed, 35 insertions(+), 38 deletions(-) diff --git a/bin/vows b/bin/vows index 5cff10db..55c863b7 100755 --- a/bin/vows +++ b/bin/vows @@ -64,8 +64,6 @@ if (args.length === 0) { options.reporter = reporter = require('vows/reporters/watch'); } -vows.config(options); - msg('bin', 'argv', args); msg('bin', 'options', { reporter: options.reporter.name, matcher: options.matcher }); @@ -263,7 +261,7 @@ function runSuites(suites, callback) { var suite = suites.shift(); if (suite) { msg('runner', "running", suite.subject); - suite.run(function (result) { + suite.run(options, function (result) { Object.keys(result).forEach(function (k) { results[k] += result[k]; }); diff --git a/lib/vows.js b/lib/vows.js index d166222e..f8200d64 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -41,6 +41,7 @@ vows.inspect = function inspect(val) { }; vows.prepare = require('vows/extras').prepare; +vows.tryEnd = require('vows/suite').tryEnd; // // Assertion Macros & Extensions @@ -48,25 +49,6 @@ vows.prepare = require('vows/extras').prepare; require('./assert/error'); require('./assert/macros'); -// -// Checks if all the tests in the batch have been run, -// and triggers the next batch (if any), by emitting the 'end' event. -// -vows.tryEnd = function (batch) { - var result, style, time; - - if (batch.honored + batch.broken + batch.errored === batch.total && - batch.remaining === 0) { - - Object.keys(batch).forEach(function (k) { - (k in batch.suite.results) && (batch.suite.results[k] += batch[k]); - }); - - vows.reporter.report(['end']); - batch.promise.emit('end', batch.honored, batch.broken, batch.errored); - } -} - // // Suite constructor // @@ -131,9 +113,9 @@ function addVow(vow) { if (exception || !vows.options.brief) { if (vow.context && batch.lastContext !== vow.context) { batch.lastContext = vow.context; - vows.reporter.report(['context', vow.context]); + batch.suite.report(['context', vow.context]); } - vows.reporter.report(['vow', { + batch.suite.report(['vow', { title: vow.description, status: status, exception: exception || null @@ -165,10 +147,3 @@ vows.describe = function (subject) { return suite; }; - -// Return the `vows` object after setting some options -vows.config = function (opts) { - for (var k in opts) { this.options[k] = opts[k] } - return this; -}; - diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 645c1e3f..18aaa357 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -5,7 +5,6 @@ require.paths.unshift(path.join(__dirname, '..')); var vows = require('vows'); var Context = require('vows/context').Context; -var tryEnd = vows.tryEnd; this.Suite = function (subject) { this.subject = subject; @@ -110,8 +109,8 @@ 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 instanceof vows.options.Emitter)) { - ctx.emitter = new(vows.options.Emitter); + if (! (topic instanceof events.EventEmitter)) { + ctx.emitter = new(events.EventEmitter); if (! ctx._callback) { process.nextTick(function (val) { @@ -174,20 +173,26 @@ this.Suite.prototype = new(function () { }); // Check if we're done running the tests batch.remaining --; - tryEnd(batch); + exports.tryEnd(batch); // This is our initial, empty context })(new(Context)({ callback: tests, context: null, description: null }, {})); } return promise; }; - this.run = function (callback) { + this.report = function () { + return this.reporter.report.apply(this.reporter, arguments); + }; + + this.run = function (options, callback) { var that = this; var start = new(Date); + this.matcher = options.matcher || vows.options.matcher; + this.reporter = options.reporter || vows.options.reporter; this.reset(); - vows.reporter.report(['subject', this.subject]); + this.report(['subject', this.subject]); (function run(batches) { var batch; @@ -199,7 +204,7 @@ this.Suite.prototype = new(function () { } else { that.results.time = (new(Date) - start) / 1000; - vows.reporter.report(['finish', that.results]); + that.report(['finish', that.results]); if (callback) { callback(that.results) } @@ -221,3 +226,22 @@ this.Suite.prototype = new(function () { } }; }); + +// +// Checks if all the tests in the batch have been run, +// and triggers the next batch (if any), by emitting the 'end' event. +// +this.tryEnd = function (batch) { + var result, style, time; + + if (batch.honored + batch.broken + batch.errored === batch.total && + batch.remaining === 0) { + + Object.keys(batch).forEach(function (k) { + (k in batch.suite.results) && (batch.suite.results[k] += batch[k]); + }); + + batch.suite.report(['end']); + batch.promise.emit('end', batch.honored, batch.broken, batch.errored); + } +}; From ee77415b9c0f0441c6d31a4db2c7f83ccccd8226 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 16:22:51 -0400 Subject: [PATCH 198/409] Suite-level matcher/reporter --- lib/vows/suite.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 18aaa357..e5b6d5a6 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -8,6 +8,8 @@ var Context = require('vows/context').Context; this.Suite = function (subject) { this.subject = subject; + this.matcher = /.*/; + this.reporter = require('vows/reporters/dot-matrix'); this.batches = []; this.reset(); }; From 247015bead1613f9e5763eeb8756828b69692deb Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 16:35:30 -0400 Subject: [PATCH 199/409] use options in run() or default to Suite --- lib/vows/suite.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index e5b6d5a6..16466a40 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -187,13 +187,15 @@ this.Suite.prototype = new(function () { }; this.run = function (options, callback) { - var that = this; - var start = new(Date); + var that = this, start; + + this.matcher = options.matcher || this.matcher; + this.reporter = options.reporter || this.reporter; - this.matcher = options.matcher || vows.options.matcher; - this.reporter = options.reporter || vows.options.reporter; this.reset(); + start = new(Date); + this.report(['subject', this.subject]); (function run(batches) { From 7beb71de61529b95b04e375a03d0cadb3347da00 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 16:41:01 -0400 Subject: [PATCH 200/409] parse vows at run-time, so we can apply a matcher --- lib/vows/suite.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 16466a40..5191df23 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -31,7 +31,7 @@ this.Suite.prototype = new(function () { }; this.addVows = function (tests) { - var batch = { + this.batches.push({ tests: tests, suite: this, remaining: 0, @@ -40,9 +40,12 @@ this.Suite.prototype = new(function () { broken: 0, errored: 0, total: 0 - }; + }); + return this; + }; - this.batches.push(batch); + this.parseVows = function (batch, matcher) { + var tests = batch.tests; if (typeof(tests) === 'object') { if ('topic' in tests) { @@ -70,8 +73,6 @@ this.Suite.prototype = new(function () { })(tests); } batch._remaining = batch.remaining; - - return this; }; this.runVows = function (batch) { @@ -192,6 +193,10 @@ this.Suite.prototype = new(function () { this.matcher = options.matcher || this.matcher; this.reporter = options.reporter || this.reporter; + this.batches.forEach(function (batch) { + that.parseVows(batch, that.matcher); + }); + this.reset(); start = new(Date); From a2e15a225185c8cfebe8d34695878d1caea73574 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 16:46:34 -0400 Subject: [PATCH 201/409] better vow counting --- lib/vows/suite.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 5191df23..b4643801 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -55,22 +55,24 @@ this.Suite.prototype = new(function () { // so we know when the tests are over. // We match the keys against `matcher`, to decide // whether or not they should be included in the test. - (function count(tests) { + (function count(tests, _match) { var match = false; - batch.remaining ++; + Object.keys(tests).filter(function (k) { return k !== 'topic'; }).forEach(function (key) { - if (typeof(tests[key]) === "object") { - if (! (match = count(tests[key]) || - match || vows.options.matcher.test(key))) { - delete tests[key]; - batch.remaining --; - } + match = _match || matcher.test(key); + + if (typeof(tests[key]) === 'object') { + match = count(tests[key], match); } }); + + if (match) { batch.remaining ++ } + else { tests._skip = true } + return match; - })(tests); + })(tests, false); } batch._remaining = batch.remaining; }; From b3985d8a96e692267e4af5b5172a957b277c0ec9 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 17:17:44 -0400 Subject: [PATCH 202/409] we don't support vows as functions anymore --- lib/vows/suite.js | 230 ++++++++++++++++++++++------------------------ 1 file changed, 112 insertions(+), 118 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index b4643801..57299fc0 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -47,33 +47,31 @@ this.Suite.prototype = new(function () { this.parseVows = function (batch, matcher) { var tests = batch.tests; - if (typeof(tests) === 'object') { - if ('topic' in tests) { - throw new(Error)("missing top-level context."); - } - // Count the number of vows/promises expected to fire, - // so we know when the tests are over. - // We match the keys against `matcher`, to decide - // whether or not they should be included in the test. - (function count(tests, _match) { - var match = false; - - Object.keys(tests).filter(function (k) { - return k !== 'topic'; - }).forEach(function (key) { - match = _match || matcher.test(key); - - if (typeof(tests[key]) === 'object') { - match = count(tests[key], match); - } - }); + if ('topic' in tests) { + throw new(Error)("missing top-level context."); + } + // Count the number of vows/promises expected to fire, + // so we know when the tests are over. + // We match the keys against `matcher`, to decide + // whether or not they should be included in the test. + (function count(tests, _match) { + var match = false; + + Object.keys(tests).filter(function (k) { + return k !== 'topic'; + }).forEach(function (key) { + match = _match || matcher.test(key); + + if (typeof(tests[key]) === 'object') { + match = count(tests[key], match); + } + }); - if (match) { batch.remaining ++ } - else { tests._skip = true } + if (match) { batch.remaining ++ } + else { tests._skip = true } - return match; - })(tests, false); - } + return match; + })(tests, false); batch._remaining = batch.remaining; }; @@ -82,106 +80,102 @@ this.Suite.prototype = new(function () { tests = batch.tests, promise = batch.promise = new(events.EventEmitter); - if (typeof(tests) === 'function') { - return tests.call(null); - } else { - // The test runner, it calls itself recursively, passing the - // previous context to the inner contexts. This is so the `topic` - // functions have access to all the previous context topics in their - // arguments list. - // It is defined and invoked at the same time. - // If it encounters a `topic` function, it waits for the returned - // promise to emit (the topic), at which point it runs the functions under it, - // passing the topic as an argument. - (function run(ctx, lastTopic) { - var old = false; - topic = ctx.tests.topic; - - if (typeof(topic) === 'function') { - // Run the topic, passing the previous context topics - topic = topic.apply(ctx.env, ctx.topics); - } + // The test runner, it calls itself recursively, passing the + // previous context to the inner contexts. This is so the `topic` + // functions have access to all the previous context topics in their + // arguments list. + // It is defined and invoked at the same time. + // If it encounters a `topic` function, it waits for the returned + // promise to emit (the topic), at which point it runs the functions under it, + // passing the topic as an argument. + (function run(ctx, lastTopic) { + var old = false; + topic = ctx.tests.topic; + + if (typeof(topic) === 'function') { + // Run the topic, passing the previous context topics + topic = topic.apply(ctx.env, ctx.topics); + } - // If this context has a topic, store it in `lastTopic`, - // if not, use the last topic, passed down by a parent - // context. - if (topic) { - lastTopic = topic; - } else { - old = true; - topic = lastTopic; - } + // If this context has a topic, store it in `lastTopic`, + // if not, use the last topic, passed down by a parent + // context. + if (topic) { + lastTopic = topic; + } else { + old = true; + topic = lastTopic; + } - // 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 instanceof events.EventEmitter)) { - ctx.emitter = new(events.EventEmitter); - - if (! ctx._callback) { - process.nextTick(function (val) { - return function () { ctx.emitter.emit("success", val) }; - }(topic)); - } else if (typeof(topic) !== "undefined" && !old) { - throw new(Error)("topic must not return anything when using `this.callback`."); - } - topic = ctx.emitter; + // 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 instanceof events.EventEmitter)) { + ctx.emitter = new(events.EventEmitter); + + if (! ctx._callback) { + process.nextTick(function (val) { + return function () { ctx.emitter.emit("success", val) }; + }(topic)); + } else if (typeof(topic) !== "undefined" && !old) { + throw new(Error)("topic must not return anything when using `this.callback`."); } + topic = ctx.emitter; + } - topic.addListener('success', 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) ctx.topics.unshift(val); + topic.addListener('success', 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) ctx.topics.unshift(val); + }); + + // Now run the tests, or sub-contexts + Object.keys(ctx.tests).filter(function (k) { + return ctx.tests[k] && k !== 'topic' && !ctx.tests[k]._skip; + }).forEach(function (item) { + // Create a new evaluation context, + // inheriting from the parent one. + var env = Object.create(ctx.env); + + // Holds the current test or context + var vow = Object.create({ + callback: ctx.tests[item], + context: ctx.name, + description: item, + binding: ctx.env, + batch: batch }); - // Now run the tests, or sub-contexts - Object.keys(ctx.tests).filter(function (k) { - return ctx.tests[k] && k !== 'topic'; - }).forEach(function (item) { - // Create a new evaluation context, - // inheriting from the parent one. - var env = Object.create(ctx.env); - - // Holds the current test or context - var vow = Object.create({ - callback: ctx.tests[item], - context: ctx.name, - description: item, - binding: ctx.env, - batch: batch - }); - - // If we encounter a function, add it to the callbacks - // of the `topic` function, so it'll get called once the - // topic fires. - // If we encounter an object literal, we recurse, sending it - // our current context. - if (typeof(vow.callback) === 'function') { - topic.addVow(vow); - } else if (typeof(vow.callback) === 'object' && ! Array.isArray(vow.callback)) { - // 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 - // synchronously. - if (topic) { - topic.addListener("success", function (ctx) { - return function (val) { - return run(new(Context)(vow, ctx, env), lastTopic); - }; - }(ctx)); - } else { - run(new(Context)(vow, ctx, env), lastTopic); - } + // If we encounter a function, add it to the callbacks + // of the `topic` function, so it'll get called once the + // topic fires. + // If we encounter an object literal, we recurse, sending it + // our current context. + if (typeof(vow.callback) === 'function') { + 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 + // synchronously. + if (topic) { + topic.addListener("success", function (ctx) { + return function (val) { + return run(new(Context)(vow, ctx, env), lastTopic); + }; + }(ctx)); + } else { + run(new(Context)(vow, ctx, env), lastTopic); } - }); - // Check if we're done running the tests - batch.remaining --; - exports.tryEnd(batch); - // This is our initial, empty context - })(new(Context)({ callback: tests, context: null, description: null }, {})); - } + } + }); + // Check if we're done running the tests + batch.remaining --; + exports.tryEnd(batch); + // This is our initial, empty context + })(new(Context)({ callback: tests, context: null, description: null }, {})); return promise; }; From 09e31cf7ada47b8c47c76fa1ebf2acc2f2f44cfb Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 18:17:06 -0400 Subject: [PATCH 203/409] better remaining vow detection and handling --- lib/vows.js | 4 +++- lib/vows/suite.js | 22 ++++++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index f8200d64..5fb583a5 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -129,7 +129,9 @@ function addVow(vow) { // If not, report an error message. // process.addListener('exit', function () { - if (vows.suites.filter(function (s) { return s.results.time === null }).length > 0) { + if (vows.suites.filter(function (s) { + return (s.results.total > 0) && (s.results.time === null) + }).length > 0) { vows.reporter.report(['error', { error: "An EventEmitter has failed to fire.", type: 'promise' }]); } }); diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 57299fc0..faa15b80 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -171,8 +171,10 @@ this.Suite.prototype = new(function () { } } }); + if (! ctx.tests._skip) { + batch.remaining --; + } // Check if we're done running the tests - batch.remaining --; exports.tryEnd(batch); // This is our initial, empty context })(new(Context)({ callback: tests, context: null, description: null }, {})); @@ -197,15 +199,23 @@ this.Suite.prototype = new(function () { start = new(Date); - this.report(['subject', this.subject]); + if (this.batches.filter(function (b) { return b.remaining > 0 }).length) { + this.report(['subject', this.subject]); + } (function run(batches) { - var batch; + var batch = batches.shift(); - if (batch = batches.shift()) { - that.runVows(batch).addListener('end', function () { + if (batch) { + // If the batch has no vows to run, + // go to the next one. + if (batch.remaining === 0) { run(batches); - }); + } else { + that.runVows(batch).addListener('end', function () { + run(batches); + }); + } } else { that.results.time = (new(Date) - start) / 1000; From d5b0d3471062d7cd9586d99e4006cb4e836dd144 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 18:32:51 -0400 Subject: [PATCH 204/409] pattern matching is operational --- lib/vows/suite.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index faa15b80..486d0175 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -54,24 +54,40 @@ this.Suite.prototype = new(function () { // so we know when the tests are over. // We match the keys against `matcher`, to decide // whether or not they should be included in the test. + // Any key, including assertion function keys can be matched. + // If a child matches, then the n parent topics must not be skipped. (function count(tests, _match) { var match = false; - Object.keys(tests).filter(function (k) { + var keys = Object.keys(tests).filter(function (k) { return k !== 'topic'; - }).forEach(function (key) { + }); + + for (var i = 0, key; i < keys.length; i++) { + key = keys[i]; + + // If the parent node, or this one matches. match = _match || matcher.test(key); if (typeof(tests[key]) === 'object') { match = count(tests[key], match); + } else if (! match) { + tests[key]._skip = true; } - }); + } + + // If any of the children matched, + // don't skip this node. + for (var i = 0; i < keys.length; i++) { + if (! tests[keys[i]]._skip) { match = true } + } if (match) { batch.remaining ++ } else { tests._skip = true } return match; })(tests, false); + batch._remaining = batch.remaining; }; From b851ffecd5f158ba4953ce1724800cd5092808f5 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 23:58:56 -0400 Subject: [PATCH 205/409] only the spec reporter prints subjects --- lib/vows/reporters/dot-matrix.js | 2 +- lib/vows/reporters/watch.js | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index 412d2455..5a385831 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -18,7 +18,7 @@ this.report = function (data, s) { switch (data[0]) { case 'subject': - messages.push(stylize(event, 'underline') + '\n'); + // messages.push(stylize(event, 'underline') + '\n'); break; case 'context': break; diff --git a/lib/vows/reporters/watch.js b/lib/vows/reporters/watch.js index bb69c791..226e355b 100644 --- a/lib/vows/reporters/watch.js +++ b/lib/vows/reporters/watch.js @@ -14,9 +14,6 @@ this.report = function (data) { options.stream = process.stdout; switch (data[0]) { - case 'subject': - puts(stylize(event, 'underline') + '\n'); - break; case 'vow': if (event.status !== 'honored') { if (event.status === 'broken') { From 868cd9fc4d5faf8371da4737bc9dc07c2bc1689f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 5 Jun 2010 23:59:16 -0400 Subject: [PATCH 206/409] ability to print messages without a nl --- bin/vows | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/vows b/bin/vows index 55c863b7..ffde5588 100755 --- a/bin/vows +++ b/bin/vows @@ -260,7 +260,7 @@ function runSuites(suites, callback) { (function run(suites, callback) { var suite = suites.shift(); if (suite) { - msg('runner', "running", suite.subject); + msg('runner', "running", suite.subject + ' ', true); suite.run(options, function (result) { Object.keys(result).forEach(function (k) { results[k] += result[k]; @@ -273,12 +273,12 @@ function runSuites(suites, callback) { })(suites, callback); } -function msg(cmd, subject, str) { +function msg(cmd, subject, str, p) { if (options.verbose) { - sys.puts( stylize('vows ', 'green') - + stylize(cmd, 'bold') - + ' ' + subject + ' ' - + (str ? (str[0] === '/' ? str : inspect(str)) : '') - ); + sys[p ? 'print' : 'puts']( stylize('vows ', 'green') + + stylize(cmd, 'bold') + + ' ' + subject + ' ' + + (str ? (typeof(str) === 'string' ? str : inspect(str)) : '') + ); } } From e27bdfccf06167db6de8e5b689bc90b12e93f3fe Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 6 Jun 2010 00:10:45 -0400 Subject: [PATCH 207/409] round time output --- lib/vows/console.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/console.js b/lib/vows/console.js index c8b0f8f0..8cc25f67 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -37,7 +37,7 @@ this.result = function (event) { if ('time' in event) { buffer.push("Verified " + event.total + " vows in " + - (event.time + " seconds.\n")); + (event.time.toFixed(3) + " seconds.\n")); } buffer.push(this.stylize(result, style)); From a0dacb7a8d3a84ec47d48c3e4de2cf99a61f2f94 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 7 Jun 2010 00:24:50 -0400 Subject: [PATCH 208/409] Set default for `options` in run(). Fixes error when running tests directly. --- lib/vows/suite.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 486d0175..80f4b355 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -204,6 +204,8 @@ this.Suite.prototype = new(function () { this.run = function (options, callback) { var that = this, start; + options = options || {}; + this.matcher = options.matcher || this.matcher; this.reporter = options.reporter || this.reporter; From fe7ae1807d34435c4f16c8cd91b074c516105731 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 7 Jun 2010 00:26:07 -0400 Subject: [PATCH 209/409] (dist) version bump --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ad2426af..beb9100f 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,9 @@ "keywords" : ["testing", "spec", "test", "BDD"], "author" : "Alexis Sellier ", "contributors" : [], - "dependencies" : {"eyes": ">=0.1.0"}, + "dependencies" : {"eyes": ">=0.1.3"}, "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.3.0" + "version" : "0.3.1" } From cca5d46589c245d554c7f8b6d93047300123feae Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 7 Jun 2010 00:54:55 -0400 Subject: [PATCH 210/409] move inspect() to vows/console --- lib/assert/error.js | 2 +- lib/vows.js | 6 +----- lib/vows/console.js | 5 +++++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/assert/error.js b/lib/assert/error.js index 8239493f..820571d4 100644 --- a/lib/assert/error.js +++ b/lib/assert/error.js @@ -1,5 +1,5 @@ var stylize = require('vows/console').stylize; -var inspect = require('vows').inspect; +var inspect = require('vows/console').inspect; require('assert').AssertionError.prototype.toString = function () { var that = this, diff --git a/lib/vows.js b/lib/vows.js index 5fb583a5..69ea2261 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -17,7 +17,6 @@ // var sys = require('sys'), events = require('events'), - eyes = require('eyes').inspector({ stream: null }), vows = exports; require.paths.unshift(__dirname); @@ -36,10 +35,7 @@ vows.__defineGetter__('reporter', function () { var stylize = require('vows/console').stylize; -vows.inspect = function inspect(val) { - return '\033[1m' + eyes(val) + '\033[22m'; -}; - +vows.inspect = require('vows/console').inspect; vows.prepare = require('vows/extras').prepare; vows.tryEnd = require('vows/suite').tryEnd; diff --git a/lib/vows/console.js b/lib/vows/console.js index 8cc25f67..5a926604 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -1,3 +1,4 @@ +var eyes = require('eyes').inspector({ stream: null }); // Stylize a string this.stylize = function stylize(str, style) { @@ -43,3 +44,7 @@ this.result = function (event) { return buffer; }; + +this.inspect = function inspect(val) { + return '\033[1m' + eyes(val) + '\033[22m'; +}; From 4079f573720716072229aa396056ebde49b3d28d Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 7 Jun 2010 00:55:24 -0400 Subject: [PATCH 211/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index beb9100f..bcc9b8ec 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.3.1" + "version" : "0.3.2" } From 06b556374e9025b76eb6ddb0b0654ddcf2a869d4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 7 Jun 2010 01:11:12 -0400 Subject: [PATCH 212/409] (doc) updated README --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index ac89fdf0..f597ccb2 100644 --- a/README.md +++ b/README.md @@ -17,17 +17,18 @@ synopsis var vows = require('vows'), assert = require('assert'); - vows.describe('Deep Thought').addVows(function () { - question('what is the answer to the universe?').addVow(function (answer) { - assert.equals(answer, 42); - }, 'it should know the answer to the ultimate question of life'); - }); + vows.describe('Deep Thought').addVows({ + 'An instance of DeepThought': { + topic: new DeepThought, -In the example above, `question()` would be a function which returns an `EventEmitter`. -When the `"success"` event is emitted, the function passed to `addVow` is run, -and the results output to the console. + 'should know the answer to the ultimate question of life': function (deepThought) { + assert.equal (deepThought.question('what is the answer to the universe?'), 42); + } + } + }); -Vows are run as soon as the promise completes, so the order in which they are run is undefined. +Documenation coming soon. For now, have a look at the tests in to +get an idea. installation ------------ @@ -69,7 +70,7 @@ writing specs } } } - }); + }).run(); assertion macros ---------------- From 55a7a920f2643faeb86ae2deaf271a857a15c5ef Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 7 Jun 2010 02:23:57 -0400 Subject: [PATCH 213/409] print contexts in dot-matrix & watch output --- lib/vows.js | 7 +++++-- lib/vows/reporters/dot-matrix.js | 28 +++++++++++++++------------- lib/vows/reporters/watch.js | 4 ++-- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 69ea2261..1bceb9df 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -106,13 +106,16 @@ function addVow(vow) { } function output(status, exception) { + var context; + if (exception || !vows.options.brief) { if (vow.context && batch.lastContext !== vow.context) { - batch.lastContext = vow.context; - batch.suite.report(['context', vow.context]); + batch.lastContext = context = vow.context; + batch.suite.report(['context', context]); } batch.suite.report(['vow', { title: vow.description, + context: context, status: status, exception: exception || null }]); diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index 5a385831..e08a5501 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -25,19 +25,21 @@ this.report = function (data, s) { case 'vow': if (event.status === 'honored') { sys.print(stylize('.', 'green')); - } else if (event.status === 'broken') { - sys.print(stylize('B', 'yellow')); - messages.push(' - ' + stylize(event.title, 'yellow')); - messages.push(' ~ ' + event.exception); - messages.push(''); - } else if (event.status === 'errored') { - sys.print(stylize('E', 'red')); - messages.push(' - ' + stylize(event.title, 'red')); - if (event.exception.type === 'promise') { - messages.push(' * ' + stylize("An 'error' event was caught: " + - stylize(event.exception.error, 'bold'), 'red')); - } else { - messages.push(' ! ' + stylize(event.exception, 'red')); + } else { + event.context && messages.push(event.context); + if (event.status === 'broken') { + sys.print(stylize('B', 'yellow')); + messages.push(' - ' + stylize(event.title, 'yellow')); + messages.push(' ~ ' + event.exception); + } else if (event.status === 'errored') { + sys.print(stylize('E', 'red')); + messages.push(' - ' + stylize(event.title, 'red')); + if (event.exception.type === 'promise') { + messages.push(' * ' + stylize("An 'error' event was caught: " + + stylize(event.exception.error, 'bold'), 'red')); + } else { + messages.push(' ! ' + stylize(event.exception, 'red')); + } } messages.push(''); } diff --git a/lib/vows/reporters/watch.js b/lib/vows/reporters/watch.js index 226e355b..2237cca0 100644 --- a/lib/vows/reporters/watch.js +++ b/lib/vows/reporters/watch.js @@ -16,10 +16,10 @@ this.report = function (data) { switch (data[0]) { case 'vow': if (event.status !== 'honored') { + event.context && puts(event.context); if (event.status === 'broken') { puts(' - ' + stylize(event.title, 'yellow')); puts(' ~ ' + event.exception); - puts(''); } else if (event.status === 'errored') { puts(' - ' + stylize(event.title, 'red')); if (event.exception.type === 'promise') { @@ -28,8 +28,8 @@ this.report = function (data) { } else { puts(' ! ' + stylize(event.exception, 'red')); } - puts(''); } + puts(''); } break; case 'error': From 311df5f25049df9ecb68f20ddaba5748f67d50a3 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 8 Jun 2010 01:12:37 -0400 Subject: [PATCH 214/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bcc9b8ec..9b839b5e 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.3.2" + "version" : "0.3.3" } From 062450c1a0afeb4da219bb376e97854e277e198a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 8 Jun 2010 02:27:04 -0400 Subject: [PATCH 215/409] handle this.callback called synchronously --- lib/vows/context.js | 13 +++++++++++-- test/vows-test.js | 8 ++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/vows/context.js b/lib/vows/context.js index 5fdfa090..9b3590fb 100644 --- a/lib/vows/context.js +++ b/lib/vows/context.js @@ -9,10 +9,19 @@ this.Context = function (vow, ctx, env) { this.env.context = this; this.env.__defineGetter__('callback', function () { that._callback = true; + return function (e, res) { var args = Array.prototype.slice.call(arguments, 1); - if (e) { that.emitter.emit('error', e) } - else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } + var emit = function () { + if (e) { that.emitter.emit('error', e) } + else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } + }; + // If `this.callback` is called synchronously, + // the emitter will not have been set yet, + // so we defer the emition, that way it'll behave + // asynchronously. + if (that.emitter) { emit() } + else { process.nextTick(emit) } }; }); this.name = (ctx.name ? ctx.name + ' ' : '') + diff --git a/test/vows-test.js b/test/vows-test.js index 99a62153..c864febf 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -235,6 +235,14 @@ vows.describe("Vows").addVows({ assert.equal(e, "ERROR"); assert.equal(res, undefined); } + }, + "using this.callback synchronously": { + topic: function () { + this.callback(null, 'hello'); + }, + "should work the same as returning a value": function (res) { + assert.equal(res, 'hello'); + } } } }).addVows({ From 11a1edd4c0e967e9ef8c01c5215210bcfa1dfd38 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 8 Jun 2010 03:12:39 -0400 Subject: [PATCH 216/409] (new) tests can be 'pending' --- bin/vows | 3 ++- lib/vows.js | 5 +++++ lib/vows/console.js | 8 +++++--- lib/vows/reporters/dot-matrix.js | 2 ++ lib/vows/suite.js | 17 ++++++++++++----- 5 files changed, 26 insertions(+), 9 deletions(-) diff --git a/bin/vows b/bin/vows index ffde5588..7f211e07 100755 --- a/bin/vows +++ b/bin/vows @@ -171,7 +171,7 @@ if (args.length > 0) { // Run the matching tests and change the status. // function changed(file) { - status = { honored: 0, broken: 0, errored: 0 }; + status = { honored: 0, broken: 0, errored: 0, pending: 0 }; msg('watcher', 'detected change in', file + '.js'); @@ -254,6 +254,7 @@ function runSuites(suites, callback) { honored: 0, broken: 0, errored: 0, + pending: 0, total: 0, time: 0 }; diff --git a/lib/vows.js b/lib/vows.js index 1bceb9df..ac66ec16 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -86,6 +86,11 @@ function addVow(vow) { function runTest(args) { var exception, topic, status; + if (vow.callback instanceof String) { + batch.pending ++; + return output('pending'); + } + // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { diff --git a/lib/vows/console.js b/lib/vows/console.js index 5a926604..279c1aa7 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -6,6 +6,7 @@ this.stylize = function stylize(str, style) { 'bold' : [1, 22], 'italic' : [3, 23], 'underline' : [4, 24], + 'cyan' : [96, 39], 'yellow' : [33, 39], 'green' : [32, 39], 'red' : [31, 39], @@ -30,9 +31,10 @@ this.puts = function (options) { this.result = function (event) { var result = event.honored + " honored, " + event.broken + " broken, " + - event.errored + " errored", - style = event.honored === event.total ? ('green') - : (event.errored === 0 ? 'yellow' : 'red'), + event.errored + " errored, " + + event.pending + " pending", + style = event.honored === event.total ? ('green') + : (event.pending ? 'cyan' : (event.errored ? 'red' : 'yellow')), buffer = []; diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index e08a5501..360b70eb 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -25,6 +25,8 @@ this.report = function (data, s) { case 'vow': if (event.status === 'honored') { sys.print(stylize('.', 'green')); + } else if (event.status === 'pending') { + sys.print(stylize('.', 'cyan')); } else { event.context && messages.push(event.context); if (event.status === 'broken') { diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 80f4b355..812a5082 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -20,6 +20,7 @@ this.Suite.prototype = new(function () { honored: 0, broken: 0, errored: 0, + pending: 0, total: 0, time: null }; @@ -39,6 +40,7 @@ this.Suite.prototype = new(function () { honored: 0, broken: 0, errored: 0, + pending: 0, total: 0 }); return this; @@ -71,8 +73,13 @@ this.Suite.prototype = new(function () { if (typeof(tests[key]) === 'object') { match = count(tests[key], match); - } else if (! match) { - tests[key]._skip = true; + } else { + if (typeof(tests[key]) === 'string') { + tests[key] = new(String)(tests[key]); + } + if (! match) { + tests[key]._skip = true; + } } } @@ -170,7 +177,7 @@ 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') { + 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, @@ -267,7 +274,7 @@ this.Suite.prototype = new(function () { this.tryEnd = function (batch) { var result, style, time; - if (batch.honored + batch.broken + batch.errored === batch.total && + if (batch.honored + batch.broken + batch.errored + batch.pending === batch.total && batch.remaining === 0) { Object.keys(batch).forEach(function (k) { @@ -275,6 +282,6 @@ this.tryEnd = function (batch) { }); batch.suite.report(['end']); - batch.promise.emit('end', batch.honored, batch.broken, batch.errored); + batch.promise.emit('end', batch.honored, batch.broken, batch.errored, batch.pending); } }; From 3e677500498a4dffb15bc06609e18a7ffbdd9ab4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 9 Jun 2010 02:30:07 -0400 Subject: [PATCH 217/409] remove deprecated 'brief' option --- bin/vows | 1 - lib/vows.js | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bin/vows b/bin/vows index 7f211e07..0791d5cf 100755 --- a/bin/vows +++ b/bin/vows @@ -18,7 +18,6 @@ var _reporter = require('vows/reporters/dot-matrix'), reporter = { var options = { reporter: reporter, - brief: false, matcher: /.*/ }; diff --git a/lib/vows.js b/lib/vows.js index ac66ec16..ac1db1e5 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -24,7 +24,6 @@ require.paths.unshift(__dirname); // Options vows.options = { Emitter: events.EventEmitter, - brief: false, reporter: require('vows/reporters/dot-matrix'), matcher: /.*/ }; @@ -113,7 +112,7 @@ function addVow(vow) { function output(status, exception) { var context; - if (exception || !vows.options.brief) { + if (exception) { if (vow.context && batch.lastContext !== vow.context) { batch.lastContext = context = vow.context; batch.suite.report(['context', context]); From c533efaab4292301605168ed600842d951384492 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 9 Jun 2010 03:27:32 -0400 Subject: [PATCH 218/409] fix context reporting for dot-matrix --- bin/vows | 4 ++++ lib/vows.js | 22 +++++++++------------- lib/vows/reporters/dot-matrix.js | 11 +++++++++-- lib/vows/reporters/watch.js | 10 +++++++++- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/bin/vows b/bin/vows index 0791d5cf..195a8485 100755 --- a/bin/vows +++ b/bin/vows @@ -83,6 +83,8 @@ if (args.length > 0) { options.verbose ? _reporter.print('\n') : _reporter.print(' '); } }; + reporter.reset = function () { _reporter.reset && _reporter.reset() }; + suites = args.map(function (a) { a = path.join(process.cwd(), a.replace(/\.js$/, '')); msg('runner', "loading", a); @@ -257,6 +259,8 @@ function runSuites(suites, callback) { total: 0, time: 0 }; + reporter.reset(); + (function run(suites, callback) { var suite = suites.shift(); if (suite) { diff --git a/lib/vows.js b/lib/vows.js index ac1db1e5..f2e93775 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -110,20 +110,16 @@ function addVow(vow) { } function output(status, exception) { - var context; - - if (exception) { - if (vow.context && batch.lastContext !== vow.context) { - batch.lastContext = context = vow.context; - batch.suite.report(['context', context]); - } - batch.suite.report(['vow', { - title: vow.description, - context: context, - status: status, - exception: exception || null - }]); + if (vow.context && batch.lastContext !== vow.context) { + batch.lastContext = vow.context; + batch.suite.report(['context', vow.context]); } + batch.suite.report(['vow', { + title: vow.description, + context: vow.context, + status: status, + exception: exception || null + }]); } }; diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index 360b70eb..98bad511 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -8,9 +8,13 @@ var stylize = console.stylize, // // Console reporter // -var stream, messages = []; +var stream, messages = [], lastContext; this.name = 'dot-matrix'; +this.reset = function () { + messages = []; + lastContext = null; +}; this.report = function (data, s) { var event = data[1]; @@ -28,7 +32,10 @@ this.report = function (data, s) { } else if (event.status === 'pending') { sys.print(stylize('.', 'cyan')); } else { - event.context && messages.push(event.context); + if (lastContext !== event.context) { + lastContext = event.context; + messages.push(event.context); + } if (event.status === 'broken') { sys.print(stylize('B', 'yellow')); messages.push(' - ' + stylize(event.title, 'yellow')); diff --git a/lib/vows/reporters/watch.js b/lib/vows/reporters/watch.js index 2237cca0..7621802d 100644 --- a/lib/vows/reporters/watch.js +++ b/lib/vows/reporters/watch.js @@ -7,7 +7,12 @@ var stylize = console.stylize, // // Console reporter // +var lastContext; + this.name = 'watch'; +this.reset = function () { + lastContext = null; +}; this.report = function (data) { var event = data[1]; @@ -16,7 +21,10 @@ this.report = function (data) { switch (data[0]) { case 'vow': if (event.status !== 'honored') { - event.context && puts(event.context); + if (lastContext !== event.context) { + lastContext = event.context; + puts(event.context); + } if (event.status === 'broken') { puts(' - ' + stylize(event.title, 'yellow')); puts(' ~ ' + event.exception); From df83078651270ead45fd45b3a6515b7119dcd7c0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 9 Jun 2010 03:28:11 -0400 Subject: [PATCH 219/409] print a different cue when running tests in watch mode --- bin/vows | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/bin/vows b/bin/vows index 195a8485..88de731c 100755 --- a/bin/vows +++ b/bin/vows @@ -105,12 +105,14 @@ if (args.length > 0) { // Watch mode // (function () { - var clock = [ + var pendulum = [ '. ', '.. ', '... ', ' ...', ' ..', ' .', ' .', ' ..', '... ', '.. ', '. ' ]; + var strobe = ['.', ' ']; var status, + cue, current = 0, testFolder, running = 0, @@ -141,14 +143,18 @@ if (args.length > 0) { // Run every 100ms function tick() { - if (running) { return } + if (running && (cue !== strobe)) { + cue = strobe, current = 0; + } else if (!running && (cue !== pendulum)) { + cue = pendulum, current = 0; + } cursorSave(); eraseLine(); - lastRun && esc(colors[status.errored ? 2 : (status.broken ? 1 : 0)]); - print(clock[current]); + lastRun && !running && esc(colors[status.errored ? 2 : (status.broken ? 1 : 0)]); + print(cue[current]); - if (current == clock.length - 1) { current = -1 } + if (current == cue.length - 1) { current = -1 } current ++; esc('39m'); From 8052146906661823d79ca7e37867c1d6565f3d29 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 9 Jun 2010 03:28:37 -0400 Subject: [PATCH 220/409] fix/improve the cleanup on exit --- bin/vows | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bin/vows b/bin/vows index 88de731c..10f312d4 100755 --- a/bin/vows +++ b/bin/vows @@ -132,8 +132,7 @@ if (args.length > 0) { process.addListener('uncaughtException', cleanup); process.addListener('exit', cleanup); process.addListener('SIGINT', function () { - cleanup(); - process.exit(); + process.exit(0); }); process.addListener('SIGQUIT', function () { changed(); @@ -171,7 +170,7 @@ if (args.length > 0) { function cursorRestore() { esc("u") } function cursorHide() { esc("?25l") } function cursorShow() { esc("?25h") } - function cleanup() { cursorShow(), print('\n') } + function cleanup() { eraseLine(), cursorShow(), print('\n') } // // Called when a file has been modified. From 25abf72300d5813c9ad4e8b96ebca025876488a1 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 9 Jun 2010 03:32:28 -0400 Subject: [PATCH 221/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b839b5e..91040a39 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.3.3" + "version" : "0.3.4" } From 4b92fa428019b93977cee2652f3bc0f2424c071c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 11 Jun 2010 01:15:28 -0400 Subject: [PATCH 222/409] (new api) '-m' matches a string, changes -R to -r --- bin/vows | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/vows b/bin/vows index 10f312d4..016bb627 100755 --- a/bin/vows +++ b/bin/vows @@ -38,8 +38,14 @@ while (arg = argv.shift()) { } else { arg = arg.match(/^--?(.+)/)[1]; - if (arg[0] === 'R') { + if (arg[0] === 'r') { options.matcher = new(RegExp)(arg.slice(1)); + } else if (arg[0] === 'm') { + options.matcher = (function (str) { // Create an escaped RegExp + var specials = '. * + ? | ( ) [ ] { } \\ ^ ? ! = : $'.split(' ').join('|\\'), + regex = new(RegExp)('(\\' + specials + ')', 'g'); + return new(RegExp)(str.replace(regex, '\\$1')); + })(arg.slice(1)); } else if (arg in options) { options[arg] = true; } else { From 3416f44714b61e4bef80a4aa3ac8aabff1a4947a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 11 Jun 2010 01:18:57 -0400 Subject: [PATCH 223/409] fix spec reporter + pending vow --- lib/vows/reporters/spec.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/vows/reporters/spec.js b/lib/vows/reporters/spec.js index 405b3e4f..cd8a96ba 100644 --- a/lib/vows/reporters/spec.js +++ b/lib/vows/reporters/spec.js @@ -24,7 +24,10 @@ this.report = function (data, s) { break; case 'vow': puts(' - ' + stylize(event.title, ({ - honored: 'green', broken: 'yellow', errored: 'red' + honored: 'green', + broken: 'yellow', + errored: 'red', + pending: 'cyan' })[event.status])); if (event.status === 'broken') { puts(' ~ ' + event.exception); From 1cdfd1cd15532b827b2e37c6bb6ec4cd3ad4e11c Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 11 Jun 2010 01:35:42 -0400 Subject: [PATCH 224/409] don't output contexts for pending vows in watch mode --- lib/vows/reporters/watch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/reporters/watch.js b/lib/vows/reporters/watch.js index 7621802d..5d59790e 100644 --- a/lib/vows/reporters/watch.js +++ b/lib/vows/reporters/watch.js @@ -20,7 +20,7 @@ this.report = function (data) { switch (data[0]) { case 'vow': - if (event.status !== 'honored') { + if (['honored', 'pending'].indexOf(event.status) === -1) { if (lastContext !== event.context) { lastContext = event.context; puts(event.context); From 90e0bae56965fabdbd75e17a7db7347e53954c61 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 11 Jun 2010 01:36:33 -0400 Subject: [PATCH 225/409] (api) watch mode is activated with -w --- bin/vows | 100 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 57 insertions(+), 43 deletions(-) diff --git a/bin/vows b/bin/vows index 016bb627..6e36853e 100755 --- a/bin/vows +++ b/bin/vows @@ -18,7 +18,8 @@ var _reporter = require('vows/reporters/dot-matrix'), reporter = { var options = { reporter: reporter, - matcher: /.*/ + matcher: /.*/, + watch: false }; var suites = []; @@ -27,6 +28,10 @@ var suites = []; // ('node' in most cases) var arg, args = [], argv = process.argv.slice(2); +// Current directory index, +// and path of test folder. +var root, testFolder; + // // Parse command-line parameters // @@ -60,19 +65,37 @@ while (arg = argv.shift()) { case 'v': options.verbose = true; break; + case 'watch': + case 'w': + options.watch = true; + break; } } } } -if (args.length === 0) { +if (options.watch) { options.reporter = reporter = require('vows/reporters/watch'); } msg('bin', 'argv', args); msg('bin', 'options', { reporter: options.reporter.name, matcher: options.matcher }); -if (args.length > 0) { +if (args.length === 0) { + root = fs.readdirSync('.'); + + if (root.indexOf('test') !== -1) { + testFolder = 'test'; + } else if (root.indexOf('spec') !== -1) { + testFolder = 'spec'; + } else { + throw new(Error)("Couldn't find test folder"); + } + + args = paths(testFolder); +} + +if (! options.watch) { reporter.report = function (data) { switch (data[0]) { case 'subject': @@ -120,20 +143,10 @@ if (args.length > 0) { var status, cue, current = 0, - testFolder, running = 0, lastRun, colors = ['32m', '33m', '31m'], - timer = setInterval(tick, 100), - root = fs.readdirSync('.'); - - if (root.indexOf('test') !== -1) { - testFolder = 'test'; - } else if (root.indexOf('spec') !== -1) { - testFolder = 'spec'; - } else { - throw new(Error)("Couldn't find test folder"); - } + timer = setInterval(tick, 100); process.addListener('uncaughtException', cleanup); process.addListener('exit', cleanup); @@ -215,35 +228,6 @@ if (args.length > 0) { running --; }); } - // - // Recursively traverse a hierarchy, returning - // a list of all relevant .js files. - // - function paths(dir) { - var paths = []; - - try { fs.statSync(dir) } - catch (e) { return [] } - - (function traverse(dir, stack) { - stack.push(dir); - fs.readdirSync(stack.join('/')).forEach(function (file) { - var path = stack.concat([file]).join('/'), - stat = fs.statSync(path); - - if (file[0] == '.' || file === 'vendor') { - return; - } else if (stat.isFile() && /\.js$/.test(file)) { - paths.push(path); - } else if (stat.isDirectory()) { - traverse(file, stack); - } - }); - stack.pop(); - })(dir || '.', []); - - return paths; - } // // Watch all relevant files in lib/ and src/, @@ -288,6 +272,36 @@ function runSuites(suites, callback) { })(suites, callback); } +// +// Recursively traverse a hierarchy, returning +// a list of all relevant .js files. +// +function paths(dir) { + var paths = []; + + try { fs.statSync(dir) } + catch (e) { return [] } + + (function traverse(dir, stack) { + stack.push(dir); + fs.readdirSync(stack.join('/')).forEach(function (file) { + var path = stack.concat([file]).join('/'), + stat = fs.statSync(path); + + if (file[0] == '.' || file === 'vendor') { + return; + } else if (stat.isFile() && /\.js$/.test(file)) { + paths.push(path); + } else if (stat.isDirectory()) { + traverse(file, stack); + } + }); + stack.pop(); + })(dir || '.', []); + + return paths; +} + function msg(cmd, subject, str, p) { if (options.verbose) { sys[p ? 'print' : 'puts']( stylize('vows ', 'green') From bb9a5af8395cf1e235c2ea62a33e6a8d75266117 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 11 Jun 2010 01:55:31 -0400 Subject: [PATCH 226/409] abort() function to exit with an error --- bin/vows | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bin/vows b/bin/vows index 6e36853e..30ef033e 100755 --- a/bin/vows +++ b/bin/vows @@ -89,7 +89,7 @@ if (args.length === 0) { } else if (root.indexOf('spec') !== -1) { testFolder = 'spec'; } else { - throw new(Error)("Couldn't find test folder"); + abort("runner", "couldn't find test folder"); } args = paths(testFolder); @@ -311,3 +311,9 @@ function msg(cmd, subject, str, p) { ); } } + +function abort(cmd, str) { + sys.puts(stylize('vows ', 'red') + stylize(cmd, 'bold') + ' ' + str); + sys.puts(stylize('vows ', 'red') + stylize(cmd, 'bold') + ' exiting'); + process.exit(-1); +} From 47c2d1f00b4b977f9779f3f6e6274f614aed3645 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 11 Jun 2010 02:34:56 -0400 Subject: [PATCH 227/409] (new) support multiple test suites per file --- bin/vows | 29 ++++++++++++++++++++--------- lib/vows/suite.js | 2 +- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bin/vows b/bin/vows index 30ef033e..0a894750 100755 --- a/bin/vows +++ b/bin/vows @@ -22,7 +22,7 @@ var options = { watch: false }; -var suites = []; +var files = []; // Get rid of process runner // ('node' in most cases) @@ -82,6 +82,7 @@ msg('bin', 'argv', args); msg('bin', 'options', { reporter: options.reporter.name, matcher: options.matcher }); if (args.length === 0) { + msg('bin', 'discovering', 'folder structure'); root = fs.readdirSync('.'); if (root.indexOf('test') !== -1) { @@ -91,6 +92,7 @@ if (args.length === 0) { } else { abort("runner", "couldn't find test folder"); } + msg('bin', 'discovered', "./" + testFolder); args = paths(testFolder); } @@ -114,12 +116,11 @@ if (! options.watch) { }; reporter.reset = function () { _reporter.reset && _reporter.reset() }; - suites = args.map(function (a) { - a = path.join(process.cwd(), a.replace(/\.js$/, '')); - msg('runner', "loading", a); - return require(a).vows; + files = args.map(function (a) { + return path.join(process.cwd(), a.replace(/\.js$/, '')); }); - runSuites(suites, function (results) { + + runSuites(importSuites(files), function (results) { !options.verbose && _reporter.print('\n'); msg('runner', 'finish'); _reporter.report(['finish', results], { @@ -210,17 +211,16 @@ if (! options.watch) { file = null; } - var suites = (/-(test|spec)\.js$/.test(file) ? [file] : paths(testFolder)).map(function (p) { + var files = (/-(test|spec)\.js$/.test(file) ? [file] : paths(testFolder)).map(function (p) { return path.join(process.cwd(), p.replace(/\.js$/, '')); }).map(function (p) { delete(require.main.moduleCache[p]); return p; }); - msg('watcher', 'loading', suites); running ++; - runSuites(suites.map(function (s) { return require(s).vows }), function (results) { + runSuites(importSuites(files), function (results) { delete(results.time); print(console.result(results).join('') + '\n\n'); lastRun = new(Date); @@ -272,6 +272,17 @@ function runSuites(suites, callback) { })(suites, callback); } +function importSuites(files) { + msg(options.watcher ? 'watcher' : 'runner', 'loading', files); + + return files.reduce(function (suites, f) { + var obj = require(f); + return suites.concat(Object.keys(obj).map(function (s) { + return obj[s]; + })); + }, []) +} + // // Recursively traverse a hierarchy, returning // a list of all relevant .js files. diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 812a5082..c8c5c3bf 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -262,7 +262,7 @@ this.Suite.prototype = new(function () { if (require.main === module) { return this.run(); } else { - return module.exports.vows = this; + return module.exports[this.subject] = this; } }; }); From 1e9018852ba518b39263d7736def978eef3a1cba Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 11 Jun 2010 20:17:38 -0400 Subject: [PATCH 228/409] set styles to false for inspector --- lib/vows/console.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/console.js b/lib/vows/console.js index 279c1aa7..4202a0f0 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -1,4 +1,4 @@ -var eyes = require('eyes').inspector({ stream: null }); +var eyes = require('eyes').inspector({ stream: null, styles: false }); // Stylize a string this.stylize = function stylize(str, style) { From 72eecd7813984c9f9171a6922018426633dab3b2 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 13 Jun 2010 15:18:48 -0400 Subject: [PATCH 229/409] (new) added new assertions --- lib/assert/macros.js | 50 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 024ee9d4..02468853 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -30,6 +30,41 @@ assert.match = function (actual, expected, message) { }; assert.matches = assert.match; +assert.isTrue = function (actual, message) { + if (actual !== true) { + assert.fail(actual, true, message || "expected {actual} to be {expected}", "===", assert.isTrue); + } +}; +assert.isFalse = function (actual, message) { + if (actual !== false) { + assert.fail(actual, false, message || "expected {actual} to be {expected}", "===", assert.isFalse); + } +}; +assert.isZero = function (actual, message) { + if (actual !== 0) { + assert.fail(actual, 0, message || "expected {expected}, got {actual}", "===", assert.isZero); + } +}; +assert.isNotZero = function (actual, message) { + if (actual === 0) { + assert.fail(actual, 0, message || "expected {expected}, got {actual}", "===", assert.isNotZero); + } +}; + +assert.greater = function (actual, expected, message) { + if (actual <= expected) { + assert.fail(actual, expected, message || "expected {actual} to be greater than {expected}", ">", assert.greater); + } +}; +assert.lesser = function (actual, expected, message) { + if (actual >= expected) { + assert.fail(actual, expected, message || "expected {actual} to be lesser than {expected}", "<", assert.lesser); + } +}; + +// +// Inclusion +// assert.include = function (actual, expected, message) { if ((function (obj) { if (isArray(obj) || isString(obj)) { @@ -44,6 +79,9 @@ assert.include = function (actual, expected, message) { }; assert.includes = assert.include; +// +// Length +// assert.isEmpty = function (actual, message) { if ((isObject(actual) && Object.keys(actual).length > 0) || actual.length > 0) { assert.fail(actual, 0, message || "expected {actual} to be empty", "length", assert.isEmpty); @@ -56,6 +94,9 @@ assert.length = function (actual, expected, message) { } }; +// +// Type +// assert.isArray = function (actual, message) { assertTypeOf(actual, 'array', message || "expected {actual} to be an Array", assert.isArray); }; @@ -76,12 +117,17 @@ assert.isNaN = function (actual, message) { }; assert.isNull = function (actual, message) { if (actual !== null) { - assert.fail(actual, 'null', message || "expected {actual} to be null", "===", assert.isNull); + assert.fail(actual, null, message || "expected {actual} to be {expected}", "===", assert.isNull); + } +}; +assert.isNotNull = function (actual, message) { + if (actual === null) { + assert.fail(actual, null, message || "expected {actual} to not be {expected}", "===", assert.isNotNull); } }; assert.isUndefined = function (actual, message) { if (actual !== undefined) { - assert.fail(actual, 'undefined', message || "expected {actual} to be undefined", "===", assert.isUndefined); + assert.fail(actual, undefined, message || "expected {actual} to be {expected}", "===", assert.isUndefined); } }; assert.isString = function (actual, message) { From f10884d1c4a2a34d78f6afe2aded1409b79093ae Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 13 Jun 2010 15:30:54 -0400 Subject: [PATCH 230/409] improved assertion error messages. added tests --- lib/assert/macros.js | 10 +++++----- test/vows-test.js | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 02468853..bd7f5098 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -32,12 +32,12 @@ assert.matches = assert.match; assert.isTrue = function (actual, message) { if (actual !== true) { - assert.fail(actual, true, message || "expected {actual} to be {expected}", "===", assert.isTrue); + assert.fail(actual, true, message || "expected {expected}, got {actual}", "===", assert.isTrue); } }; assert.isFalse = function (actual, message) { if (actual !== false) { - assert.fail(actual, false, message || "expected {actual} to be {expected}", "===", assert.isFalse); + assert.fail(actual, false, message || "expected {expected}, got {actual}", "===", assert.isFalse); } }; assert.isZero = function (actual, message) { @@ -47,7 +47,7 @@ assert.isZero = function (actual, message) { }; assert.isNotZero = function (actual, message) { if (actual === 0) { - assert.fail(actual, 0, message || "expected {expected}, got {actual}", "===", assert.isNotZero); + assert.fail(actual, 0, message || "expected non-zero value, got {actual}", "===", assert.isNotZero); } }; @@ -117,12 +117,12 @@ assert.isNaN = function (actual, message) { }; assert.isNull = function (actual, message) { if (actual !== null) { - assert.fail(actual, null, message || "expected {actual} to be {expected}", "===", assert.isNull); + assert.fail(actual, null, message || "expected {expected}, got {actual}", "===", assert.isNull); } }; assert.isNotNull = function (actual, message) { if (actual === null) { - assert.fail(actual, null, message || "expected {actual} to not be {expected}", "===", assert.isNotNull); + assert.fail(actual, null, message || "expected non-null value, got {actual}", "===", assert.isNotNull); } }; assert.isUndefined = function (actual, message) { diff --git a/test/vows-test.js b/test/vows-test.js index c864febf..cb289776 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -44,6 +44,16 @@ vows.describe("Vows").addVows({ assert.isNumber(0); assert.isNaN(0/0); }, + "testing value": function (it) { + assert.isFalse(false); + assert.isTrue(true); + assert.isZero(0); + assert.isNotZero(1); + assert.isNull(null); + assert.isNotNull(0); + assert.greater(5, 4); + assert.lesser(4, 5); + }, "testing emptiness": function (it) { assert.isEmpty({}); assert.isEmpty([]); From 4a1a65ac07ba39a8f97ef4abce76486de50a1d9f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 13 Jun 2010 15:31:42 -0400 Subject: [PATCH 231/409] (dist) version bump --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 91040a39..1fdb8a59 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,9 @@ "keywords" : ["testing", "spec", "test", "BDD"], "author" : "Alexis Sellier ", "contributors" : [], - "dependencies" : {"eyes": ">=0.1.3"}, + "dependencies" : {"eyes": ">=0.1.6"}, "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.3.4" + "version" : "0.3.5" } From 6baca02e7646cb98c9e7a1b746eaaabbbbba522b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 14 Jun 2010 14:28:40 -0400 Subject: [PATCH 232/409] nicer output. refactor of formatters --- bin/vows | 5 ++- lib/vows/console.js | 42 +++++++++++++++++++------ lib/vows/reporters/dot-matrix.js | 22 +++++-------- lib/vows/reporters/spec.js | 53 +++++++++++++++++++++----------- lib/vows/reporters/watch.js | 16 ++-------- 5 files changed, 82 insertions(+), 56 deletions(-) diff --git a/bin/vows b/bin/vows index 0a894750..6b503fa0 100755 --- a/bin/vows +++ b/bin/vows @@ -5,7 +5,10 @@ var path = require('path'), fs = require('fs'), events = require('events'); -var inspect = require('eyes').inspector({ stream: null }); +var inspect = require('eyes').inspector({ + stream: null, + styles: { string: 'grey', regexp: 'grey' } +}); require.paths.unshift(path.join(__dirname, '..', 'lib')); diff --git a/lib/vows/console.js b/lib/vows/console.js index 4202a0f0..79a118a7 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -17,6 +17,19 @@ this.stylize = function stylize(str, style) { '\033[' + styles[style][1] + 'm'; }; +var $ = this.$ = function (str) { + str = new(String)(str); + + ['bold', 'grey', 'yellow', 'red', 'green', 'white', 'cyan', 'italic'].forEach(function (style) { + Object.defineProperty(str, style, { + get: function () { + return exports.$(exports.stylize(this, style)); + } + }); + }); + return str; +}; + this.puts = function (options) { var stylize = exports.stylize; return function (args) { @@ -29,20 +42,29 @@ this.puts = function (options) { }; this.result = function (event) { - var result = event.honored + " honored, " + - event.broken + " broken, " + - event.errored + " errored, " + - event.pending + " pending", - style = event.honored === event.total ? ('green') - : (event.pending ? 'cyan' : (event.errored ? 'red' : 'yellow')), - buffer = []; + var result = [], buffer = [], time = '', header; + var status = (event.errored && 'errored') || (event.broken && 'broken') || + (event.honored && 'honored') || (event.pending && 'pending'); + + event.honored && result.push($(event.honored).bold + " honored"); + event.broken && result.push($(event.broken).bold + " broken"); + event.errored && result.push($(event.errored).bold + " errored"); + event.pending && result.push($(event.pending).bold + " pending"); + + result = result.join(' ∙ '); + header = { + honored: '✓ ' + $('OK').bold.green, + broken: '✗ ' + $('Broken').bold.yellow, + errored: '✗ ' + $('Errored').bold.red, + pending: '- ' + $('Pending').bold.cyan + }[status] + ' » '; if ('time' in event) { - buffer.push("Verified " + event.total + " vows in " + - (event.time.toFixed(3) + " seconds.\n")); + time = ' (' + event.time.toFixed(3) + 's)'; + time = this.stylize(time, 'grey'); } - buffer.push(this.stylize(result, style)); + buffer.push(header + result + time); return buffer; }; diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index 98bad511..e6f2d70a 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -3,6 +3,7 @@ var sys = require('sys'); var options = {}; var console = require('vows/console'); +var spec = require('vows/reporters/spec'); var stylize = console.stylize, puts = console.puts(options); // @@ -28,27 +29,20 @@ this.report = function (data, s) { break; case 'vow': if (event.status === 'honored') { - sys.print(stylize('.', 'green')); + sys.print(stylize('·', 'green')); } else if (event.status === 'pending') { - sys.print(stylize('.', 'cyan')); + sys.print(stylize('-', 'cyan')); } else { if (lastContext !== event.context) { lastContext = event.context; - messages.push(event.context); + messages.push(spec.contextText(event.context)); } if (event.status === 'broken') { - sys.print(stylize('B', 'yellow')); - messages.push(' - ' + stylize(event.title, 'yellow')); - messages.push(' ~ ' + event.exception); + sys.print(stylize('✗', 'yellow')); + messages.push(spec.vowText(event)); } else if (event.status === 'errored') { - sys.print(stylize('E', 'red')); - messages.push(' - ' + stylize(event.title, 'red')); - if (event.exception.type === 'promise') { - messages.push(' * ' + stylize("An 'error' event was caught: " + - stylize(event.exception.error, 'bold'), 'red')); - } else { - messages.push(' ! ' + stylize(event.exception, 'red')); - } + sys.print(stylize('✗', 'red')); + messages.push(spec.vowText(event)); } messages.push(''); } diff --git a/lib/vows/reporters/spec.js b/lib/vows/reporters/spec.js index cd8a96ba..43aa26ca 100644 --- a/lib/vows/reporters/spec.js +++ b/lib/vows/reporters/spec.js @@ -17,28 +17,13 @@ this.report = function (data, s) { switch (data[0]) { case 'subject': - puts('\n' + stylize(event, 'underline') + '\n'); + puts('\n' + stylize(event, 'bold') + '\n'); break; case 'context': - puts(event); + puts(this.contextText(event)); break; case 'vow': - puts(' - ' + stylize(event.title, ({ - honored: 'green', - broken: 'yellow', - errored: 'red', - pending: 'cyan' - })[event.status])); - if (event.status === 'broken') { - puts(' ~ ' + event.exception); - } else if (event.status === 'errored') { - if (event.exception.type === 'promise') { - puts(' * ' + stylize("An 'error' event was caught: " + - stylize(event.exception.error, 'bold'), 'red')); - } else { - puts(' ! ' + stylize(event.exception, 'red')); - } - } + puts(this.vowText(event)); break; case 'end': sys.print('\n'); @@ -52,6 +37,38 @@ this.report = function (data, s) { } }; +this.contextText = function (event) { + return ' ' + event; +}; + +this.vowText = function (event) { + var buffer = []; + + buffer.push(' ' + { + honored: ' ✓ ', + broken: ' ✗ ', + errored: ' ✗ ', + pending: ' - ' + }[event.status] + stylize(event.title, ({ + honored: 'green', + broken: 'yellow', + errored: 'red', + pending: 'cyan' + })[event.status])); + + if (event.status === 'broken') { + buffer.push(' » ' + event.exception); + } else if (event.status === 'errored') { + if (event.exception.type === 'promise') { + buffer.push(' * ' + stylize("An 'error' event was caught: " + + stylize(event.exception.error, 'bold'), 'red')); + } else { + buffer.push(' ' + stylize(event.exception, 'red')); + } + } + return buffer.join('\n'); +}; + this.print = function (str) { sys.print(str); }; diff --git a/lib/vows/reporters/watch.js b/lib/vows/reporters/watch.js index 5d59790e..e0f98e50 100644 --- a/lib/vows/reporters/watch.js +++ b/lib/vows/reporters/watch.js @@ -2,6 +2,7 @@ var sys = require('sys'); var options = {}; var console = require('vows/console'); +var spec = require('vows/reporters/spec'); var stylize = console.stylize, puts = console.puts(options); // @@ -23,20 +24,9 @@ this.report = function (data) { if (['honored', 'pending'].indexOf(event.status) === -1) { if (lastContext !== event.context) { lastContext = event.context; - puts(event.context); - } - if (event.status === 'broken') { - puts(' - ' + stylize(event.title, 'yellow')); - puts(' ~ ' + event.exception); - } else if (event.status === 'errored') { - puts(' - ' + stylize(event.title, 'red')); - if (event.exception.type === 'promise') { - puts(' * ' + stylize("An 'error' event was caught: " + - stylize(event.exception.error, 'bold'), 'red')); - } else { - puts(' ! ' + stylize(event.exception, 'red')); - } + puts(spec.contextText(event.context)); } + puts(spec.vowText(event)); puts(''); } break; From 7edb97a377ab8b3b52dfb7bc83210d3275610ec4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 14 Jun 2010 14:31:34 -0400 Subject: [PATCH 233/409] reset pending vows in Suite#reset --- lib/vows/suite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index c8c5c3bf..aa32b7ad 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -27,7 +27,7 @@ this.Suite.prototype = new(function () { this.batches.forEach(function (b) { b.lastContext = null; b.remaining = b._remaining; - b.honored = b.broken = b.errored = b.total = 0; + b.honored = b.broken = b.errored = b.total = b.pending = 0; }); }; From 6546552dd9392fb9e67857cf2cfb4fe1ec4d6d71 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 14 Jun 2010 14:53:45 -0400 Subject: [PATCH 234/409] don't try to exist when tests complete --- lib/vows/suite.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index aa32b7ad..324155f4 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -243,15 +243,8 @@ this.Suite.prototype = new(function () { } } else { that.results.time = (new(Date) - start) / 1000; - that.report(['finish', that.results]); - if (callback) { callback(that.results) } - - // Don't exit until stdout is empty - process.stdout.addListener('drain', function () { - process.exit(that.results.broken || that.results.errored ? 1 : 0); - }); } })(this.batches.slice(0)); }; From 5a3362fb43c1aed0a8e191236fa679ac655c6118 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 14 Jun 2010 17:03:56 -0400 Subject: [PATCH 235/409] catch silent async failures on exit --- lib/vows.js | 23 +++++++++++++++++++---- lib/vows/console.js | 2 +- lib/vows/reporters/dot-matrix.js | 2 +- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index f2e93775..187b4e42 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -33,6 +33,7 @@ vows.__defineGetter__('reporter', function () { }); var stylize = require('vows/console').stylize; +var console = require('vows/console'); vows.inspect = require('vows/console').inspect; vows.prepare = require('vows/extras').prepare; @@ -128,10 +129,24 @@ function addVow(vow) { // If not, report an error message. // process.addListener('exit', function () { - if (vows.suites.filter(function (s) { - return (s.results.total > 0) && (s.results.time === null) - }).length > 0) { - vows.reporter.report(['error', { error: "An EventEmitter has failed to fire.", type: 'promise' }]); + var results = { honored: 0, broken: 0, errored: 0, pending: 0, total: 0 }; + + vows.suites.forEach(function (s) { + if ((s.results.total > 0) && (s.results.time === null)) { + vows.reporter.report(['error', { error: "Asynchronous Error", suite: s }]); + process.exit(1); + } + s.batches.forEach(function (b) { + if (b.status !== 'end') { + results.errored ++; + results.total ++; + vows.reporter.report(['error', { error: "A callback hasn't fired", batch: b, suite: s }]); + } + Object.keys(results).forEach(function (k) { results[k] += b[k] }); + }); + }); + if (results.total) { + sys.puts(console.result(results)); } }); diff --git a/lib/vows/console.js b/lib/vows/console.js index 79a118a7..2ce0b750 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -60,7 +60,7 @@ this.result = function (event) { pending: '- ' + $('Pending').bold.cyan }[status] + ' » '; - if ('time' in event) { + if (typeof(event.time) === 'number') { time = ' (' + event.time.toFixed(3) + 's)'; time = this.stylize(time, 'grey'); } diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index e6f2d70a..ba13a660 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -59,7 +59,7 @@ this.report = function (data, s) { puts(console.result(event).join('\n')); break; case 'error': - puts('\n\n * ' + stylize(event.error, 'red')); + sys.puts('\n* ' + stylize(event.error + ' in ', 'red') + event.suite.subject); break; } }; From 98418c7aad88c724339abfdf0dbbc2e69d76ee1d Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 14 Jun 2010 17:07:55 -0400 Subject: [PATCH 236/409] set batch.status to 'end' when ended --- lib/vows/suite.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 324155f4..0bd14f7a 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -274,6 +274,7 @@ this.tryEnd = function (batch) { (k in batch.suite.results) && (batch.suite.results[k] += batch[k]); }); + batch.status = 'end'; batch.suite.report(['end']); batch.promise.emit('end', batch.honored, batch.broken, batch.errored, batch.pending); } From 29161c70366968b4ebbbc1317298511d61735d32 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 14 Jun 2010 17:11:48 -0400 Subject: [PATCH 237/409] make sure we only output on exit, if there's a failure --- lib/vows.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 187b4e42..bc43dd66 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -129,7 +129,7 @@ function addVow(vow) { // If not, report an error message. // process.addListener('exit', function () { - var results = { honored: 0, broken: 0, errored: 0, pending: 0, total: 0 }; + 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)) { @@ -138,6 +138,7 @@ process.addListener('exit', function () { } s.batches.forEach(function (b) { if (b.status !== 'end') { + failure = true; results.errored ++; results.total ++; vows.reporter.report(['error', { error: "A callback hasn't fired", batch: b, suite: s }]); @@ -145,7 +146,7 @@ process.addListener('exit', function () { Object.keys(results).forEach(function (k) { results[k] += b[k] }); }); }); - if (results.total) { + if (failure) { sys.puts(console.result(results)); } }); From 3e0fb87a904dd8117edb7c57e0d7d278d904049f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 15 Jun 2010 03:24:44 -0400 Subject: [PATCH 238/409] improve subject appearance in spec.js --- lib/vows/reporters/spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/reporters/spec.js b/lib/vows/reporters/spec.js index 43aa26ca..bdfb0eef 100644 --- a/lib/vows/reporters/spec.js +++ b/lib/vows/reporters/spec.js @@ -17,7 +17,7 @@ this.report = function (data, s) { switch (data[0]) { case 'subject': - puts('\n' + stylize(event, 'bold') + '\n'); + puts('\n♢ ' + stylize(event, 'bold') + '\n'); break; case 'context': puts(this.contextText(event)); From 0b32f54de7915a0e78a2b22467b2092a9be93dce Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 15 Jun 2010 03:30:08 -0400 Subject: [PATCH 239/409] (dist) version bump to 0.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1fdb8a59..09f32e38 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.3.5" + "version" : "0.4.0" } From 5f415df072a855cf7011436b488f9d1b421f1f37 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 16 Jun 2010 00:20:28 -0400 Subject: [PATCH 240/409] output function name in AssertionError, if {expected} is a function --- lib/assert/error.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/assert/error.js b/lib/assert/error.js index 820571d4..b4829bda 100644 --- a/lib/assert/error.js +++ b/lib/assert/error.js @@ -7,8 +7,10 @@ require('assert').AssertionError.prototype.toString = function () { function parse(str) { return str.replace(/{actual}/g, inspect(that.actual)). - replace(/{expected}/g, inspect(that.expected)). - replace(/{operator}/g, stylize(that.operator, 'bold')); + replace(/{operator}/g, stylize(that.operator, 'bold')). + replace(/{expected}/g, (that.expected instanceof Function) + ? that.expected.name + : inspect(that.expected)); } if (this.message) { From f2ff9b56fc6d3b95086b69296c33dfeacdab2578 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 16 Jun 2010 00:20:51 -0400 Subject: [PATCH 241/409] (test) move assert module tests to its own file --- test/assert-test.js | 101 ++++++++++++++++++++++++++++++++++++++++++++ test/vows-test.js | 34 --------------- 2 files changed, 101 insertions(+), 34 deletions(-) create mode 100644 test/assert-test.js diff --git a/test/assert-test.js b/test/assert-test.js new file mode 100644 index 00000000..f2d8076d --- /dev/null +++ b/test/assert-test.js @@ -0,0 +1,101 @@ +var vows = require('vows'); +var assert = require('assert'); + +vows.describe('vows/assert').addVows({ + "The Assertion module": { + topic: require('assert'), + + "`equal`": function (assert) { + assert.equal("hello world", "hello world"); + assert.equal(1, true); + }, + "`match`": function (assert) { + assert.match("hello world", /^[a-z]+ [a-z]+$/); + }, + "`length`": function (assert) { + assert.length("hello world", 11); + assert.length([1, 2, 3], 3); + }, + "`include`": function (assert) { + assert.include("hello world", "world"); + assert.include([0, 42, 0], 42); + assert.include({goo:true}, 'goo'); + }, + "`typeOf`": function (assert) { + assert.typeOf('goo', 'string'); + assert.typeOf(42, 'number'); + assert.typeOf([], 'array'); + assert.typeOf({}, 'object'); + assert.typeOf(false, 'boolean'); + }, + "`instanceOf`": function (assert) { + assert.instanceOf([], Array); + assert.instanceOf(function () {}, Function); + }, + "`isArray`": function (assert) { + assert.isArray([]); + assertError(assert.isArray, {}); + }, + "`isString`": function (assert) { + assert.isString(""); + }, + "`isObject`": function (assert) { + assert.isObject({}); + assertError(assert.isObject, []); + }, + "`isNumber`": function (assert) { + assert.isNumber(0); + }, + "`isNan`": function (assert) { + assert.isNaN(0/0); + }, + "`isTrue`": function (assert) { + assert.isTrue(true); + assertError(assert.isTrue, 1); + }, + "`isFalse`": function (assert) { + assert.isFalse(false); + assertError(assert.isFalse, 0); + }, + "`isZero`": function (assert) { + assert.isZero(0); + assertError(assert.isZero, null); + }, + "`isNotZero`": function (assert) { + assert.isNotZero(1); + }, + "`isUndefined`": function (assert) { + assert.isUndefined(undefined); + assertError(assert.isUndefined, null); + }, + "`isNull`": function (assert) { + assert.isNull(null); + assertError(assert.isNull, 0); + assertError(assert.isNull, undefined); + }, + "`isNotNull`": function (assert) { + assert.isNotNull(0); + }, + "`greater` and `lesser`": function (assert) { + assert.greater(5, 4); + assert.lesser(4, 5); + }, + "`isEmpty`": function (assert) { + assert.isEmpty({}); + assert.isEmpty([]); + assert.isEmpty(""); + } + } +}).export(module); + +function assertError(assertion, value, fail) { + try { + assertion(value); + fail = true; + } catch (e) {/* Success */} + + fail && assert.fail(value, assert.AssertionError, + "expected an AssertionError for {actual}", + "assertError", assertError); +} + diff --git a/test/vows-test.js b/test/vows-test.js index cb289776..9f232d8b 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -25,40 +25,6 @@ vows.describe("Vows").addVows({ "A context": { topic: promiser("hello world"), - "testing equality": function (it) { - assert.equal(it, "hello world"); - }, - "testing match": function (it) { - assert.match(it, /[a-z]+ [a-z]+/); - }, - "testing length": function (it) { - assert.length(it, 11); - }, - "testing inclusion": function (it) { - assert.include(it, "world"); - }, - "testing type": function (it) { - assert.typeOf(it, 'string'); - assert.isArray([]); - assert.isObject({}); - assert.isNumber(0); - assert.isNaN(0/0); - }, - "testing value": function (it) { - assert.isFalse(false); - assert.isTrue(true); - assert.isZero(0); - assert.isNotZero(1); - assert.isNull(null); - assert.isNotNull(0); - assert.greater(5, 4); - assert.lesser(4, 5); - }, - "testing emptiness": function (it) { - assert.isEmpty({}); - assert.isEmpty([]); - assert.isEmpty(""); - }, "with a nested context": { topic: function (parent) { this.state = 42; From 159376846d7b96ce46ff702013353d2ac62ba8aa Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 16 Jun 2010 00:21:28 -0400 Subject: [PATCH 242/409] (test) remove other-test.js --- test/other-test.js | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 test/other-test.js diff --git a/test/other-test.js b/test/other-test.js deleted file mode 100644 index 013a50cf..00000000 --- a/test/other-test.js +++ /dev/null @@ -1,15 +0,0 @@ -var path = require('path'); - -require.paths.unshift(path.join(__dirname, '..', 'lib')); - -var assert = require('assert'); -var vows = require('vows'); - -vows.describe("Vows/other").addVows({ - "Another test": { - topic: true, - "exists in the test/ folder!": function (topic) { - assert.ok (topic); - } - } -}).export(module); From e53338c592f48a334a550cc7f58d2e4b0a11f2b5 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 16 Jun 2010 00:51:45 -0400 Subject: [PATCH 243/409] (api) don't add space between context descriptions in some cases --- lib/vows/context.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/vows/context.js b/lib/vows/context.js index 9b3590fb..812de0a0 100644 --- a/lib/vows/context.js +++ b/lib/vows/context.js @@ -24,7 +24,9 @@ this.Context = function (vow, ctx, env) { else { process.nextTick(emit) } }; }); - this.name = (ctx.name ? ctx.name + ' ' : '') + - (vow.description || ''); + this.name = [ + ctx.name || '', + vow.description || '' + ].join(/^[#.:]/.test(vow.description) ? '' : ' ').trim(); }; From a7f9f30b5b99d6a4b9232882cfe199bec27a8d72 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 16 Jun 2010 00:52:08 -0400 Subject: [PATCH 244/409] (test) improve test descriptions --- test/vows-test.js | 57 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/test/vows-test.js b/test/vows-test.js index 9f232d8b..7b73ea4f 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -30,13 +30,10 @@ vows.describe("Vows").addVows({ this.state = 42; return promiser(parent)(); }, - "testing equality": function (it) { - assert.equal(it, "hello world"); - }, "has access to the environment": function () { assert.equal(this.state, 42); }, - "a sub context": { + "and a sub nested context": { topic: function () { return this.state; }, @@ -51,19 +48,19 @@ vows.describe("Vows").addVows({ } } }, - "Nested contexts": { + "A nested context": { topic: promiser(1), - "have": { + ".": { topic: function (a) { return promiser(2)() }, - "access": { + ".": { topic: function (b, a) { return promiser(3)() }, - "to": { + ".": { topic: function (c, b, a) { return promiser([4, c, b, a])() }, - "the parent topics": function (topics) { + "should have access to the parent topics": function (topics) { assert.equal(topics.join(), [4, 3, 2, 1].join()); } }, @@ -78,27 +75,27 @@ vows.describe("Vows").addVows({ } } }, - "Nested contexts with no topics": { + "A nested context with no topics": { topic: 45, - "should": { - "pass": { - "the value down": function (topic) { + ".": { + ".": { + "should pass the value down": function (topic) { assert.equal(topic, 45); } } } }, - "Nested contexts with topic gaps": { + "A Nested context with topic gaps": { topic: 45, - "should": { - "pass": { + ".": { + ".": { topic: 101, - "the": { - "values": { + ".": { + ".": { topic: function (prev, prev2) { return this.context.topics.slice(0); }, - "down": function (topics) { + "should pass the topics down": function (topics) { assert.length(topics, 2); assert.equal(topics[0], 101); assert.equal(topics[1], 45); @@ -108,7 +105,7 @@ vows.describe("Vows").addVows({ } } }, - "Non-promise return value": { + "A non-promise return value": { topic: function () { return 1 }, "should be converted to a promise": function (val) { assert.equal(val, 1); @@ -128,14 +125,14 @@ vows.describe("Vows").addVows({ } } }, - "Non-functions as topics": { + "A non-function topic": { topic: 45, "should work as expected": function (topic) { assert.equal(topic, 45); } }, - "topics returning functions": { + "A topic returning a function": { topic: function () { return function () { return 42 }; }, @@ -180,7 +177,7 @@ vows.describe("Vows").addVows({ assert.equal(res, true); } }, - "a topic with callback-style async": { + "A topic with callback-style async": { "when successful": { topic: function () { function async(callback) { @@ -207,8 +204,10 @@ vows.describe("Vows").addVows({ } async(this.callback); }, - "should work like an event-emitter": function (e, res) { + "should have a non-null error value": function (e, res) { assert.equal(e, "ERROR"); + }, + "should work like an event-emitter": function (e, res) { assert.equal(res, undefined); } }, @@ -222,7 +221,7 @@ vows.describe("Vows").addVows({ } } }).addVows({ - "Sibling contexts": { + "A Sibling context": { "'A', with `this.foo = true`": { topic: function () { this.foo = true; @@ -236,13 +235,13 @@ vows.describe("Vows").addVows({ topic: function () { return this.foo; }, - "should have `this.foo` be undefined": function (res) { + "shouldn't have access to `this.foo`": function (res) { assert.isUndefined(res); } } } }).addVows({ - "A 2nd test suite": { + "A 2nd batch": { topic: function () { var p = new(events.EventEmitter); setTimeout(function () { @@ -253,7 +252,7 @@ vows.describe("Vows").addVows({ "should run after the first": function () {} } }).addVows({ - "A 3rd test suite": { - "should run last": function () {} + "A 3rd batch": { + topic: true, "should run last": function () {} } }).export(module); From 75ff4abc19654bf4f8299d760b0e21eab75b9989 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 16 Jun 2010 19:25:06 -0400 Subject: [PATCH 245/409] (api) addVows => addBatch --- README.md | 4 ++-- lib/vows.js | 2 +- lib/vows/suite.js | 3 ++- test/assert-test.js | 2 +- test/vows-test.js | 8 ++++---- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f597ccb2..d29375b7 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ synopsis var vows = require('vows'), assert = require('assert'); - vows.describe('Deep Thought').addVows({ + vows.describe('Deep Thought').addBatch({ 'An instance of DeepThought': { topic: new DeepThought, @@ -38,7 +38,7 @@ installation writing specs ------------- - vows.describe('A Database library').addVows({ + vows.describe('A Database library').addBatch({ 'A DB object': { // run this once, and execute the following tests when it completes topic: function () { return new(DB) }, diff --git a/lib/vows.js b/lib/vows.js index bc43dd66..d65da7c5 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -5,7 +5,7 @@ // // var vows = require('vows'); // -// vows.describe('Deep Thought').addVows({ +// vows.describe('Deep Thought').addBatch({ // "An instance of DeepThought": { // topic: new DeepThought, // diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 0bd14f7a..2fa8fe13 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -31,7 +31,7 @@ this.Suite.prototype = new(function () { }); }; - this.addVows = function (tests) { + this.addBatch = function (tests) { this.batches.push({ tests: tests, suite: this, @@ -45,6 +45,7 @@ this.Suite.prototype = new(function () { }); return this; }; + this.addVows = this.addBatch; this.parseVows = function (batch, matcher) { var tests = batch.tests; diff --git a/test/assert-test.js b/test/assert-test.js index f2d8076d..e3765052 100644 --- a/test/assert-test.js +++ b/test/assert-test.js @@ -1,7 +1,7 @@ var vows = require('vows'); var assert = require('assert'); -vows.describe('vows/assert').addVows({ +vows.describe('vows/assert').addBatch({ "The Assertion module": { topic: require('assert'), diff --git a/test/vows-test.js b/test/vows-test.js index 7b73ea4f..6450aa64 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -21,7 +21,7 @@ var promiser = function (val) { } }; -vows.describe("Vows").addVows({ +vows.describe("Vows").addBatch({ "A context": { topic: promiser("hello world"), @@ -220,7 +220,7 @@ vows.describe("Vows").addVows({ } } } -}).addVows({ +}).addBatch({ "A Sibling context": { "'A', with `this.foo = true`": { topic: function () { @@ -240,7 +240,7 @@ vows.describe("Vows").addVows({ } } } -}).addVows({ +}).addBatch({ "A 2nd batch": { topic: function () { var p = new(events.EventEmitter); @@ -251,7 +251,7 @@ vows.describe("Vows").addVows({ }, "should run after the first": function () {} } -}).addVows({ +}).addBatch({ "A 3rd batch": { topic: true, "should run last": function () {} } From 82f6e5e3e035cdcdecf9428e02b4e0ad483f1b52 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 01:54:06 -0400 Subject: [PATCH 246/409] (new) added more options to bin --- bin/vows | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/bin/vows b/bin/vows index 6b503fa0..59d1d7a7 100755 --- a/bin/vows +++ b/bin/vows @@ -19,6 +19,23 @@ var _reporter = require('vows/reporters/dot-matrix'), reporter = { name: _reporter.name, }; +var help = [ + "usage: vows [FILE, ...] [options]", + "", + "options:", + " -v, --verbose Enable verbose output", + " -w, --watch Watch mode", + " -s, --silent Don't report", + " -m'abc' Only run tests matching the string", + " -r'^abc$' Only run tests matching the regexp", + " --json Use JSON reporter", + " --spec Use Spec reporter", + " --dot-matrix Use Dot-Matrix reporter", + //" --no-color Don't use terminal colors", + " --version Show version", + " -h, --help You're staring at it" +].join('\n'); + var options = { reporter: reporter, matcher: /.*/, @@ -64,6 +81,13 @@ while (arg = argv.shift()) { case 'spec': _reporter = require('vows/reporters/spec'); break; + case 'dot-matrix': + _reporter = require('vows/reporters/dot-matrix'); + break; + case 'silent': + case 's': + _reporter = require('vows/reporters/silent'); + break; case 'verbose': case 'v': options.verbose = true; @@ -72,6 +96,19 @@ while (arg = argv.shift()) { case 'w': options.watch = true; break; + case 'no-color': + options.nocolor = true; + break; + case 'version': + sys.print('vows '); + sys.puts(fs.readFileSync(path.join(__dirname, '..', 'package.json')) + .toString().match(/"version"\s*:\s*"([\d.]+)"/)[1]); + process.exit(0); + case 'help': + case 'h': + sys.puts(help); + process.exit(0); + break; } } } From 7ed9f65e584fae8823b957c07b2f467918d42682 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 01:55:27 -0400 Subject: [PATCH 247/409] (api) '-m' and '-r' now require a space between pattern --- bin/vows | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/vows b/bin/vows index 59d1d7a7..37b8ff7b 100755 --- a/bin/vows +++ b/bin/vows @@ -64,13 +64,13 @@ while (arg = argv.shift()) { arg = arg.match(/^--?(.+)/)[1]; if (arg[0] === 'r') { - options.matcher = new(RegExp)(arg.slice(1)); + options.matcher = new(RegExp)(argv.shift()); } else if (arg[0] === 'm') { options.matcher = (function (str) { // Create an escaped RegExp var specials = '. * + ? | ( ) [ ] { } \\ ^ ? ! = : $'.split(' ').join('|\\'), regex = new(RegExp)('(\\' + specials + ')', 'g'); return new(RegExp)(str.replace(regex, '\\$1')); - })(arg.slice(1)); + })(argv.shift()); } else if (arg in options) { options[arg] = true; } else { From 692cb71ec2100e0f1dcb3add2d14dc93ef864e9f Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 01:55:46 -0400 Subject: [PATCH 248/409] try to handle async errors more intelligently --- lib/vows.js | 3 +-- lib/vows/suite.js | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index d65da7c5..20da5d0f 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -134,10 +134,9 @@ process.addListener('exit', function () { vows.suites.forEach(function (s) { if ((s.results.total > 0) && (s.results.time === null)) { vows.reporter.report(['error', { error: "Asynchronous Error", suite: s }]); - process.exit(1); } s.batches.forEach(function (b) { - if (b.status !== 'end') { + if (b.status === 'begin') { failure = true; results.errored ++; results.total ++; diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 2fa8fe13..d23a714c 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -104,6 +104,8 @@ this.Suite.prototype = new(function () { tests = batch.tests, promise = batch.promise = new(events.EventEmitter); + batch.status = 'begin'; + // The test runner, it calls itself recursively, passing the // previous context to the inner contexts. This is so the `topic` // functions have access to all the previous context topics in their From 3f3cc665f9c2b98d10f4fa2acbdbaa158d5b08f0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 01:55:58 -0400 Subject: [PATCH 249/409] silent reporter --- lib/vows/reporters/silent.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/vows/reporters/silent.js diff --git a/lib/vows/reporters/silent.js b/lib/vows/reporters/silent.js new file mode 100644 index 00000000..fe90a333 --- /dev/null +++ b/lib/vows/reporters/silent.js @@ -0,0 +1,8 @@ +// +// Silent reporter - "Shhh" +// +this.name = 'silent'; +this.reset = function () {}; +this.report = function () {}; +this.print = function () {}; + From 0acf40e892299bbcff2da898f634e12392160ba0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 01:59:43 -0400 Subject: [PATCH 250/409] update --help command --- bin/vows | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/vows b/bin/vows index 37b8ff7b..a39e7f46 100755 --- a/bin/vows +++ b/bin/vows @@ -26,8 +26,8 @@ var help = [ " -v, --verbose Enable verbose output", " -w, --watch Watch mode", " -s, --silent Don't report", - " -m'abc' Only run tests matching the string", - " -r'^abc$' Only run tests matching the regexp", + " -m PATTERN Only run tests matching the PATTERN string", + " -r PATTERN Only run tests matching the PATTERN regexp", " --json Use JSON reporter", " --spec Use Spec reporter", " --dot-matrix Use Dot-Matrix reporter", From 8ac48b9fa7f765a4a2cfd393a5952d811e3421bf Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 02:03:30 -0400 Subject: [PATCH 251/409] rename some internal functions for consistency --- lib/vows/suite.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index d23a714c..244988a7 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -47,7 +47,7 @@ this.Suite.prototype = new(function () { }; this.addVows = this.addBatch; - this.parseVows = function (batch, matcher) { + this.parseBatch = function (batch, matcher) { var tests = batch.tests; if ('topic' in tests) { @@ -99,7 +99,7 @@ this.Suite.prototype = new(function () { batch._remaining = batch.remaining; }; - this.runVows = function (batch) { + this.runBatch = function (batch) { var topic, tests = batch.tests, promise = batch.promise = new(events.EventEmitter); @@ -220,7 +220,7 @@ this.Suite.prototype = new(function () { this.reporter = options.reporter || this.reporter; this.batches.forEach(function (batch) { - that.parseVows(batch, that.matcher); + that.parseBatch(batch, that.matcher); }); this.reset(); @@ -240,7 +240,7 @@ this.Suite.prototype = new(function () { if (batch.remaining === 0) { run(batches); } else { - that.runVows(batch).addListener('end', function () { + that.runBatch(batch).addListener('end', function () { run(batches); }); } From 31c46cffb33639ff5b317477361c6c19d049e66d Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 02:03:57 -0400 Subject: [PATCH 252/409] (dist) fix Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b2fc4b8a..6bf89912 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,6 @@ # Run all tests # test: - @@bin/vows test/vows-test.js test/other-test.js + @@bin/vows test/* .PHONY: test install From 8917efe21842f2c9f1148958335dbf5e42483f2d Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 19:29:18 -0400 Subject: [PATCH 253/409] fix indentation in assert.equal error message --- lib/assert/macros.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/assert/macros.js b/lib/assert/macros.js index bd7f5098..4a31b08e 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -1,7 +1,7 @@ var assert = require('assert'); var messages = { - 'equal' : "expected {expected},\n got {actual} ({operator})", + 'equal' : "expected {expected},\n\tgot\t {actual} ({operator})", 'notEqual' : "didn't expect {actual} ({operator})", 'throws' : "expected {expected} to be thrown", 'doesNotThrow': "didn't expect {actual} to be thrown", From e1d1ea5619d3c1e6e38b5d615f26d7e91a3696f0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 19:30:16 -0400 Subject: [PATCH 254/409] track vows and vow statuses in batches --- lib/vows.js | 3 +++ lib/vows/suite.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lib/vows.js b/lib/vows.js index 20da5d0f..059dd103 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -59,6 +59,7 @@ function addVow(vow) { var batch = vow.batch; batch.total ++; + batch.vows.push(vow); return this.addListener("success", function () { var args = Array.prototype.slice.call(arguments); @@ -111,6 +112,8 @@ function addVow(vow) { } function output(status, exception) { + vow.status = status; + if (vow.context && batch.lastContext !== vow.context) { batch.lastContext = vow.context; batch.suite.report(['context', vow.context]); diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 244988a7..74687fd5 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -28,6 +28,7 @@ this.Suite.prototype = new(function () { b.lastContext = null; b.remaining = b._remaining; b.honored = b.broken = b.errored = b.total = b.pending = 0; + b.vows.forEach(function (vow) { vow.status = null }); }); }; @@ -35,6 +36,7 @@ this.Suite.prototype = new(function () { this.batches.push({ tests: tests, suite: this, + vows: [], remaining: 0, _remaining: 0, honored: 0, @@ -172,6 +174,7 @@ this.Suite.prototype = new(function () { context: ctx.name, description: item, binding: ctx.env, + status: null, batch: batch }); From 3d1217aa406f46b9b2dfefaaa59eb38339f21962 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 19:33:14 -0400 Subject: [PATCH 255/409] detect un-fired vows on exit, and report error --- lib/vows.js | 22 +++++++++++++++++++++- lib/vows/console.js | 5 +++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/vows.js b/lib/vows.js index 059dd103..299e04ca 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -139,11 +139,31 @@ process.addListener('exit', function () { vows.reporter.report(['error', { error: "Asynchronous Error", suite: s }]); } s.batches.forEach(function (b) { + var unFired = []; + + b.vows.forEach(function (vow) { + if (! vow.status) { + if (unFired.indexOf(vow.context) === -1) { + unFired.push(vow.context); + } + } + }); + + if (unFired.length > 0) { sys.print('\n') } + + unFired.forEach(function (title) { + sys.puts(console.error({ + error: "not fired!", + context: title, + batch: b, + suite: s + })); + }); + if (b.status === 'begin') { failure = true; results.errored ++; results.total ++; - vows.reporter.report(['error', { error: "A callback hasn't fired", batch: b, suite: s }]); } Object.keys(results).forEach(function (k) { results[k] += b[k] }); }); diff --git a/lib/vows/console.js b/lib/vows/console.js index 2ce0b750..aebf3003 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -72,3 +72,8 @@ this.result = function (event) { this.inspect = function inspect(val) { return '\033[1m' + eyes(val) + '\033[22m'; }; + +this.error = function (obj) { + return '✗ ' + $('Errored ').red + '» ' + + $('' + $(obj.context).italic + ' ∙ ' + obj.error).red; +}; From 833a2a016972caf45abf1143f8cb6a2b6a2fe2cb Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 21:46:00 -0400 Subject: [PATCH 256/409] console.result prints 'dropped' vows --- lib/vows/console.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/vows/console.js b/lib/vows/console.js index aebf3003..d20fde2b 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -43,6 +43,7 @@ this.puts = function (options) { this.result = function (event) { var result = [], buffer = [], time = '', header; + var complete = event.honored + event.pending + event.errored + event.broken; var status = (event.errored && 'errored') || (event.broken && 'broken') || (event.honored && 'honored') || (event.pending && 'pending'); @@ -51,6 +52,10 @@ this.result = function (event) { event.errored && result.push($(event.errored).bold + " errored"); event.pending && result.push($(event.pending).bold + " pending"); + if (complete < event.total) { + result.push($(event.total - complete).bold + " dropped"); + } + result = result.join(' ∙ '); header = { From cf3f4e2767fc89236e8f794bf30b1d9b610d2912 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 22:02:34 -0400 Subject: [PATCH 257/409] use suite's reporter for errors --- bin/vows | 4 ++-- lib/vows.js | 6 +++--- lib/vows/console.js | 2 +- lib/vows/reporters/dot-matrix.js | 2 +- lib/vows/reporters/spec.js | 2 +- lib/vows/reporters/watch.js | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bin/vows b/bin/vows index a39e7f46..d692110f 100755 --- a/bin/vows +++ b/bin/vows @@ -141,10 +141,9 @@ if (! options.watch) { reporter.report = function (data) { switch (data[0]) { case 'subject': - _reporter.report(data); - break; case 'vow': case 'context': + case 'error': _reporter.report(data); break; case 'end': @@ -152,6 +151,7 @@ if (! options.watch) { break; case 'finish': options.verbose ? _reporter.print('\n') : _reporter.print(' '); + break; } }; reporter.reset = function () { _reporter.reset && _reporter.reset() }; diff --git a/lib/vows.js b/lib/vows.js index 299e04ca..b4357240 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -136,7 +136,7 @@ process.addListener('exit', function () { vows.suites.forEach(function (s) { if ((s.results.total > 0) && (s.results.time === null)) { - vows.reporter.report(['error', { error: "Asynchronous Error", suite: s }]); + s.reporter.report(['error', { error: "Asynchronous Error", suite: s }]); } s.batches.forEach(function (b) { var unFired = []; @@ -152,12 +152,12 @@ process.addListener('exit', function () { if (unFired.length > 0) { sys.print('\n') } unFired.forEach(function (title) { - sys.puts(console.error({ + s.reporter.report(['error', { error: "not fired!", context: title, batch: b, suite: s - })); + }]); }); if (b.status === 'begin') { diff --git a/lib/vows/console.js b/lib/vows/console.js index d20fde2b..0c23358e 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -80,5 +80,5 @@ this.inspect = function inspect(val) { this.error = function (obj) { return '✗ ' + $('Errored ').red + '» ' - + $('' + $(obj.context).italic + ' ∙ ' + obj.error).red; + + $('' + $(obj.context).italic + ' ∙ ') + $(obj.error).red; }; diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index ba13a660..e3056ec0 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -59,7 +59,7 @@ this.report = function (data, s) { puts(console.result(event).join('\n')); break; case 'error': - sys.puts('\n* ' + stylize(event.error + ' in ', 'red') + event.suite.subject); + puts(console.error(event)); break; } }; diff --git a/lib/vows/reporters/spec.js b/lib/vows/reporters/spec.js index bdfb0eef..ecba77e5 100644 --- a/lib/vows/reporters/spec.js +++ b/lib/vows/reporters/spec.js @@ -32,7 +32,7 @@ this.report = function (data, s) { puts(console.result(event).join('\n')); break; case 'error': - puts('\n * ' + stylize(event.error, 'red')); + puts(console.error(event)); break; } }; diff --git a/lib/vows/reporters/watch.js b/lib/vows/reporters/watch.js index e0f98e50..d895b22b 100644 --- a/lib/vows/reporters/watch.js +++ b/lib/vows/reporters/watch.js @@ -31,7 +31,7 @@ this.report = function (data) { } break; case 'error': - puts('\n\n * ' + stylize(event.error, 'red')); + puts(console.error(event)); break; } }; From df248d4195f7c6e97dfdd776627c3ef72aa6d175 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 22:09:29 -0400 Subject: [PATCH 258/409] include subject in error message --- lib/vows/console.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/console.js b/lib/vows/console.js index 0c23358e..f87d25d5 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -79,6 +79,6 @@ this.inspect = function inspect(val) { }; this.error = function (obj) { - return '✗ ' + $('Errored ').red + '» ' + return '✗ ' + $('Errored ').red + '» ' + $(obj.suite.subject).bold + ': ' + $('' + $(obj.context).italic + ' ∙ ') + $(obj.error).red; }; From a2f11f0c9a60f386c44a070fbc2086b8712de8e5 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 22:09:58 -0400 Subject: [PATCH 259/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 09f32e38..461234a8 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.4.0" + "version" : "0.4.1" } From 629822748635edd9919ed2510780c07eac0b5760 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 17 Jun 2010 22:13:23 -0400 Subject: [PATCH 260/409] (minor) fixed grammar in assertion message --- lib/assert/macros.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 4a31b08e..1f5168ea 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -90,7 +90,7 @@ assert.isEmpty = function (actual, message) { assert.length = function (actual, expected, message) { if (actual.length !== expected) { - assert.fail(actual, expected, message || "expected {actual} to have {expected} elements", "length", assert.length); + assert.fail(actual, expected, message || "expected {actual} to have {expected} element(s)", "length", assert.length); } }; From cd4a7638261312654324fbee824f20151e7c4256 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 19 Jun 2010 19:32:34 -0400 Subject: [PATCH 261/409] remove throw/doesNotThrow message customization, cause it's fucked --- lib/assert/macros.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 1f5168ea..14c3de37 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -2,9 +2,7 @@ var assert = require('assert'); var messages = { 'equal' : "expected {expected},\n\tgot\t {actual} ({operator})", - 'notEqual' : "didn't expect {actual} ({operator})", - 'throws' : "expected {expected} to be thrown", - 'doesNotThrow': "didn't expect {actual} to be thrown", + 'notEqual' : "didn't expect {actual} ({operator})" }; messages['strictEqual'] = messages['deepEqual'] = messages['equal']; messages['notStrictEqual'] = messages['notDeepEqual'] = messages['notEqual']; From a532f176e653f8cbc860cb94c30046ab48ac1a85 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 19 Jun 2010 19:42:23 -0400 Subject: [PATCH 262/409] rename context.name => context.description, and make context.name be the last level only --- lib/vows/context.js | 5 +++-- lib/vows/suite.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/vows/context.js b/lib/vows/context.js index 812de0a0..a3205f97 100644 --- a/lib/vows/context.js +++ b/lib/vows/context.js @@ -24,8 +24,9 @@ this.Context = function (vow, ctx, env) { else { process.nextTick(emit) } }; }); - this.name = [ - ctx.name || '', + this.name = vow.description; + this.title = [ + ctx.title || '', vow.description || '' ].join(/^[#.:]/.test(vow.description) ? '' : ' ').trim(); }; diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 74687fd5..25ff9c47 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -171,7 +171,7 @@ this.Suite.prototype = new(function () { // Holds the current test or context var vow = Object.create({ callback: ctx.tests[item], - context: ctx.name, + context: ctx.title, description: item, binding: ctx.env, status: null, From b94c047094e98df894fb1aaeb483215bccdf3da6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 23 Jun 2010 11:08:56 -0700 Subject: [PATCH 263/409] fixed watch mode in OS X Terminal --- bin/vows | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bin/vows b/bin/vows index d692110f..6ad69aae 100755 --- a/bin/vows +++ b/bin/vows @@ -208,7 +208,6 @@ if (! options.watch) { cue = pendulum, current = 0; } - cursorSave(); eraseLine(); lastRun && !running && esc(colors[status.errored ? 2 : (status.broken ? 1 : 0)]); print(cue[current]); @@ -224,10 +223,9 @@ if (! options.watch) { // Utility functions // function print(str) { sys.print(str) } - function esc(str) { print("\033[" + str) } - function eraseLine() { esc("2K") } - function cursorSave() { esc("s") } - function cursorRestore() { esc("u") } + function esc(str) { print("\x1b[" + str) } + function eraseLine() { esc("0K") } + function cursorRestore() { esc("0G") } function cursorHide() { esc("?25l") } function cursorShow() { esc("?25h") } function cleanup() { eraseLine(), cursorShow(), print('\n') } From 3d8350205e55108126451b539554b1589e8854e8 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 23 Jun 2010 11:09:15 -0700 Subject: [PATCH 264/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 461234a8..3db6fc65 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.4.1" + "version" : "0.4.2" } From 4e1da2f02acb4f9626984b17790fe1bf587233f4 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 24 Jun 2010 00:06:50 -0700 Subject: [PATCH 265/409] allow this.callback to be used more flexibly --- lib/vows/context.js | 28 ++++++++++++---------------- lib/vows/suite.js | 4 ++-- test/vows-test.js | 20 +++++++++----------- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/lib/vows/context.js b/lib/vows/context.js index a3205f97..00d2b581 100644 --- a/lib/vows/context.js +++ b/lib/vows/context.js @@ -7,23 +7,19 @@ this.Context = function (vow, ctx, env) { this.emitter = null; this.env = env || {}; this.env.context = this; - this.env.__defineGetter__('callback', function () { - that._callback = true; - - return function (e, res) { - var args = Array.prototype.slice.call(arguments, 1); - var emit = function () { - if (e) { that.emitter.emit('error', e) } - else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } - }; - // If `this.callback` is called synchronously, - // the emitter will not have been set yet, - // so we defer the emition, that way it'll behave - // asynchronously. - if (that.emitter) { emit() } - else { process.nextTick(emit) } + this.env.callback = function (e, res) { + var args = Array.prototype.slice.call(arguments, 1); + var emit = function () { + if (e) { that.emitter.emit('error', e) } + else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } }; - }); + // If `this.callback` is called synchronously, + // the emitter will not have been set yet, + // so we defer the emition, that way it'll behave + // asynchronously. + if (that.emitter) { emit() } + else { process.nextTick(emit) } + }; this.name = vow.description; this.title = [ ctx.title || '', diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 25ff9c47..2a9048c3 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -123,6 +123,8 @@ this.Suite.prototype = new(function () { if (typeof(topic) === 'function') { // Run the topic, passing the previous context topics topic = topic.apply(ctx.env, ctx.topics); + + if (typeof(topic) === 'undefined') { ctx._callback = true } } // If this context has a topic, store it in `lastTopic`, @@ -144,8 +146,6 @@ this.Suite.prototype = new(function () { process.nextTick(function (val) { return function () { ctx.emitter.emit("success", val) }; }(topic)); - } else if (typeof(topic) !== "undefined" && !old) { - throw new(Error)("topic must not return anything when using `this.callback`."); } topic = ctx.emitter; } diff --git a/test/vows-test.js b/test/vows-test.js index 6450aa64..6679cfb2 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -180,12 +180,10 @@ vows.describe("Vows").addBatch({ "A topic with callback-style async": { "when successful": { topic: function () { - function async(callback) { - process.nextTick(function () { - callback(null, "OK"); - }); - } - async(this.callback); + var that = this; + process.nextTick(function () { + that.callback(null, "OK"); + }); }, "should work like an event-emitter": function (res) { assert.equal(res, "OK"); @@ -225,18 +223,18 @@ vows.describe("Vows").addBatch({ "'A', with `this.foo = true`": { topic: function () { this.foo = true; - return this.foo; + return this; }, "should have `this.foo` set to true": function (res) { - assert.equal(res, true); + assert.equal(res.foo, true); } }, "'B', with nothing set": { topic: function () { - return this.foo; + return this; }, - "shouldn't have access to `this.foo`": function (res) { - assert.isUndefined(res); + "shouldn't have access to `this.foo`": function (e, res) { + assert.isUndefined(res.foo); } } } From 787536683d72edbe709bc646642b50c06de72cfc Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 24 Jun 2010 01:13:50 -0700 Subject: [PATCH 266/409] return an appropriate exit code from bin/vows, depending on success of the tests. --- bin/vows | 1 + lib/vows/suite.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bin/vows b/bin/vows index 6ad69aae..d7798c35 100755 --- a/bin/vows +++ b/bin/vows @@ -168,6 +168,7 @@ if (! options.watch) { sys.print(str.replace(/^\n\n/, '\n')); } }); + process.exit(results.honored + results.pending == results.total ? 0 : 1); }); } else { msg('watcher', 'watching files in', process.cwd() + '/'); diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 2a9048c3..9f95ef48 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -234,7 +234,7 @@ this.Suite.prototype = new(function () { this.report(['subject', this.subject]); } - (function run(batches) { + return (function run(batches) { var batch = batches.shift(); if (batch) { @@ -250,7 +250,14 @@ this.Suite.prototype = new(function () { } else { that.results.time = (new(Date) - start) / 1000; that.report(['finish', that.results]); + if (callback) { callback(that.results) } + + if (that.results.honored + that.results.pending === that.results.total) { + return 0; + } else { + return 1; + } } })(this.batches.slice(0)); }; From 335a8ee0c48c61606aecc4ae68ccb37b4403c16b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 24 Jun 2010 01:14:49 -0700 Subject: [PATCH 267/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3db6fc65..6dd89e3c 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.4.2" + "version" : "0.4.3" } From ae169164cd0105fe27601515452402a34837c6e3 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 26 Jun 2010 01:04:21 -0400 Subject: [PATCH 268/409] fixed a bug with falsy topics --- lib/vows/suite.js | 2 +- test/vows-test.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 9f95ef48..ac8abbba 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -130,7 +130,7 @@ this.Suite.prototype = new(function () { // If this context has a topic, store it in `lastTopic`, // if not, use the last topic, passed down by a parent // context. - if (topic) { + if (typeof(topic) !== 'undefined') { lastTopic = topic; } else { old = true; diff --git a/test/vows-test.js b/test/vows-test.js index 6679cfb2..a0d1b351 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -132,6 +132,13 @@ vows.describe("Vows").addBatch({ assert.equal(topic, 45); } }, + "A non-function topic with a falsy value": { + topic: 0, + + "should work as expected": function (topic) { + assert.equal(topic, 0); + } + }, "A topic returning a function": { topic: function () { return function () { return 42 }; From 93aeaa321837c153ceb58c7dd9a2616bd987218b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 26 Jun 2010 18:25:47 -0400 Subject: [PATCH 269/409] result of this.callback is passed down to nested topics --- lib/vows/suite.js | 2 +- test/vows-test.js | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index ac8abbba..aa87795c 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -130,7 +130,7 @@ this.Suite.prototype = new(function () { // If this context has a topic, store it in `lastTopic`, // if not, use the last topic, passed down by a parent // context. - if (typeof(topic) !== 'undefined') { + if (typeof(topic) !== 'undefined' || ctx._callback) { lastTopic = topic; } else { old = true; diff --git a/test/vows-test.js b/test/vows-test.js index a0d1b351..d96db2cb 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -3,7 +3,9 @@ var path = require('path'); require.paths.unshift(path.join(__dirname, '..', 'lib')); var events = require('events'), - assert = require('assert'); + assert = require('assert'), + fs = require('fs'); + var vows = require('vows'); var api = vows.prepare({ @@ -75,6 +77,24 @@ vows.describe("Vows").addBatch({ } } }, + "Nested contexts with callback-style async": { + topic: function () { + fs.stat(__dirname + '/vows-test.js', this.callback); + }, + 'after a successful `fs.stat`': { + topic: function (stat) { + fs.open(__dirname + '/vows-test.js', "r", stat.mode, this.callback); + }, + 'after a successful `fs.open`': { + topic: function (fd, stat) { + fs.read(fd, stat.size, 0, "utf8", this.callback); + }, + 'after a successful `fs.read`': function (data) { + assert.match (data, /after a successful `fs.read`/); + } + } + } + }, "A nested context with no topics": { topic: 45, ".": { From 92aafedd33fe5dbbb4f54b6a888488e2e8041f4b Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 27 Jun 2010 13:41:26 -0400 Subject: [PATCH 270/409] improved error message when callback returns uncaught error --- lib/vows/reporters/spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/reporters/spec.js b/lib/vows/reporters/spec.js index ecba77e5..8aace81a 100644 --- a/lib/vows/reporters/spec.js +++ b/lib/vows/reporters/spec.js @@ -60,7 +60,7 @@ this.vowText = function (event) { buffer.push(' » ' + event.exception); } else if (event.status === 'errored') { if (event.exception.type === 'promise') { - buffer.push(' * ' + stylize("An 'error' event was caught: " + + buffer.push(' » ' + stylize("An unexpected error was caught: " + stylize(event.exception.error, 'bold'), 'red')); } else { buffer.push(' ' + stylize(event.exception, 'red')); From d1b71d85a4b64d6dfb56bc5af425ba768855b81e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 27 Jun 2010 13:41:41 -0400 Subject: [PATCH 271/409] (test) add an empty batch to make sure it works --- test/vows-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/vows-test.js b/test/vows-test.js index d96db2cb..638233bb 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -280,4 +280,4 @@ vows.describe("Vows").addBatch({ "A 3rd batch": { topic: true, "should run last": function () {} } -}).export(module); +}).addBatch({}).export(module); From 30e66880ddb558996c05daddcb35d7021effc227 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 27 Jun 2010 21:31:12 -0400 Subject: [PATCH 272/409] don't exit until stdout is drained --- bin/vows | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/vows b/bin/vows index d7798c35..f8133568 100755 --- a/bin/vows +++ b/bin/vows @@ -168,7 +168,9 @@ if (! options.watch) { sys.print(str.replace(/^\n\n/, '\n')); } }); - process.exit(results.honored + results.pending == results.total ? 0 : 1); + process.stdout.addListener('drain', function () { + process.exit(results.honored + results.pending == results.total ? 0 : 1); + }); }); } else { msg('watcher', 'watching files in', process.cwd() + '/'); From afd3aab0590545af054831cba2f33efcc71d34de Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 27 Jun 2010 21:31:36 -0400 Subject: [PATCH 273/409] handle edge case in this.callback, where a single boolean is returned --- lib/vows/context.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/vows/context.js b/lib/vows/context.js index 00d2b581..b5acb99e 100644 --- a/lib/vows/context.js +++ b/lib/vows/context.js @@ -10,8 +10,19 @@ this.Context = function (vow, ctx, env) { this.env.callback = function (e, res) { var args = Array.prototype.slice.call(arguments, 1); var emit = function () { - if (e) { that.emitter.emit('error', e) } - else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } + // Convert callback-style results into events. + // + // We handle a special case, where the first argument is a + // boolean, in which case we treat it as a result, and not + // an error. This is useful for `path.exists` and other + // functions like it, which only pass a single boolean + // parameter instead of the more common (error, result) pair. + if (typeof(e) === 'boolean' && args.length === 0) { + that.emitter.emit('success', e); + } else { + if (e) { that.emitter.emit('error', e) } + else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } + } }; // If `this.callback` is called synchronously, // the emitter will not have been set yet, From e0ffeeaa1997587dd4c2f0aecb6dac79ba4d2fc7 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 27 Jun 2010 22:19:20 -0400 Subject: [PATCH 274/409] fix --version --- bin/vows | 4 +--- lib/vows.js | 5 +++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bin/vows b/bin/vows index f8133568..c9e98bbc 100755 --- a/bin/vows +++ b/bin/vows @@ -100,9 +100,7 @@ while (arg = argv.shift()) { options.nocolor = true; break; case 'version': - sys.print('vows '); - sys.puts(fs.readFileSync(path.join(__dirname, '..', 'package.json')) - .toString().match(/"version"\s*:\s*"([\d.]+)"/)[1]); + sys.puts('vows ' + vows.version); process.exit(0); case 'help': case 'h': diff --git a/lib/vows.js b/lib/vows.js index b4357240..85e3806d 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -16,6 +16,7 @@ // }).run(); // var sys = require('sys'), + path = require('path'), events = require('events'), vows = exports; @@ -186,3 +187,7 @@ vows.describe = function (subject) { return suite; }; + + +vows.version = require('fs').readFileSync(path.join(__dirname, '..', 'package.json')) + .toString().match(/"version"\s*:\s*"([\d.]+)"/)[1]; From 9ea324ff20ad906918e3ca4b741db059fe945322 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 27 Jun 2010 22:19:32 -0400 Subject: [PATCH 275/409] (dist) version bump --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6dd89e3c..4d46c718 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name" : "vows", "description" : "Asynchronous BDD & continuous integration for node.js", - "url" : "http://cloudhead.io/vows", + "url" : "http://vowsjs.org", "keywords" : ["testing", "spec", "test", "BDD"], "author" : "Alexis Sellier ", "contributors" : [], @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.4.3" + "version" : "0.4.4" } From 5cdc2ba629093e24f6ee0eba74cde38ff1f48c71 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 28 Jun 2010 13:04:14 -0400 Subject: [PATCH 276/409] (api) watch mode can take arguments, fixed a couple edge cases --- bin/vows | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/bin/vows b/bin/vows index c9e98bbc..8a29aaf6 100755 --- a/bin/vows +++ b/bin/vows @@ -119,7 +119,7 @@ if (options.watch) { msg('bin', 'argv', args); msg('bin', 'options', { reporter: options.reporter.name, matcher: options.matcher }); -if (args.length === 0) { +if (args.length === 0 || options.watch) { msg('bin', 'discovering', 'folder structure'); root = fs.readdirSync('.'); @@ -132,7 +132,14 @@ if (args.length === 0) { } msg('bin', 'discovered', "./" + testFolder); - args = paths(testFolder); + if (args.length === 0) { + args = paths(testFolder); + + if (options.watch) { + args = args.concat(paths('lib'), + paths('src')); + } + } } if (! options.watch) { @@ -171,7 +178,6 @@ if (! options.watch) { }); }); } else { - msg('watcher', 'watching files in', process.cwd() + '/'); // // Watch mode // @@ -229,7 +235,7 @@ if (! options.watch) { function cursorRestore() { esc("0G") } function cursorHide() { esc("?25l") } function cursorShow() { esc("?25h") } - function cleanup() { eraseLine(), cursorShow(), print('\n') } + function cleanup() { eraseLine(), cursorShow(), clearInterval(timer), print('\n') } // // Called when a file has been modified. @@ -238,10 +244,10 @@ if (! options.watch) { function changed(file) { status = { honored: 0, broken: 0, errored: 0, pending: 0 }; - msg('watcher', 'detected change in', file + '.js'); + msg('watcher', 'detected change in', file); - file = (/-(test|spec)$/.test(file) ? path.join(testFolder, file) - : path.join(testFolder, file + '-' + testFolder)) + '.js'; + file = (/-(test|spec)\.js$/.test(file) ? path.join(testFolder, file) + : path.join(testFolder, file + '-' + testFolder)); try { fs.statSync(file); @@ -268,16 +274,18 @@ if (! options.watch) { }); } + msg('watcher', 'watching', args); + // - // Watch all relevant files in lib/ and src/, + // Watch all relevant files, // and call `changed()` on change. // - [].concat(paths('lib'), paths('src'), paths(testFolder)).forEach(function (p) { + args.forEach(function (p) { fs.watchFile(p, function (current, previous) { if (new(Date)(current.mtime).valueOf() === new(Date)(previous.mtime).valueOf()) { return } else { - changed(path.basename(p, '.js')); + changed(p); } }); }); @@ -298,7 +306,7 @@ function runSuites(suites, callback) { (function run(suites, callback) { var suite = suites.shift(); if (suite) { - msg('runner', "running", suite.subject + ' ', true); + msg('runner', "running", suite.subject + ' ', options.watch ? false : true); suite.run(options, function (result) { Object.keys(result).forEach(function (k) { results[k] += result[k]; From ed576d70f77677d558fbc6e385ed3dd4525c49b5 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 28 Jun 2010 13:05:58 -0400 Subject: [PATCH 277/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4d46c718..ce1828a2 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.4.4" + "version" : "0.4.5" } From 726b82f1223e9261207d5bfd8e9f695b5514c637 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 28 Jun 2010 14:45:23 -0400 Subject: [PATCH 278/409] updated README for site --- README.md | 79 ++++--------------------------------------------------- 1 file changed, 5 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index d29375b7..6bec2c00 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,15 @@ Vows > Asynchronous BDD & continuous integration for node.js + +-------------------- + introduction ------------ There are two reasons why we might want asynchronous testing. The first, and obvious reason is that node.js is asynchronous, and therefore our tests need to be. The second reason is to make test suites which target I/O libraries run much faster. _Vows_ is an experiment in making this possible, while adding a minimum of overhead. -![vows-ss](http://files.droplr.com/files/36156834/ZfmbC.Screen%20shot%202010-05-11%20at%2020:19:25.png) - synopsis -------- @@ -27,83 +28,13 @@ synopsis } }); -Documenation coming soon. For now, have a look at the tests in to -get an idea. - installation ------------ $ npm install vows -writing specs +documentation ------------- - vows.describe('A Database library').addBatch({ - 'A DB object': { - // run this once, and execute the following tests when it completes - topic: function () { return new(DB) }, - - 'set() should store a k/v pair': { - // the inner context gets the return values of the outer contexts - // passed as arguments. Here, `db` is new(DB). - topic: function (db) { return db.set('pulp', 'orange') }, - - // `res` is the value emitted by the above `db.set` - 'and return OK': function (res) { - assert.equal(res, "OK"); - }, - 'and when checked for existence': { - // here, we need to access `db`, from the parent context. - // It's passed as the 2nd argument to `topic`, we discard the first, - // which would have been the above `res`. - topic: function (_, db) { return db.exists('pulp') }, - - 'return true': function (re) { - assert.equal(re, true); - } - } - }, - 'get()': { - topic: function (db) { return db.get('dream') }, - 'should return the stored value': function (res) { - assert.equal(res, 'catcher'); - } - } - } - }).run(); - -assertion macros ----------------- - -### equality # - -- assert.equal -- assert.notEqual -- assert.strictEqual -- assert.strictNotEqual - -### type # - -- assert.isFunction -- assert.isObject -- assert.isNaN -- assert.isString -- assert.isArray -- assert.isBoolean -- assert.isNumber -- assert.isNull -- assert.isUndefined -- assert.typeOf -- assert.instanceOf - -### properties # - -- assert.include -- assert.match -- assert.length -- assert.isEmpty - -### exceptions # +Head over to -- assert.throws -- assert.doesNotThrow From c98bcc45b75ec2737af56c8652dc4fcbff5a081a Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Mon, 28 Jun 2010 11:46:00 -0700 Subject: [PATCH 279/409] (doc) fix README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 6bec2c00..5d58634a 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ Vows > Asynchronous BDD & continuous integration for node.js - --------------------- +### # introduction ------------ From ccf6ec021773bf2d87a59e38c71e0de80fcedf9a Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Mon, 28 Jun 2010 15:05:07 -0700 Subject: [PATCH 280/409] (doc) fix link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d58634a..58c8ff5c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Vows > Asynchronous BDD & continuous integration for node.js -### # +#### # introduction ------------ From cca54cf2ff54f8a176ed38539f9d7d1cf7b8a336 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 1 Jul 2010 18:02:33 +0200 Subject: [PATCH 281/409] refactor counter updates --- lib/vows.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 85e3806d..bf687f0f 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -89,7 +89,6 @@ function addVow(vow) { var exception, topic, status; if (vow.callback instanceof String) { - batch.pending ++; return output('pending'); } @@ -98,21 +97,19 @@ function addVow(vow) { try { vow.callback.apply(vow.binding || null, args); output('honored', exception); - batch.honored ++; } catch (e) { if (e.name && e.name.match(/AssertionError/)) { exception = e.toString(); output('broken', exception); - batch.broken ++; } else { exception = e.stack || e.message || e.toString() || e; - batch.errored ++; output('errored', exception); } } } function output(status, exception) { + batch[status] ++; vow.status = status; if (vow.context && batch.lastContext !== vow.context) { From f2eb7b238ced96a493cc6eabcb19a6eba0f10ecd Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 1 Jul 2010 18:05:27 +0200 Subject: [PATCH 282/409] more refactoring in addVow --- lib/vows.js | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index bf687f0f..e212618b 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -73,20 +73,16 @@ function addVow(vow) { vows.tryEnd(batch); }).addListener("error", function (err) { - var exception; - if (vow.callback.length >= 2) { runTest([err]); } else { - exception = { type: 'promise', error: err }; - batch.errored ++; - output('errored', exception); + output('errored', { type: 'promise', error: err }); } vows.tryEnd(batch); }); function runTest(args) { - var exception, topic, status; + var topic, status; if (vow.callback instanceof String) { return output('pending'); @@ -96,14 +92,12 @@ function addVow(vow) { // increment counters accordingly. try { vow.callback.apply(vow.binding || null, args); - output('honored', exception); + output('honored'); } catch (e) { if (e.name && e.name.match(/AssertionError/)) { - exception = e.toString(); - output('broken', exception); + output('broken', e.toString()); } else { - exception = e.stack || e.message || e.toString() || e; - output('errored', exception); + output('errored', e.stack || e.message || e.toString() || e); } } } From 4fc9097044241fa46ef946b4545179a8e8153b54 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 1 Jul 2010 18:43:28 +0200 Subject: [PATCH 283/409] (new) --no-error For cases when callbacks don't expect an error argument. --- bin/vows | 6 +++++- lib/vows.js | 7 ++++--- lib/vows/context.js | 37 ++++++++++++++++++++++++------------- lib/vows/suite.js | 1 + 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/bin/vows b/bin/vows index 8a29aaf6..85c861ff 100755 --- a/bin/vows +++ b/bin/vows @@ -39,7 +39,8 @@ var help = [ var options = { reporter: reporter, matcher: /.*/, - watch: false + watch: false, + error: true }; var files = []; @@ -99,6 +100,9 @@ while (arg = argv.shift()) { case 'no-color': options.nocolor = true; break; + case 'no-error': + options.error = false; + break; case 'version': sys.puts('vows ' + vows.version); process.exit(0); diff --git a/lib/vows.js b/lib/vows.js index e212618b..a46e9cf7 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -26,7 +26,8 @@ require.paths.unshift(__dirname); vows.options = { Emitter: events.EventEmitter, reporter: require('vows/reporters/dot-matrix'), - matcher: /.*/ + matcher: /.*/, + error: true // Handle "error" event }; vows.__defineGetter__('reporter', function () { @@ -66,14 +67,14 @@ function addVow(vow) { var args = Array.prototype.slice.call(arguments); // 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) { + if (vow.callback.length >= 2 && batch.suite.options.error) { args.unshift(null); } runTest(args); vows.tryEnd(batch); }).addListener("error", function (err) { - if (vow.callback.length >= 2) { + if (vow.callback.length >= 2 || !batch.suite.options.error) { runTest([err]); } else { output('errored', { type: 'promise', error: err }); diff --git a/lib/vows/context.js b/lib/vows/context.js index b5acb99e..4fd8e57b 100644 --- a/lib/vows/context.js +++ b/lib/vows/context.js @@ -7,23 +7,34 @@ this.Context = function (vow, ctx, env) { this.emitter = null; this.env = env || {}; this.env.context = this; - this.env.callback = function (e, res) { - var args = Array.prototype.slice.call(arguments, 1); - var emit = function () { + + this.env.callback = function (/* arguments */) { + var args = Array.prototype.slice.call(arguments); + var emit = (function (args) { + // // Convert callback-style results into events. // - // We handle a special case, where the first argument is a - // boolean, in which case we treat it as a result, and not - // an error. This is useful for `path.exists` and other - // functions like it, which only pass a single boolean - // parameter instead of the more common (error, result) pair. - if (typeof(e) === 'boolean' && args.length === 0) { - that.emitter.emit('success', e); + if (vow.batch.suite.options.error) { + return function () { + var e = args.shift(); + // We handle a special case, where the first argument is a + // boolean, in which case we treat it as a result, and not + // an error. This is useful for `path.exists` and other + // functions like it, which only pass a single boolean + // parameter instead of the more common (error, result) pair. + if (typeof(e) === 'boolean' && args.length === 0) { + that.emitter.emit('success', e); + } else { + if (e) { that.emitter.emit('error', e) } + else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } + } + }; } else { - if (e) { that.emitter.emit('error', e) } - else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } + return function () { + that.emitter.emit.apply(that.emitter, ['success'].concat(args)); + }; } - }; + })(args.slice(0)); // If `this.callback` is called synchronously, // the emitter will not have been set yet, // so we defer the emition, that way it'll behave diff --git a/lib/vows/suite.js b/lib/vows/suite.js index aa87795c..bbe011e1 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -221,6 +221,7 @@ this.Suite.prototype = new(function () { this.matcher = options.matcher || this.matcher; this.reporter = options.reporter || this.reporter; + this.options = options; this.batches.forEach(function (batch) { that.parseBatch(batch, that.matcher); From 2b7398e830bf4888446de33e8db97006cc2e7c99 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 1 Jul 2010 19:06:01 +0200 Subject: [PATCH 284/409] ability to pass suite options to export method --- bin/vows | 3 +-- lib/vows/suite.js | 12 ++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/bin/vows b/bin/vows index 85c861ff..b3f71f15 100755 --- a/bin/vows +++ b/bin/vows @@ -39,8 +39,7 @@ var help = [ var options = { reporter: reporter, matcher: /.*/, - watch: false, - error: true + watch: false }; var files = []; diff --git a/lib/vows/suite.js b/lib/vows/suite.js index bbe011e1..c1b771d0 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -11,6 +11,7 @@ this.Suite = function (subject) { this.matcher = /.*/; this.reporter = require('vows/reporters/dot-matrix'); this.batches = []; + this.options = { error: true }; this.reset(); }; @@ -219,9 +220,10 @@ this.Suite.prototype = new(function () { options = options || {}; - this.matcher = options.matcher || this.matcher; - this.reporter = options.reporter || this.reporter; - this.options = options; + for (var k in options) { this.options[k] = options[k] } + + this.matcher = this.options.matcher || this.matcher; + this.reporter = this.options.reporter || this.reporter; this.batches.forEach(function (batch) { that.parseBatch(batch, that.matcher); @@ -265,7 +267,9 @@ this.Suite.prototype = new(function () { this.runParallel = function () {}; - this.export = function (module) { + this.export = function (module, options) { + for (var k in (options || {})) { this.options[k] = options[k] } + if (require.main === module) { return this.run(); } else { From a6c51c4f88a65128ac0fe0e361200f4e67c82b67 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 1 Jul 2010 19:10:50 +0200 Subject: [PATCH 285/409] better assert.isNaN check --- lib/assert/macros.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 14c3de37..548f765b 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -109,8 +109,8 @@ assert.isNumber = function (actual, message) { } }; assert.isNaN = function (actual, message) { - if (! isNaN(actual)) { - assert.fail(actual, 'NaN', message || "expected {actual} to be NaN", "isNaN", assert.isNaN); + if (actual === actual) { + assert.fail(actual, 'NaN', message || "expected {actual} to be NaN", "===", assert.isNaN); } }; assert.isNull = function (actual, message) { From d78e098f85f8647c07b1d2c1a97cf2f6cce8f2ea Mon Sep 17 00:00:00 2001 From: cloudhead Date: Thu, 1 Jul 2010 19:12:22 +0200 Subject: [PATCH 286/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce1828a2..5e9b996a 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.4.5" + "version" : "0.4.6" } From 36c5c47424b4fe7fcd7a417fdc9c808c647e0d1f Mon Sep 17 00:00:00 2001 From: Travis Swicegood Date: Sat, 3 Jul 2010 02:46:41 +0800 Subject: [PATCH 287/409] Add ability to run .coffee files Other than the "works on my system" setup, not sure the best way to go about testing this. Compiling it and running it on my system works, all vows continue to work. In addition, I've also added typos to the `require('coffee-script')` line to force it to act like CoffeeScript isn't available. It continues to run the `*.js` files and ignores the `*.coffee` files. --- bin/vows | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/bin/vows b/bin/vows index b3f71f15..a73e8a27 100755 --- a/bin/vows +++ b/bin/vows @@ -5,6 +5,21 @@ var path = require('path'), fs = require('fs'), events = require('events'); +// +// Attempt to load Coffee-Script. If it's not available, continue on our +// merry way, if it is available, set it up so we can include `*.coffee` +// scripts and start searching for them. +// +try { + var coffee = require('coffee-script'); + require.registerExtension('.coffee', function(content) { return coffee.compile(content); }); + var fileExtRegex = /\.(js|coffee)$/, + specFileExtRegex = /-(test|spec)\.(js|coffee)$/; +} catch (err) { + var fileExtRegex = /\.js$/, + specFileExtRegex = /-(test|spec)\.js$/; +} + var inspect = require('eyes').inspector({ stream: null, styles: { string: 'grey', regexp: 'grey' } @@ -165,7 +180,7 @@ if (! options.watch) { reporter.reset = function () { _reporter.reset && _reporter.reset() }; files = args.map(function (a) { - return path.join(process.cwd(), a.replace(/\.js$/, '')); + return path.join(process.cwd(), a.replace(fileExtRegex, '')); }); runSuites(importSuites(files), function (results) { @@ -249,7 +264,7 @@ if (! options.watch) { msg('watcher', 'detected change in', file); - file = (/-(test|spec)\.js$/.test(file) ? path.join(testFolder, file) + file = (specFileExtRegex.test(file) ? path.join(testFolder, file) : path.join(testFolder, file + '-' + testFolder)); try { @@ -259,8 +274,8 @@ if (! options.watch) { file = null; } - var files = (/-(test|spec)\.js$/.test(file) ? [file] : paths(testFolder)).map(function (p) { - return path.join(process.cwd(), p.replace(/\.js$/, '')); + var files = (specFileExtRegex.test(file) ? [file] : paths(testFolder)).map(function (p) { + return path.join(process.cwd(), p.replace(fileExtRegex, '')); }).map(function (p) { delete(require.main.moduleCache[p]); return p; @@ -351,7 +366,7 @@ function paths(dir) { if (file[0] == '.' || file === 'vendor') { return; - } else if (stat.isFile() && /\.js$/.test(file)) { + } else if (stat.isFile() && fileExtRegex.test(file)) { paths.push(path); } else if (stat.isDirectory()) { traverse(file, stack); From db57e70acec33df85480e4df0798a5e27dc858ce Mon Sep 17 00:00:00 2001 From: Travis Swicegood Date: Sat, 3 Jul 2010 03:19:30 +0800 Subject: [PATCH 288/409] Add ability to circumvent `addBatch` This makes it possible to skip the calls to `addBatch` by using optional parameters inside the `describe` method. This is a small change that allows the developer to focus on the code of the tests, thinking of it think of them in terms of data structure instead of Vows' API. The less that the Vows API is present in a test case, the less distracting it is. With this, you could choose to focus solely on the structure of the test and ignore Vows is present by importing only `describe`. This is an entirely backwards compatible change that does not effect any other code. It is fully tested inside the existing tests. --- lib/vows.js | 9 +++++++++ test/vows-test.js | 21 +++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/vows.js b/lib/vows.js index a46e9cf7..e7115eb3 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -177,6 +177,15 @@ vows.describe = function (subject) { this.options.Emitter.prototype.addVow = addVow; this.suites.push(suite); + // + // Add any additional arguments as batches if they're present + // + if (arguments.length > 1) { + for (var i = 1, l = arguments.length; i < l; ++i) { + suite.addBatch(arguments[i]); + } + } + return suite; }; diff --git a/test/vows-test.js b/test/vows-test.js index 638233bb..8eb808b5 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -281,3 +281,24 @@ vows.describe("Vows").addBatch({ topic: true, "should run last": function () {} } }).addBatch({}).export(module); + +vows.describe("Vows with a single batch", { + "This is a batch that's added as the optional parameter to describe()": { + topic: true, + "A batch added without calling addBatch()": function() {} + } +}).export(module); + + +vows.describe("Vows with multiple batches added as optional parameters", { + "First batch": { + topic: true, + "should be run first": function() {} + } +}, { + "Second batch": { + topic: true, + "should be run second": function() {} + } +}).export(module); + From e8cf93e3c03f2f0782a7f3c56bb72683324c8bec Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 3 Jul 2010 00:43:57 +0200 Subject: [PATCH 289/409] (minor) naming/style changes --- bin/vows | 28 +++++++++++++++------------- test/vows-test.js | 7 +++---- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/bin/vows b/bin/vows index a73e8a27..0184c7aa 100755 --- a/bin/vows +++ b/bin/vows @@ -6,18 +6,20 @@ var path = require('path'), events = require('events'); // -// Attempt to load Coffee-Script. If it's not available, continue on our +// Attempt to load Coffee-Script. If it's not available, continue on our // merry way, if it is available, set it up so we can include `*.coffee` // scripts and start searching for them. // +var fileExt, specFileExt; + try { var coffee = require('coffee-script'); - require.registerExtension('.coffee', function(content) { return coffee.compile(content); }); - var fileExtRegex = /\.(js|coffee)$/, - specFileExtRegex = /-(test|spec)\.(js|coffee)$/; -} catch (err) { - var fileExtRegex = /\.js$/, - specFileExtRegex = /-(test|spec)\.js$/; + require.registerExtension('.coffee', function (content) { return coffee.compile(content) }); + fileExt = /\.(js|coffee)$/; + specFileExt = /-(test|spec)\.(js|coffee)$/; +} catch (_) { + fileExt = /\.js$/; + specFileExt = /-(test|spec)\.js$/; } var inspect = require('eyes').inspector({ @@ -180,7 +182,7 @@ if (! options.watch) { reporter.reset = function () { _reporter.reset && _reporter.reset() }; files = args.map(function (a) { - return path.join(process.cwd(), a.replace(fileExtRegex, '')); + return path.join(process.cwd(), a.replace(fileExt, '')); }); runSuites(importSuites(files), function (results) { @@ -264,8 +266,8 @@ if (! options.watch) { msg('watcher', 'detected change in', file); - file = (specFileExtRegex.test(file) ? path.join(testFolder, file) - : path.join(testFolder, file + '-' + testFolder)); + file = (specFileExt.test(file) ? path.join(testFolder, file) + : path.join(testFolder, file + '-' + testFolder)); try { fs.statSync(file); @@ -274,8 +276,8 @@ if (! options.watch) { file = null; } - var files = (specFileExtRegex.test(file) ? [file] : paths(testFolder)).map(function (p) { - return path.join(process.cwd(), p.replace(fileExtRegex, '')); + var files = (specFileExt.test(file) ? [file] : paths(testFolder)).map(function (p) { + return path.join(process.cwd(), p.replace(fileExt, '')); }).map(function (p) { delete(require.main.moduleCache[p]); return p; @@ -366,7 +368,7 @@ function paths(dir) { if (file[0] == '.' || file === 'vendor') { return; - } else if (stat.isFile() && fileExtRegex.test(file)) { + } else if (stat.isFile() && fileExt.test(file)) { paths.push(path); } else if (stat.isDirectory()) { traverse(file, stack); diff --git a/test/vows-test.js b/test/vows-test.js index 8eb808b5..09e1935c 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -285,20 +285,19 @@ vows.describe("Vows").addBatch({ vows.describe("Vows with a single batch", { "This is a batch that's added as the optional parameter to describe()": { topic: true, - "A batch added without calling addBatch()": function() {} + "And a vow": function () {} } }).export(module); - vows.describe("Vows with multiple batches added as optional parameters", { "First batch": { topic: true, - "should be run first": function() {} + "should be run first": function () {} } }, { "Second batch": { topic: true, - "should be run second": function() {} + "should be run second": function () {} } }).export(module); From 7cdf94f3315a479b4b683c3ebd702bbb2b5b177d Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 10 Aug 2010 21:32:04 -0400 Subject: [PATCH 290/409] (dist) version bump, update package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5e9b996a..85c99c90 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "author" : "Alexis Sellier ", "contributors" : [], "dependencies" : {"eyes": ">=0.1.6"}, - "directories" : {"lib": "./lib/vows"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "version" : "0.4.6" + "directories" : { "test": "./test" }, + "version" : "0.5.0" } From c3ad80d64dc81f8cf3d422e1f5bdbd11aeb79106 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 24 Aug 2010 22:58:37 -0400 Subject: [PATCH 291/409] (new) basic teardown support --- lib/vows/suite.js | 11 +++++++++-- test/vows-test.js | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index c1b771d0..3726d2fa 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -66,7 +66,7 @@ this.Suite.prototype = new(function () { var match = false; var keys = Object.keys(tests).filter(function (k) { - return k !== 'topic'; + return k !== 'topic' && k !== 'teardown'; }); for (var i = 0, key; i < keys.length; i++) { @@ -163,7 +163,8 @@ this.Suite.prototype = new(function () { // Now run the tests, or sub-contexts Object.keys(ctx.tests).filter(function (k) { - return ctx.tests[k] && k !== 'topic' && !ctx.tests[k]._skip; + return ctx.tests[k] && k !== 'topic' && + k !== 'teardown' && !ctx.tests[k]._skip; }).forEach(function (item) { // Create a new evaluation context, // inheriting from the parent one. @@ -201,6 +202,12 @@ this.Suite.prototype = new(function () { } } }); + // Teardown + topic.addListener("success", function () { + if (ctx.tests.teardown) { + ctx.tests.teardown.apply(ctx.env, ctx.topics); + } + }); if (! ctx.tests._skip) { batch.remaining --; } diff --git a/test/vows-test.js b/test/vows-test.js index 09e1935c..097045a3 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -301,3 +301,23 @@ vows.describe("Vows with multiple batches added as optional parameters", { } }).export(module); +vows.describe("Vows with teardowns").addBatch({ + "A context": { + topic: function () { + return { flag: true }; + }, + "And a vow": function (topic) { + assert.isTrue(topic.flag); + }, + "And another vow": function (topic) { + assert.isTrue(topic.flag); + }, + "And a final vow": function (topic) { + assert.isTrue(topic.flag); + }, + teardown: function (topic) { + topic.flag = false; + } + } +}).export(module); + From 679e8a671d3c8d9364d430189264cb336ffc4ecf Mon Sep 17 00:00:00 2001 From: cloudhead Date: Tue, 24 Aug 2010 22:59:48 -0400 Subject: [PATCH 292/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 85c99c90..41bc5391 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test" }, - "version" : "0.5.0" + "version" : "0.5.1" } From 213d6cd03505e98e3a2bd75c4ac0d3aa766ad599 Mon Sep 17 00:00:00 2001 From: bnoguchi Date: Wed, 25 Aug 2010 07:55:26 +0800 Subject: [PATCH 293/409] Made a change that eliminates the following bug (see http://github.com/cloudhead/vows/issues#issue/16): Sometimes you want to test an object that inherits from EventEmitter. In this case, if you return said testable object as the topic, then the code hangs if the EventEmitter subclass instance that I'm testing doesn't emit "success" or "error." --- lib/vows/suite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 3726d2fa..a969f396 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -140,7 +140,7 @@ 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 instanceof events.EventEmitter)) { + if (! (topic && topic.constructor === events.EventEmitter)) { ctx.emitter = new(events.EventEmitter); if (! ctx._callback) { From 50077aab2bf7db6bdf112ea627dfb7d3c9bc57fc Mon Sep 17 00:00:00 2001 From: Yurii Rashkovskii Date: Wed, 15 Sep 2010 15:34:38 +0800 Subject: [PATCH 294/409] Pass suite reference to batches --- lib/vows/suite.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index a969f396..dbb11a61 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -107,6 +107,8 @@ this.Suite.prototype = new(function () { tests = batch.tests, promise = batch.promise = new(events.EventEmitter); + var that = this; + batch.status = 'begin'; // The test runner, it calls itself recursively, passing the @@ -169,6 +171,7 @@ this.Suite.prototype = new(function () { // Create a new evaluation context, // inheriting from the parent one. var env = Object.create(ctx.env); + env.suite = that; // Holds the current test or context var vow = Object.create({ From 61c01d966aad5a2c6066e05b8cb0f21137715829 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 13 Oct 2010 15:00:00 -0400 Subject: [PATCH 295/409] tell user if no tests were run. --- lib/vows/console.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/vows/console.js b/lib/vows/console.js index f87d25d5..631f9236 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -47,6 +47,10 @@ this.result = function (event) { var status = (event.errored && 'errored') || (event.broken && 'broken') || (event.honored && 'honored') || (event.pending && 'pending'); + if (event.total === 0) { + return [$("Could not find any tests to run.").bold.red]; + } + event.honored && result.push($(event.honored).bold + " honored"); event.broken && result.push($(event.broken).bold + " broken"); event.errored && result.push($(event.errored).bold + " errored"); From 349437b772168ae4930aa16c766c10ab7de6ceab Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 13 Oct 2010 15:01:04 -0400 Subject: [PATCH 296/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 41bc5391..9365db17 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test" }, - "version" : "0.5.1" + "version" : "0.5.2" } From a00c89de5d378d491502ee95867dae3275d30f3c Mon Sep 17 00:00:00 2001 From: Jeremiah Wuenschel Date: Sat, 20 Nov 2010 19:55:18 -0800 Subject: [PATCH 297/409] Updated teardown to execute after subcontexts complete --- lib/vows/suite.js | 19 +++++++++++++------ test/vows-test.js | 11 +++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index dbb11a61..f6eae109 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -30,6 +30,7 @@ this.Suite.prototype = new(function () { b.remaining = b._remaining; b.honored = b.broken = b.errored = b.total = b.pending = 0; b.vows.forEach(function (vow) { vow.status = null }); + b.teardowns = []; }); }; @@ -44,7 +45,8 @@ this.Suite.prototype = new(function () { broken: 0, errored: 0, pending: 0, - total: 0 + total: 0, + teardowns: [] }); return this; }; @@ -206,11 +208,9 @@ this.Suite.prototype = new(function () { } }); // Teardown - topic.addListener("success", function () { - if (ctx.tests.teardown) { - ctx.tests.teardown.apply(ctx.env, ctx.topics); - } - }); + if (ctx.tests.teardown) { + batch.teardowns.push(ctx); + } if (! ctx.tests._skip) { batch.remaining --; } @@ -302,6 +302,13 @@ this.tryEnd = function (batch) { (k in batch.suite.results) && (batch.suite.results[k] += batch[k]); }); + if(batch.teardowns) { + while(batch.teardowns.length > 0) { + var ctx = batch.teardowns.pop(); + ctx.tests.teardown.apply(ctx.env, ctx.topics); + } + } + batch.status = 'end'; batch.suite.report(['end']); batch.promise.emit('end', batch.honored, batch.broken, batch.errored, batch.pending); diff --git a/test/vows-test.js b/test/vows-test.js index 097045a3..a92f2f9f 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -317,6 +317,17 @@ vows.describe("Vows with teardowns").addBatch({ }, teardown: function (topic) { topic.flag = false; + }, + "with a subcontext" : { + topic: function (topic) { + var that = this; + process.nextTick(function () { + that.callback(null, topic); + }); + }, + "Waits for the subcontext before teardown" : function(topic) { + assert.isTrue(topic.flag); + } } } }).export(module); From 64760fe0bee22523a19d8150233f50775e224ca9 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 29 Dec 2010 12:19:54 +0100 Subject: [PATCH 298/409] (bin) fix exit status --- bin/vows | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/bin/vows b/bin/vows index 0184c7aa..8a350e8c 100755 --- a/bin/vows +++ b/bin/vows @@ -186,6 +186,8 @@ if (! options.watch) { }); runSuites(importSuites(files), function (results) { + var status = results.errored ? 2 : (results.broken ? 1 : 0); + !options.verbose && _reporter.print('\n'); msg('runner', 'finish'); _reporter.report(['finish', results], { @@ -193,9 +195,13 @@ if (! options.watch) { sys.print(str.replace(/^\n\n/, '\n')); } }); - process.stdout.addListener('drain', function () { - process.exit(results.honored + results.pending == results.total ? 0 : 1); - }); + if (process.stdout.write('')) { // Check if stdout is drained + process.exit(status); + } else { + process.stdout.on('drain', function () { + process.exit(status); + }); + } }); } else { // From 936e18ad36058c830de18c989d30136d6f5795c8 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 29 Dec 2010 12:20:20 +0100 Subject: [PATCH 299/409] fix some error messages --- lib/vows.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index e7115eb3..ec4e8817 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -77,7 +77,7 @@ function addVow(vow) { if (vow.callback.length >= 2 || !batch.suite.options.error) { runTest([err]); } else { - output('errored', { type: 'promise', error: err }); + output('errored', { type: 'promise', error: err.stack || err.message || JSON.stringify(err) }); } vows.tryEnd(batch); }); @@ -98,7 +98,7 @@ function addVow(vow) { if (e.name && e.name.match(/AssertionError/)) { output('broken', e.toString()); } else { - output('errored', e.stack || e.message || e.toString() || e); + output('errored', e.stack || e.message || e); } } } From 3d125534dbdbf55097e7f227935fe6a1a0c3150a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 29 Dec 2010 12:20:34 +0100 Subject: [PATCH 300/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9365db17..d877250b 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test" }, - "version" : "0.5.2" + "version" : "0.5.3" } From f0f823d70fc52d12dd7e3f53a2a2d6e3f7715cef Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 29 Dec 2010 12:38:02 +0100 Subject: [PATCH 301/409] (bin) fix auto-discover mode --- bin/vows | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bin/vows b/bin/vows index 8a350e8c..ec65092f 100755 --- a/bin/vows +++ b/bin/vows @@ -153,7 +153,9 @@ if (args.length === 0 || options.watch) { msg('bin', 'discovered', "./" + testFolder); if (args.length === 0) { - args = paths(testFolder); + args = paths(testFolder).filter(function (f) { + return new(RegExp)('-' + testFolder + '.(js|coffee)$').test(f); + }); if (options.watch) { args = args.concat(paths('lib'), From 3b1545a9b25acc7459d806ca9d69f77bffdd99d7 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 29 Dec 2010 12:41:43 +0100 Subject: [PATCH 302/409] (bin) update for node 0.2.5 --- bin/vows | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/bin/vows b/bin/vows index ec65092f..1112d34d 100755 --- a/bin/vows +++ b/bin/vows @@ -1,8 +1,8 @@ #!/usr/bin/env node var path = require('path'), - sys = require('sys'), fs = require('fs'), + util = require('util'), events = require('events'); // @@ -30,7 +30,7 @@ var inspect = require('eyes').inspector({ require.paths.unshift(path.join(__dirname, '..', 'lib')); var vows = require('vows'); -var console = require('vows/console'); +var cutils = require('vows/console'); var stylize = require('vows/console').stylize; var _reporter = require('vows/reporters/dot-matrix'), reporter = { name: _reporter.name, @@ -120,11 +120,11 @@ while (arg = argv.shift()) { options.error = false; break; case 'version': - sys.puts('vows ' + vows.version); + console.log('vows ' + vows.version); process.exit(0); case 'help': case 'h': - sys.puts(help); + console.log(help); process.exit(0); break; } @@ -194,7 +194,7 @@ if (! options.watch) { msg('runner', 'finish'); _reporter.report(['finish', results], { write: function (str) { - sys.print(str.replace(/^\n\n/, '\n')); + util.print(str.replace(/^\n\n/, '\n')); } }); if (process.stdout.write('')) { // Check if stdout is drained @@ -224,12 +224,12 @@ if (! options.watch) { colors = ['32m', '33m', '31m'], timer = setInterval(tick, 100); - process.addListener('uncaughtException', cleanup); - process.addListener('exit', cleanup); - process.addListener('SIGINT', function () { + process.on('uncaughtException', cleanup); + process.on('exit', cleanup); + process.on('SIGINT', function () { process.exit(0); }); - process.addListener('SIGQUIT', function () { + process.on('SIGQUIT', function () { changed(); }); @@ -257,7 +257,7 @@ if (! options.watch) { // // Utility functions // - function print(str) { sys.print(str) } + function print(str) { util.print(str) } function esc(str) { print("\x1b[" + str) } function eraseLine() { esc("0K") } function cursorRestore() { esc("0G") } @@ -295,7 +295,7 @@ if (! options.watch) { runSuites(importSuites(files), function (results) { delete(results.time); - print(console.result(results).join('') + '\n\n'); + print(cutils.result(results).join('') + '\n\n'); lastRun = new(Date); status = results; running --; @@ -390,7 +390,7 @@ function paths(dir) { function msg(cmd, subject, str, p) { if (options.verbose) { - sys[p ? 'print' : 'puts']( stylize('vows ', 'green') + util[p ? 'print' : 'puts']( stylize('vows ', 'green') + stylize(cmd, 'bold') + ' ' + subject + ' ' + (str ? (typeof(str) === 'string' ? str : inspect(str)) : '') @@ -399,7 +399,7 @@ function msg(cmd, subject, str, p) { } function abort(cmd, str) { - sys.puts(stylize('vows ', 'red') + stylize(cmd, 'bold') + ' ' + str); - sys.puts(stylize('vows ', 'red') + stylize(cmd, 'bold') + ' exiting'); + console.log(stylize('vows ', 'red') + stylize(cmd, 'bold') + ' ' + str); + console.log(stylize('vows ', 'red') + stylize(cmd, 'bold') + ' exiting'); process.exit(-1); } From 398443dab28d4ed3aa313bdf22915df097a9a451 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 29 Dec 2010 12:49:19 +0100 Subject: [PATCH 303/409] (minor) aliased export to exportTo --- lib/vows/suite.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index dbb11a61..ddb508f3 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -286,6 +286,9 @@ this.Suite.prototype = new(function () { return module.exports[this.subject] = this; } }; + this.exportTo = function (module, options) { // Alias, for JSLint + return this.export(module, options); + }; }); // From 8fb1a56faf0b180c421044691120fb213cbcae97 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 29 Jan 2011 14:40:54 +0000 Subject: [PATCH 304/409] support for multiple arguments passed to sub-topics --- lib/vows/suite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index ddb508f3..be41bdab 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -160,7 +160,7 @@ this.Suite.prototype = new(function () { // If we're using the parent topic, no need to // prepend it to the topics list, or we'll get // duplicates. - if (! old) ctx.topics.unshift(val); + if (! old) Array.prototype.unshift.apply(ctx.topics, arguments); }); // Now run the tests, or sub-contexts From 1c18b6643fe52f36ae78e22e4e83aecc3cbb7632 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 29 Jan 2011 14:41:22 +0000 Subject: [PATCH 305/409] remove listeners warning on topics --- lib/vows/suite.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index be41bdab..d5850610 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -162,6 +162,7 @@ this.Suite.prototype = new(function () { // duplicates. if (! old) Array.prototype.unshift.apply(ctx.topics, arguments); }); + topic.setMaxListeners(Infinity); // Now run the tests, or sub-contexts Object.keys(ctx.tests).filter(function (k) { From 3030206d6d0ba742e136f0db9a0d7062a9e82482 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 29 Jan 2011 14:41:35 +0000 Subject: [PATCH 306/409] (test) test for multiple arguments in callbacks --- test/vows-test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/vows-test.js b/test/vows-test.js index 097045a3..d35ee2c2 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -243,6 +243,25 @@ vows.describe("Vows").addBatch({ "should work the same as returning a value": function (res) { assert.equal(res, 'hello'); } + }, + "with multiple arguments": { + topic: function () { + this.callback(null, 1, 2, 3); + }, + "should pass them to the vow": function (e, a, b, c) { + assert.strictEqual(e, null); + assert.strictEqual(a, 1); + assert.strictEqual(b, 2); + assert.strictEqual(c, 3); + }, + "and a sub-topic": { + topic: function (a, b, c) { + return [a, b, c]; + }, + "should receive them too": function (val) { + assert.deepEqual(val, [1, 2, 3]); + } + } } } }).addBatch({ From eb4d50d9422ce11448d04aa4fedffe3ad0c9cc12 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 29 Jan 2011 14:44:49 +0000 Subject: [PATCH 307/409] support '.' in filenames --- lib/assert/error.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/assert/error.js b/lib/assert/error.js index b4829bda..af9885c1 100644 --- a/lib/assert/error.js +++ b/lib/assert/error.js @@ -3,7 +3,7 @@ var inspect = require('vows/console').inspect; require('assert').AssertionError.prototype.toString = function () { var that = this, - source = this.stack.match(/([a-zA-Z0-9_-]+\.js)(:\d+):\d+/); + source = this.stack.match(/([a-zA-Z0-9._-]+\.js)(:\d+):\d+/); function parse(str) { return str.replace(/{actual}/g, inspect(that.actual)). From 4361e42057cd76bd78a1b3d346b587a8ac10066e Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 29 Jan 2011 15:02:03 +0000 Subject: [PATCH 308/409] use 'on' instead of 'addListener' --- lib/vows.js | 8 ++++---- lib/vows/suite.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index ec4e8817..73cfada5 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -54,7 +54,7 @@ var Suite = require('vows/suite').Suite; // // This function gets added to events.EventEmitter.prototype, by default. -// It's essentially a wrapper around `addListener`, which adds all the specification +// It's essentially a wrapper around `on`, which adds all the specification // goodness. // function addVow(vow) { @@ -63,7 +63,7 @@ function addVow(vow) { batch.total ++; batch.vows.push(vow); - return this.addListener("success", function () { + return this.on("success", function () { var args = Array.prototype.slice.call(arguments); // If the callback is expecting two or more arguments, // pass the error as the first (null) and the result after. @@ -73,7 +73,7 @@ function addVow(vow) { runTest(args); vows.tryEnd(batch); - }).addListener("error", function (err) { + }).on("error", function (err) { if (vow.callback.length >= 2 || !batch.suite.options.error) { runTest([err]); } else { @@ -124,7 +124,7 @@ function addVow(vow) { // On exit, check that all promises have been fired. // If not, report an error message. // -process.addListener('exit', function () { +process.on('exit', function () { var results = { honored: 0, broken: 0, errored: 0, pending: 0, total: 0 }, failure; vows.suites.forEach(function (s) { diff --git a/lib/vows/suite.js b/lib/vows/suite.js index d5850610..550bfccd 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -153,7 +153,7 @@ this.Suite.prototype = new(function () { topic = ctx.emitter; } - topic.addListener('success', function (val) { + topic.on('success', 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. @@ -196,7 +196,7 @@ this.Suite.prototype = new(function () { // before calling the inner context. Else, just run the inner context // synchronously. if (topic) { - topic.addListener("success", function (ctx) { + topic.on("success", function (ctx) { return function (val) { return run(new(Context)(vow, ctx, env), lastTopic); }; @@ -207,7 +207,7 @@ this.Suite.prototype = new(function () { } }); // Teardown - topic.addListener("success", function () { + topic.on("success", function () { if (ctx.tests.teardown) { ctx.tests.teardown.apply(ctx.env, ctx.topics); } @@ -257,7 +257,7 @@ this.Suite.prototype = new(function () { if (batch.remaining === 0) { run(batches); } else { - that.runBatch(batch).addListener('end', function () { + that.runBatch(batch).on('end', function () { run(batches); }); } From c2633dcb76ae23dde99fa6ae03ce7f618c6080f2 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sat, 29 Jan 2011 15:02:20 +0000 Subject: [PATCH 309/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d877250b..6ef37c0d 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,5 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test" }, - "version" : "0.5.3" + "version" : "0.5.4" } From d88924d2dc4d3deb537e24f8458d78357dd87ffe Mon Sep 17 00:00:00 2001 From: cloudhead Date: Sun, 30 Jan 2011 17:26:51 +0000 Subject: [PATCH 310/409] (dist) update package.json to include node version --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 6ef37c0d..c9e809b5 100644 --- a/package.json +++ b/package.json @@ -9,5 +9,6 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test" }, - "version" : "0.5.4" + "version" : "0.5.4", + "engines" : {"node": ">=0.3.6"} } From d6ba141e570afceaa4c28f6c4d9ce972c2776ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Th=C3=B8gersen?= Date: Mon, 31 Jan 2011 21:46:14 +0800 Subject: [PATCH 311/409] added simple xunit support, so vows can be used together with Hudson --- bin/vows | 4 ++ lib/vows/reporters/xunit.js | 90 +++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 lib/vows/reporters/xunit.js diff --git a/bin/vows b/bin/vows index 1112d34d..468eae9e 100755 --- a/bin/vows +++ b/bin/vows @@ -48,6 +48,7 @@ var help = [ " --json Use JSON reporter", " --spec Use Spec reporter", " --dot-matrix Use Dot-Matrix reporter", + " --xunit Use xUnit reporter", //" --no-color Don't use terminal colors", " --version Show version", " -h, --help You're staring at it" @@ -105,6 +106,9 @@ while (arg = argv.shift()) { case 's': _reporter = require('vows/reporters/silent'); break; + case 'xunit': + _reporter = require('vows/reporters/xunit'); + break; case 'verbose': case 'v': options.verbose = true; diff --git a/lib/vows/reporters/xunit.js b/lib/vows/reporters/xunit.js new file mode 100644 index 00000000..411a9481 --- /dev/null +++ b/lib/vows/reporters/xunit.js @@ -0,0 +1,90 @@ +// xunit outoput for vows, so we can run things under hudson +// +// The translation to xunit is simple. Most likely more tags/attributes can be +// added, see: http://ant.1045680.n5.nabble.com/schema-for-junit-xml-output-td1375274.html +// + +var puts = require('util').puts; + +var buffer = [], + curSubject = null; + +function xmlEnc(value) { + return !value ? value : String(value).replace(/&/g, "&") + .replace(/>/g, ">") + .replace(/'; +} + +function cdata(data) { + return ''; +} + +this.name = 'xunit'; +this.report = function (data) { + var event = data[1]; + + switch (data[0]) { + case 'subject': + curSubject = event; + break; + case 'context': + break; + case 'vow': + switch (event.status) { + case 'honored': + buffer.push(tag('testcase', {classname: curSubject, name: event.context + ': ' + event.title}, true)); + break; + case 'broken': + var err = tag('error', {type: 'vows.event.broken', message: 'Broken test'}, false, cdata(event.exception)); + buffer.push(tag('testcase', {classname: curSubject, name: event.context + ': ' + event.title}, false, err)); + break; + case 'errored': + var skip = tag('skipped', {type: 'vows.event.errored', message: 'Errored test'}, false, cdata(event.exception)); + buffer.push(tag('testcase', {classname: curSubject, name: event.context + ': ' + event.title}, false, skip)); + break; + case 'pending': + // nop + break; + } + break; + case 'end': + buffer.push(end('testcase')); + break; + case 'finish': + buffer.unshift(tag('testsuite', {name: 'Vows test', tests: event.total, timestamp: (new Date()).toUTCString(), errors: event.errored, failures: event.broken, skip: event.pending, time: event.time})); + buffer.push(end('testsuite')); + puts(buffer.join('\n')); + break; + case 'error': + break; + } +}; + +this.print = function (str) { }; From f1ff2c17f348f6859e7168d467ae889656da1ac6 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 31 Jan 2011 13:01:21 +0000 Subject: [PATCH 312/409] preserve 0.2.6 compatibility --- lib/vows/suite.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 550bfccd..076d65e2 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -162,7 +162,7 @@ this.Suite.prototype = new(function () { // duplicates. if (! old) Array.prototype.unshift.apply(ctx.topics, arguments); }); - topic.setMaxListeners(Infinity); + if (topic.setMaxListeners) { topic.setMaxListeners(Infinity) } // Now run the tests, or sub-contexts Object.keys(ctx.tests).filter(function (k) { diff --git a/package.json b/package.json index c9e809b5..13e654fd 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test" }, - "version" : "0.5.4", + "version" : "0.5.5", "engines" : {"node": ">=0.3.6"} } From 0b54a986d45940df2cbd9fb36f268b4e94222858 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 31 Jan 2011 13:17:22 +0000 Subject: [PATCH 313/409] (dist) revert to node 0.2.6, version bump to 0.5.6 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 13e654fd..081a30fa 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test" }, - "version" : "0.5.5", - "engines" : {"node": ">=0.3.6"} + "version" : "0.5.6", + "engines" : {"node": ">=0.2.6"} } From 402e309babbcf8d2dcbbf28e9d345b0293319471 Mon Sep 17 00:00:00 2001 From: Matteo Collina Date: Wed, 22 Dec 2010 17:23:07 +0800 Subject: [PATCH 314/409] Fixed watch mode. --- bin/vows | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/bin/vows b/bin/vows index 468eae9e..bf5b7491 100755 --- a/bin/vows +++ b/bin/vows @@ -289,10 +289,15 @@ if (! options.watch) { } var files = (specFileExt.test(file) ? [file] : paths(testFolder)).map(function (p) { - return path.join(process.cwd(), p.replace(fileExt, '')); + return path.join(process.cwd(), p); }).map(function (p) { - delete(require.main.moduleCache[p]); + var cache = (require.main.moduleCache) ? require.main.moduleCache : require.cache; + if(cache[p]) { + delete(cache[p]); + } return p; + }).map(function (p) { + return p.replace(fileExt, ''); }); running ++; From 93da10b728bb20f62248eb5e469980320b924181 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Mon, 31 Jan 2011 13:28:52 +0000 Subject: [PATCH 315/409] (minor) cleanup --- bin/vows | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bin/vows b/bin/vows index bf5b7491..e1b9fdb9 100755 --- a/bin/vows +++ b/bin/vows @@ -291,10 +291,8 @@ if (! options.watch) { var files = (specFileExt.test(file) ? [file] : paths(testFolder)).map(function (p) { return path.join(process.cwd(), p); }).map(function (p) { - var cache = (require.main.moduleCache) ? require.main.moduleCache : require.cache; - if(cache[p]) { - delete(cache[p]); - } + var cache = require.main.moduleCache || require.cache; + if (cache[p]) { delete(cache[p]) } return p; }).map(function (p) { return p.replace(fileExt, ''); From 1ddf5b1a4100abaaea6281018169fdc9b8f70469 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sun, 20 Feb 2011 21:56:34 -0500 Subject: [PATCH 316/409] (api) support for /.test.js$/ filenames --- bin/vows | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/vows b/bin/vows index e1b9fdb9..4b4d2796 100755 --- a/bin/vows +++ b/bin/vows @@ -16,10 +16,10 @@ try { var coffee = require('coffee-script'); require.registerExtension('.coffee', function (content) { return coffee.compile(content) }); fileExt = /\.(js|coffee)$/; - specFileExt = /-(test|spec)\.(js|coffee)$/; + specFileExt = /[.-](test|spec)\.(js|coffee)$/; } catch (_) { fileExt = /\.js$/; - specFileExt = /-(test|spec)\.js$/; + specFileExt = /[.-](test|spec)\.js$/; } var inspect = require('eyes').inspector({ From 332b5224ed60ef9a9bb3e5cda4899c6f55661b4c Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sun, 20 Feb 2011 21:57:03 -0500 Subject: [PATCH 317/409] include test filename in some error reports --- bin/vows | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/vows b/bin/vows index 4b4d2796..5386225d 100755 --- a/bin/vows +++ b/bin/vows @@ -169,13 +169,13 @@ if (args.length === 0 || options.watch) { } if (! options.watch) { - reporter.report = function (data) { + reporter.report = function (data, filename) { switch (data[0]) { case 'subject': case 'vow': case 'context': case 'error': - _reporter.report(data); + _reporter.report(data, filename); break; case 'end': (options.verbose || _reporter.name === 'json') && _reporter.report(data); @@ -186,6 +186,7 @@ if (! options.watch) { } }; reporter.reset = function () { _reporter.reset && _reporter.reset() }; + reporter.print = _reporter.print; files = args.map(function (a) { return path.join(process.cwd(), a.replace(fileExt, '')); @@ -360,6 +361,7 @@ function importSuites(files) { return files.reduce(function (suites, f) { var obj = require(f); return suites.concat(Object.keys(obj).map(function (s) { + obj[s]._filename = f.replace(process.cwd() + '/', '') + '.js'; return obj[s]; })); }, []) From 7874f541e83d07df77a8c2997f133f55581513e2 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sun, 20 Feb 2011 21:57:37 -0500 Subject: [PATCH 318/409] improve async error report --- lib/vows.js | 3 ++- lib/vows/console.js | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 73cfada5..446284b6 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -129,6 +129,7 @@ process.on('exit', function () { vows.suites.forEach(function (s) { if ((s.results.total > 0) && (s.results.time === null)) { + s.reporter.print('\n\n'); s.reporter.report(['error', { error: "Asynchronous Error", suite: s }]); } s.batches.forEach(function (b) { @@ -146,7 +147,7 @@ process.on('exit', function () { unFired.forEach(function (title) { s.reporter.report(['error', { - error: "not fired!", + error: "callback not fired", context: title, batch: b, suite: s diff --git a/lib/vows/console.js b/lib/vows/console.js index 631f9236..391e6bcc 100644 --- a/lib/vows/console.js +++ b/lib/vows/console.js @@ -83,6 +83,11 @@ this.inspect = function inspect(val) { }; this.error = function (obj) { - return '✗ ' + $('Errored ').red + '» ' + $(obj.suite.subject).bold + ': ' - + $('' + $(obj.context).italic + ' ∙ ') + $(obj.error).red; + var string = '✗ ' + $('Errored ').red + '» '; + string += $(obj.error).red.bold + '\n'; + string += (obj.context ? ' in ' + $(obj.context).red + '\n': ''); + string += ' in ' + $(obj.suite.subject).red + '\n'; + string += ' in ' + $(obj.suite._filename).red; + + return string; }; From 7b20446b30da3926a8ccf54aa70dbe3a4c5c9b7b Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sun, 20 Feb 2011 21:59:02 -0500 Subject: [PATCH 319/409] support for this.callback.call({}, ...) --- lib/vows.js | 8 ++++---- lib/vows/context.js | 8 ++++++-- test/vows-test.js | 8 ++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 446284b6..5710bc8b 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -70,19 +70,19 @@ function addVow(vow) { if (vow.callback.length >= 2 && batch.suite.options.error) { args.unshift(null); } - runTest(args); + runTest(args, this.ctx); vows.tryEnd(batch); }).on("error", function (err) { if (vow.callback.length >= 2 || !batch.suite.options.error) { - runTest([err]); + runTest([err], this.ctx); } else { output('errored', { type: 'promise', error: err.stack || err.message || JSON.stringify(err) }); } vows.tryEnd(batch); }); - function runTest(args) { + function runTest(args, ctx) { var topic, status; if (vow.callback instanceof String) { @@ -92,7 +92,7 @@ function addVow(vow) { // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { - vow.callback.apply(vow.binding || null, args); + vow.callback.apply(ctx || vow.binding || null, args); output('honored'); } catch (e) { if (e.name && e.name.match(/AssertionError/)) { diff --git a/lib/vows/context.js b/lib/vows/context.js index 4fd8e57b..74307479 100644 --- a/lib/vows/context.js +++ b/lib/vows/context.js @@ -9,7 +9,9 @@ this.Context = function (vow, ctx, env) { this.env.context = this; this.env.callback = function (/* arguments */) { + var ctx = this; var args = Array.prototype.slice.call(arguments); + var emit = (function (args) { // // Convert callback-style results into events. @@ -17,20 +19,22 @@ this.Context = function (vow, ctx, env) { if (vow.batch.suite.options.error) { return function () { var e = args.shift(); + that.emitter.ctx = ctx; // We handle a special case, where the first argument is a // boolean, in which case we treat it as a result, and not // an error. This is useful for `path.exists` and other // functions like it, which only pass a single boolean // parameter instead of the more common (error, result) pair. if (typeof(e) === 'boolean' && args.length === 0) { - that.emitter.emit('success', e); + that.emitter.emit.call(that.emitter, 'success', e); } else { - if (e) { that.emitter.emit('error', e) } + if (e) { that.emitter.emit.call(that.emitter, 'error', e) } else { that.emitter.emit.apply(that.emitter, ['success'].concat(args)) } } }; } else { return function () { + that.emitter.ctx = ctx; that.emitter.emit.apply(that.emitter, ['success'].concat(args)); }; } diff --git a/test/vows-test.js b/test/vows-test.js index fae4bd12..19a63aeb 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -244,6 +244,14 @@ vows.describe("Vows").addBatch({ assert.equal(res, 'hello'); } }, + "using this.callback with a user context": { + topic: function () { + this.callback.call({ boo: true }, null, 'hello'); + }, + "should work the same as returning a value": function (res) { + assert.isTrue(this.boo); + } + }, "with multiple arguments": { topic: function () { this.callback(null, 1, 2, 3); From f700eed02d7259fb8452011a4e27952d22be522d Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sun, 20 Feb 2011 21:59:53 -0500 Subject: [PATCH 320/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 081a30fa..07bcdb36 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test" }, - "version" : "0.5.6", + "version" : "0.5.7", "engines" : {"node": ">=0.2.6"} } From 3291d77ae6a662f24f603a103ca63de393c3edc5 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sun, 20 Feb 2011 22:14:21 -0500 Subject: [PATCH 321/409] fix vow context when global --- lib/vows.js | 2 +- test/vows-test.js | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/vows.js b/lib/vows.js index 5710bc8b..6fc8266f 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -92,7 +92,7 @@ function addVow(vow) { // Run the test, and try to catch `AssertionError`s and other exceptions; // increment counters accordingly. try { - vow.callback.apply(ctx || vow.binding || null, args); + vow.callback.apply(ctx === global || !ctx ? vow.binding : ctx, args); output('honored'); } catch (e) { if (e.name && e.name.match(/AssertionError/)) { diff --git a/test/vows-test.js b/test/vows-test.js index 19a63aeb..3fe6f908 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -248,7 +248,19 @@ vows.describe("Vows").addBatch({ topic: function () { this.callback.call({ boo: true }, null, 'hello'); }, - "should work the same as returning a value": function (res) { + "should give access to the user context": function (res) { + assert.isTrue(this.boo); + } + }, + "passing this.callback to a function": { + topic: function () { + this.boo = true; + var async = function (callback) { + callback(null); + }; + async(this.callback); + }, + "should give access to the topic context": function (e, t, tt) { assert.isTrue(this.boo); } }, From 697ada45eea28da80db6b5c3a3e20799ff764253 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sun, 20 Feb 2011 22:15:05 -0500 Subject: [PATCH 322/409] (minor test) cleanup --- test/vows-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/vows-test.js b/test/vows-test.js index 3fe6f908..60b71d97 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -260,7 +260,7 @@ vows.describe("Vows").addBatch({ }; async(this.callback); }, - "should give access to the topic context": function (e, t, tt) { + "should give access to the topic context": function () { assert.isTrue(this.boo); } }, From 381c0a38f961a28e72d50c0f8fa6368e08c8f778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20Hietam=C3=A4ki?= Date: Sun, 27 Feb 2011 00:40:14 +0800 Subject: [PATCH 323/409] Fixed CoffeeScript support on Node 0.3+ --- bin/vows | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bin/vows b/bin/vows index 5386225d..7013b595 100755 --- a/bin/vows +++ b/bin/vows @@ -14,7 +14,14 @@ var fileExt, specFileExt; try { var coffee = require('coffee-script'); - require.registerExtension('.coffee', function (content) { return coffee.compile(content) }); + if(require.extensions) { + require.extensions['.coffee'] = function(module, filename) { + var content = coffee.compile(fs.readFileSync(filename, 'utf8')); + return module._compile(content, filename); + }; + } else { + require.registerExtension('.coffee', function (content) { return coffee.compile(content) }); + } fileExt = /\.(js|coffee)$/; specFileExt = /[.-](test|spec)\.(js|coffee)$/; } catch (_) { From 72b9299662d67572b23e5b7b54e2ecb9e8c54b48 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sat, 12 Mar 2011 22:28:35 -0500 Subject: [PATCH 324/409] (style) ws --- bin/vows | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/vows b/bin/vows index 7013b595..1fbc2806 100755 --- a/bin/vows +++ b/bin/vows @@ -14,8 +14,8 @@ var fileExt, specFileExt; try { var coffee = require('coffee-script'); - if(require.extensions) { - require.extensions['.coffee'] = function(module, filename) { + if (require.extensions) { + require.extensions['.coffee'] = function (module, filename) { var content = coffee.compile(fs.readFileSync(filename, 'utf8')); return module._compile(content, filename); }; From 7c9b21d235b158be31fe94d2b0992b960bf64139 Mon Sep 17 00:00:00 2001 From: Alexis Sellier Date: Sat, 12 Mar 2011 22:29:34 -0500 Subject: [PATCH 325/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 07bcdb36..eb4e1065 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test" }, - "version" : "0.5.7", + "version" : "0.5.8", "engines" : {"node": ">=0.2.6"} } From 27f683ad77f6d5ac23555ec8bd612ff7a4539682 Mon Sep 17 00:00:00 2001 From: mynyml Date: Sun, 24 Apr 2011 15:52:45 -0700 Subject: [PATCH 326/409] added assert.deepInclude --- lib/assert/macros.js | 14 +++++++- lib/assert/utils.js | 77 ++++++++++++++++++++++++++++++++++++++++++++ test/assert-test.js | 5 +++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 lib/assert/utils.js diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 548f765b..525a68d3 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -1,4 +1,5 @@ -var assert = require('assert'); +var assert = require('assert'), + utils = require('assert/utils'); var messages = { 'equal' : "expected {expected},\n\tgot\t {actual} ({operator})", @@ -77,6 +78,16 @@ assert.include = function (actual, expected, message) { }; assert.includes = assert.include; +assert.deepInclude = function (actual, expected, message) { + if (!isArray(actual)) { + return assert.include(actual, expected, message); + } + if (!actual.some(function (item) { return utils.deepEqual(item, expected) })) { + assert.fail(actual, expected, message || "expected {actual} to include {expected}", "include", assert.deepInclude); + } +}; +assert.deepIncludes = assert.deepInclude; + // // Length // @@ -146,6 +157,7 @@ assert.instanceOf = function (actual, expected, message) { // // Utility functions // + function assertTypeOf(actual, expected, message, caller) { if (typeOf(actual) !== expected) { assert.fail(actual, expected, message || "expected {actual} to be of type {expected}", "typeOf", caller); diff --git a/lib/assert/utils.js b/lib/assert/utils.js new file mode 100644 index 00000000..dccd0f65 --- /dev/null +++ b/lib/assert/utils.js @@ -0,0 +1,77 @@ + +// Taken from node/lib/assert.js +exports.deepEqual = function (actual, expected) { + if (actual === expected) { + return true; + + } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + return true; + + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + } else if (typeof actual != 'object' && typeof expected != 'object') { + return actual == expected; + + } else { + return objEquiv(actual, expected); + } +} + +// Taken from node/lib/assert.js +exports.notDeepEqual = function (actual, expected, message) { + if (exports.deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +} + +// Taken from node/lib/assert.js +function isUndefinedOrNull(value) { + return value === null || value === undefined; +} + +// Taken from node/lib/assert.js +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +// Taken from node/lib/assert.js +function objEquiv(a, b) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + if (a.prototype !== b.prototype) return false; + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return exports.deepEqual(a, b); + } + try { + var ka = Object.keys(a), + kb = Object.keys(b), + key, i; + } catch (e) { + return false; + } + if (ka.length != kb.length) + return false; + ka.sort(); + kb.sort(); + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!exports.deepEqual(a[key], b[key])) return false; + } + return true; +} + diff --git a/test/assert-test.js b/test/assert-test.js index e3765052..3f49fec9 100644 --- a/test/assert-test.js +++ b/test/assert-test.js @@ -21,6 +21,11 @@ vows.describe('vows/assert').addBatch({ assert.include([0, 42, 0], 42); assert.include({goo:true}, 'goo'); }, + "`deepInclude`": function (assert) { + assert.deepInclude([{a:'b'},{c:'d'}], {a:'b'}); + assert.deepInclude("hello world", "world"); + assert.deepInclude({goo:true}, 'goo'); + }, "`typeOf`": function (assert) { assert.typeOf('goo', 'string'); assert.typeOf(42, 'number'); From 342dbae65faa024836609908c8634817c4624305 Mon Sep 17 00:00:00 2001 From: mynyml Date: Mon, 25 Apr 2011 06:52:45 +0800 Subject: [PATCH 327/409] added assert.deepInclude --- lib/assert/macros.js | 14 +++++++- lib/assert/utils.js | 77 ++++++++++++++++++++++++++++++++++++++++++++ test/assert-test.js | 5 +++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 lib/assert/utils.js diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 548f765b..525a68d3 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -1,4 +1,5 @@ -var assert = require('assert'); +var assert = require('assert'), + utils = require('assert/utils'); var messages = { 'equal' : "expected {expected},\n\tgot\t {actual} ({operator})", @@ -77,6 +78,16 @@ assert.include = function (actual, expected, message) { }; assert.includes = assert.include; +assert.deepInclude = function (actual, expected, message) { + if (!isArray(actual)) { + return assert.include(actual, expected, message); + } + if (!actual.some(function (item) { return utils.deepEqual(item, expected) })) { + assert.fail(actual, expected, message || "expected {actual} to include {expected}", "include", assert.deepInclude); + } +}; +assert.deepIncludes = assert.deepInclude; + // // Length // @@ -146,6 +157,7 @@ assert.instanceOf = function (actual, expected, message) { // // Utility functions // + function assertTypeOf(actual, expected, message, caller) { if (typeOf(actual) !== expected) { assert.fail(actual, expected, message || "expected {actual} to be of type {expected}", "typeOf", caller); diff --git a/lib/assert/utils.js b/lib/assert/utils.js new file mode 100644 index 00000000..dccd0f65 --- /dev/null +++ b/lib/assert/utils.js @@ -0,0 +1,77 @@ + +// Taken from node/lib/assert.js +exports.deepEqual = function (actual, expected) { + if (actual === expected) { + return true; + + } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + return true; + + } else if (actual instanceof Date && expected instanceof Date) { + return actual.getTime() === expected.getTime(); + + } else if (typeof actual != 'object' && typeof expected != 'object') { + return actual == expected; + + } else { + return objEquiv(actual, expected); + } +} + +// Taken from node/lib/assert.js +exports.notDeepEqual = function (actual, expected, message) { + if (exports.deepEqual(actual, expected)) { + fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); + } +} + +// Taken from node/lib/assert.js +function isUndefinedOrNull(value) { + return value === null || value === undefined; +} + +// Taken from node/lib/assert.js +function isArguments(object) { + return Object.prototype.toString.call(object) == '[object Arguments]'; +} + +// Taken from node/lib/assert.js +function objEquiv(a, b) { + if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) + return false; + if (a.prototype !== b.prototype) return false; + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return exports.deepEqual(a, b); + } + try { + var ka = Object.keys(a), + kb = Object.keys(b), + key, i; + } catch (e) { + return false; + } + if (ka.length != kb.length) + return false; + ka.sort(); + kb.sort(); + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) + return false; + } + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!exports.deepEqual(a[key], b[key])) return false; + } + return true; +} + diff --git a/test/assert-test.js b/test/assert-test.js index e3765052..3f49fec9 100644 --- a/test/assert-test.js +++ b/test/assert-test.js @@ -21,6 +21,11 @@ vows.describe('vows/assert').addBatch({ assert.include([0, 42, 0], 42); assert.include({goo:true}, 'goo'); }, + "`deepInclude`": function (assert) { + assert.deepInclude([{a:'b'},{c:'d'}], {a:'b'}); + assert.deepInclude("hello world", "world"); + assert.deepInclude({goo:true}, 'goo'); + }, "`typeOf`": function (assert) { assert.typeOf('goo', 'string'); assert.typeOf(42, 'number'); From 41442710d1267868eb60809c5cd4a49f3f63275e Mon Sep 17 00:00:00 2001 From: Joshua Kehn Date: Wed, 8 Jun 2011 00:09:01 -0400 Subject: [PATCH 328/409] Implemented isBoolean and tests to match --- .gitignore | 1 + lib/assert/macros.js | 5 +++++ test/assert-test.js | 5 +++++ 3 files changed, 11 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 525a68d3..a5d70795 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -119,6 +119,11 @@ assert.isNumber = function (actual, message) { assertTypeOf(actual, 'number', message || "expected {actual} to be a Number", assert.isNumber); } }; +assert.isBoolean = function (actual, message) { + if (actual !== true && actual !== false) { + assert.fail(actual, 'NaN', message || "expected {actual} to be a Boolean", "===", assert.isBoolean); + } +}; assert.isNaN = function (actual, message) { if (actual === actual) { assert.fail(actual, 'NaN', message || "expected {actual} to be NaN", "===", assert.isNaN); diff --git a/test/assert-test.js b/test/assert-test.js index 3f49fec9..0183276c 100644 --- a/test/assert-test.js +++ b/test/assert-test.js @@ -51,6 +51,11 @@ vows.describe('vows/assert').addBatch({ "`isNumber`": function (assert) { assert.isNumber(0); }, + "`isBoolean`": function (assert){ + assert.isBoolean(true); + assert.isBoolean(false); + assertError(assert.isBoolean, 0); + }, "`isNan`": function (assert) { assert.isNaN(0/0); }, From db608e2e49461dd93c9a34b0cb4388200352b9e0 Mon Sep 17 00:00:00 2001 From: Joshua Kehn Date: Wed, 8 Jun 2011 00:10:53 -0400 Subject: [PATCH 329/409] NaN !== Boolean --- lib/assert/macros.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/assert/macros.js b/lib/assert/macros.js index a5d70795..27a442a5 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -121,7 +121,7 @@ assert.isNumber = function (actual, message) { }; assert.isBoolean = function (actual, message) { if (actual !== true && actual !== false) { - assert.fail(actual, 'NaN', message || "expected {actual} to be a Boolean", "===", assert.isBoolean); + assert.fail(actual, 'boolean', message || "expected {actual} to be a Boolean", "===", assert.isBoolean); } }; assert.isNaN = function (actual, message) { From bc868fae6a26628b5ca816c287b4413fd6c259a3 Mon Sep 17 00:00:00 2001 From: mynyml Date: Tue, 28 Jun 2011 23:44:24 +0800 Subject: [PATCH 330/409] (new) added assert.inDelta --- lib/assert/macros.js | 8 ++++++++ test/assert-test.js | 14 +++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 27a442a5..4de0f2ec 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -61,6 +61,14 @@ assert.lesser = function (actual, expected, message) { } }; +assert.inDelta = function (actual, expected, delta, message) { + lower = expected - delta; + upper = expected + delta; + if (actual < lower || actual > upper) { + assert.fail(actual, expected, message || "expected {actual} to be in within *"+ delta.toString() +"* of {expected}", null, assert.inDelta); + } +}; + // // Inclusion // diff --git a/test/assert-test.js b/test/assert-test.js index 0183276c..e5e9ac84 100644 --- a/test/assert-test.js +++ b/test/assert-test.js @@ -90,6 +90,13 @@ vows.describe('vows/assert').addBatch({ assert.greater(5, 4); assert.lesser(4, 5); }, + "`inDelta`": function (assert) { + assert.inDelta(42, 40, 5); + assert.inDelta(42, 40, 2); + assert.inDelta(42, 42, 0); + assert.inDelta(3.1, 3.0, 0.2); + assertError(assert.inDelta, [42, 40, 1]); + }, "`isEmpty`": function (assert) { assert.isEmpty({}); assert.isEmpty([]); @@ -98,13 +105,14 @@ vows.describe('vows/assert').addBatch({ } }).export(module); -function assertError(assertion, value, fail) { +function assertError(assertion, args, fail) { + if (!Array.isArray(args)) { args = [args]; } try { - assertion(value); + assertion.apply(null, args); fail = true; } catch (e) {/* Success */} - fail && assert.fail(value, assert.AssertionError, + fail && assert.fail(args.join(' '), assert.AssertionError, "expected an AssertionError for {actual}", "assertError", assertError); } From 3d400b84730d96000ea0ebd08a8ebfd19c208d06 Mon Sep 17 00:00:00 2001 From: Jerry Sievert Date: Sat, 16 Jul 2011 19:42:00 -0700 Subject: [PATCH 331/409] adds coverage map functionality adds code coverage map functionality via jscoverage. --cover-plain - outputs any coverage output in plain test --cover-html - outputs any coverage output to a local copy of coverage.html output occurs if jscoverage is encountered in source files during run of vows. --- bin/vows | 17 ++++++- lib/vows/coverage/file.js | 29 ++++++++++++ .../coverage/fragments/coverage-foot.html | 2 + .../coverage/fragments/coverage-head.html | 38 ++++++++++++++++ lib/vows/coverage/report-html.js | 44 +++++++++++++++++++ lib/vows/coverage/report-plain.js | 38 ++++++++++++++++ 6 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 lib/vows/coverage/file.js create mode 100644 lib/vows/coverage/fragments/coverage-foot.html create mode 100644 lib/vows/coverage/fragments/coverage-head.html create mode 100644 lib/vows/coverage/report-html.js create mode 100644 lib/vows/coverage/report-plain.js diff --git a/bin/vows b/bin/vows index 1fbc2806..5945a3c2 100755 --- a/bin/vows +++ b/bin/vows @@ -42,6 +42,7 @@ var stylize = require('vows/console').stylize; var _reporter = require('vows/reporters/dot-matrix'), reporter = { name: _reporter.name, }; +var _coverage; var help = [ "usage: vows [FILE, ...] [options]", @@ -56,6 +57,8 @@ var help = [ " --spec Use Spec reporter", " --dot-matrix Use Dot-Matrix reporter", " --xunit Use xUnit reporter", + " --cover-plain Print plain coverage map if detected", + " --cover-html Write coverage map to \"coverage.html\"", //" --no-color Don't use terminal colors", " --version Show version", " -h, --help You're staring at it" @@ -64,7 +67,8 @@ var help = [ var options = { reporter: reporter, matcher: /.*/, - watch: false + watch: false, + coverage: false }; var files = []; @@ -116,6 +120,14 @@ while (arg = argv.shift()) { case 'xunit': _reporter = require('vows/reporters/xunit'); break; + case 'cover-plain': + options.coverage = true; + _coverage = require('vows/coverage/report-plain'); + break; + case 'cover-html': + options.coverage = true; + _coverage = require('vows/coverage/report-html'); + break; case 'verbose': case 'v': options.verbose = true; @@ -209,6 +221,9 @@ if (! options.watch) { util.print(str.replace(/^\n\n/, '\n')); } }); + if (options.coverage === true && _$jscoverage !== undefined) { + _coverage.report(_$jscoverage); + } if (process.stdout.write('')) { // Check if stdout is drained process.exit(status); } else { diff --git a/lib/vows/coverage/file.js b/lib/vows/coverage/file.js new file mode 100644 index 00000000..5bdef903 --- /dev/null +++ b/lib/vows/coverage/file.js @@ -0,0 +1,29 @@ + +exports.coverage = function (filename, data) { + var ret = { + filename: filename, + coverage: 0, + hits: 0, + misses: 0, + sloc : 0 + }; + + var source = data.source; + ret.source = source.map(function (line, num) { + num++; + + if (data[num] === 0) { + ret.misses++; + ret.sloc++; + } else if (data[num] !== undefined) { + ret.hits++; + ret.sloc++; + } + + return { line: line, coverage: (data[num] === undefined ? '' : data[num]) }; + }); + + ret.coverage = (ret.hits / ret.sloc) * 100; + + return ret; +}; \ No newline at end of file diff --git a/lib/vows/coverage/fragments/coverage-foot.html b/lib/vows/coverage/fragments/coverage-foot.html new file mode 100644 index 00000000..691287b6 --- /dev/null +++ b/lib/vows/coverage/fragments/coverage-foot.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/lib/vows/coverage/fragments/coverage-head.html b/lib/vows/coverage/fragments/coverage-head.html new file mode 100644 index 00000000..543946ec --- /dev/null +++ b/lib/vows/coverage/fragments/coverage-head.html @@ -0,0 +1,38 @@ + + + + + + diff --git a/lib/vows/coverage/report-html.js b/lib/vows/coverage/report-html.js new file mode 100644 index 00000000..2a431987 --- /dev/null +++ b/lib/vows/coverage/report-html.js @@ -0,0 +1,44 @@ +var sys = require('sys'), + fs = require('fs'), + file = require('vows/coverage/file'); + +this.name = 'coverage-report-html'; + +this.report = function (coverageMap) { + var out, head, foot; + + try { + out = fs.openSync("coverage.html", "w"); + head = fs.readFileSync(__dirname + "/fragments/coverage-head.html", "utf8"); + foot = fs.readFileSync(__dirname + "/fragments/coverage-foot.html", "utf8"); + } catch (error) { + sys.print("Error: Unable to write to file coverage.html\n"); + return; + } + + fs.writeSync(out, head); + + for (var filename in coverageMap) { + if (coverageMap.hasOwnProperty(filename)) { + var data = file.coverage(filename, coverageMap[filename]); + + fs.writeSync(out, "

" + filename + "

\n"); + fs.writeSync(out, '' + "[ hits: " + data.hits); + fs.writeSync(out, ", misses: " + data.misses + ", sloc: " + data.sloc); + fs.writeSync(out, ", coverage: " + data.coverage.toFixed(2) + "% ]" + "\n"); + fs.writeSync(out, "
    \n"); + + for (var i = 0; i < data.source.length; i++) { + fs.writeSync(out, '
  1. '); + fs.writeSync(out, data.source[i].line + "
  2. \n"); + } + + fs.writeSync(out, "
\n"); + } + } + + fs.writeSync(out, foot); + fs.close(out); +}; \ No newline at end of file diff --git a/lib/vows/coverage/report-plain.js b/lib/vows/coverage/report-plain.js new file mode 100644 index 00000000..e82e42f9 --- /dev/null +++ b/lib/vows/coverage/report-plain.js @@ -0,0 +1,38 @@ +var sys = require('sys'), + file = require('./file'); + +this.name = 'coverage-report-plain'; + +function lpad(str, width) { + str = String(str); + var n = width - str.length; + + if (n < 1) { + return str; + } + + while (n--) { + str = ' ' + str; + } + + return str; +} + + +this.report = function (coverageMap) { + for (var filename in coverageMap) { + if (coverageMap.hasOwnProperty(filename)) { + var data = file.coverage(filename, coverageMap[filename]); + + sys.print(filename + ":\n"); + sys.print("[ hits: " + data.hits + ", misses: " + data.misses); + sys.print(", sloc: " + data.sloc + ", coverage: " + data.coverage.toFixed(2) + "% ]\n"); + + for (var i = 0; i < data.source.length; i++) { + sys.print(lpad(data.source[i].coverage, 5) + " | " + data.source[i].line + "\n"); + } + + sys.print("\n"); + } + } +}; \ No newline at end of file From 3e98285850381310704649e7bcc86f3eb963ec3c Mon Sep 17 00:00:00 2001 From: seebees Date: Sun, 17 Jul 2011 16:38:06 -0700 Subject: [PATCH 332/409] Test for change These don't need to be pulled. They are mostly here to confirm that what I'm saying is true. Signed-off-by: seebees --- test/testInherit.js | 133 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 test/testInherit.js diff --git a/test/testInherit.js b/test/testInherit.js new file mode 100644 index 00000000..25bc4c0c --- /dev/null +++ b/test/testInherit.js @@ -0,0 +1,133 @@ +/* + * instanceof is a more complete check then .constructor === + * + * It works when using node's built-in util.inherits function + * and it also honors a class's entire ancestry + * + * Here I am only testing the change to vows in suite.js at line 147 to change + * the check from .constructor === to instanceof. These tests should demonstrate + * that this change should work both cases. For completness I also check + * the case when EventEmitter is an ancestor, not the parent Class. + * + */ +var EventEmitter = process.EventEmitter, + util = require('util'), + vows = require('vows'), + assert = require('assert'); + +vows.describe('EventEmitters as a return value from a topic').addBatch({ + 'returning an EventEmitter' : { + topic : function () { + //Make an event emitter + var tmp = new EventEmitter(); + //set it to emit success in a bit + setTimeout(function () { + //pass a value to make sure this all works + tmp.emit('success', 'I work'); + }, 10); + + return tmp; + }, + 'will catch what I pass to success' : function (ret) { + assert.strictEqual(ret, 'I work'); + } + }, + 'returning a class that uses util.inherit to inherit from EventEmitter' : { + topic : function () { + //Make a class that will util.inherit from EventEmitter + var Class = function () { + EventEmitter.call(this); + }, + tmp; + + //inherit from EventEmitter + util.inherits(Class, EventEmitter); + //Get a new one + tmp = new Class(); + //set it to emit success in a bit + setTimeout(function () { + //pass a value to make sure this all works + tmp.emit('success', 'I work'); + }, 10); + + return tmp; + }, + 'will catch what I pass to success' : function (ret) { + assert.strictEqual(ret, 'I work'); + } + }, + 'returning a class that uses Class.prototype = new EventEmitter()' : { + topic : function () { + //Make a class that will inherit from EventEmitter + var Class = function () {}, tmp; + //inherit + Class.prototype = new EventEmitter(); + //Get a new one + tmp = new Class(); + //set it to emit success in a bit + setTimeout(function () { + //pass a value to make sure this all works + tmp.emit('success', 'I work'); + }, 10); + + return tmp; + }, + 'will catch what I pass to success' : function (ret) { + assert.strictEqual(ret, 'I work'); + } + }, + 'returning a class that uses util.inherit to inherit from a class that inherits from EventEmitter ' : { + topic : function () { + //Class1 inherits from EventEmitter + var Class1 = function () { + var self = this; + EventEmitter.call(self); + }, + //Class2 inherits from Class1 + Class2 = function () { + Class1.call(this); + }, tmp; + //Inherit + util.inherits(Class1, EventEmitter); + util.inherits(Class2, Class1); + //Get a new one + tmp = new Class2(); + //set it to emit success in a bit + setTimeout(function () { + //pass a value to make sure this all works + tmp.emit('success', 'I work'); + },10); + + return tmp; + }, + 'will catch what I pass to success' : function (ret) { + assert.strictEqual(ret, 'I work'); + } + }, + 'returning a class that uses Class2.prototype = new Class1() and Class1.prototype = new EventEmitter()' : { + topic : function () { + //Class1 will inherit from EventEmitter + var Class1 = function () {}, + //Class2 will inherit from Class1 + Class2 = function () {}, tmp; + //Inherit + Class1.prototype = new EventEmitter(); + Class2.prototype = new Class1(); + //Get a new one + tmp = new Class2(); + //seit it to emit success in a bit + setTimeout(function () { + //pass a value to make sure this all works + tmp.emit('success', 'I work'); + },10); + + return tmp; + }, + 'will catch what I pass to success' : function (ret) { + assert.strictEqual(ret, 'I work'); + } + } +}).export(module); + + + From 96a17a2a52a1f3dcfe3dee7bec34c2dac9328454 Mon Sep 17 00:00:00 2001 From: seebees Date: Sun, 17 Jul 2011 16:39:02 -0700 Subject: [PATCH 333/409] use instanceof to check if the return value from a topic is an EventEmitter Signed-off-by: seebees --- lib/vows/suite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 29c646b0..8b88f3ae 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -144,7 +144,7 @@ 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)) { + if (! (topic instanceof events.EventEmitter)) { ctx.emitter = new(events.EventEmitter); if (! ctx._callback) { From 9418795d24a88f8f14c3e63992267eec991f33d3 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 22 Jul 2011 12:49:09 -0400 Subject: [PATCH 334/409] remove `require.paths` dependency --- bin/vows | 26 ++++++++++++-------------- lib/assert/error.js | 4 ++-- lib/assert/macros.js | 2 +- lib/vows.js | 16 +++++++--------- lib/vows/coverage/report-html.js | 2 +- lib/vows/reporters/dot-matrix.js | 4 ++-- lib/vows/reporters/spec.js | 2 +- lib/vows/reporters/watch.js | 4 ++-- lib/vows/suite.js | 8 +++----- test/assert-test.js | 2 +- test/vows-test.js | 12 ++++-------- 11 files changed, 36 insertions(+), 46 deletions(-) diff --git a/bin/vows b/bin/vows index 5945a3c2..15ccf137 100755 --- a/bin/vows +++ b/bin/vows @@ -34,12 +34,10 @@ var inspect = require('eyes').inspector({ styles: { string: 'grey', regexp: 'grey' } }); -require.paths.unshift(path.join(__dirname, '..', 'lib')); - -var vows = require('vows'); -var cutils = require('vows/console'); -var stylize = require('vows/console').stylize; -var _reporter = require('vows/reporters/dot-matrix'), reporter = { +var vows = require('../lib/vows'); +var cutils = require('../lib/vows/console'); +var stylize = require('../lib/vows/console').stylize; +var _reporter = require('../lib/vows/reporters/dot-matrix'), reporter = { name: _reporter.name, }; var _coverage; @@ -105,28 +103,28 @@ while (arg = argv.shift()) { } else { switch (arg) { case 'json': - _reporter = require('vows/reporters/json'); + _reporter = require('../lib/vows/reporters/json'); break; case 'spec': - _reporter = require('vows/reporters/spec'); + _reporter = require('../lib/vows/reporters/spec'); break; case 'dot-matrix': - _reporter = require('vows/reporters/dot-matrix'); + _reporter = require('../lib/vows/reporters/dot-matrix'); break; case 'silent': case 's': - _reporter = require('vows/reporters/silent'); + _reporter = require('../lib/vows/reporters/silent'); break; case 'xunit': - _reporter = require('vows/reporters/xunit'); + _reporter = require('../lib/vows/reporters/xunit'); break; case 'cover-plain': options.coverage = true; - _coverage = require('vows/coverage/report-plain'); + _coverage = require('../lib/vows/coverage/report-plain'); break; case 'cover-html': options.coverage = true; - _coverage = require('vows/coverage/report-html'); + _coverage = require('../lib/vows/coverage/report-html'); break; case 'verbose': case 'v': @@ -156,7 +154,7 @@ while (arg = argv.shift()) { } if (options.watch) { - options.reporter = reporter = require('vows/reporters/watch'); + options.reporter = reporter = require('../lib/vows/reporters/watch'); } msg('bin', 'argv', args); diff --git a/lib/assert/error.js b/lib/assert/error.js index af9885c1..12840175 100644 --- a/lib/assert/error.js +++ b/lib/assert/error.js @@ -1,5 +1,5 @@ -var stylize = require('vows/console').stylize; -var inspect = require('vows/console').inspect; +var stylize = require('../vows/console').stylize; +var inspect = require('../vows/console').inspect; require('assert').AssertionError.prototype.toString = function () { var that = this, diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 4de0f2ec..5c0d499f 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -1,5 +1,5 @@ var assert = require('assert'), - utils = require('assert/utils'); + utils = require('./utils'); var messages = { 'equal' : "expected {expected},\n\tgot\t {actual} ({operator})", diff --git a/lib/vows.js b/lib/vows.js index 6fc8266f..0e75cb92 100644 --- a/lib/vows.js +++ b/lib/vows.js @@ -20,12 +20,10 @@ var sys = require('sys'), events = require('events'), vows = exports; -require.paths.unshift(__dirname); - // Options vows.options = { Emitter: events.EventEmitter, - reporter: require('vows/reporters/dot-matrix'), + reporter: require('./vows/reporters/dot-matrix'), matcher: /.*/, error: true // Handle "error" event }; @@ -34,12 +32,12 @@ vows.__defineGetter__('reporter', function () { return vows.options.reporter; }); -var stylize = require('vows/console').stylize; -var console = require('vows/console'); +var stylize = require('./vows/console').stylize; +var console = require('./vows/console'); -vows.inspect = require('vows/console').inspect; -vows.prepare = require('vows/extras').prepare; -vows.tryEnd = require('vows/suite').tryEnd; +vows.inspect = require('./vows/console').inspect; +vows.prepare = require('./vows/extras').prepare; +vows.tryEnd = require('./vows/suite').tryEnd; // // Assertion Macros & Extensions @@ -50,7 +48,7 @@ require('./assert/macros'); // // Suite constructor // -var Suite = require('vows/suite').Suite; +var Suite = require('./vows/suite').Suite; // // This function gets added to events.EventEmitter.prototype, by default. diff --git a/lib/vows/coverage/report-html.js b/lib/vows/coverage/report-html.js index 2a431987..b577fa7f 100644 --- a/lib/vows/coverage/report-html.js +++ b/lib/vows/coverage/report-html.js @@ -1,6 +1,6 @@ var sys = require('sys'), fs = require('fs'), - file = require('vows/coverage/file'); + file = require('./file'); this.name = 'coverage-report-html'; diff --git a/lib/vows/reporters/dot-matrix.js b/lib/vows/reporters/dot-matrix.js index e3056ec0..b9ddef33 100644 --- a/lib/vows/reporters/dot-matrix.js +++ b/lib/vows/reporters/dot-matrix.js @@ -2,8 +2,8 @@ var sys = require('sys'); var options = {}; -var console = require('vows/console'); -var spec = require('vows/reporters/spec'); +var console = require('../../vows/console'); +var spec = require('./spec'); var stylize = console.stylize, puts = console.puts(options); // diff --git a/lib/vows/reporters/spec.js b/lib/vows/reporters/spec.js index 8aace81a..a7835aeb 100644 --- a/lib/vows/reporters/spec.js +++ b/lib/vows/reporters/spec.js @@ -1,7 +1,7 @@ var sys = require('sys'); var options = {}; -var console = require('vows/console'); +var console = require('../../vows/console'); var stylize = console.stylize, puts = console.puts(options); // diff --git a/lib/vows/reporters/watch.js b/lib/vows/reporters/watch.js index d895b22b..e47e1ad3 100644 --- a/lib/vows/reporters/watch.js +++ b/lib/vows/reporters/watch.js @@ -1,8 +1,8 @@ var sys = require('sys'); var options = {}; -var console = require('vows/console'); -var spec = require('vows/reporters/spec'); +var console = require('../../vows/console'); +var spec = require('../../vows/reporters/spec'); var stylize = console.stylize, puts = console.puts(options); // diff --git a/lib/vows/suite.js b/lib/vows/suite.js index 29c646b0..6665ed30 100644 --- a/lib/vows/suite.js +++ b/lib/vows/suite.js @@ -1,15 +1,13 @@ var events = require('events'), path = require('path'); -require.paths.unshift(path.join(__dirname, '..')); - -var vows = require('vows'); -var Context = require('vows/context').Context; +var vows = require('../vows'); +var Context = require('../vows/context').Context; this.Suite = function (subject) { this.subject = subject; this.matcher = /.*/; - this.reporter = require('vows/reporters/dot-matrix'); + this.reporter = require('./reporters/dot-matrix'); this.batches = []; this.options = { error: true }; this.reset(); diff --git a/test/assert-test.js b/test/assert-test.js index e5e9ac84..b3aa8e07 100644 --- a/test/assert-test.js +++ b/test/assert-test.js @@ -1,4 +1,4 @@ -var vows = require('vows'); +var vows = require('../lib/vows'); var assert = require('assert'); vows.describe('vows/assert').addBatch({ diff --git a/test/vows-test.js b/test/vows-test.js index 60b71d97..34f90aed 100644 --- a/test/vows-test.js +++ b/test/vows-test.js @@ -1,12 +1,8 @@ -var path = require('path'); - -require.paths.unshift(path.join(__dirname, '..', 'lib')); - -var events = require('events'), +var path = require('path'), + events = require('events'), assert = require('assert'), - fs = require('fs'); - -var vows = require('vows'); + fs = require('fs'), + vows = require('../lib/vows'); var api = vows.prepare({ get: function (id, callback) { From d597378d462a83010173eda83f0314583c77ad18 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 22 Jul 2011 12:50:46 -0400 Subject: [PATCH 335/409] fix assert.inDelta global vars --- lib/assert/macros.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/assert/macros.js b/lib/assert/macros.js index 5c0d499f..bdfe7d4f 100644 --- a/lib/assert/macros.js +++ b/lib/assert/macros.js @@ -62,10 +62,10 @@ assert.lesser = function (actual, expected, message) { }; assert.inDelta = function (actual, expected, delta, message) { - lower = expected - delta; - upper = expected + delta; + var lower = expected - delta; + var upper = expected + delta; if (actual < lower || actual > upper) { - assert.fail(actual, expected, message || "expected {actual} to be in within *"+ delta.toString() +"* of {expected}", null, assert.inDelta); + assert.fail(actual, expected, message || "expected {actual} to be in within *" + delta.toString() + "* of {expected}", null, assert.inDelta); } }; From 76e91753989ddf7bfda8ad6cccc5fc422c9542bb Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 22 Jul 2011 12:51:01 -0400 Subject: [PATCH 336/409] add /bin folder to package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eb4e1065..36cae90e 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "dependencies" : {"eyes": ">=0.1.6"}, "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, - "directories" : { "test": "./test" }, + "directories" : { "test": "./test", "bin": "./bin" }, "version" : "0.5.8", "engines" : {"node": ">=0.2.6"} } From e80e96d503ff158f8b8fb0b2ca9bcccc34c0ee3a Mon Sep 17 00:00:00 2001 From: cloudhead Date: Fri, 22 Jul 2011 12:51:30 -0400 Subject: [PATCH 337/409] (dist) version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 36cae90e..148a5e98 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ "main" : "./lib/vows", "bin" : { "vows": "./bin/vows" }, "directories" : { "test": "./test", "bin": "./bin" }, - "version" : "0.5.8", + "version" : "0.5.9", "engines" : {"node": ">=0.2.6"} } From 63a15e7ad93c76c3abdef1fe748cc23e08f0f0d1 Mon Sep 17 00:00:00 2001 From: ciaranj Date: Mon, 25 Jul 2011 21:02:56 +0100 Subject: [PATCH 338/409] Provide some very rudimentary CSS & JS to collapse the 'covered' source by default and use colours to draw your eye to the areas that need tackling --- .../coverage/fragments/coverage-head.html | 23 +++++++++++++++++++ lib/vows/coverage/report-html.js | 18 +++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/lib/vows/coverage/fragments/coverage-head.html b/lib/vows/coverage/fragments/coverage-head.html index 543946ec..aa2f107e 100644 --- a/lib/vows/coverage/fragments/coverage-head.html +++ b/lib/vows/coverage/fragments/coverage-head.html @@ -12,11 +12,24 @@ font-size: 0.8em; color: #333; } + .coverage.fullCoverage { + background-color:#0f0; + color: #111; + } + .coverage.okCoverage { + background-color: orange; + } + .coverage.poorCoverage { + background-color: red; + } .code { font-family: "Consolas", "Courier New", Courier, mono; white-space: pre; line-height: 16px; } + .collapsed { + display: none; + } body { margin-left: 20px; margin-top: 8px; @@ -34,5 +47,15 @@ box-shadow: 10px 10px 5px #888; } + diff --git a/lib/vows/coverage/report-html.js b/lib/vows/coverage/report-html.js index b577fa7f..8ae15dfa 100644 --- a/lib/vows/coverage/report-html.js +++ b/lib/vows/coverage/report-html.js @@ -4,6 +4,15 @@ var sys = require('sys'), this.name = 'coverage-report-html'; +function getCoverageClass( data ) { + var fullCoverage= (data.coverage == 100); + var okCoverage= (!fullCoverage && data.coverage >=60); + var coverageClass= ''; + if( fullCoverage ) coverageClass= 'fullCoverage'; + else if( okCoverage) coverageClass= 'okCoverage'; + else coverageClass= 'poorCoverage'; + return coverageClass; +} this.report = function (coverageMap) { var out, head, foot; @@ -21,12 +30,12 @@ this.report = function (coverageMap) { for (var filename in coverageMap) { if (coverageMap.hasOwnProperty(filename)) { var data = file.coverage(filename, coverageMap[filename]); - + var coverageClass= getCoverageClass( data ); fs.writeSync(out, "

" + filename + "

\n"); - fs.writeSync(out, '' + "[ hits: " + data.hits); + fs.writeSync(out, '' + "[ hits: " + data.hits); fs.writeSync(out, ", misses: " + data.misses + ", sloc: " + data.sloc); - fs.writeSync(out, ", coverage: " + data.coverage.toFixed(2) + "% ]" + "\n"); - fs.writeSync(out, "
    \n"); + fs.writeSync(out, ", coverage: " + data.coverage.toFixed(2) + "% ]" + " [+]\n"); + fs.writeSync(out, "