uupaa.test.js
Version:
Test Runner.
473 lines (406 loc) • 16.1 kB
JavaScript
(function(global) {
;
// --- dependency modules ----------------------------------
var Task = global["Task"] || require("uupaa.task.js");
// --- define / local variables ----------------------------
var _runOnNode = "process" in global;
var _runOnWorker = "WorkerLocation" in global;
var _runOnBrowser = "document" in global;
var _stylish = _isConsoleStyleReady();
var _testSecondaryModule = !!(global["MESSAGE"] || 0)["SECONDARY"]; // from WebWorker
var CONSOLE_COLOR = {
RED: "\u001b[31m",
YELLOW: "\u001b[33m",
GREEN: "\u001b[32m",
CLEAR: "\u001b[0m"
};
// --- class / interfaces ----------------------------------
function Test(moduleName, // @arg String - target module.
param) { // @arg Object = {} - { disable, browser, worker, node, button, both }
// @param.disable Boolean = false - Disable all tests.
// @param.browser Boolean = false - Enable the browser test.
// @param.worker Boolean = false - Enable the webWorker test.
// @param.node Boolean = false - Enable the node.js test.
// @param.button Boolean = false - Show test buttons.
// @param.both Boolean = false - Test the primary and secondary module.
//{@dev
$valid($type(param, "Object"), Test, "param");
$valid($keys(param, "disable,browser,worker,node,button,both"), Test, "param");
//}@dev
param = param || {};
this._items = []; // test items.
this._swaped = false;
this._module = moduleName;
this._browser = param["browser"] || false;
this._worker = param["worker"] || false;
this._node = param["node"] || false;
this._button = param["button"] || false;
this._both = param["both"] || false;
if (param["disable"]) {
this._browser = false;
this._worker = false;
this._node = false;
this._button = false;
this._both = false;
}
// if (_runOnBrowser) {
// setUncaughtExceptionHandler();
// }
}
//{@dev
Test["repository"] = "https://github.com/uupaa/Test.js";
//}@dev
Test["prototype"]["add"] = Test_add; // Test#add(items:TestItemFunctionArray):this
Test["prototype"]["run"] = Test_run; // Test#run(callback:Function = null):this
Test["prototype"]["clone"] = Test_clone; // Test#clone():TestItemFunctionArray
// --- implements ------------------------------------------
//function setUncaughtExceptionHandler() {
// global.addEventListener("error", function(event) { // message, lineno, filename
// document.body["style"]["backgroundColor"] = "red";
// console.log("Catch exception. Test.js::setUncaughtExceptionHandler");
// try {
// var object = {
// type: event.type || "",
// message: event.message || "",
// lineno: event.lineno || 0,
// filename: event.filename || ""
// };
// console.log( JSON.stringify(object, null, 2) );
// } catch (o_O) {}
// debugger;
// });
//}
function Test_add(items) { // @arg TestItemFunctionArray - test items. [fn, ...]
// @desc add test item(s).
//{@dev
$valid($type(items, "Array"), Test_add, "items");
if (items.length) {
$valid(items.every(isFunction), Test_add, "items");
}
function isFunction(fn) { return typeof fn === "function"; }
//}@dev
this._items = this._items.concat(items);
return this;
}
function Test_run(callback) { // @arg Function = null - callback(err:Error)
// @ret this
var that = this;
var route = "node_1st > browser_1st > worker_1st";
if (that._both) {
if (_runOnWorker) {
route += " > 1000 > swap > node_2nd > browser_2nd";
} else {
route += " > 1000 > swap > node_2nd > browser_2nd > worker_2nd";
}
}
Task.run(route, {
node_1st: function(task) { _nodeTestRunner(that, task); },
browser_1st: function(task) { _browserTestRunner(that, task); },
worker_1st: function(task) { _workerTestRunner(that, task); },
swap: function(task) {
_swap(that);
task.pass();
},
node_2nd: function(task) { _nodeTestRunner(that, task); },
browser_2nd: function(task) { _browserTestRunner(that, task); },
worker_2nd: function(task) { _workerTestRunner(that, task); }
}, function(err) {
_undo(that);
if (callback) {
callback(err);
}
});
return this;
}
function Test_clone() { // @ret TestItemFunctionArray
return this._items.slice();
}
function _browserTestRunner(that, task) {
if (that._browser) {
if (_runOnBrowser) {
if (document["readyState"] === "complete") { // already document loaded
_onload();
} else {
global.addEventListener("load", _onload);
}
return;
}
}
task.pass();
function _onload() {
_testRunner(that, function(err) {
_finishedLog(that, err);
document.body["style"]["backgroundColor"] = err ? "red" : "lime";
if (that._button) {
_addTestButtons(that, that._items);
}
task.done(err);
});
}
}
function _workerTestRunner(that, task) {
if (that._worker) {
if (_runOnWorker) {
if (_testSecondaryModule) {
_swap(that);
}
_testRunner(that, function(err) {
if ("console" in global) { // WebWorker console impl?
_finishedLog(that, err);
}
if (err) {
global["errorMessage"] = err.message; // [!] set WorkerGlobal.errorMessage
}
if (_testSecondaryModule) {
_undo(that);
}
task.done(err);
});
return;
} else if (_runOnBrowser) {
_createWorker(that, task);
return;
}
}
task.pass();
}
function _nodeTestRunner(that, task) {
if (that._node) {
if (_runOnNode) {
_testRunner(that, function(err) {
_finishedLog(that, err);
task.done(err);
if (err) {
process.exit(1); // failure ( need Travis-CI )
}
});
return;
}
}
task.pass();
}
// ----------------------------------------------
// [deprecated]
function Next(task, order, itemName) {
this._task = task;
this._order = order;
this._itemName = itemName;
}
Next["prototype"]["pass"] = Next_pass;
Next["prototype"]["miss"] = Next_miss;
function Next_pass(message) { // @arg String = ""
_flow(this._order, this._itemName + " pass", this._itemName + " miss").pass(message || "");
this._task.pass();
}
function Next_miss(message) { // @arg String = ""
_flow(this._order, this._itemName + " pass", this._itemName + " miss").miss(message || "");
this._task.miss();
}
// ----------------------------------------------
function _testRunner(that, // @arg this
finishedCallback) { // @arg Function = null
var items = that._items.slice(); // clone
var task = new Task(items.length, _callback, { "tick": _next });
_next();
function _next() {
var fn = items.shift();
if (fn) {
var itemName = fn["name"] || (fn + "").split(" ")[1].split("\x28")[0];
var length = fn.length;
if (length === 0) {
if ("Help" in global) {
global["Help"](fn, itemName);
}
throw new Error("Function " + itemName + " has no argument.");
}
var flow = _flow(_order(that), itemName + " pass", itemName + " miss");
// --- [deprecated] ---
if (length === 1) {
var next = new Next(task, _order(that), itemName); //, style);
if (_runOnNode) {
try {
// textXxx(test) {
// if (true) {
// test.pass();
// } else {
// test.miss();
// }
// }
fn(next);
} catch (o_O) { // [!] catch uncaught exception
next.miss("Catch " + o_O.message + " in " + itemName);
throw o_O;
}
} else {
fn(next);
}
} else {
// --- [deprecated] ---
if (_runOnNode) {
try {
// textXxx(test, pass, miss) {
// if (true) {
// test.done(pass());
// } else {
// test.done(miss());
// }
// }
fn(task, flow.pass, flow.miss);
} catch (o_O) { // [!] catch uncaught exception
task.message("Catch " + o_O.message + " in " + itemName).miss();
throw o_O;
}
} else {
fn(task, flow.pass, flow.miss);
}
}
}
}
function _callback(err) {
if (finishedCallback) {
finishedCallback(err);
}
}
}
function _createWorker(that, task) {
var src = _createObjectURL("#worker"); // "blob:null/...."
if (src) {
var worker = new Worker(src);
worker.onmessage = function(event) {
if ( /^blob:/.test(src) ) { // src is objectURL?
(global["URL"] || global["webkitURL"]).revokeObjectURL(src); // [!] GC
}
var errorMessage = event.data.error;
if (errorMessage) {
document.body.style.backgroundColor = "red"; // [!] RED screen
}
task.done(errorMessage ? new Error(errorMessage) : null);
};
var baseDir = location.href.split("/").slice(0, -1).join("/") + "/";
worker.postMessage({
"WORKER_ID": 1,
"REQUEST_ID": 1,
"INIT": true,
"ORIGIN": location.href,
"SCRIPT": [],
"BASE_DIR": baseDir,
"SECONDARY": that._swaped
});
} else {
task.pass();
}
}
function _createObjectURL(nodeSelector) {
var node = document.querySelector(nodeSelector);
if (node && "Blob" in global) {
// create Worker from inline <script id="worker" type="javascript/worker"> content
var blob = new Blob([ node.textContent ], { type: "application/javascript" });
return (global["URL"] || global["webkitURL"]).createObjectURL(blob);
}
return "";
}
function _swap(that) {
if (that._both) {
var moduleName = that._module;
if (!that._swaped) {
that._swaped = true;
global["$$$" + moduleName + "$$$"] = global[moduleName];
global[moduleName] = global[moduleName + "_"]; // swap primary <-> secondary module
}
}
}
function _undo(that) {
if (that._both) {
var moduleName = that._module;
if (that._swaped) {
that._swaped = false;
global[moduleName] = global["$$$" + moduleName + "$$$"];
delete global["$$$" + moduleName + "$$$"];
}
}
}
function _isConsoleStyleReady() {
if (global["navigator"]) {
if ( /Chrome/.test( global["navigator"]["userAgent"] || "" ) ) {
return true;
}
}
return false;
}
function _order(that) {
return that._swaped ? "secondary" : "primary";
}
function _flow(order, // @arg String - "primary" or "secondary"
passMessage, // @arg String
missMessage, // @arg String
style) { // @arg String = "" - "node" or "worker" or "color " or "browser"
if (global["console"]) {
style = _runOnNode ? "node"
: _runOnWorker ? "worker"
: _stylish ? "color"
: "browser";
}
var pass = null;
var miss = null;
switch (style) {
case "node": pass = console.log.bind(console, CONSOLE_COLOR.GREEN +
"Node(" + order + "): " + CONSOLE_COLOR.CLEAR + passMessage); break;
case "worker": pass = console.log.bind(console, "Worker(" + order + "): " + passMessage); break;
case "color": pass = console.log.bind(console, "%cBrowser(" + order + "): " + passMessage + "%c ", "color:#0c0", ""); break;
case "browser": pass = console.log.bind(console, "Browser(" + order + "): " + passMessage);
}
switch (style) {
case "node": miss = console.error.bind(console, CONSOLE_COLOR.RED +
"Node(" + order + "): " + CONSOLE_COLOR.CLEAR + missMessage); break;
case "worker": miss = console.error.bind(console, "Worker(" + order + "): " + missMessage); break;
case "color": miss = console.error.bind(console, "%cBrowser(" + order + "): " + missMessage + "%c ", "color:#red", ""); break;
case "browser": miss = console.error.bind(console, "Browser(" + order + "): " + missMessage);
}
return {
pass: pass,
miss: function() {
miss.apply(console, [].slice.call(arguments, 0, arguments.length));
return new Error();
}
};
}
function _finishedLog(that, err) {
var flow = _flow(_order(that), "ALL PASSED.", "SOME MISSED.");
if (err) {
flow.miss();
} else {
flow.pass();
}
}
function _addTestButtons(that, items) { // @arg TestItemFunctionArray
// add <input type="button" onclick="test()" value="test()" /> buttons
items.forEach(function(fn, index) {
var itemName = fn["name"] || (fn + "").split(" ")[1].split("\x28")[0];
if (!document.querySelector("#" + itemName)) {
var inputNode = document.createElement("input");
var next = "{pass:function(){},miss:function(){},done:function(){}}";
var pass = "function(){console.log('" + itemName + " pass')}";
var miss = "function(){console.error('" + itemName + " miss')}";
inputNode.setAttribute("id", itemName);
inputNode.setAttribute("type", "button");
inputNode.setAttribute("value", itemName + "()");
inputNode.setAttribute("onclick", "ModuleTest" + that._module +
"[" + index + "](" + next + ", " + pass + ", " + miss + ")");
document.body.appendChild(inputNode);
}
});
}
// --- validate / assertions -------------------------------
//{@dev
function $valid(val, fn, hint) { if (global["Valid"]) { global["Valid"](val, fn, hint); } }
function $type(obj, type) { return global["Valid"] ? global["Valid"].type(obj, type) : true; }
function $keys(obj, str) { return global["Valid"] ? global["Valid"].keys(obj, str) : true; }
//function $some(val, str, ignore) { return global["Valid"] ? global["Valid"].some(val, str, ignore) : true; }
//function $args(fn, args) { if (global["Valid"]) { global["Valid"].args(fn, args); } }
//}@dev
// --- exports ---------------------------------------------
if ("process" in global) {
module["exports"] = Test;
}
global["Test" in global ? "Test_" : "Test"] = Test; // switch module. http://git.io/Minify
})((this || 0).self || global); // WebModule idiom. http://git.io/WebModule