Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Mature testing for asynchronous operations and browser/terminal envir…

…onments.
  • Loading branch information...
commit 5c77169cf5abbc5b69b6d5433b3549c9dd6397f7 1 parent 84a6c14
@Yuffster authored
View
200 describe.js
@@ -1,36 +1,196 @@
-require("string-color");
-describe = function(series, obj) {
+(function(scope) {
- console.log(series.toUpperCase().color('yellow')+":");
+ var isModule = false;
- var passed = 0, failed = 0, total = 0;
+ // Export with CommonJS
+ if (typeof module !== 'undefined' && typeof require !== 'undefined') {
+ isModule = true;
+ }
- for (var k in obj) {
+ var options = {
+ timeout: 500,
+ callbackMode: false
+ };
+
+ function outputDOM(data, options) {
+ String.prototype.color = function(color) {
+ return '<span style="color:'+color+';font-weight:bold;">'+this+"</span>";
+ };
+ document.body.innerHTML = "<pre>"+outputConsole(data, options)+"</pre>";
+ }
+
+ function outputConsole(data, options) {
+
+ var color, test, lines, errors = {},
+ passed = 0, tests = 0, totalTests = 0, totalPassed = 0,
+ output = [];
+
+ options = options || {};
+
+ function log(mess) {
+ output[output.length] = mess;
+ }
- if (!obj.hasOwnProperty(k)) continue;
+ if (!String.prototype.color) require('string-color');
+ for (var groupName in data) {
+ lines = [], passed = 0, tests = 0;
+ for (test in data[groupName]) {
+ tests++;
+ if (data[groupName][test]===null) {
+ color = 'green';
+ passed++;
+ } else {
+ color = 'red';
+ errors[groupName] = errors[groupName] || {};
+ errors[groupName][test] = data[groupName][test];
+ }
+ lines[lines.length] = ('\t'+test).color(color);
+ }
+ color = (tests==passed) ? 'green' : 'red';
+ if (options.verbose) {
+ alert("HOAI");
+ log((groupName+' ('+passed+'/'+tests+')').color(color));
+ log(lines.join('\n'));
+ }
+ totalPassed += passed;
+ totalTests += tests;
+ }
+ if (totalPassed===totalTests) {
+ result = 'passed';
+ color = 'green';
+ } else {
+ color = 'red';
+ result = 'failed';
+ }
+ for (var groupName in errors) {
+ for (var test in errors[groupName]) {
+ log((groupName+"#"+test).color('red'));
+ log(errors[groupName][test].stack);
+ }
+ }
+ log(
+ ("Tests "+result+": "+totalPassed+'/'+totalTests).color(color)
+ );
+ if (typeof process != "undefined") {
+ console.log(output.join('\n'));
+ process.exit(totalTests-totalPassed);
+ } else {
+ return output.join('\n');
+ }
+ }
+
+ function expect(subject, expected, callback, options) {
+ if (expected===undefined) {
+ expected = subject;
+ subject = null;
+ return function(response,data) {
+ if (options.callbackMode=='node') {
+ if (response) {
+ return callback(response);
+ } else response = data;
+ }
+ expect(response, expected, callback);
+ };
+ } else {
+ if (subject==expected) callback(null);
+ else callback(new Error("Expected "+expected+" but got "+subject));
+ }
+ }
- var test = obj[k], desc = k;
+ function runTest(fun, callback, options) {
+
+ var done = false, timer;
- total++;
try {
- test();
+ fun.call({
+ expect: function(a,b) {
+ done = true;
+ clearTimeout(timer);
+ return expect(a,b,callback,options);
+ }
+ });
} catch (e) {
- failed++;
- console.log("\t"+desc.color('red'));
- console.log("\t\t====> "+e.stack);
- continue;
+ if (done) return;
+ done = true;
+ callback(e);
+ clearTimeout(timer);
}
- passed++;
+ timer = setTimeout(function() {
+ if (done) return;
+ callback(new Error("Test timeout after "+options.timeout+"ms"));
+ done = true;
+ }, options.timeout);
+
+ }
+
+ function Group(name, tests, config) {
+ this.options = options;
+ for (var k in config) this.options[k] = config[k];
+ this.tests = tests;
+ }
+
+ Group.prototype.execute = function(callback) {
+
+ var pending = 0, results = {}, my = this;
+
+ for (var name in this.tests) {
+ if (this.tests.hasOwnProperty(name)) pending++;
+ }
- console.log("\t"+desc.color('green'));
+ for (name in this.tests) {
+ if (this.tests.hasOwnProperty(name)) (function(name){
+ var returned = false;
+ runTest(my.tests[name], function(error) {
+ if (returned) {
+ results[name] = new Error("Duplicate callback");
+ return;
+ }
+ returned = true;
+ pending--;
+ results[name] = error;
+ if (pending===0) callback(results);
+ }, my.options);
+ }(name));
+ }
};
- console.log(
- "PASSED".color('green')+": "+passed+
- " / "+total
- );
+ var results = {}, pendingGroups = 0, resultCallbacks = [];
+
+ function describe(name, tests, config) {
+ pendingGroups++;
+ new Group(name, tests, config).execute(function(data) {
+ results[name] = data;
+ pendingGroups--;
+ if (pendingGroups===0) {
+ var next = resultCallbacks.shift();
+ while (next) {
+ next(results);
+ next = resultCallbacks.shift();
+ }
+ }
+ });
+ }
+
+ describe.config = function(k,v) {
+ if (v) options[k] = v;
+ return options[k] || false;
+ }
+
+ describe.getResults = function(cb) {
+ if (pendingGroups===0) cb(results);
+ else resultCallbacks[resultCallbacks.length] = cb;
+ }
+
+ describe.logResults = function(options) {
+ describe.getResults(function(data) {
+ if (typeof window !== "undefined") outputDOM(data, options);
+ else outputConsole(data, options);
+ });
+ }
+ if (isModule) module.exports = describe;
+ else scope.describe = describe;
-};
+}(this));
View
18 test.js
@@ -1,18 +0,0 @@
-require('./describe');
-var assert = require('assert');
-
-describe("Does it work?", {
-
- 'Yes, it appears to': function() {
- assert.ok(true);
- },
-
- 'This test should fail.': function() {
- assert.ok(false);
- },
-
- 'But this should pass.': function() {
- assert.ok(true);
- }
-
-});
View
13 tests/run_tests.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test Results</title>
+</head>
+<body>
+
+ <script src="../describe.js"></script>
+ <script src="./tests.js"></script>
+ <script>describe.logResults();</script>
+
+</body>
+</html>
View
4 tests/run_tests.js
@@ -0,0 +1,4 @@
+var describe = require('../describe'),
+ tests = require('./tests');
+
+describe.logResults();
View
72 tests/tests.js
@@ -0,0 +1,72 @@
+if (typeof module !== 'undefined' && typeof require !== 'undefined') {
+ var describe = require('../describe');
+}
+
+function addThingsSync() {
+ var n = 0;
+ for (var i in arguments) n+=arguments[i];
+ return n;
+}
+
+function addThingsAsync() {
+ var n = 0, callback;
+ for (var i in arguments) {
+ if (i==arguments.length-1) callback = arguments[i];
+ else n+=arguments[i];
+ } callback(n);
+}
+
+function asyncNodeError(callback) {
+ callback('test error', null);
+}
+
+function asyncNodeData(callback) {
+ callback(null, 'data');
+}
+
+describe("synchronous operations", {
+
+ 'basic expectation': function() {
+ this.expect(true, true);
+ },
+
+ 'log error on exception (this should fail)': function() {
+ throw new Error("intentional failure");
+ },
+
+ 'failed expectation (this should fail)': function() {
+ this.expect(false, true);
+ },
+
+ 'synchronous function call': function() {
+ this.expect(addThingsSync(1,2,3), 6);
+ }
+
+});
+
+describe("asynchronous operations", {
+
+ 'asynchronous function call': function() {
+ addThingsAsync(1,2,3,this.expect(6));
+ },
+
+ 'asynchronous timeout (this should fail)': function() {
+ var my = this;
+ setTimeout(function() { my.expect(42); }, 1000);
+ }
+
+});
+
+describe("node error style", {
+
+ 'asynchronous node-style error (this should fail)': function() {
+ asyncNodeError(this.expect(true));
+ },
+
+ 'asynchronous node-style data': function() {
+ asyncNodeData(this.expect('data'));
+ }
+
+}, {
+ callbackMode: 'node'
+});
Please sign in to comment.
Something went wrong with that request. Please try again.