blob: 90af5984263ddfc64cce90f2db1e626c4cccf1d5 [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.
*/
var init = function (window) {
var djstest = {};
djstest.indexedDB = window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB || window.indexedDB;
djstest.cleanStoreOnIndexedDb = function (storeObjects, done) {
/** Cleans all the test data saved in the IndexedDb database.
* @param {Array} storeNames - Array of store objects with a property that is the name of the store
* @param {Function} done - Callback function
*/
var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || {};
var deleteObjectStores = function (db) {
$.each(db.objectStoreNames, function (_, storeName) {
db.deleteObjectStore(storeName);
});
};
if (djstest.indexedDB) {
var job = new djstest.Job();
$.each(storeObjects, function (_, storeObject) {
job.queue((function (storeObject) {
return function (success, fail) {
var dbname = "_datajs_" + storeObject.name;
var request = djstest.indexedDB.open(dbname);
request.onsuccess = function (event) {
var db = request.result;
if ("setVersion" in db) {
var versionRequest = db.setVersion("0.1");
versionRequest.onsuccess = function (event) {
var transaction = versionRequest.transaction;
transaction.oncomplete = function () {
db.close();
success();
}
deleteObjectStores(db);
};
versionRequest.onerror = function (e) {
djstest.fail("Error on cleanup - code: " + e.code + " name: " + e.name + "message: " + message);
fail();
};
return;
}
// new api cleanup
db.close();
var deleteRequest = djstest.indexedDB.deleteDatabase(dbname);
deleteRequest.onsuccess = function (event) {
djstest.log("djstest indexeddb cleanup - deleted database " + dbname);
success();
};
deleteRequest.onerror = function (e) {
djstest.fail("djstest indexeddb cleanup - error deleting database " + dbname);
fail();
};
djstest.log("djstest indexeddb cleanup - requested deletion of database " + dbname);
};
request.onerror = function (e) {
djstest.fail(e.code + ": " + e.message);
};
};
})(storeObject));
});
}
if (job) {
job.run(function (succeeded) {
if (!succeeded) {
djstest.fail("cleanup job failed");
}
done();
});
}
else {
done();
}
};
djstest.Job = function () {
/** Constructs a Job object that allows for enqueuing and synchronizing the execution of functions.
* @returns {Object} Job object
*/
var currentTask = -1;
var tasks = [];
var failedTasks = 0;
this.queue = function (fn) {
/** Adds a function to the job queue regardless if the queue is already executing or not.
* @param {Function} fn - Function to execute.
*/
tasks.push(fn);
};
this.queueNext = function (fn) {
/** Adds a function to the front of the job queue regardless if the queue is already executing or not.
* @param {Function} fn - Function to execute.
*/
if (currentTask < 0) {
tasks.unshift(fn);
} else {
tasks.splice(currentTask + 1, 0, fn);
}
};
this.run = function (done) {
/** Starts the execution of this job.
* @param {Function} done - Callback invoked when the job has finished executing all of its enqueued tasks.
*/
/// 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;
}
var makeTaskDoneCallBack = function (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();
}
};
};
var runNextTask = function () {
/** Executes the next function in the queue.
*/
defer(function () {
try {
tasks[currentTask](makeTaskDoneCallBack(false), makeTaskDoneCallBack(true));
} catch (e) {
makeTaskDoneCallBack(true)();
}
});
};
currentTask = 0;
runNextTask();
};
};
var defer = function (fn) {
/** Defers the execution of an arbitrary function that takes no parameters.
* @param {Function} fn - Function to schedule for later execution.
*/
setTimeout(fn, 0);
}
var exposeDateValues = function (data) {
/** Exposes date values for Date objects to facilitate debugging
* @param {Object} data - The object to operate on
*/
if (typeof data === "object") {
if (data instanceof Date) {
data["__date__"] = data.toUTCString();
}
else {
for (var prop in data) {
exposeDateValues(data[prop]);
}
}
}
return data;
}
var extractFunctionName = function (text) {
/** Determines the name of a function.
* @param {String} text - Function text.
* @returns {String} The name of the function from text if found; the original text otherwise.
*/
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;
};
var removeMetadata = function (data) {
/** Removes metadata annotations from the specified object.
* @param data - Object to remove metadata from; possibly null.
*/
if (typeof data === "object" && data !== null) {
delete data["__metadata"];
for (prop in data) {
removeMetadata(data[prop]);
}
}
};
djstest.addFullTest = function (disable, fn, name, arg, timeout) {
/** Add the unit test cases
* @param disable - Indicate whether this test case should be disabled
*/
if (disable != true) {
djstest.addTest(fn, name, arg, timeout);
}
};
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);
});
};
djstest.assert = function (test, message) {
/** Asserts that a condition is true.
* @param {Boolean} test - Condition to test.
* @param {String} message - Text message for condition being tested.
*/
QUnit.ok(test, message);
};
djstest.assertAreEqual = function (actual, expected, message) {
/** Asserts that the values of the expected and actualobjects are equal.
*/
QUnit.equal(actual, expected, message);
};
djstest.assertAreEqualDeep = function (actual, expected, message) {
/** Asserts that the actual and expected objects are the same.
*/
QUnit.deepEqual(exposeDateValues(actual), exposeDateValues(expected), message);
};
djstest.assertWithoutMetadata = function (actual, expected, message) {
removeMetadata(actual)
removeMetadata(expected);
djstest.assertAreEqualDeep(actual, expected, message);
};
djstest.asyncDo = function (asyncActions, done) {
/** 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.
*/
var count = 0;
var doneOne = function () {
count++;
if (count >= asyncActions.length) {
done();
}
};
if (asyncActions.length > 0) {
$.each(asyncActions, function (_, asyncAction) {
asyncAction(doneOne);
});
} else {
done();
}
}
djstest.clone = function (object) {
/** Makes a deep copy of an object.
*/
return $.extend(true, {}, object);
};
djstest.destroyCacheAndDone = function (cache) {
/** Destroys the cache and then completes the test
* @param cache - The cache to destroy
*/
cache.clear().then(function () {
djstest.done();
}, function (err) {
djstest.fail("Failed to destroy cache: " + djstest.toString(err));
djstest.done();
});
};
djstest.done = function () {
/** Indicates that the currently running test has finished.
*/
QUnit.start();
};
djstest.expectException = function (testFunction, message) {
/** Test passes if and only if an exception is thrown.
*/
try {
testFunction();
djstest.fail("Expected exception but function succeeded: " + " " + message);
}
catch (e) {
// Swallow exception.
djstest.pass("Thrown exception expected");
}
};
djstest.assertsExpected = function (asserts) {
/** Indicates the expected number of asserts, fails test if number is not met.
* @param {Number} asserts - Number of asserts expected in test.
*/
expect(asserts);
}
djstest.fail = function (message) {
/** Marks the current test as failed.
* @param {String} message - Failure message.
*/
QUnit.ok(false, message);
};
djstest.failAndDoneCallback = function (message, cleanupCallback) {
/** 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.
*/
return function (err) {
message = "" + message + (err) ? window.JSON.stringify(err) : "";
djstest.fail(message);
if (cleanupCallback) {
try {
cleanupCallback();
} catch (e) {
djstest.fail("error during cleanupCallback: " + window.JSON.stringify(e));
}
}
djstest.done();
};
};
djstest.log = function (message) {
/** Logs a test message.
* @param {String} message - Test message.
*/
var context = { result: true, actual: true, expected: true, message: message };
QUnit.log(context);
};
djstest.pass = function (message) {
/** Marks the current test as failed.
* @param {String} message - Failure message.
*/
QUnit.ok(true, message);
};
djstest.toString = function (obj) {
/** Dumps the object as a string
* @param {Object} obj - Object to dump
*/
return QUnit.jsDump.parse(obj);
};
djstest.wait = function (fn) {
/** 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>
*/
QUnit.stop();
fn(function () {
QUnit.start();
});
};
// Disable caching to ensure that every test-related AJAX request is actually being sent,
// and set up a default error handler
if (window !== undefined) {//TODO improve
$.ajaxSetup({
cache: false,
error: function (jqXHR, textStatus, errorThrown) {
// Work around bug in IE-Mobile on Windows Phone 7
if (jqXHR.status !== 1223) {
var err = {
status: jqXHR.status,
statusText: jqXHR.statusText,
responseText: jqXHR.responseText
};
djstest.fail("AJAX request failed with: " + djstest.toString(err));
}
djstest.done();
}
});
}
return djstest;
};
if (typeof window !== 'undefined') {
//in browser call init() directly window as context
window.djstest = init(window);
} else {
//expose function init to be called with a custom context
module.exports.init = init;
}