├── .gitignore ├── README.textile ├── stylesheets └── screen.css ├── test ├── reporting.js └── assertions.js ├── index.html ├── lib ├── test.js └── assert.js └── turing-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *.DS_Store 4 | *.pid 5 | tmp/ 6 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h3. Turing Test 2 | 3 | This project is part of a tutorial series on DailyJS. 4 | 5 | Goals: 6 | 7 | * CommonJS-based tests that run in the browser, Node, Narwhal, and more 8 | * Rely the environment's assert module if one exists, else provide our own 9 | * A test runner with clean output 10 | 11 | 12 | -------------------------------------------------------------------------------- /stylesheets/screen.css: -------------------------------------------------------------------------------- 1 | body { font-family: helvetica, arial, sans-serif } 2 | #results { list-style-type: none } 3 | #results li { margin: 0.5em 0 } 4 | .pass { color: green } 5 | .fail, .error { color: red } 6 | .header { padding: 1em 0; font-weight: bold } 7 | .trace { font-family: courier; font-size: 90%; white-space: pre; padding: 1em 0 } 8 | -------------------------------------------------------------------------------- /test/reporting.js: -------------------------------------------------------------------------------- 1 | if (typeof require !== 'undefined') { 2 | if (typeof require.paths !== 'undefined') 3 | require.paths.unshift('lib/'); 4 | var assert = require('assert'); 5 | } 6 | 7 | exports['test reports'] = function() { 8 | assert.ok(true, 'This should be reported as a test from reporting.js'); 9 | }; 10 | 11 | if (typeof module !== 'undefined') 12 | if (module === require.main) 13 | require('test').run(exports); 14 | 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | CommonJS Test Harness 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/assertions.js: -------------------------------------------------------------------------------- 1 | require.paths.unshift('./lib'); 2 | 3 | var test = require('test'), 4 | assert = require('assert'); 5 | 6 | 7 | exports['test equal'] = function() { 8 | assert.equal(true, true, 'True should be true'); 9 | }; 10 | 11 | exports['test ok'] = function() { 12 | assert.ok(true, 'True should be OK'); 13 | }; 14 | 15 | exports['test strictEqual'] = function() { 16 | assert.strictEqual('1', '1', "'1' should be equal to '1'"); 17 | assert.strictEqual(1, 1, '1 should be equal to 1'); 18 | }; 19 | 20 | exports['test notStrictEqual'] = function() { 21 | assert.notStrictEqual(1, '1'); 22 | assert.notStrictEqual('1', 1); 23 | }; 24 | 25 | exports['test deepEqual'] = function() { 26 | assert.deepEqual(1, 1); 27 | assert.deepEqual('1', '1'); 28 | assert.deepEqual(new Date(1), new Date(1)); 29 | assert.deepEqual([1, 2, 3], [1, 2, 3]); 30 | assert.deepEqual( 31 | [[3, 2, 1], 2, [1, 2, 3]], 32 | [[3, 2, 1], 2, [1, 2, 3]] 33 | ); 34 | assert.deepEqual( 35 | { name: 'Alex', position: 'Expert Button Pusher' }, 36 | { name: 'Alex', position: 'Expert Button Pusher' } 37 | ); 38 | }; 39 | 40 | exports['test notDeepEqual'] = function() { 41 | assert.notDeepEqual(1, 2); 42 | assert.notDeepEqual('1', '2'); 43 | assert.notDeepEqual(new Date(1), new Date(2)); 44 | assert.notDeepEqual([1, 2, 3], [3, 2, 1]); 45 | assert.notDeepEqual( 46 | [[3, 2, 1], 2, [1, 2, 3]], 47 | [[3, 2, 1], 2, [1, 2, 1]] 48 | ); 49 | assert.notDeepEqual( 50 | { name: 'Alex', position: 'Expert Button Pusher' }, 51 | { name: 'Mike', position: 'Expert Button Pusher' } 52 | ); 53 | }; 54 | 55 | exports['test throws'] = function() { 56 | assert.throws(function() { 57 | throw 'This is an exception'; 58 | }); 59 | 60 | function CustomException() { 61 | this.message = 'Custom excpetion'; 62 | this.name = 'CustomException'; 63 | } 64 | 65 | assert.throws(function() { 66 | throw new CustomException(); 67 | }, CustomException); 68 | 69 | assert.throws(function() { 70 | throw new CustomException(); 71 | }, CustomException, 'This is an error'); 72 | }; 73 | 74 | exports['test doesNotThrow'] = function() { 75 | assert.doesNotThrow(function() { 76 | return true; 77 | }, 'this is a message'); 78 | 79 | assert.throws(function() { 80 | throw 'This is an exception'; 81 | }, 'this is a message'); 82 | }; 83 | 84 | test.run(exports); 85 | -------------------------------------------------------------------------------- /lib/test.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var logger, 3 | Tests, 4 | printMessage, 5 | colorize = true; 6 | 7 | printMessage = (function() { 8 | function htmlEntityToUTF(text) { 9 | switch (text) { 10 | case '✕': 11 | return '\u2715'; 12 | break; 13 | 14 | case '✓': 15 | return '\u2713'; 16 | break; 17 | 18 | case '☠': 19 | return '\u2620'; 20 | break; 21 | } 22 | return text; 23 | } 24 | 25 | function messageTypeToColor(messageType) { 26 | switch (messageType) { 27 | case 'pass': 28 | return '32'; 29 | break; 30 | 31 | case 'fail': 32 | return '31'; 33 | break; 34 | } 35 | 36 | return ''; 37 | } 38 | 39 | if (typeof window !== 'undefined') { 40 | return function(message, messageType, prefix) { 41 | var li = document.createElement('li'); 42 | li.innerHTML = (prefix ? prefix + ' ' : '') + (message && message.length > 0 ? message.replace(/\n/, '
') : message.toString()); 43 | if (messageType) li.className = messageType; 44 | document.getElementById('results').appendChild(li); 45 | } 46 | } else if (typeof console !== 'undefined') { 47 | return function(message, messageType, prefix) { 48 | var col = colorize ? messageTypeToColor(messageType) : false; 49 | startCol = col ? '\033[' + col + 'm' : '', 50 | endCol = col ? '\033[0m' : '', 51 | console.log(startCol + (prefix ? htmlEntityToUTF(prefix) + ' ' : '') + message + endCol); 52 | }; 53 | } else { 54 | return function() {}; 55 | } 56 | })(); 57 | 58 | logger = { 59 | display: function(message, className, prefix) { 60 | printMessage(message, className || 'trace', prefix || ''); 61 | }, 62 | 63 | error: function(message) { 64 | this.display(message, 'error', '☠'); 65 | }, 66 | 67 | pass: function(message) { 68 | this.display(message, 'pass', '✓'); 69 | }, 70 | 71 | fail: function(message) { 72 | this.display(message, 'fail', '✕'); 73 | } 74 | }; 75 | 76 | function run(obj) { 77 | for (var testName in obj) { 78 | // TODO: Run objects that match ^test 79 | if (testName.match(/^test/i)) 80 | Tests.run(testName, obj); 81 | } 82 | } 83 | 84 | Tests = { 85 | results: [], 86 | passed: 0, 87 | failed: 0, 88 | errors: 0, 89 | 90 | Result: function(testName) { 91 | return { name: testName, message: null }; 92 | }, 93 | 94 | run: function(testName, obj) { 95 | var result = new Tests.Result(testName); 96 | 97 | function showException(e) { 98 | if (!!e.stack) { 99 | logger.display(e.stack); 100 | } else { 101 | logger.display(e); 102 | } 103 | } 104 | 105 | if (typeof obj[testName] === 'object') { 106 | logger.display('Running: ' + testName); 107 | return run(obj[testName]); 108 | } 109 | 110 | try { 111 | // TODO: Setup 112 | obj[testName](); 113 | this.passed += 1; 114 | logger.pass(testName); 115 | } catch (e) { 116 | if (e.name === 'AssertionError') { 117 | result.message = e.toString(); 118 | logger.fail('Assertion failed in: ' + testName); 119 | showException(e); 120 | this.failed += 1; 121 | } else { 122 | logger.error('Error in: ' + testName); 123 | showException(e); 124 | this.errors += 1; 125 | } 126 | } finally { 127 | // TODO: Teardown 128 | } 129 | 130 | this.results.push(result); 131 | }, 132 | 133 | report: function() { 134 | logger.display(''); 135 | logger.display('Report:', 'header'); 136 | logger.pass('Passed: ' + this.passed); 137 | logger.fail('Failed: ' + this.failed); 138 | logger.error('Errors: ' + this.errors); 139 | }, 140 | 141 | runAll: function(tests) { 142 | if (typeof TuringTest !== 'undefined' && TuringTest.isLoading) { 143 | setTimeout(function() { Tests.runAll(tests); }, 10); 144 | } else { 145 | run(tests); 146 | Tests.report(); 147 | } 148 | } 149 | }; 150 | 151 | if (typeof window === 'undefined') { 152 | exports.run = Tests.runAll; 153 | } else if (TuringTest) { 154 | TuringTest.testRunner = { run: Tests.runAll }; 155 | } 156 | })(); 157 | 158 | -------------------------------------------------------------------------------- /turing-test.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var env, node, tt; 3 | 4 | if (typeof require !== 'undefined') { 5 | require.paths.unshift('./'); 6 | } 7 | 8 | function detectEnvironment() { 9 | if (typeof env !== 'undefined') { 10 | return env; 11 | } 12 | 13 | env = (function() { 14 | if (typeof XPCOMCore !== 'undefined') { 15 | return 'xpcomcore'; 16 | } else if (typeof window === 'undefined' && typeof java !== 'undefined') { 17 | return 'rhino'; 18 | } else if (typeof exports !== 'undefined') { 19 | // TODO: Node should be checked more thoroughly 20 | node = { 21 | fs: require('fs'), 22 | sys: require('sys') 23 | } 24 | 25 | return 'node'; 26 | } else if (typeof window === 'undefined') { 27 | return 'non-browser-interpreter'; 28 | } else { 29 | return 'browser'; 30 | } 31 | })(); 32 | 33 | return env; 34 | } 35 | 36 | tt = TuringTest = { 37 | isLoading: false, 38 | loadingItems: 0, 39 | 40 | webRelativePath: '', 41 | browserPaths: [], 42 | 43 | loading: function() { 44 | tt.loadingItems++; 45 | tt.isLoading = true; 46 | }, 47 | 48 | doneLoading: function(request) { 49 | tt.loadingItems--; 50 | if (tt.loadingItems === 0) tt.isLoading = false; 51 | }, 52 | 53 | load: function(script, eval) { 54 | if (!window.__turingTestInit) { 55 | window.__turingTestInit = true; 56 | TuringTest.init(); 57 | } 58 | 59 | if (!script.match(/\.js$/)) { 60 | script = script + '.js'; 61 | } 62 | 63 | if (!script.match(/\//)) { 64 | script = tt.browserPaths[0] + '/' + script; 65 | script = script.replace(/\.\//, ''); 66 | } 67 | 68 | function loadIEScript() { 69 | var id = '__id_' + (new Date()).valueOf(), 70 | timer, 71 | scriptTag; 72 | document.write(''); 73 | scriptTag = document.getElementById(id); 74 | 75 | timer = setInterval(function() { 76 | if (/loaded|complete/.test(scriptTag.readyState)) { 77 | clearInterval(timer); 78 | tt.doneLoading(); 79 | } 80 | }, 10); 81 | } 82 | 83 | function loadOtherScript() { 84 | var scriptTag = document.createElement('script'), 85 | head = document.getElementsByTagName('head'); 86 | scriptTag.onload = function() { tt.doneLoading(); }; 87 | scriptTag.setAttribute('type', 'text/javascript'); 88 | scriptTag.setAttribute('src', script); 89 | head[0].insertBefore(scriptTag, head.firstChild); 90 | } 91 | 92 | switch (detectEnvironment()) { 93 | case 'xpcomcore': 94 | case 'rhino': 95 | case 'non-browser-interpreter': 96 | load(script); 97 | break; 98 | 99 | case 'node': 100 | // Evaluate the required code in the global context, like Rhino's load() would 101 | eval(node.fs.readFileSync(script).toString()); 102 | break; 103 | 104 | case 'browser': 105 | this.loading(); 106 | if (document.attachEvent) { 107 | loadIEScript(); 108 | } else { 109 | loadOtherScript(); 110 | } 111 | break; 112 | } 113 | }, 114 | 115 | fakeTest: { 116 | run: function(tests) { 117 | if (tt.isLoading) { 118 | setTimeout(function() { tt.fakeTest.run(tests); }, 10); 119 | } else { 120 | return tt.testRunner.run(tests); 121 | } 122 | } 123 | }, 124 | 125 | installBrowserPatching: function() { 126 | window.exports = []; 127 | window.__dirname = ''; 128 | 129 | window.require = function(path) { 130 | exports = {}; 131 | tt.load(path); 132 | 133 | if (path === 'test') { 134 | return tt.fakeTest; 135 | } else { 136 | return {}; 137 | } 138 | }; 139 | 140 | window.require.paths = { 141 | unshift: function(path) { 142 | tt.browserPaths.push(path); 143 | } 144 | }; 145 | }, 146 | 147 | init: function(options) { 148 | options = options || {}; 149 | 150 | switch (detectEnvironment()) { 151 | case 'node': 152 | exports.load = tt.load; 153 | exports.test = require(__dirname + '/lib/test'); 154 | exports.assert = require(__dirname + '/lib/assert').assert; 155 | break; 156 | 157 | case 'browser': 158 | this.webRelativePath = options.webRelativePath || ''; 159 | break; 160 | } 161 | 162 | if (options.webScripts && options.eval) { 163 | for (var i = 0; i < options.webScripts.length; i++) { 164 | tt.load(options.webScripts[i], options.eval); 165 | } 166 | } 167 | } 168 | }; 169 | 170 | switch (detectEnvironment()) { 171 | case 'node': 172 | exports.init = tt.init; 173 | break; 174 | 175 | case 'browser': 176 | tt.installBrowserPatching(); 177 | break; 178 | } 179 | })(); 180 | -------------------------------------------------------------------------------- /lib/assert.js: -------------------------------------------------------------------------------- 1 | (function(global) { 2 | var assert = {}; 3 | 4 | assert.AssertionError = function AssertionError(options) { 5 | this.name = 'AssertionError'; 6 | this.message = options.message; 7 | this.actual = options.actual; 8 | this.expected = options.expected; 9 | this.operator = options.operator; 10 | var stackStartFunction = options.stackStartFunction || fail; 11 | }; 12 | 13 | assert.AssertionError.prototype.summary = function() { 14 | return this.name + (this.message ? ': ' + this.message : ''); 15 | }; 16 | 17 | assert.AssertionError.prototype.details = function() { 18 | return 'In "' + this.operator + '":\n\tExpected: ' + this.expected + '\n\tFound: ' + this.actual; 19 | }; 20 | 21 | assert.AssertionError.prototype.toString = function() { 22 | return this.summary() + '\n' + this.details(); 23 | }; 24 | 25 | function fail(actual, expected, message, operator, stackStartFunction) { 26 | throw new assert.AssertionError({ 27 | message: message, 28 | actual: actual, 29 | expected: expected, 30 | operator: operator, 31 | stackStartFunction: stackStartFunction 32 | }); 33 | } 34 | 35 | assert.fail = fail; 36 | 37 | assert.ok = function(value, message) { 38 | if (!!!value) 39 | fail(value, true, message, '==', assert.ok); 40 | }; 41 | 42 | assert.equal = function(actual, expected, message) { 43 | if (actual != expected) 44 | fail(actual, expected, message, '==', assert.equal); 45 | }; 46 | 47 | assert.notEqual = function(actual, expected, message) { 48 | if (actual == expected) 49 | fail(actual, expected, message, '!=', assert.equal); 50 | }; 51 | 52 | assert.strictEqual = function(actual, expected, message) { 53 | if (actual !== expected) 54 | fail(actual, expected, message, '===', assert.equal); 55 | }; 56 | 57 | assert.notStrictEqual = function(actual, expected, message) { 58 | if (actual === expected) 59 | fail(actual, expected, message, '!==', assert.equal); 60 | }; 61 | 62 | assert.deepEqual = function(actual, expected, message) { 63 | if (!deepEqual(actual, expected)) 64 | fail(actual, expected, message, 'deepEqual', assert.equal); 65 | }; 66 | 67 | assert.notDeepEqual = function(actual, expected, message) { 68 | if (deepEqual(actual, expected)) 69 | fail(actual, expected, message, 'notDeepEqual', assert.equal); 70 | }; 71 | 72 | assert.throws = function(block, error, message) { 73 | throws.apply(this, [true].concat(Array.prototype.slice.call(arguments))); 74 | }; 75 | 76 | assert.doesNotThrow = function(block, error, message) { 77 | throws.apply(this, [false].concat(Array.prototype.slice.call(arguments))); 78 | }; 79 | 80 | function deepEqual(actual, expected) { 81 | if (actual === expected) { 82 | return true; 83 | } else if (actual instanceof Date && expected instanceof Date) { 84 | return actual.getTime() === expected.getTime(); 85 | } else if (typeof actual != 'object' && typeof expected != 'object') { 86 | return actual == expected; 87 | } else { 88 | return objEquiv(actual, expected); 89 | } 90 | } 91 | 92 | function isUndefinedOrNull(value) { 93 | return value === null || value === undefined; 94 | } 95 | 96 | function isArguments(object) { 97 | return Object.prototype.toString.call(object) == '[object Arguments]'; 98 | } 99 | 100 | function objKeys(o) { 101 | var result = []; 102 | for (var name in o) { 103 | if (o.hasOwnProperty(name)) 104 | result.push(name); 105 | } 106 | return result; 107 | } 108 | 109 | function objEquiv(a, b) { 110 | if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) 111 | return false; 112 | 113 | if (a.prototype !== b.prototype) return false; 114 | if (isArguments(a)) { 115 | if (!isArguments(b)) { 116 | return false; 117 | } 118 | a = Array.prototype.slice.call(a); 119 | b = Array.prototype.slice.call(b); 120 | return deepEqual(a, b); 121 | } 122 | 123 | try { 124 | var ka = objKeys(a), 125 | kb = objKeys(b), 126 | key, i; 127 | } catch (e) { 128 | console.log(a, b) 129 | console.log(e); 130 | return false; 131 | } 132 | 133 | if (ka.length !== kb.length) 134 | return false; 135 | 136 | ka.sort(); 137 | kb.sort(); 138 | 139 | for (i = ka.length - 1; i >= 0; i--) { 140 | if (ka[i] != kb[i]) 141 | return false; 142 | } 143 | 144 | for (i = ka.length - 1; i >= 0; i--) { 145 | key = ka[i]; 146 | if (!deepEqual(a[key], b[key] )) 147 | return false; 148 | } 149 | 150 | return true; 151 | } 152 | 153 | function throws(expected, block, error, message) { 154 | var exception, 155 | actual, 156 | actual = false, 157 | operator = expected ? 'throws' : 'doesNotThrow'; 158 | callee = expected ? assert.throws : assert.doesNotThrow; 159 | 160 | if (typeof error === 'string' && !message) { 161 | message = error; 162 | error = null; 163 | } 164 | 165 | message = message || ''; 166 | 167 | try { 168 | block(); 169 | } catch (e) { 170 | actual = true; 171 | exception = e; 172 | } 173 | 174 | if (expected && !actual) { 175 | fail((exception || Error), (error || Error), 'Exception was not thrown\n' + message, operator, callee); 176 | } else if (!expected && actual) { 177 | fail((exception || Error), null, 'Unexpected exception was thrown\n' + message, operator, callee); 178 | } else if (expected && actual && error && exception.constructor != error) { 179 | fail((exception || Error), null, 'Unexpected exception was thrown\n' + message, operator, callee); 180 | } 181 | }; 182 | 183 | global.assert = assert; 184 | })(typeof window === 'undefined' ? this : window); 185 | 186 | --------------------------------------------------------------------------------