├── .gitignore
├── test.html
├── istype.js
├── test.js
└── riot.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.sw?
2 |
--------------------------------------------------------------------------------
/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Riot
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/istype.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | function isNull(obj, type) {
3 | return obj === null && type === null;
4 | }
5 |
6 | function isUndefined(obj, type) {
7 | return typeof obj === 'undefined' && obj === type;
8 | }
9 |
10 | function matchesConstructor(obj, type) {
11 | return obj.constructor === type;
12 | }
13 |
14 | isType = function(obj, type) {
15 | if (isUndefined(obj, type)) {
16 | return true;
17 | } else if (isNull(obj, type)) {
18 | return true;
19 | } else if (matchesConstructor(obj, type)) {
20 | return true;
21 | }
22 | return false;
23 | }
24 | })();
25 |
26 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | load('riot.js');
2 | Riot.require('istype.js');
3 |
4 | Riot.context('istype.js', function() {
5 | given('Undefined', function() {
6 | var boop;
7 | should('identify undefined', isType(undefined, undefined)).isTrue();
8 | should('identify undefined', isType(boop, undefined)).isTrue();
9 | });
10 |
11 | given('A Boolean', function() {
12 | should('identify true', isType(true, Boolean)).isTrue();
13 | should('identify false', isType(false, Boolean)).isTrue();
14 | should('not confuse it with a string', isType('false', Boolean)).isFalse();
15 | });
16 |
17 | given('Null', function() {
18 | should('identify null', isType(null, null)).isTrue();
19 | });
20 |
21 | given('A String', function() {
22 | var s = 'Hello';
23 | should('identify it as a string', isType(s, String)).isTrue();
24 | should('not identify it is an array', isType([1, 2, 3], String)).isFalse();
25 | });
26 |
27 | given('A Number', function() {
28 | should('identify it as a number', isType(1, Number)).isTrue();
29 | should('not identify it is a strong', isType(1, String)).isFalse();
30 | });
31 |
32 | given('An Object', function() {
33 | should('identify it as an object', isType({}, Object)).isTrue();
34 | });
35 |
36 | given('A Function', function() {
37 | should('identify it as a function', isType(function(){}, Function)).isTrue();
38 | });
39 |
40 | given('A Regular Expression', function() {
41 | should('identify a literal', isType(/test/, RegExp)).isTrue();
42 | should('identify an instantiated regexp', isType(new RegExp('/test/'), RegExp)).isTrue();
43 | });
44 |
45 | given('An Array', function() {
46 | var a = [1, 2, 3];
47 | should('identify it as an Array', isType(a, Array)).isTrue();
48 | should('not identify it is a string', isType('test', Array)).isFalse();
49 | });
50 |
51 | given('A Date', function() {
52 | should('identify it as a Date', isType(new Date(), Date)).isTrue();
53 | should('not identify it as an Array', isType(new Date(), Array)).isFalse();
54 | });
55 | });
56 |
57 | Riot.run();
58 |
--------------------------------------------------------------------------------
/riot.js:
--------------------------------------------------------------------------------
1 | /*jslint white: false plusplus: false onevar: false browser: true evil: true*/
2 | /*global window: true*/
3 | (function(global) {
4 | var Riot = {
5 | results: [],
6 | contexts: [],
7 |
8 | run: function(tests) {
9 | switch (Riot.detectEnvironment()) {
10 | case 'xpcomcore':
11 | Riot.formatter = new Riot.Formatters.XPComCore();
12 | Riot.runAndReport(tests);
13 | Sys.exit(Riot.exitCode);
14 | break;
15 |
16 | case 'rhino':
17 | Riot.formatter = new Riot.Formatters.Text();
18 | Riot.runAndReport(tests);
19 | java.lang.System.exit(Riot.exitCode);
20 | break;
21 |
22 | case 'non-browser-interpreter':
23 | Riot.formatter = new Riot.Formatters.Text();
24 | Riot.runAndReport(tests);
25 | if (typeof quit !== 'undefined') {
26 | quit(Riot.exitCode);
27 | }
28 | break;
29 |
30 | case 'browser':
31 | Riot.formatter = new Riot.Formatters.HTML();
32 | if (typeof window.onload === 'undefined' || window.onload == null) {
33 | Riot.browserAutoLoad(tests);
34 | }
35 | break;
36 | }
37 | },
38 |
39 | browserAutoLoad: function(tests) {
40 | var timer;
41 | function fireContentLoadedEvent() {
42 | if (document.loaded) return;
43 | if (timer) window.clearTimeout(timer);
44 | document.loaded = true;
45 |
46 | if (Riot.requiredFiles.length > 0) {
47 | Riot.loadBrowserScripts(Riot.requiredFiles, tests);
48 | } else {
49 | Riot.runAndReport(tests);
50 | }
51 | }
52 |
53 | function checkReadyState() {
54 | if (document.readyState === 'complete') {
55 | document.detachEvent('readystatechange', checkReadyState);
56 | fireContentLoadedEvent();
57 | }
58 | }
59 |
60 | function pollDoScroll() {
61 | try { document.documentElement.doScroll('left'); }
62 | catch(e) {
63 | timer = setTimeout(pollDoScroll, 10);
64 | return;
65 | }
66 | fireContentLoadedEvent();
67 | }
68 |
69 | if (document.addEventListener) {
70 | document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
71 | } else {
72 | document.attachEvent('readystatechange', checkReadyState);
73 | if (window == top)
74 | timer = setTimeout(pollDoScroll, 10);
75 | }
76 |
77 | window.onload = fireContentLoadedEvent;
78 | },
79 |
80 | loadBrowserScripts: function(files, tests) {
81 | var i, file;
82 |
83 | function loadBrowserScript(src, callback) {
84 | var script = document.createElement('script'),
85 | head = document.getElementsByTagName('head')[0],
86 | readyState;
87 | script.setAttribute('type', 'text/javascript');
88 | script.setAttribute('src', src);
89 | script.onload = script.onreadystatechange = function() {
90 | if (!(readyState = script.readyState) || /loaded|complete/.test(readyState)) {
91 | script.onload = script.onreadystatechange = null;
92 | head.removeChild(script);
93 | if (callback) {
94 | setTimeout(callback, 1);
95 | }
96 | }
97 | };
98 |
99 | head.insertBefore(script, head.firstChild);
100 | }
101 |
102 | if (files.length > 1) {
103 | file = files[0];
104 | loadBrowserScript(file, function() { Riot.loadBrowserScripts(files.slice(1), tests); });
105 | } else {
106 | file = files[0];
107 | loadBrowserScript(file, function() { Riot.runAndReport(tests); });
108 | }
109 | },
110 |
111 | load: function() {
112 | switch (Riot.detectEnvironment()) {
113 | case 'xpcomcore':
114 | case 'rhino':
115 | case 'non-browser-interpreter':
116 | load(arguments[0]);
117 | break;
118 | case 'browser':
119 | var script = document.createElement('script'),
120 | head = document.getElementsByTagName('head');
121 | script.setAttribute('type', 'text/javascript');
122 | script.setAttribute('src', arguments[0]);
123 | head[0].insertBefore(script, head.firstChild);
124 | break;
125 | }
126 | },
127 |
128 | requiredFiles: [],
129 |
130 | indexOf: function(array, value) {
131 | for (var i = 0; i < array.length; i++) {
132 | if (array[i] === value) {
133 | return i;
134 | }
135 | }
136 | return -1;
137 | },
138 |
139 | require: function() {
140 | if (this.indexOf(this.requiredFiles, arguments[0]) == -1) {
141 | this.requiredFiles.push(arguments[0]);
142 | if (Riot.detectEnvironment() !== 'browser') {
143 | this.load(arguments[0]);
144 | }
145 | }
146 | },
147 |
148 | detectEnvironment: function() {
149 | if (typeof this.env !== 'undefined') {
150 | return this.env;
151 | }
152 |
153 | if (typeof XPCOMCore !== 'undefined') {
154 | return 'xpcomcore';
155 | } else if (typeof window === 'undefined' && typeof java !== 'undefined') {
156 | return 'rhino';
157 | } else if (typeof window === 'undefined') {
158 | return 'non-browser-interpreter';
159 | } else {
160 | return 'browser';
161 | }
162 | },
163 |
164 | runAndReport: function(tests) {
165 | this.running = true;
166 | var benchmark = Riot.Benchmark.run(1, function() { Riot.runAllContexts(tests); });
167 | Riot.formatter.separator();
168 | Riot.summariseAllResults();
169 | Riot.formatter.line(benchmark);
170 | this.running = false;
171 | },
172 |
173 | runAllContexts: function(tests) {
174 | if (typeof tests !== 'undefined') {
175 | this.withDSL(tests)();
176 | }
177 |
178 | for (var i = 0; i < this.contexts.length; i++) {
179 | this.contexts[i].run();
180 | }
181 | },
182 |
183 | functionBody: function(fn) {
184 | return '(' + fn.toString().replace(/\s+$/, '') + ')()';
185 | },
186 |
187 | withDSL: function(fn, context) {
188 | var body = this.functionBody(fn),
189 | f = new Function('context', 'given', 'asserts', 'should', 'setup', 'teardown', body),
190 | args = [
191 | Riot.context,
192 | Riot.given,
193 | function() { return context.asserts.apply(context, arguments); },
194 | function() { return context.should.apply(context, arguments); },
195 | function() { return context.setup.apply(context, arguments); },
196 | function() { return context.teardown.apply(context, arguments); }
197 | ];
198 |
199 | return function() { f.apply(Riot, args); };
200 | },
201 |
202 | context: function(title, callback) {
203 | var context = new Riot.Context(title, callback);
204 |
205 | if (this.running) {
206 | context.run();
207 | } else {
208 | Riot.contexts.push(context);
209 | }
210 |
211 | return context;
212 | },
213 |
214 | given: function(title, callback) {
215 | title = 'Given ' + title;
216 | return Riot.context(title, callback);
217 | },
218 |
219 | summariseAllResults: function() { return this.summarise(this.results); },
220 |
221 | summarise: function(results) {
222 | var failures = 0;
223 | for (var i = 0; i < results.length; i++) {
224 | if (!results[i].pass) { failures++; }
225 | }
226 | this.formatter.line(results.length + ' assertions: ' + failures + ' failures');
227 | this.exitCode = failures > 0 ? 1 : 0;
228 | },
229 |
230 | addResult: function(context, assertion, pass) {
231 | var result = {
232 | assertion: assertion,
233 | pass: pass,
234 | context: context
235 | };
236 | this.results.push(result);
237 | }
238 | };
239 |
240 | Riot.Benchmark = {
241 | results: [],
242 |
243 | addResult: function(start, end) {
244 | this.results.push(end - start);
245 | },
246 |
247 | displayResults: function() {
248 | var total = 0,
249 | seconds = 0,
250 | i = 0;
251 | for (i = 0; i < this.results.length; i++) {
252 | total += this.results[i];
253 | }
254 | seconds = total / 1000;
255 | return 'Elapsed time: ' + total + 'ms (' + seconds + ' seconds)';
256 | },
257 |
258 | run: function(times, callback) {
259 | this.results = [];
260 | for (var i = 0; i < times; i++) {
261 | var start = new Date(),
262 | end = null;
263 | callback();
264 | end = new Date();
265 | this.addResult(start, end);
266 | }
267 | return this.displayResults();
268 | }
269 | };
270 |
271 | Riot.Formatters = {
272 | HTML: function() {
273 | function display(html) {
274 | var results = document.getElementById('test-results');
275 | results.innerHTML += html;
276 | }
277 |
278 | this.line = function(text) {
279 | display('' + text + '
');
280 | };
281 |
282 | this.pass = function(message) {
283 | display('' + message + '
');
284 | };
285 |
286 | this.fail = function(message) {
287 | display('' + message + '
');
288 | };
289 |
290 | this.error = function(message, exception) {
291 | this.fail(message);
292 | display('Exception: ' + exception + '
');
293 | };
294 |
295 | this.context = function(name) {
296 | display('' + name + '
');
297 | };
298 |
299 | this.given = function(name) {
300 | display('' + name + '
');
301 | };
302 |
303 | this.separator = function() {
304 | display('
');
305 | };
306 | },
307 |
308 | Text: function() {
309 | function display(text) {
310 | print(text);
311 | }
312 |
313 | this.line = function(text) {
314 | display(text);
315 | };
316 |
317 | this.pass = function(message) {
318 | this.line(' +\033[32m ' + message + '\033[0m');
319 | };
320 |
321 | this.fail = function(message) {
322 | this.line(' -\033[31m ' + message + '\033[0m');
323 | };
324 |
325 | this.error = function(message, exception) {
326 | this.fail(message);
327 | this.line(' Exception: ' + exception);
328 | };
329 |
330 | this.context = function(name) {
331 | this.line(name);
332 | };
333 |
334 | this.given = function(name) {
335 | this.line(name);
336 | };
337 |
338 | this.separator = function() {
339 | this.line('');
340 | };
341 | },
342 |
343 | XPComCore: function() {
344 | var formatter = new Riot.Formatters.Text();
345 | formatter.line = function(text) {
346 | puts(text);
347 | };
348 | return formatter;
349 | }
350 | };
351 |
352 | Riot.Context = function(name, callback) {
353 | this.name = name;
354 | this.callback = callback;
355 | this.assertions = [];
356 | };
357 |
358 | Riot.Context.prototype = {
359 | asserts: function(name, result) {
360 | var assertion = new Riot.Assertion(this.name, name, result);
361 | this.assertions.push(assertion);
362 | return assertion;
363 | },
364 |
365 | should: function(name, result) {
366 | return this.asserts('should ' + name, result);
367 | },
368 |
369 | setup: function(setupFunction) {
370 | this.setupFunction = setupFunction;
371 | },
372 |
373 | teardown: function(teardownFunction) {
374 | this.teardownFunction = teardownFunction;
375 | },
376 |
377 | runSetup: function() {
378 | if (typeof this.setupFunction !== 'undefined') {
379 | return this.setupFunction();
380 | }
381 | },
382 |
383 | runTeardown: function() {
384 | if (typeof this.teardownFunction !== 'undefined') {
385 | return this.teardownFunction();
386 | }
387 | },
388 |
389 | formatContextName: function() {
390 | if (this.name.match(/^Given/)) {
391 | Riot.formatter.given(this.name);
392 | } else {
393 | Riot.formatter.context(this.name);
394 | }
395 | },
396 |
397 | run: function() {
398 | this.formatContextName();
399 | Riot.withDSL(this.callback, this)();
400 | this.runSetup();
401 | for (var i = 0; i < this.assertions.length; i++) {
402 | var pass = false,
403 | assertion = this.assertions[i];
404 | try {
405 | assertion.run();
406 | pass = true;
407 | Riot.formatter.pass(assertion.name);
408 | } catch (e) {
409 | if (typeof e.name !== 'undefined' && e.name === 'Riot.AssertionFailure') {
410 | Riot.formatter.fail(e.message);
411 | } else {
412 | Riot.formatter.error(assertion.name, e);
413 | }
414 | }
415 |
416 | Riot.addResult(this.name, assertion.name, pass);
417 | }
418 | this.runTeardown();
419 | }
420 | };
421 |
422 | Riot.AssertionFailure = function(message) {
423 | var error = new Error(message);
424 | error.name = 'Riot.AssertionFailure';
425 | return error;
426 | };
427 |
428 | Riot.Assertion = function(contextName, name, expected) {
429 | this.name = name;
430 | this.expectedValue = expected;
431 | this.contextName = contextName;
432 | this.kindOf = this.typeOf;
433 | this.isTypeOf = this.typeOf;
434 |
435 | this.setAssertion(function(actual) {
436 | if ((actual() === null) || (actual() === undefined)) {
437 | throw(new Riot.AssertionFailure("Expected a value but got '" + actual() + "'"));
438 | }
439 | });
440 | };
441 |
442 | Riot.Assertion.prototype = {
443 | setAssertion: function(assertion) {
444 | this.assertion = assertion;
445 | },
446 |
447 | run: function() {
448 | var that = this;
449 | this.assertion(function() { return that.expected(); });
450 | },
451 |
452 | fail: function(message) {
453 | throw(new Riot.AssertionFailure(this.name + ': ' + message));
454 | },
455 |
456 | expected: function() {
457 | if (typeof this.expectedMemo === 'undefined') {
458 | if (typeof this.expectedValue === 'function') {
459 | try {
460 | this.expectedMemo = this.expectedValue();
461 | } catch (exception) {
462 | this.expectedValue = exception;
463 | }
464 | } else {
465 | this.expectedMemo = this.expectedValue;
466 | }
467 | }
468 | return this.expectedMemo;
469 | },
470 |
471 | // Based on http://github.com/visionmedia/jspec/blob/master/lib/jspec.js
472 | // Short-circuits early, can compare arrays
473 | isEqual: function(a, b) {
474 | if (typeof a != typeof b) return;
475 | if (a === b) return true;
476 | if (a instanceof RegExp) {
477 | return a.toString() === b.toString();
478 | }
479 | if (a instanceof Date) {
480 | return Number(a) === Number(b);
481 | }
482 | if (typeof a != 'object') return;
483 | if (a.length !== undefined) {
484 | if (a.length !== b.length) {
485 | return;
486 | } else {
487 | for (var i = 0, len = a.length; i < len; ++i) {
488 | if (!this.isEqual(a[i], b[i])) {
489 | return;
490 | }
491 | }
492 | }
493 | }
494 | for (var key in a) {
495 | if (!this.isEqual(a[key], b[key])) {
496 | return;
497 | }
498 | }
499 | return true;
500 | },
501 |
502 | /* Assertions */
503 | equals: function(expected) {
504 | this.setAssertion(function(actual) {
505 | if (!this.isEqual(actual(), expected)) {
506 | this.fail(expected + ' does not equal: ' + actual());
507 | }
508 | });
509 | },
510 |
511 | matches: function(expected) {
512 | this.setAssertion(function(actual) {
513 | if (!expected.test(actual())) {
514 | this.fail("Expected '" + actual() + "' to match '" + expected + "'");
515 | }
516 | });
517 | },
518 |
519 | raises: function(expected) {
520 | this.setAssertion(function(actual) {
521 | try {
522 | actual();
523 | return;
524 | } catch (exception) {
525 | if (expected !== exception) {
526 | this.fail('raised ' + exception + ' instead of ' + expected);
527 | }
528 | }
529 | this.fail('did not raise ' + expected);
530 | });
531 | },
532 |
533 | typeOf: function(expected) {
534 | this.setAssertion(function(actual) {
535 | var t = typeof actual();
536 | if (t === 'object') {
537 | if (actual()) {
538 | if (typeof actual().length === 'number' &&
539 | !(actual.propertyIsEnumerable('length')) &&
540 | typeof actual().splice === 'function') {
541 | t = 'array';
542 | }
543 | } else {
544 | t = 'null';
545 | }
546 | }
547 |
548 | if (t !== expected.toLowerCase()) {
549 | this.fail(expected + ' is not a type of ' + actual());
550 | }
551 | });
552 | },
553 |
554 | isTrue: function() {
555 | this.setAssertion(function(actual) {
556 | if (actual() !== true) {
557 | this.fail(actual() + ' was not true');
558 | }
559 | });
560 | },
561 |
562 | isFalse: function() {
563 | this.setAssertion(function(actual) {
564 | if (actual() !== false) {
565 | this.fail(actual() + ' was not false');
566 | }
567 | });
568 | },
569 |
570 | isNull: function() {
571 | this.setAssertion(function(actual) {
572 | if (actual() !== null) {
573 | this.fail(actual() + ' was not null');
574 | }
575 | });
576 | },
577 |
578 | isNotNull: function() {
579 | this.setAssertion(function(actual) {
580 | if (actual() === null) {
581 | this.fail(actual() + ' was null');
582 | }
583 | });
584 | }
585 | };
586 |
587 | if (typeof global.Riot === 'undefined') {
588 | global.Riot = Riot;
589 |
590 | if (typeof global.load === 'undefined') {
591 | global.load = function() { };
592 | }
593 | }
594 | })(typeof window === 'undefined' ? this : window);
595 |
--------------------------------------------------------------------------------