blob: 526846731429801b2fcb4581c5b0fbaa3c6d6abe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// Because this code contains a init function to be useable directly inside the browser as well as in nodejs
// we define the @namespace djstest here instead of the a @module name djstest
/** Create namespace djstest in window.djstest when this file is loaded as JavaScript by the browser
* @namespace djstest
*/
var init = function init () {
var djstest = {};
/** Constructs a Job object that allows for enqueuing and synchronizing the execution of functions.
* @class Job
* @constructor
* @returns {Object} Job object
*/
djstest.Job = function () {
var currentTask = -1;
var tasks = [];
var failedTasks = 0;
/** Adds a function to the job queue regardless if the queue is already executing or not.
* @method djstest.Job#queue
* @param {Function} fn - Function to execute.
*/
this.queue = function (fn) {
tasks.push(fn);
};
/** Adds a function to the front of the job queue regardless if the queue is already executing or not.
* @method djstest.Job#queueNext
* @param {Function} fn - Function to execute.
*/
this.queueNext = function (fn) {
if (currentTask < 0) {
tasks.unshift(fn);
} else {
tasks.splice(currentTask + 1, 0, fn);
}
};
/** Starts the execution of this job.
* @method djstest.Job#run
* @param {Function} done - Callback invoked when the job has finished executing all of its enqueued tasks.
*/
this.run = function (done) {
/// This method does nothing if called on a unit of work that is already executing.
if (currentTask >= 0) {
return;
}
if (tasks.length === 0) {
done(true);
return;
}
/**
* @method djstest.Job~makeTaskDoneCallBack
*/
function makeTaskDoneCallBack(failed) {
return function () {
// Track the failed task and continue the execution of the job.
if (failed) {
failedTasks++;
}
currentTask++;
if (currentTask === tasks.length) {
done(failedTasks === 0);
} else {
runNextTask();
}
};
}
/** Executes the next function in the queue.
* @method djstest.Job~runNextTask
*/
function runNextTask() {
defer(function () {
try {
tasks[currentTask](makeTaskDoneCallBack(false), makeTaskDoneCallBack(true));
} catch (e) {
makeTaskDoneCallBack(true)();
}
});
}
currentTask = 0;
runNextTask();
};
};
/** Defers the execution of an arbitrary function that takes no parameters.
* @memberof djstest
* @inner
* @param {Function} fn - Function to schedule for later execution.
*/
function defer(fn) {
setTimeout(fn, 0);
}
/** Exposes date values for Date objects to facilitate debugging
* @memberof djstest
* @inner
* @param {Object} data - The object to operate on
*/
function exposeDateValues(data) {
if (typeof data === "object") {
if (data instanceof Date) {
data.__date__ = data.toUTCString();
}
else {
for (var prop in data) {
exposeDateValues(data[prop]);
}
}
}
return data;
}
/** Determines the name of a function.
* @memberof djstest
* @inner
* @param {String} text - Function text.
* @returns {String} The name of the function from text if found; the original text otherwise.
*/
function extractFunctionName(text) {
var index = text.indexOf("function ");
if (index < 0) {
return text;
}
var nameStart = index + "function ".length;
var parensIndex = text.indexOf("(", nameStart);
if (parensIndex < 0) {
return text;
}
var result = text.substr(nameStart, parensIndex - nameStart);
if (result.indexOf("test") === 0) {
result = result.substr("test".length);
}
return result;
}
/** Removes metadata annotations from the specified object.
* @memberof djstest
* @inner
* @param data - Object to remove metadata from; possibly null.
*/
function removeMetadata(data) {
if (typeof data === "object" && data !== null) {
delete data.__metadata;
for (var prop in data) {
removeMetadata(data[prop]);
}
}
}
/** Add the unit test cases
* @param disable - Indicate whether this test case should be disabled
*/
djstest.addFullTest = function (disable, fn, name, arg, timeout) {
if (disable !== true) {
djstest.addTest(fn, name, arg, timeout);
}
};
/** Add the unit test cases
* @param disable - Indicate whether this test case should be disabled
*/
djstest.addTest = function (fn, name, arg, timeout) {
if (!name) {
name = extractFunctionName(fn.toString());
}
test(name, function () {
if (!timeout) {
timeout = 20000;
}
QUnit.config.testTimeout = timeout;
QUnit.stop();
fn.call(this, arg);
});
};
/** Asserts that a condition is true.
* @param {Boolean} test - Condition to test.
* @param {String} message - Text message for condition being tested.
*/
djstest.assert = function (test, message) {
QUnit.ok(test, message);
};
/** Asserts that the values of the expected and actualobjects are equal.
* @memberof djstest
* @inner
*/
djstest.assertAreEqual = function (actual, expected, message) {
QUnit.equal(actual, expected, message);
};
/** Asserts that the actual and expected objects are the same.
*/
djstest.assertAreEqualDeep = function (actual, expected, message) {
QUnit.deepEqual(exposeDateValues(actual), exposeDateValues(expected), message);
};
/** Asserts that the actual and expected objects are the same but removes the metadata bevore
*/
djstest.assertWithoutMetadata = function (actual, expected, message) {
removeMetadata(actual);
removeMetadata(expected);
djstest.assertAreEqualDeep(actual, expected, message);
};
/** Calls each async action in asyncActions, passing each action a function which keeps a count and
* calls the passed done function when all async actions complete
* @param {Array} asyncActions -Array of asynchronous actions to be executed,
* each taking a single parameter - the callback function to call when the action is done.</param>
* @param {Function} done - Function to be executed in the last async action to complete.
*/
djstest.asyncDo = function (asyncActions, done) {
var count = 0;
var doneOne = function () {
count++;
if (count >= asyncActions.length) {
done();
}
};
if (asyncActions.length > 0) {
for ( var i = 0; i < asyncActions.length; i++) {
asyncActions[i](doneOne);
}
} else {
done();
}
};
/** Makes a deep copy of an object.
*/
djstest.clone = function (object) {
if ( object === undefined ) {
return undefined;
} else if (object === null) {
return null;
} else if (typeof(object) !== 'object') {
return object;
} else {
var ret = {};
for(var key in object) {
if(object.hasOwnProperty(key)) {
ret[key] = this.clone(object[key]);
}
}
return ret;
}
throw("Error cloning an object");
};
/** Destroys the cache and then completes the test
* @param cache - The cache to destroy
*/
djstest.destroyCacheAndDone = function (cache) {
cache.clear().then(function () {
djstest.done();
}, function (err) {
djstest.fail("Failed to destroy cache: " + djstest.toString(err));
djstest.done();
});
};
/** Indicates that the currently running test has finished.
*/
djstest.done = function () {
QUnit.start();
};
/** Test passes if and only if an exception is thrown.
*/
djstest.expectException = function (testFunction, message) {
try {
testFunction();
djstest.fail("Expected exception but function succeeded: " + " " + message);
}
catch (e) {
// Swallow exception.
djstest.pass("Thrown exception expected");
}
};
/** Indicates the expected number of asserts, fails test if number is not met.
* @param {Number} asserts - Number of asserts expected in test.
*/
djstest.assertsExpected = function (asserts) {
expect(asserts);
};
/** Marks the current test as failed.
* @param {String} message - Failure message.
*/
djstest.fail = function (message) {
QUnit.ok(false, message);
};
/** Returns a function that when invoked will fail this test and be done with it.
* @param {String} message - Failure message.
* @param {Function} [cleanupCallback] -
* @returns {Function} A new function.
*/
djstest.failAndDoneCallback = function (message, cleanupCallback) {
return function (err) {
message = "" + message + (err) ? JSON.stringify(err) : "";
djstest.fail(message);
if (cleanupCallback) {
try {
cleanupCallback();
} catch (e) {
djstest.fail("error during cleanupCallback: " + JSON.stringify(e));
}
}
djstest.done();
};
};
/** Logs a test message.
* @param {String} message - Test message.
*/
djstest.log = function (message) {
var context = { result: true, actual: true, expected: true, message: message };
QUnit.log(context);
};
/** Marks the current test as failed.
* @param {String} message - Failure message.
*/
djstest.pass = function (message) {
QUnit.ok(true, message);
};
/** Dumps the object as a string
* @param {Object} obj - Object to dump
*/
djstest.toString = function (obj) {
return QUnit.jsDump.parse(obj);
};
/** Executes the function, pausing test execution until the callback is called
* @param {Function} fn - Function to execute; takes one parameter which is the callback
* This function is typically used in asynchronous setup/teardown methods</remarks>
*/
djstest.wait = function (fn) {
QUnit.stop();
fn(function () {
QUnit.start();
});
};
return djstest;
};
//export djstest
if (typeof window !== 'undefined') {
//expose to browsers window object
if ( window.djstest === undefined) {
window.djstest = init();
} else {
var tmp = init();
$.extend( window.djstest,tmp);
}
} else {
//expose in commonjs style
module.exports = init();
}