UNPKG

lettuce

Version:

Lettuce JS, Mini Mobile Framework for Romantic with DSL.

624 lines (536 loc) 16.8 kB
/*jslint white: false plusplus: false onevar: false browser: true evil: true*/ /*riotGlobal window: true*/ (function(riotGlobal) { var Riot = { results: [], contexts: [], run: function(tests) { switch (Riot.detectEnvironment()) { case 'xpcomcore': Riot.formatter = new Riot.Formatters.XPComCore(); Riot.runAndReport(tests); Sys.exit(Riot.exitCode); break; case 'rhino': Riot.formatter = new Riot.Formatters.Text(); Riot.runAndReport(tests); java.lang.System.exit(Riot.exitCode); break; case 'node': Riot.formatter = new Riot.Formatters.Text(); Riot.runAndReport(tests); // TODO: exit with exit code from riot break; case 'non-browser-interpreter': Riot.formatter = new Riot.Formatters.Text(); Riot.runAndReport(tests); if (typeof quit !== 'undefined') { quit(Riot.exitCode); } break; case 'browser': Riot.formatter = new Riot.Formatters.HTML(); if (typeof window.onload === 'undefined' || window.onload == null) { Riot.browserAutoLoad(tests); } break; } }, browserAutoLoad: function(tests) { var timer; function fireContentLoadedEvent() { if (document.loaded) return; if (timer) window.clearTimeout(timer); document.loaded = true; if (Riot.requiredFiles.length > 0) { Riot.loadBrowserScripts(Riot.requiredFiles, tests); } else { Riot.runAndReport(tests); } } function checkReadyState() { if (document.readyState === 'complete') { document.detachEvent('readystatechange', checkReadyState); fireContentLoadedEvent(); } } function pollDoScroll() { try { document.documentElement.doScroll('left'); } catch(e) { timer = setTimeout(pollDoScroll, 10); return; } fireContentLoadedEvent(); } if (document.addEventListener) { document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { document.attachEvent('readystatechange', checkReadyState); if (window == top) timer = setTimeout(pollDoScroll, 10); } window.onload = fireContentLoadedEvent; }, loadBrowserScripts: function(files, tests) { var i, file; function loadBrowserScript(src, callback) { var script = document.createElement('script'), head = document.getElementsByTagName('head')[0], readyState; script.setAttribute('type', 'text/javascript'); script.setAttribute('src', src); script.onload = script.onreadystatechange = function() { if (!(readyState = script.readyState) || /loaded|complete/.test(readyState)) { script.onload = script.onreadystatechange = null; head.removeChild(script); if (callback) { setTimeout(callback, 1); } } }; head.insertBefore(script, head.firstChild); } if (files.length > 1) { file = files[0]; loadBrowserScript(file, function() { Riot.loadBrowserScripts(files.slice(1), tests); }); } else { file = files[0]; loadBrowserScript(file, function() { Riot.runAndReport(tests); }); } }, load: function() { switch (Riot.detectEnvironment()) { case 'xpcomcore': case 'rhino': case 'non-browser-interpreter': load(arguments[0]); break; case 'node': // Evaluate the required code in the global context, like load() would global.eval.call(global, Riot.node.fs.readFileSync(arguments[0]).toString()); break; case 'browser': var script = document.createElement('script'), head = document.getElementsByTagName('head'); script.setAttribute('type', 'text/javascript'); script.setAttribute('src', arguments[0]); head[0].insertBefore(script, head.firstChild); break; } }, requiredFiles: [], indexOf: function(array, value) { for (var i = 0; i < array.length; i++) { if (array[i] === value) { return i; } } return -1; }, require: function() { if (this.indexOf(this.requiredFiles, arguments[0]) == -1) { this.requiredFiles.push(arguments[0]); if (Riot.detectEnvironment() !== 'browser') { this.load(arguments[0]); } } }, detectEnvironment: function() { if (typeof this.env !== 'undefined') { return this.env; } this.env = (function() { if (typeof XPCOMCore !== 'undefined') { Riot.puts = print; return 'xpcomcore'; } else if (typeof window === 'undefined' && typeof java !== 'undefined') { Riot.puts = print; return 'rhino'; } else if (typeof exports !== 'undefined') { // TODO: Node should be checked more thoroughly Riot.node = { fs: require('fs'), sys: require('sys') } Riot.puts = Riot.node.sys.puts; return 'node'; } else if (typeof window === 'undefined') { Riot.puts = print; return 'non-browser-interpreter'; } else { return 'browser'; } })(); return this.env; }, runAndReport: function(tests) { this.running = true; var benchmark = Riot.Benchmark.run(1, function() { Riot.runAllContexts(tests); }); Riot.formatter.separator(); Riot.summariseAllResults(); Riot.formatter.line(benchmark); this.running = false; }, runAllContexts: function(tests) { if (typeof tests !== 'undefined') { this.withDSL(tests)(); } for (var i = 0; i < this.contexts.length; i++) { this.contexts[i].run(); } }, functionBody: function(fn) { return '(' + fn.toString().replace(/\s+$/, '') + ')()'; }, withDSL: function(fn, context) { var body = this.functionBody(fn), f = new Function('context', 'given', 'asserts', 'should', 'setup', 'teardown', body), args = [ Riot.context, Riot.given, function() { return context.asserts.apply(context, arguments); }, function() { return context.should.apply(context, arguments); }, function() { return context.setup.apply(context, arguments); }, function() { return context.teardown.apply(context, arguments); } ]; return function() { f.apply(Riot, args); }; }, context: function(title, callback) { var context = new Riot.Context(title, callback); if (this.running) { context.run(); } else { Riot.contexts.push(context); } return context; }, given: function(title, callback) { title = 'Given ' + title; return Riot.context(title, callback); }, summariseAllResults: function() { return this.summarise(this.results); }, summarise: function(results) { var failures = 0; for (var i = 0; i < results.length; i++) { if (!results[i].pass) { failures++; } } this.formatter.line(results.length + ' assertions: ' + failures + ' failures'); this.exitCode = failures > 0 ? 1 : 0; }, addResult: function(context, assertion, pass) { var result = { assertion: assertion, pass: pass, context: context }; this.results.push(result); } }; Riot.Benchmark = { results: [], addResult: function(start, end) { this.results.push(end - start); }, displayResults: function() { var total = 0, seconds = 0, i = 0; for (i = 0; i < this.results.length; i++) { total += this.results[i]; } seconds = total / 1000; return 'Elapsed time: ' + total + 'ms (' + seconds + ' seconds)'; }, run: function(times, callback) { this.results = []; for (var i = 0; i < times; i++) { var start = new Date(), end = null; callback(); end = new Date(); this.addResult(start, end); } return this.displayResults(); } }; Riot.Formatters = { HTML: function() { function display(html) { var results = document.getElementById('test-results'); results.innerHTML += html; } this.line = function(text) { display('<p>' + text + '</p>'); }; this.pass = function(message) { display('<p class="pass">' + message + '</p>'); }; this.fail = function(message) { display('<p class="fail">' + message + '</p>'); }; this.error = function(message, exception) { this.fail(message); display('<p class="exception">Exception: ' + exception + '</p>'); }; this.context = function(name) { display('<h3>' + name + '</h3>'); }; this.given = function(name) { display('<h4>' + name + '</h4>'); }; this.separator = function() { display('<hr />'); }; }, Text: function() { function display(text) { Riot.puts(text); } this.line = function(text) { display(text); }; this.pass = function(message) { this.line(' +\033[32m ' + message + '\033[0m'); }; this.fail = function(message) { this.line(' -\033[31m ' + message + '\033[0m'); }; this.error = function(message, exception) { this.fail(message); this.line(' Exception: ' + exception); }; this.context = function(name) { this.line(name); }; this.given = function(name) { this.line(name); }; this.separator = function() { this.line(''); }; }, XPComCore: function() { var formatter = new Riot.Formatters.Text(); formatter.line = function(text) { puts(text); }; return formatter; } }; Riot.Context = function(name, callback) { this.name = name; this.callback = callback; this.assertions = []; }; Riot.Context.prototype = { asserts: function(name, result) { var assertion = new Riot.Assertion(this.name, name, result); this.assertions.push(assertion); return assertion; }, should: function(name, result) { return this.asserts('should ' + name, result); }, setup: function(setupFunction) { this.setupFunction = setupFunction; }, teardown: function(teardownFunction) { this.teardownFunction = teardownFunction; }, runSetup: function() { if (typeof this.setupFunction !== 'undefined') { return this.setupFunction(); } }, runTeardown: function() { if (typeof this.teardownFunction !== 'undefined') { return this.teardownFunction(); } }, formatContextName: function() { if (this.name.match(/^Given/)) { Riot.formatter.given(this.name); } else { Riot.formatter.context(this.name); } }, run: function() { this.formatContextName(); Riot.withDSL(this.callback, this)(); this.runSetup(); for (var i = 0; i < this.assertions.length; i++) { var pass = false, assertion = this.assertions[i]; try { assertion.run(); pass = true; Riot.formatter.pass(assertion.name); } catch (e) { if (typeof e.name !== 'undefined' && e.name === 'Riot.AssertionFailure') { Riot.formatter.fail(e.message); } else { Riot.formatter.error(assertion.name, e); } } Riot.addResult(this.name, assertion.name, pass); } this.runTeardown(); } }; Riot.AssertionFailure = function(message) { var error = new Error(message); error.name = 'Riot.AssertionFailure'; return error; }; Riot.Assertion = function(contextName, name, expected) { this.name = name; this.expectedValue = expected; this.contextName = contextName; this.kindOf = this.typeOf; this.isTypeOf = this.typeOf; this.setAssertion(function(actual) { if ((actual() === null) || (actual() === undefined)) { throw(new Riot.AssertionFailure("Expected a value but got '" + actual() + "'")); } }); }; Riot.Assertion.prototype = { setAssertion: function(assertion) { this.assertion = assertion; }, run: function() { var that = this; this.assertion(function() { return that.expected(); }); }, fail: function(message) { throw(new Riot.AssertionFailure(this.name + ': ' + message)); }, expected: function() { if (typeof this.expectedMemo === 'undefined') { if (typeof this.expectedValue === 'function') { try { this.expectedMemo = this.expectedValue(); } catch (exception) { this.expectedValue = exception; } } else { this.expectedMemo = this.expectedValue; } } return this.expectedMemo; }, // Based on http://github.com/visionmedia/jspec/blob/master/lib/jspec.js // Short-circuits early, can compare arrays isEqual: function(a, b) { if (typeof a != typeof b) return; if (a === b) return true; if (a instanceof RegExp) { return a.toString() === b.toString(); } if (a instanceof Date) { return Number(a) === Number(b); } if (typeof a != 'object') return; if (a.length !== undefined) { if (a.length !== b.length) { return; } else { for (var i = 0, len = a.length; i < len; ++i) { if (!this.isEqual(a[i], b[i])) { return; } } } } for (var key in b) { if (!this.isEqual(a[key], b[key])) { return; } } return true; }, /* Assertions */ equals: function(expected) { this.setAssertion(function(actual) { if (!this.isEqual(actual(), expected)) { this.fail(expected + ' does not equal: ' + actual()); } }); }, matches: function(expected) { this.setAssertion(function(actual) { if (!expected.test(actual())) { this.fail("Expected '" + actual() + "' to match '" + expected + "'"); } }); }, raises: function(expected) { this.setAssertion(function(actual) { try { actual(); return; } catch (exception) { if (expected !== exception) { this.fail('raised ' + exception + ' instead of ' + expected); } } this.fail('did not raise ' + expected); }); }, typeOf: function(expected) { this.setAssertion(function(actual) { var t = typeof actual(); if (t === 'object') { if (actual()) { if (typeof actual().length === 'number' && !(actual.propertyIsEnumerable('length')) && typeof actual().splice === 'function') { t = 'array'; } } else { t = 'null'; } } if (t !== expected.toLowerCase()) { this.fail(expected + ' is not a type of ' + actual()); } }); }, isTrue: function() { this.setAssertion(function(actual) { if (actual() !== true) { this.fail(actual() + ' was not true'); } }); }, isFalse: function() { this.setAssertion(function(actual) { if (actual() !== false) { this.fail(actual() + ' was not false'); } }); }, isNull: function() { this.setAssertion(function(actual) { if (actual() !== null) { this.fail(actual() + ' was not null'); } }); }, isNotNull: function() { this.setAssertion(function(actual) { if (actual() === null) { this.fail(actual() + ' was null'); } }); } }; if (typeof exports !== 'undefined') { exports.Riot = Riot; } else if (typeof riotGlobal.Riot === 'undefined') { riotGlobal.Riot = Riot; if (typeof riotGlobal.load === 'undefined') { riotGlobal.load = function() { }; } } })(typeof window === 'undefined' ? this : window);