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