├── .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 |
--------------------------------------------------------------------------------