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
JavaScript
/*!
* 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) {