UNPKG

smoosic

Version:

<sub>[Github site](https://github.com/Smoosic/smoosic) | [source documentation](https://smoosic.github.io/Smoosic/release/docs/modules.html) | [change notes](https://aarondavidnewman.github.io/Smoosic/changes.html) | [application](https://smoosic.github.i

1,731 lines (1,455 loc) 126 kB
/*! * QUnit 1.19.0 * http://qunitjs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * * Date: 2015-09-01T15:00Z */ (function (global) { var QUnit = {}; var Date = global.Date; var now = Date.now || function () { return new Date().getTime(); }; var setTimeout = global.setTimeout; var clearTimeout = global.clearTimeout; // Store a local window from the global to allow direct references. var window = global.window; var defined = { document: window && window.document !== undefined, setTimeout: setTimeout !== undefined, sessionStorage: (function () { var x = 'qunit-test-string'; try { sessionStorage.setItem(x, x); sessionStorage.removeItem(x); return true; } catch (e) { return false; } })(), }; var fileName = (sourceFromStacktrace(0) || '').replace(/(:\d+)+\)?/, '').replace(/.+\//, ''); var globalStartCalled = false; var runStarted = false; var toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty; // returns a new Array with the elements that are in a but not in b function diff(a, b) { var i, j, result = a.slice(); for (i = 0; i < result.length; i++) { for (j = 0; j < b.length; j++) { if (result[i] === b[j]) { result.splice(i, 1); i--; break; } } } return result; } // from jquery.js function inArray(elem, array) { if (array.indexOf) { return array.indexOf(elem); } for (var i = 0, length = array.length; i < length; i++) { if (array[i] === elem) { return i; } } return -1; } /** * Makes a clone of an object using only Array or Object as base, * and copies over the own enumerable properties. * * @param {Object} obj * @return {Object} New object with only the own properties (recursively). */ function objectValues(obj) { var key, val, vals = QUnit.is('array', obj) ? [] : {}; for (key in obj) { if (hasOwn.call(obj, key)) { val = obj[key]; vals[key] = val === Object(val) ? objectValues(val) : val; } } return vals; } function extend(a, b, undefOnly) { for (var prop in b) { if (hasOwn.call(b, prop)) { // Avoid "Member not found" error in IE8 caused by messing with window.constructor // This block runs on every environment, so `global` is being used instead of `window` // to avoid errors on node. if (prop !== 'constructor' || a !== global) { if (b[prop] === undefined) { delete a[prop]; } else if (!(undefOnly && typeof a[prop] !== 'undefined')) { a[prop] = b[prop]; } } } } return a; } function objectType(obj) { if (typeof obj === 'undefined') { return 'undefined'; } // Consider: typeof null === object if (obj === null) { return 'null'; } var match = toString.call(obj).match(/^\[object\s(.*)\]$/), type = (match && match[1]) || ''; switch (type) { case 'Number': if (isNaN(obj)) { return 'nan'; } return 'number'; case 'String': case 'Boolean': case 'Array': case 'Set': case 'Map': case 'Date': case 'RegExp': case 'Function': return type.toLowerCase(); } if (typeof obj === 'object') { return 'object'; } return undefined; } // Safe object type checking function is(type, obj) { return QUnit.objectType(obj) === type; } var getUrlParams = function () { var i, current; var urlParams = {}; var location = window.location; var params = location.search.slice(1).split('&'); var length = params.length; if (params[0]) { for (i = 0; i < length; i++) { current = params[i].split('='); current[0] = decodeURIComponent(current[0]); // allow just a key to turn on a flag, e.g., test.html?noglobals current[1] = current[1] ? decodeURIComponent(current[1]) : true; if (urlParams[current[0]]) { urlParams[current[0]] = [].concat(urlParams[current[0]], current[1]); } else { urlParams[current[0]] = current[1]; } } } return urlParams; }; // Doesn't support IE6 to IE9, it will return undefined on these browsers // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack function extractStacktrace(e, offset) { offset = offset === undefined ? 4 : offset; var stack, include, i; if (e.stack) { stack = e.stack.split('\n'); if (/^error$/i.test(stack[0])) { stack.shift(); } if (fileName) { include = []; for (i = offset; i < stack.length; i++) { if (stack[i].indexOf(fileName) !== -1) { break; } include.push(stack[i]); } if (include.length) { return include.join('\n'); } } return stack[offset]; // Support: Safari <=6 only } else if (e.sourceURL) { // exclude useless self-reference for generated Error objects if (/qunit.js$/.test(e.sourceURL)) { return; } // for actual exceptions, this is useful return e.sourceURL + ':' + e.line; } } function sourceFromStacktrace(offset) { var error = new Error(); // Support: Safari <=7 only, IE <=10 - 11 only // Not all browsers generate the `stack` property for `new Error()`, see also #636 if (!error.stack) { try { throw error; } catch (err) { error = err; } } return extractStacktrace(error, offset); } /** * Config object: Maintain internal state * Later exposed as QUnit.config * `config` initialized at top of scope */ var config = { // The queue of tests to run queue: [], // block until document ready blocking: true, // by default, run previously failed tests first // very useful in combination with "Hide passed tests" checked reorder: true, // by default, modify document.title when suite is done altertitle: true, // by default, scroll to top of the page when suite is done scrolltop: true, // depth up-to which object will be dumped maxDepth: 5, // when enabled, all tests must call expect() requireExpects: false, // add checkboxes that are persisted in the query-string // when enabled, the id is set to `true` as a `QUnit.config` property urlConfig: [ { id: 'hidepassed', label: 'Hide passed tests', tooltip: 'Only show tests and assertions that fail. Stored as query-strings.', }, { id: 'noglobals', label: 'Check for Globals', tooltip: 'Enabling this will test if any test introduces new properties on the ' + 'global object (`window` in Browsers). Stored as query-strings.', }, { id: 'notrycatch', label: 'No try-catch', tooltip: 'Enabling this will run tests outside of a try-catch block. Makes debugging ' + 'exceptions in IE reasonable. Stored as query-strings.', }, ], // Set of all modules. modules: [], // The first unnamed module currentModule: { name: '', tests: [], }, callbacks: {}, }; var urlParams = defined.document ? getUrlParams() : {}; // Push a loose unnamed module to the modules collection config.modules.push(config.currentModule); if (urlParams.filter === true) { delete urlParams.filter; } // String search anywhere in moduleName+testName config.filter = urlParams.filter; config.testId = []; if (urlParams.testId) { // Ensure that urlParams.testId is an array urlParams.testId = decodeURIComponent(urlParams.testId).split(','); for (var i = 0; i < urlParams.testId.length; i++) { config.testId.push(urlParams.testId[i]); } } var loggingCallbacks = {}; // Register logging callbacks function registerLoggingCallbacks(obj) { var i, l, key, callbackNames = ['begin', 'done', 'log', 'testStart', 'testDone', 'moduleStart', 'moduleDone']; function registerLoggingCallback(key) { var loggingCallback = function (callback) { if (objectType(callback) !== 'function') { throw new Error('QUnit logging methods require a callback function as their first parameters.'); } config.callbacks[key].push(callback); }; // DEPRECATED: This will be removed on QUnit 2.0.0+ // Stores the registered functions allowing restoring // at verifyLoggingCallbacks() if modified loggingCallbacks[key] = loggingCallback; return loggingCallback; } for (i = 0, l = callbackNames.length; i < l; i++) { key = callbackNames[i]; // Initialize key collection of logging callback if (objectType(config.callbacks[key]) === 'undefined') { config.callbacks[key] = []; } obj[key] = registerLoggingCallback(key); } } function runLoggingCallbacks(key, args) { var i, l, callbacks; callbacks = config.callbacks[key]; for (i = 0, l = callbacks.length; i < l; i++) { callbacks[i](args); } } // DEPRECATED: This will be removed on 2.0.0+ // This function verifies if the loggingCallbacks were modified by the user // If so, it will restore it, assign the given callback and print a console warning function verifyLoggingCallbacks() { var loggingCallback, userCallback; for (loggingCallback in loggingCallbacks) { if (QUnit[loggingCallback] !== loggingCallbacks[loggingCallback]) { userCallback = QUnit[loggingCallback]; // Restore the callback function QUnit[loggingCallback] = loggingCallbacks[loggingCallback]; // Assign the deprecated given callback QUnit[loggingCallback](userCallback); if (global.console && global.console.warn) { global.console.warn( 'QUnit.' + loggingCallback + ' was replaced with a new value.\n' + 'Please, check out the documentation on how to apply logging callbacks.\n' + 'Reference: http://api.qunitjs.com/category/callbacks/' ); } } } } (function () { if (!defined.document) { return; } // `onErrorFnPrev` initialized at top of scope // Preserve other handlers var onErrorFnPrev = window.onerror; // Cover uncaught exceptions // Returning true will suppress the default browser handler, // returning false will let it run. window.onerror = function (error, filePath, linerNr) { var ret = false; if (onErrorFnPrev) { ret = onErrorFnPrev(error, filePath, linerNr); } // Treat return value as window.onerror itself does, // Only do our handling if not suppressed. if (ret !== true) { if (QUnit.config.current) { if (QUnit.config.current.ignoreGlobalErrors) { return true; } QUnit.pushFailure(error, filePath + ':' + linerNr); } else { QUnit.test( 'global failure', extend( function () { QUnit.pushFailure(error, filePath + ':' + linerNr); }, { validTest: true } ) ); } return false; } return ret; }; })(); QUnit.urlParams = urlParams; // Figure out if we're running the tests from a server or not QUnit.isLocal = !(defined.document && window.location.protocol !== 'file:'); // Expose the current QUnit version QUnit.version = '1.19.0'; extend(QUnit, { // call on start of module test to prepend name to all tests module: function (name, testEnvironment) { var currentModule = { name: name, testEnvironment: testEnvironment, tests: [], }; // DEPRECATED: handles setup/teardown functions, // beforeEach and afterEach should be used instead if (testEnvironment && testEnvironment.setup) { testEnvironment.beforeEach = testEnvironment.setup; delete testEnvironment.setup; } if (testEnvironment && testEnvironment.teardown) { testEnvironment.afterEach = testEnvironment.teardown; delete testEnvironment.teardown; } config.modules.push(currentModule); config.currentModule = currentModule; }, // DEPRECATED: QUnit.asyncTest() will be removed in QUnit 2.0. asyncTest: asyncTest, test: test, skip: skip, // DEPRECATED: The functionality of QUnit.start() will be altered in QUnit 2.0. // In QUnit 2.0, invoking it will ONLY affect the `QUnit.config.autostart` blocking behavior. start: function (count) { var globalStartAlreadyCalled = globalStartCalled; if (!config.current) { globalStartCalled = true; if (runStarted) { throw new Error('Called start() outside of a test context while already started'); } else if (globalStartAlreadyCalled || count > 1) { throw new Error('Called start() outside of a test context too many times'); } else if (config.autostart) { throw new Error('Called start() outside of a test context when ' + 'QUnit.config.autostart was true'); } else if (!config.pageLoaded) { // The page isn't completely loaded yet, so bail out and let `QUnit.load` handle it config.autostart = true; return; } } else { // If a test is running, adjust its semaphore config.current.semaphore -= count || 1; // Don't start until equal number of stop-calls if (config.current.semaphore > 0) { return; } // throw an Error if start is called more often than stop if (config.current.semaphore < 0) { config.current.semaphore = 0; QUnit.pushFailure( "Called start() while already started (test's semaphore was 0 already)", sourceFromStacktrace(2) ); return; } } resumeProcessing(); }, // DEPRECATED: QUnit.stop() will be removed in QUnit 2.0. stop: function (count) { // If there isn't a test running, don't allow QUnit.stop() to be called if (!config.current) { throw new Error('Called stop() outside of a test context'); } // If a test is running, adjust its semaphore config.current.semaphore += count || 1; pauseProcessing(); }, config: config, is: is, objectType: objectType, extend: extend, load: function () { config.pageLoaded = true; // Initialize the configuration options extend( config, { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, started: 0, updateRate: 1000, autostart: true, filter: '', }, true ); config.blocking = false; if (config.autostart) { resumeProcessing(); } }, stack: function (offset) { offset = (offset || 0) + 2; return sourceFromStacktrace(offset); }, }); registerLoggingCallbacks(QUnit); function begin() { var i, l, modulesLog = []; // If the test run hasn't officially begun yet if (!config.started) { // Record the time of the test run's beginning config.started = now(); verifyLoggingCallbacks(); // Delete the loose unnamed module if unused. if (config.modules[0].name === '' && config.modules[0].tests.length === 0) { config.modules.shift(); } // Avoid unnecessary information by not logging modules' test environments for (i = 0, l = config.modules.length; i < l; i++) { modulesLog.push({ name: config.modules[i].name, tests: config.modules[i].tests, }); } // The test run is officially beginning now runLoggingCallbacks('begin', { totalTests: Test.count, modules: modulesLog, }); } config.blocking = false; process(true); } function process(last) { function next() { process(last); } var start = now(); config.depth = (config.depth || 0) + 1; while (config.queue.length && !config.blocking) { if (!defined.setTimeout || config.updateRate <= 0 || now() - start < config.updateRate) { if (config.current) { // Reset async tracking for each phase of the Test lifecycle config.current.usedAsync = false; } config.queue.shift()(); } else { setTimeout(next, 13); break; } } config.depth--; if (last && !config.blocking && !config.queue.length && config.depth === 0) { done(); } } function pauseProcessing() { config.blocking = true; if (config.testTimeout && defined.setTimeout) { clearTimeout(config.timeout); config.timeout = setTimeout(function () { if (config.current) { config.current.semaphore = 0; QUnit.pushFailure('Test timed out', sourceFromStacktrace(2)); } else { throw new Error('Test timed out'); } resumeProcessing(); }, config.testTimeout); } } function resumeProcessing() { runStarted = true; // A slight delay to allow this iteration of the event loop to finish (more assertions, etc.) if (defined.setTimeout) { setTimeout(function () { if (config.current && config.current.semaphore > 0) { return; } if (config.timeout) { clearTimeout(config.timeout); } begin(); }, 13); } else { begin(); } } function done() { var runtime, passed; config.autorun = true; // Log the last module results if (config.previousModule) { runLoggingCallbacks('moduleDone', { name: config.previousModule.name, tests: config.previousModule.tests, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all, runtime: now() - config.moduleStats.started, }); } delete config.previousModule; runtime = now() - config.started; passed = config.stats.all - config.stats.bad; runLoggingCallbacks('done', { failed: config.stats.bad, passed: passed, total: config.stats.all, runtime: runtime, }); } function Test(settings) { var i, l; ++Test.count; extend(this, settings); this.assertions = []; this.semaphore = 0; this.usedAsync = false; this.module = config.currentModule; this.stack = sourceFromStacktrace(3); // Register unique strings for (i = 0, l = this.module.tests; i < l.length; i++) { if (this.module.tests[i].name === this.testName) { this.testName += ' '; } } this.testId = generateHash(this.module.name, this.testName); this.module.tests.push({ name: this.testName, testId: this.testId, }); if (settings.skip) { // Skipped tests will fully ignore any sent callback this.callback = function () {}; this.async = false; this.expected = 0; } else { this.assert = new Assert(this); } } Test.count = 0; Test.prototype = { before: function () { if ( // Emit moduleStart when we're switching from one module to another this.module !== config.previousModule || // They could be equal (both undefined) but if the previousModule property doesn't // yet exist it means this is the first test in a suite that isn't wrapped in a // module, in which case we'll just emit a moduleStart event for 'undefined'. // Without this, reporters can get testStart before moduleStart which is a problem. !hasOwn.call(config, 'previousModule') ) { if (hasOwn.call(config, 'previousModule')) { runLoggingCallbacks('moduleDone', { name: config.previousModule.name, tests: config.previousModule.tests, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all, runtime: now() - config.moduleStats.started, }); } config.previousModule = this.module; config.moduleStats = { all: 0, bad: 0, started: now() }; runLoggingCallbacks('moduleStart', { name: this.module.name, tests: this.module.tests, }); } config.current = this; if (this.module.testEnvironment) { delete this.module.testEnvironment.beforeEach; delete this.module.testEnvironment.afterEach; } this.testEnvironment = extend({}, this.module.testEnvironment); this.started = now(); runLoggingCallbacks('testStart', { name: this.testName, module: this.module.name, testId: this.testId, }); if (!config.pollution) { saveGlobal(); } }, run: function () { var promise; config.current = this; if (this.async) { QUnit.stop(); } this.callbackStarted = now(); if (config.notrycatch) { promise = this.callback.call(this.testEnvironment, this.assert); this.resolvePromise(promise); return; } try { promise = this.callback.call(this.testEnvironment, this.assert); this.resolvePromise(promise); } catch (e) { this.pushFailure( 'Died on test #' + (this.assertions.length + 1) + ' ' + this.stack + ': ' + (e.message || e), extractStacktrace(e, 0) ); // else next test will carry the responsibility saveGlobal(); // Restart the tests if they're blocking if (config.blocking) { QUnit.start(); } } }, after: function () { checkPollution(); }, queueHook: function (hook, hookName) { var promise, test = this; return function runHook() { config.current = test; if (config.notrycatch) { promise = hook.call(test.testEnvironment, test.assert); test.resolvePromise(promise, hookName); return; } try { promise = hook.call(test.testEnvironment, test.assert); test.resolvePromise(promise, hookName); } catch (error) { test.pushFailure( hookName + ' failed on ' + test.testName + ': ' + (error.message || error), extractStacktrace(error, 0) ); } }; }, // Currently only used for module level hooks, can be used to add global level ones hooks: function (handler) { var hooks = []; // Hooks are ignored on skipped tests if (this.skip) { return hooks; } if (this.module.testEnvironment && QUnit.objectType(this.module.testEnvironment[handler]) === 'function') { hooks.push(this.queueHook(this.module.testEnvironment[handler], handler)); } return hooks; }, finish: function () { config.current = this; if (config.requireExpects && this.expected === null) { this.pushFailure('Expected number of assertions to be defined, but expect() was ' + 'not called.', this.stack); } else if (this.expected !== null && this.expected !== this.assertions.length) { this.pushFailure( 'Expected ' + this.expected + ' assertions, but ' + this.assertions.length + ' were run', this.stack ); } else if (this.expected === null && !this.assertions.length) { this.pushFailure( 'Expected at least one assertion, but none were run - call ' + 'expect(0) to accept zero assertions.', this.stack ); } var i, bad = 0; this.runtime = now() - this.started; config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; for (i = 0; i < this.assertions.length; i++) { if (!this.assertions[i].result) { bad++; config.stats.bad++; config.moduleStats.bad++; } } runLoggingCallbacks('testDone', { name: this.testName, module: this.module.name, skipped: !!this.skip, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length, runtime: this.runtime, // HTML Reporter use assertions: this.assertions, testId: this.testId, // Source of Test source: this.stack, // DEPRECATED: this property will be removed in 2.0.0, use runtime instead duration: this.runtime, }); // QUnit.reset() is deprecated and will be replaced for a new // fixture reset function on QUnit 2.0/2.1. // It's still called here for backwards compatibility handling QUnit.reset(); config.current = undefined; }, queue: function () { var bad, test = this; if (!this.valid()) { return; } function run() { // each of these can by async synchronize([ function () { test.before(); }, test.hooks('beforeEach'), function () { test.run(); }, test.hooks('afterEach').reverse(), function () { test.after(); }, function () { test.finish(); }, ]); } // `bad` initialized at top of scope // defer when previous test run passed, if storage is available bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem('qunit-test-' + this.module.name + '-' + this.testName); if (bad) { run(); } else { synchronize(run, true); } }, push: function (result, actual, expected, message, negative) { var source, details = { module: this.module.name, name: this.testName, result: result, message: message, actual: actual, expected: expected, testId: this.testId, negative: negative || false, runtime: now() - this.started, }; if (!result) { source = sourceFromStacktrace(); if (source) { details.source = source; } } runLoggingCallbacks('log', details); this.assertions.push({ result: !!result, message: message, }); }, pushFailure: function (message, source, actual) { if (!(this instanceof Test)) { throw new Error('pushFailure() assertion outside test context, was ' + sourceFromStacktrace(2)); } var details = { module: this.module.name, name: this.testName, result: false, message: message || 'error', actual: actual || null, testId: this.testId, runtime: now() - this.started, }; if (source) { details.source = source; } runLoggingCallbacks('log', details); this.assertions.push({ result: false, message: message, }); }, resolvePromise: function (promise, phase) { var then, message, test = this; if (promise != null) { then = promise.then; if (QUnit.objectType(then) === 'function') { QUnit.stop(); then.call( promise, function () { QUnit.start(); }, function (error) { message = 'Promise rejected ' + (!phase ? 'during' : phase.replace(/Each$/, '')) + ' ' + test.testName + ': ' + (error.message || error); test.pushFailure(message, extractStacktrace(error, 0)); // else next test will carry the responsibility saveGlobal(); // Unblock QUnit.start(); } ); } } }, valid: function () { var include, filter = config.filter && config.filter.toLowerCase(), module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), fullName = (this.module.name + ': ' + this.testName).toLowerCase(); // Internally-generated tests are always valid if (this.callback && this.callback.validTest) { return true; } if (config.testId.length > 0 && inArray(this.testId, config.testId) < 0) { return false; } if (module && (!this.module.name || this.module.name.toLowerCase() !== module)) { return false; } if (!filter) { return true; } include = filter.charAt(0) !== '!'; if (!include) { filter = filter.slice(1); } // If the filter matches, we need to honour include if (fullName.indexOf(filter) !== -1) { return include; } // Otherwise, do the opposite return !include; }, }; // Resets the test setup. Useful for tests that modify the DOM. /* DEPRECATED: Use multiple tests instead of resetting inside a test. Use testStart or testDone for custom cleanup. This method will throw an error in 2.0, and will be removed in 2.1 */ QUnit.reset = function () { // Return on non-browser environments // This is necessary to not break on node tests if (!defined.document) { return; } var fixture = defined.document && document.getElementById && document.getElementById('qunit-fixture'); if (fixture) { fixture.innerHTML = config.fixture; } }; QUnit.pushFailure = function () { if (!QUnit.config.current) { throw new Error('pushFailure() assertion outside test context, in ' + sourceFromStacktrace(2)); } // Gets current test obj var currentTest = QUnit.config.current; return currentTest.pushFailure.apply(currentTest, arguments); }; // Based on Java's String.hashCode, a simple but not // rigorously collision resistant hashing function function generateHash(module, testName) { var hex, i = 0, hash = 0, str = module + '\x1C' + testName, len = str.length; for (; i < len; i++) { hash = (hash << 5) - hash + str.charCodeAt(i); hash |= 0; } // Convert the possibly negative integer hash code into an 8 character hex string, which isn't // strictly necessary but increases user understanding that the id is a SHA-like hash hex = (0x100000000 + hash).toString(16); if (hex.length < 8) { hex = '0000000' + hex; } return hex.slice(-8); } function synchronize(callback, last) { if (QUnit.objectType(callback) === 'array') { while (callback.length) { synchronize(callback.shift()); } return; } config.queue.push(callback); if (config.autorun && !config.blocking) { process(last); } } function saveGlobal() { config.pollution = []; if (config.noglobals) { for (var key in global) { if (hasOwn.call(global, key)) { // in Opera sometimes DOM element ids show up here, ignore them if (/^qunit-test-output/.test(key)) { continue; } config.pollution.push(key); } } } } function checkPollution() { var newGlobals, deletedGlobals, old = config.pollution; saveGlobal(); newGlobals = diff(config.pollution, old); if (newGlobals.length > 0) { QUnit.pushFailure('Introduced global variable(s): ' + newGlobals.join(', ')); } deletedGlobals = diff(old, config.pollution); if (deletedGlobals.length > 0) { QUnit.pushFailure('Deleted global variable(s): ' + deletedGlobals.join(', ')); } } // Will be exposed as QUnit.asyncTest function asyncTest(testName, expected, callback) { if (arguments.length === 2) { callback = expected; expected = null; } QUnit.test(testName, expected, callback, true); } // Will be exposed as QUnit.test function test(testName, expected, callback, async) { var newTest; if (arguments.length === 2) { callback = expected; expected = null; } newTest = new Test({ testName: testName, expected: expected, async: async, callback: callback, }); newTest.queue(); } // Will be exposed as QUnit.skip function skip(testName) { var test = new Test({ testName: testName, skip: true, }); test.queue(); } function Assert(testContext) { this.test = testContext; } // Assert helpers QUnit.assert = Assert.prototype = { // Specify the number of expected assertions to guarantee that failed test // (no assertions are run at all) don't slip through. expect: function (asserts) { if (arguments.length === 1) { this.test.expected = asserts; } else { return this.test.expected; } }, // Increment this Test's semaphore counter, then return a single-use function that // decrements that counter a maximum of once. async: function () { var test = this.test, popped = false; test.semaphore += 1; test.usedAsync = true; pauseProcessing(); return function done() { if (!popped) { test.semaphore -= 1; popped = true; resumeProcessing(); } else { test.pushFailure('Called the callback returned from `assert.async` more than once', sourceFromStacktrace(2)); } }; }, // Exports test.push() to the user API push: function (/* result, actual, expected, message, negative */) { var assert = this, currentTest = (assert instanceof Assert && assert.test) || QUnit.config.current; // Backwards compatibility fix. // Allows the direct use of global exported assertions and QUnit.assert.* // Although, it's use is not recommended as it can leak assertions // to other tests from async tests, because we only get a reference to the current test, // not exactly the test where assertion were intended to be called. if (!currentTest) { throw new Error('assertion outside test context, in ' + sourceFromStacktrace(2)); } if (currentTest.usedAsync === true && currentTest.semaphore === 0) { currentTest.pushFailure('Assertion after the final `assert.async` was resolved', sourceFromStacktrace(2)); // Allow this assertion to continue running anyway... } if (!(assert instanceof Assert)) { assert = currentTest.assert; } return assert.test.push.apply(assert.test, arguments); }, ok: function (result, message) { message = message || (result ? 'okay' : 'failed, expected argument to be truthy, was: ' + QUnit.dump.parse(result)); this.push(!!result, result, true, message); }, notOk: function (result, message) { message = message || (!result ? 'okay' : 'failed, expected argument to be falsy, was: ' + QUnit.dump.parse(result)); this.push(!result, result, false, message, true); }, equal: function (actual, expected, message) { /*jshint eqeqeq:false */ this.push(expected == actual, actual, expected, message); }, notEqual: function (actual, expected, message) { /*jshint eqeqeq:false */ this.push(expected != actual, actual, expected, message, true); }, propEqual: function (actual, expected, message) { actual = objectValues(actual); expected = objectValues(expected); this.push(QUnit.equiv(actual, expected), actual, expected, message); }, notPropEqual: function (actual, expected, message) { actual = objectValues(actual); expected = objectValues(expected); this.push(!QUnit.equiv(actual, expected), actual, expected, message, true); }, deepEqual: function (actual, expected, message) { this.push(QUnit.equiv(actual, expected), actual, expected, message); }, notDeepEqual: function (actual, expected, message) { this.push(!QUnit.equiv(actual, expected), actual, expected, message, true); }, strictEqual: function (actual, expected, message) { this.push(expected === actual, actual, expected, message); }, notStrictEqual: function (actual, expected, message) { this.push(expected !== actual, actual, expected, message, true); }, throws: function (block, expected, message) { var actual, expectedType, expectedOutput = expected, ok = false, currentTest = (this instanceof Assert && this.test) || QUnit.config.current; // 'expected' is optional unless doing string comparison if (message == null && typeof expected === 'string') { message = expected; expected = null; } currentTest.ignoreGlobalErrors = true; try { block.call(currentTest.testEnvironment); } catch (e) { actual = e; } currentTest.ignoreGlobalErrors = false; if (actual) { expectedType = QUnit.objectType(expected); // we don't want to validate thrown error if (!expected) { ok = true; expectedOutput = null; // expected is a regexp } else if (expectedType === 'regexp') { ok = expected.test(errorString(actual)); // expected is a string } else if (expectedType === 'string') { ok = expected === errorString(actual); // expected is a constructor, maybe an Error constructor } else if (expectedType === 'function' && actual instanceof expected) { ok = true; // expected is an Error object } else if (expectedType === 'object') { ok = actual instanceof expected.constructor && actual.name === expected.name && actual.message === expected.message; // expected is a validation function which returns true if validation passed } else if (expectedType === 'function' && expected.call({}, actual) === true) { expectedOutput = null; ok = true; } } currentTest.assert.push(ok, actual, expectedOutput, message); }, }; // Provide an alternative to assert.throws(), for enviroments that consider throws a reserved word // Known to us are: Closure Compiler, Narwhal (function () { /*jshint sub:true */ Assert.prototype.raises = Assert.prototype['throws']; })(); function errorString(error) { var name, message, resultErrorString = error.toString(); if (resultErrorString.substring(0, 7) === '[object') { name = error.name ? error.name.toString() : 'Error'; message = error.message ? error.message.toString() : ''; if (name && message) { return name + ': ' + message; } else if (name) { return name; } else if (message) { return message; } else { return 'Error'; } } else { return resultErrorString; } } // Test for equality any JavaScript type. // Author: Philippe Rathé <prathe@gmail.com> QUnit.equiv = (function () { // Call the o related callback with the given arguments. function bindCallbacks(o, callbacks, args) { var prop = QUnit.objectType(o); if (prop) { if (QUnit.objectType(callbacks[prop]) === 'function') { return callbacks[prop].apply(callbacks, args); } else { return callbacks[prop]; // or undefined } } } // the real equiv function var innerEquiv, // stack to decide between skip/abort functions callers = [], // stack to avoiding loops from circular referencing parents = [], parentsB = [], getProto = Object.getPrototypeOf || function (obj) { /* jshint camelcase: false, proto: true */ return obj.__proto__; }, callbacks = (function () { // for string, boolean, number and null function useStrictEquality(b, a) { /*jshint eqeqeq:false */ if (b instanceof a.constructor || a instanceof b.constructor) { // to catch short annotation VS 'new' annotation of a // declaration // e.g. var i = 1; // var j = new Number(1); return a == b; } else { return a === b; } } return { string: useStrictEquality, boolean: useStrictEquality, number: useStrictEquality, null: useStrictEquality, undefined: useStrictEquality, nan: function (b) { return isNaN(b); }, date: function (b, a) { return QUnit.objectType(b) === 'date' && a.valueOf() === b.valueOf(); }, regexp: function (b, a) { return ( QUnit.objectType(b) === 'regexp' && // the regex itself a.source === b.source && // and its modifiers a.global === b.global && // (gmi) ... a.ignoreCase === b.ignoreCase && a.multiline === b.multiline && a.sticky === b.sticky ); }, // - skip when the property is a method of an instance (OOP) // - abort otherwise, // initial === would have catch identical references anyway function: function () { var caller = callers[callers.length - 1]; return caller !== Object && typeof caller !== 'undefined'; }, array: function (b, a) { var i, j, len, loop, aCircular, bCircular; // b could be an object literal here if (QUnit.objectType(b) !== 'array') { return false; } len = a.length; if (len !== b.length) { // safe and faster return false; } // track reference to avoid circular references parents.push(a); parentsB.push(b); for (i = 0; i < len; i++) { loop = false; for (j = 0; j < parents.length; j++) { aCircular = parents[j] === a[i]; bCircular = parentsB[j] === b[i]; if (aCircular || bCircular) { if (a[i] === b[i] || (aCircular && bCircular)) { loop = true; } else { parents.pop(); parentsB.pop(); return false; } } } if (!loop && !innerEquiv(a[i], b[i])) { parents.pop(); parentsB.pop(); return false; } } parents.pop(); parentsB.pop(); return true; }, set: function (b, a) { var aArray, bArray; // b could be any object here if (QUnit.objectType(b) !== 'set') { return false; } aArray = []; a.forEach(function (v) { aArray.push(v); }); bArray = []; b.forEach(function (v) { bArray.push(v); }); return innerEquiv(bArray, aArray); }, map: function (b, a) { var aArray, bArray; // b could be any object here if (QUnit.objectType(b) !== 'map') { return false; } aArray = []; a.forEach(function (v, k) { aArray.push([k, v]); }); bArray = []; b.forEach(function (v, k) { bArray.push([k, v]); }); return innerEquiv(bArray, aArray); }, object: function (b, a) { /*jshint forin:false */ var i, j, loop, aCircular, bCircular, // Default to true eq = true, aProperties = [], bProperties = []; // comparing constructors is more strict than using // instanceof if (a.constructor !== b.constructor) { // Allow objects with no prototype to be equivalent to // objects with Object as their constructor. if ( !( (getProto(a) === null && getProto(b) === Object.prototype) || (getProto(b) === null && getProto(a) === Object.prototype) ) ) { return false; } } // stack constructor before traversing properties callers.push(a.constructor); // track reference to avoid circular references parents.push(a); parentsB.push(b); // be strict: don't ensure hasOwnProperty and go deep for (i in a) { loop = false; for (j = 0; j < parents.length; j++) { aCircular = parents[j] === a[i]; bCircular = parentsB[j] === b[i]; if (aCircular || bCircular) { if (a[i] === b[i] || (aCircular && bCircular)) { loop = true; } else { eq = false; break; } } } aProperties.push(i); if (!loop && !innerEquiv(a[i], b[i])) { eq = false; break; } } parents.pop(); parentsB.pop(); callers.pop(); // unstack, we are done for (i in b) { bProperties.push(i); // collect b's properties } // Ensures identical properties name return eq && innerEquiv(aProperties.sort(), bProperties.sort()); }, }; })(); innerEquiv = function () { // can take multiple arguments var args = [].slice.apply(arguments); if (args.length < 2) { return true; // end transition } return ( (function (a, b) { if (a === b) { return true; // catch the most you can } else if ( a === null || b === null || typeof a === 'undefined' || typeof b === 'undefined' || QUnit.objectType(a) !== QUnit.objectType(b) ) { // don't lose time with error prone cases return false; } else { return bindCallbacks(a, callbacks, [b, a]); } // apply transition with (1..n) arguments })(args[0], args[1]) && innerEquiv.apply(this, args.splice(1, args.length - 1)) ); }; return innerEquiv; })(); // Based on jsDump by Ariel Flesler // http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html QUnit.dump = (function () { function quote(str) { return '"' + str.toString().replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"'; } function literal(o) { return o + ''; } function join(pre, arr, post) { var s = dump.separator(), base = dump.indent(), inner = dump.indent(1); if (arr.join) { arr = arr.join(',' + s + inner); } if (!arr) {