summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2019-12-02 19:26:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2019-12-02 19:26:09 +0000
commitcd17ccf84def7aa87c97a092fc756ca686d117e3 (patch)
tree51a1a3d4ca271d6ddf4848a2d562811f5a00628f
parentInitial commit. (diff)
downloadjsunit-cd17ccf84def7aa87c97a092fc756ca686d117e3.tar.xz
jsunit-cd17ccf84def7aa87c97a092fc756ca686d117e3.zip
Adding upstream version 0.2.2.upstream/0.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rwxr-xr-x.eslintrc.js81
-rw-r--r--bootstrap.js36
-rw-r--r--chrome.manifest1
-rw-r--r--content/modules/Assert.jsm442
-rw-r--r--content/modules/jsunit-exec.js27
-rw-r--r--content/modules/jsunit-main.jsm412
-rw-r--r--content/modules/jsunit-service.js61
-rw-r--r--content/modules/jsunit-wrapper.js50
-rw-r--r--content/sample-include.js11
-rw-r--r--defaults/preferences/jsunit.js33
-rw-r--r--install.rdf1
-rw-r--r--license.txt3
-rw-r--r--manifest.json17
-rwxr-xr-xtests/sample-test.js15
-rwxr-xr-xtests/subtest1.js47
-rwxr-xr-xtests/subtest2.js50
16 files changed, 1287 insertions, 0 deletions
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100755
index 0000000..5c7adaa
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,81 @@
+module.exports = {
+ "parserOptions": {
+ "ecmaVersion": 2017
+ },
+ "rules": {
+ "linebreak-style": [
+ 2,
+ "unix"
+ ],
+ "semi": [
+ 2,
+ "always"
+ ],
+ "strict": [2, "global"],
+ "no-unused-vars": 0,
+ "no-empty": 0,
+ "comma-dangle": 2,
+ "consistent-return": 2,
+ "block-scoped-var": 2,
+ "dot-notation": 2,
+ "no-alert": 2,
+ "no-caller": 2,
+ "no-case-declarations": 2,
+ "no-div-regex": 2,
+ "no-labels": 2,
+ "no-empty-pattern": 2,
+ "no-eq-null": 2,
+ "no-eval": 2,
+ "no-extend-native": 2,
+ "no-extra-bind": 2,
+ "no-fallthrough": 2,
+ "no-floating-decimal": 2,
+ "no-implicit-coercion": 2,
+ "no-implied-eval": 2,
+ "no-invalid-this": 2,
+ "no-iterator": 2,
+ "no-irregular-whitespace": 0,
+ "no-labels": 2,
+ "no-lone-blocks": 2,
+ "no-loop-func": 2,
+ "no-multi-str": 2,
+ "no-native-reassign": 2,
+ "no-new-func": 2,
+ "no-new-wrappers": 2,
+ "no-new": 2,
+ "no-octal-escape": 2,
+ "no-process-env": 2,
+ "no-proto": 2,
+ "no-redeclare": [2, {
+ "builtinGlobals": true
+ }],
+ "no-return-assign": 2,
+ "no-script-url": 2,
+ "no-self-compare": 2,
+ "no-sequences": 2,
+ "no-unused-expressions": 2,
+ "no-useless-call": 2,
+ "no-useless-concat": 2,
+ "no-useless-escape": 0,
+ "no-void": 2,
+ "no-with": 2,
+ "radix": 2,
+ "wrap-iife": [2, "inside"],
+ "yoda": 2,
+ // TODO:
+ //"eqeqeq": 2,
+ },
+ "env": {
+ "es6": true,
+ "browser": true,
+ "node": true,
+ },
+ "extends": "eslint:recommended",
+ "globals": {
+ "ChromeUtils": true,
+ "Components": true,
+ "Cc": true,
+ "Cu": true,
+ "Ci": true
+ }
+};
diff --git a/bootstrap.js b/bootstrap.js
new file mode 100644
index 0000000..9670a56
--- /dev/null
+++ b/bootstrap.js
@@ -0,0 +1,36 @@
+/*global Components: false */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+
+/* global APP_SHUTDOWN: false */
+
+Cu.importGlobalProperties(["XMLHttpRequest"]);
+
+var gAllModules = [];
+
+function install() {}
+
+function uninstall() {}
+
+function startup(data, reason) {
+ try {
+ let JSUnitCommandLine = ChromeUtils.import("chrome://jsunit/content/modules/jsunit-service.js").JSUnitCommandLine;
+ JSUnitCommandLine.startup(reason);
+ } catch (ex) {
+ logException(ex);
+ }
+}
+
+function shutdown(data, reason) {}
+
+
+function logException(exc) {
+ try {
+ const Services = Cu.import("resource://gre/modules/Services.jsm").Services;
+ Services.console.logStringMessage(exc.toString() + "\n" + exc.stack);
+ } catch (x) {}
+} \ No newline at end of file
diff --git a/chrome.manifest b/chrome.manifest
new file mode 100644
index 0000000..4792235
--- /dev/null
+++ b/chrome.manifest
@@ -0,0 +1 @@
+content jsunit content/
diff --git a/content/modules/Assert.jsm b/content/modules/Assert.jsm
new file mode 100644
index 0000000..8ab6262
--- /dev/null
+++ b/content/modules/Assert.jsm
@@ -0,0 +1,442 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
+// When you see a javadoc comment that contains a number, it's a reference to a
+// specific section of the CommonJS spec.
+//
+// Originally from narwhal.js (http://narwhaljs.org)
+// Copyright (c) 2009 Thomas Robinson <280north.com>
+// MIT license: http://opensource.org/licenses/MIT
+
+"use strict";
+
+var EXPORTED_SYMBOLS = [
+ "Assert"
+];
+
+/**
+ * 1. The assert module provides functions that throw AssertionError's when
+ * particular conditions are not met.
+ *
+ * To use the module you'll need to instantiate it first, which allows consumers
+ * to override certain behavior on the newly obtained instance. For examples,
+ * see the javadoc comments for the `report` member function.
+ */
+function Assert(reporterFunc) {
+ if (reporterFunc)
+ this.setReporter(reporterFunc);
+}
+
+function instanceOf(object, type) {
+ return Object.prototype.toString.call(object) == "[object " + type + "]";
+}
+
+function replacer(key, value) {
+ if (value === undefined) {
+ return undefined;
+ }
+ if (typeof value === "number" && (isNaN(value) || !isFinite(value))) {
+ return value.toString();
+ }
+ if (typeof value === "function" || instanceOf(value, "RegExp")) {
+ return value.toString();
+ }
+ return value;
+}
+
+const kTruncateLength = 128;
+
+function truncate(text, newLength = kTruncateLength) {
+ if (typeof text == "string") {
+ return text.length < newLength ? text : text.slice(0, newLength);
+ } else {
+ return text;
+ }
+}
+
+function getMessage(error, prefix = "") {
+ let actual, expected;
+ // Wrap calls to JSON.stringify in try...catch blocks, as they may throw. If
+ // so, fall back to toString().
+ try {
+ actual = JSON.stringify(error.actual, replacer);
+ } catch (ex) {
+ actual = Object.prototype.toString.call(error.actual);
+ }
+ try {
+ expected = JSON.stringify(error.expected, replacer);
+ } catch (ex) {
+ expected = Object.prototype.toString.call(error.expected);
+ }
+ let message = prefix;
+ if (error.operator) {
+ message += (prefix ? " - " : "") + truncate(actual) + " " + error.operator +
+ " " + truncate(expected);
+ }
+ return message;
+}
+
+/**
+ * 2. The AssertionError is defined in assert.
+ *
+ * Example:
+ * new assert.AssertionError({
+ * message: message,
+ * actual: actual,
+ * expected: expected,
+ * operator: operator
+ * });
+ *
+ * At present only the four keys mentioned above are used and
+ * understood by the spec. Implementations or sub modules can pass
+ * other keys to the AssertionError's constructor - they will be
+ * ignored.
+ */
+Assert.AssertionError = function(options) {
+ this.name = "AssertionError";
+ this.actual = options.actual;
+ this.expected = options.expected;
+ this.operator = options.operator;
+ this.message = getMessage(this, options.message);
+ // The part of the stack that comes from this module is not interesting.
+ let stack = Components.stack;
+ while (stack.filename && stack.filename.indexOf("Assert.jsm") >= 0) {
+ stack = stack.caller;
+ }
+ this.stack = stack;
+};
+
+// assert.AssertionError instanceof Error
+Assert.AssertionError.prototype = Object.create(Error.prototype, {
+ constructor: {
+ value: Assert.AssertionError,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+});
+
+let proto = Assert.prototype;
+
+proto._reporter = null;
+/**
+ * Set a custom assertion report handler function. Arguments passed in to this
+ * function are:
+ * err (AssertionError|null) An error object when the assertion failed or null
+ * when it passed
+ * message (string) Message describing the assertion
+ * stack (stack) Stack trace of the assertion function
+ *
+ * Example:
+ * ```js
+ * Assert.setReporter(function customReporter(err, message, stack) {
+ * if (err) {
+ * do_report_result(false, err.message, err.stack);
+ * } else {
+ * do_report_result(true, message, stack);
+ * }
+ * });
+ * ```
+ *
+ * @param reporterFunc
+ * (function) Report handler function
+ */
+proto.setReporter = function(reporterFunc) {
+ this._reporter = reporterFunc;
+};
+
+/**
+ * 3. All of the following functions must throw an AssertionError when a
+ * corresponding condition is not met, with a message that may be undefined if
+ * not provided. All assertion methods provide both the actual and expected
+ * values to the assertion error for display purposes.
+ *
+ * This report method only throws errors on assertion failures, as per spec,
+ * but consumers of this module (think: xpcshell-test, mochitest) may want to
+ * override this default implementation.
+ *
+ * Example:
+ * ```js
+ * // The following will report an assertion failure.
+ * this.report(1 != 2, 1, 2, "testing JS number math!", "==");
+ * ```
+ *
+ * @param failed
+ * (boolean) Indicates if the assertion failed or not
+ * @param actual
+ * (mixed) The result of evaluating the assertion
+ * @param expected (optional)
+ * (mixed) Expected result from the test author
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ * @param operator (optional)
+ * (string) Operation qualifier used by the assertion method (ex: '==')
+ */
+proto.report = function(failed, actual, expected, message, operator) {
+ let err = new Assert.AssertionError({
+ message: message,
+ actual: actual,
+ expected: expected,
+ operator: operator
+ });
+ if (!this._reporter) {
+ // If no custom reporter is set, throw the error.
+ if (failed) {
+ throw err;
+ }
+ } else {
+ this._reporter(failed ? err : null, err.message, err.stack);
+ }
+};
+
+/**
+ * 4. Pure assertion tests whether a value is truthy, as determined by !!guard.
+ * assert.ok(guard, message_opt);
+ * This statement is equivalent to assert.equal(true, !!guard, message_opt);.
+ * To test strictly for the value true, use assert.strictEqual(true, guard,
+ * message_opt);.
+ *
+ * @param value
+ * (mixed) Test subject to be evaluated as truthy
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.ok = function(value, message) {
+ this.report(!value, value, true, message, "==");
+};
+
+/**
+ * 5. The equality assertion tests shallow, coercive equality with ==.
+ * assert.equal(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as equivalent to `expected`
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.equal = function equal(actual, expected, message) {
+ this.report(actual != expected, actual, expected, message, "==");
+};
+
+/**
+ * 6. The non-equality assertion tests for whether two objects are not equal
+ * with != assert.notEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as NOT equivalent to `expected`
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.notEqual = function notEqual(actual, expected, message) {
+ this.report(actual == expected, actual, expected, message, "!=");
+};
+
+/**
+ * 7. The equivalence assertion tests a deep equality relation.
+ * assert.deepEqual(actual, expected, message_opt);
+ *
+ * We check using the most exact approximation of equality between two objects
+ * to keep the chance of false positives to a minimum.
+ * `JSON.stringify` is not designed to be used for this purpose; objects may
+ * have ambiguous `toJSON()` implementations that would influence the test.
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as equivalent to `expected`, including nested properties
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.deepEqual = function deepEqual(actual, expected, message) {
+ this.report(!_deepEqual(actual, expected), actual, expected, message, "deepEqual");
+};
+
+function _deepEqual(actual, expected) {
+ // 7.1. All identical values are equivalent, as determined by ===.
+ if (actual === expected) {
+ return true;
+ // 7.2. If the expected value is a Date object, the actual value is
+ // equivalent if it is also a Date object that refers to the same time.
+ } else if (instanceOf(actual, "Date") && instanceOf(expected, "Date")) {
+ return actual.getTime() === expected.getTime();
+ // 7.3 If the expected value is a RegExp object, the actual value is
+ // equivalent if it is also a RegExp object with the same source and
+ // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
+ } else if (instanceOf(actual, "RegExp") && instanceOf(expected, "RegExp")) {
+ return actual.source === expected.source &&
+ actual.global === expected.global &&
+ actual.multiline === expected.multiline &&
+ actual.lastIndex === expected.lastIndex &&
+ actual.ignoreCase === expected.ignoreCase;
+ // 7.4. Other pairs that do not both pass typeof value == "object",
+ // equivalence is determined by ==.
+ } else if (typeof actual != "object" && typeof expected != "object") {
+ return actual == expected;
+ // 7.5 For all other Object pairs, including Array objects, equivalence is
+ // determined by having the same number of owned properties (as verified
+ // with Object.prototype.hasOwnProperty.call), the same set of keys
+ // (although not necessarily the same order), equivalent values for every
+ // corresponding key, and an identical 'prototype' property. Note: this
+ // accounts for both named and indexed properties on Arrays.
+ } else {
+ return objEquiv(actual, expected);
+ }
+}
+
+function isUndefinedOrNull(value) {
+ return value === null || value === undefined;
+}
+
+function isArguments(object) {
+ return instanceOf(object, "Arguments");
+}
+
+function objEquiv(a, b) {
+ if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
+ return false;
+ }
+ // An identical 'prototype' property.
+ if (a.prototype !== b.prototype) {
+ return false;
+ }
+ // Object.keys may be broken through screwy arguments passing. Converting to
+ // an array solves the problem.
+ if (isArguments(a)) {
+ if (!isArguments(b)) {
+ return false;
+ }
+ // a = pSlice.call(a);
+ // b = pSlice.call(b);
+ return _deepEqual(a, b);
+ }
+ let ka, kb, key, i;
+ try {
+ ka = Object.keys(a);
+ kb = Object.keys(b);
+ } catch (e) {
+ // Happens when one is a string literal and the other isn't
+ return false;
+ }
+ // Having the same number of owned properties (keys incorporates
+ // hasOwnProperty)
+ if (ka.length != kb.length)
+ return false;
+ // The same set of keys (although not necessarily the same order),
+ ka.sort();
+ kb.sort();
+ // Equivalent values for every corresponding key, and possibly expensive deep
+ // test
+ for (i = ka.length - 1; i >= 0; i--) {
+ key = ka[i];
+ if (!_deepEqual(a[key], b[key])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * 8. The non-equivalence assertion tests for any deep inequality.
+ * assert.notDeepEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as NOT equivalent to `expected`, including nested properties
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.notDeepEqual = function notDeepEqual(actual, expected, message) {
+ this.report(_deepEqual(actual, expected), actual, expected, message, "notDeepEqual");
+};
+
+/**
+ * 9. The strict equality assertion tests strict equality, as determined by ===.
+ * assert.strictEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as strictly equivalent to `expected`
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.strictEqual = function strictEqual(actual, expected, message) {
+ this.report(actual !== expected, actual, expected, message, "===");
+};
+
+/**
+ * 10. The strict non-equality assertion tests for strict inequality, as
+ * determined by !==. assert.notStrictEqual(actual, expected, message_opt);
+ *
+ * @param actual
+ * (mixed) Test subject to be evaluated as NOT strictly equivalent to `expected`
+ * @param expected
+ * (mixed) Test reference to evaluate against `actual`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.notStrictEqual = function notStrictEqual(actual, expected, message) {
+ this.report(actual === expected, actual, expected, message, "!==");
+};
+
+function expectedException(actual, expected) {
+ if (!actual || !expected) {
+ return false;
+ }
+
+ if (instanceOf(expected, "RegExp")) {
+ return expected.test(actual);
+ } else if (actual instanceof expected) {
+ return true;
+ } else if (expected.call({}, actual) === true) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * 11. Expected to throw an error:
+ * assert.throws(block, Error_opt, message_opt);
+ *
+ * @param block
+ * (function) Function block to evaluate and catch eventual thrown errors
+ * @param expected (optional)
+ * (mixed) Test reference to evaluate against the thrown result from `block`
+ * @param message (optional)
+ * (string) Short explanation of the expected result
+ */
+proto.throws = function(block, expected, message) {
+ let actual;
+
+ if (typeof expected === "string") {
+ message = expected;
+ expected = null;
+ }
+
+ try {
+ block();
+ } catch (e) {
+ actual = e;
+ }
+
+ message = (expected && expected.name ? " (" + expected.name + ")." : ".") +
+ (message ? " " + message : ".");
+
+ if (!actual) {
+ this.report(true, actual, expected, "Missing expected exception" + message);
+ }
+
+ if ((actual && expected && !expectedException(actual, expected))) {
+ throw actual;
+ }
+
+ this.report(false, expected, expected, message);
+}; \ No newline at end of file
diff --git a/content/modules/jsunit-exec.js b/content/modules/jsunit-exec.js
new file mode 100644
index 0000000..fbf0600
--- /dev/null
+++ b/content/modules/jsunit-exec.js
@@ -0,0 +1,27 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+/*
+ Wrapper file that is inclued for every Test executed
+*/
+/* global run_test: false, JSUnit: false */
+
+try {
+ run_test();
+}
+catch (ex) {
+
+ JSUnit.abortPendingTests();
+ JSUnit.logTestResult("RuntimeError: Caught unhandled exception: " + ex.toString(),
+ null,
+ ex.fileName +
+ " :: :: line " + ex.lineNumber);
+
+ JSUnit.printMsg("Stack: " + ex.stack);
+
+}
diff --git a/content/modules/jsunit-main.jsm b/content/modules/jsunit-main.jsm
new file mode 100644
index 0000000..d8544fd
--- /dev/null
+++ b/content/modules/jsunit-main.jsm
@@ -0,0 +1,412 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* global dump: false */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["JSUnit"];
+
+const Services = Cu.import("resource://gre/modules/Services.jsm").Services;
+const Assert = Cu.import("chrome://jsunit/content/modules/Assert.jsm").Assert;
+
+var gTestError = 0;
+var gTestSucceed = 0;
+var gTestPending = 0;
+var gLogFileStream = "";
+
+var gCurrDir = "";
+
+function DEBUG_LOG(str) {
+ dump("jsunit-main.jsm: " + str + "\n");
+}
+
+function generateQI(aCid) {
+ if ("generateQI" in ChromeUtils) {
+ return ChromeUtils.generateQI(aCid);
+ }
+ else {
+ let XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
+ return XPCOMUtils.generateQI(aCid);
+ }
+}
+
+
+var JSUnit = {
+
+ assert: null,
+
+ // printMsg: log stuff
+
+ dumpMsg: function(str) {
+ dump(str + "\n");
+ },
+
+ logToFile: function(str) {
+ gLogFileStream.write(str + "\n", str.length + 1);
+ },
+
+ logMsgToJsd: function(str) {
+ JsdConsole.log(str);
+ },
+
+ testSucceeded: function() {
+ gTestSucceed++;
+ },
+
+ testFailed: function() {
+ gTestError++;
+ },
+
+ setLogFile: function(logFileName) {
+ gLogFileStream = this.createFileStream(logFileName);
+ },
+
+ logTestResult: function(err, message, stack) {
+ if (err) {
+ JSUnit.testFailed();
+ JSUnit.printMsg(err + " - " + stack);
+ }
+ else {
+ JSUnit.testSucceeded();
+ JSUnit.printMsg("Succeed: " + message + " - " + stack);
+ }
+ },
+
+ printStats: function() {
+ JSUnit.printMsg("\nFINAL STATS\n");
+ JSUnit.printMsg("TestResult: executed : " + (JSUnit.countFailed() + JSUnit.countSucceeded()));
+ JSUnit.printMsg("TestResult: succeeded: " + JSUnit.countSucceeded());
+ JSUnit.printMsg("TestResult: failed : " + JSUnit.countFailed());
+
+ if (gLogFileStream) {
+ gLogFileStream.close();
+ }
+ gTestSucceed = 0;
+ gTestError = 0;
+ },
+
+
+ init: function(useTinyJsd, logFileName) {
+ // initialize library
+ gCurrDir = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIDirectoryServiceProvider)
+ .getFile("CurWorkD", {});
+ this.assert = new Assert(this.logTestResult);
+
+ if (logFileName) {
+ this.setLogFile(logFileName);
+ this.printMsg = this.logToFile;
+ }
+ else {
+ // fallback: command line interface
+ this.printMsg = this.dumpMsg;
+ }
+ },
+
+ setMainFile: function(fileName) {
+ },
+
+ getOS: function() {
+ return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+ },
+
+ getCwd: function() {
+ return gCurrDir.clone();
+ },
+
+ getFile: function(stack, testdirRelativePath, allowNonexistent) {
+
+ //DEBUG_LOG("getFile: "+gCurrDir);
+
+ var fn = gCurrDir.path + "/" + testdirRelativePath;
+
+ if (this.getOS() == "WINNT") {
+ fn = fn.replace(/\//g, "\\");
+ }
+
+ var lf = Components.classes["@mozilla.org/file/local;1"].createInstance(
+ Components.interfaces.nsIFile);
+ lf.initWithPath(fn);
+
+ if (!(allowNonexistent || lf.exists())) {
+ JSUnit.logTestResult("AssertionError: file '" + fn + "' not found", null,
+ stack.filename +
+ " :: " + stack.name +
+ " :: line " + stack.lineNumber);
+ return null;
+ }
+ else {
+ JSUnit.logTestResult(null, "file '" + fn + "' OK",
+ stack.filename +
+ " :: " + stack.name +
+ " :: line " + stack.lineNumber);
+ }
+ return lf;
+ },
+
+
+ makeUrl: function(scriptFile, isAbsolutePath) {
+ var isUrl = false;
+ if (scriptFile.search(/^(chrome|file|resource):\/\//) == 0) {
+ isAbsolutePath = true;
+ isUrl = true;
+ }
+
+ if (!isAbsolutePath) {
+ scriptFile = "file://" + gCurrDir.path + "/" + scriptFile;
+ }
+ if (!isUrl) {
+ scriptFile = "file://" + scriptFile;
+ }
+
+ scriptFile = scriptFile.replace(/^(file:\/\/)+/, "file://");
+ return scriptFile;
+ },
+
+ createFileStream: function(filePath) {
+
+ const NS_RDONLY = 0x01;
+ const NS_WRONLY = 0x02;
+ const NS_CREATE_FILE = 0x08;
+ const NS_TRUNCATE = 0x20;
+ const DEFAULT_FILE_PERMS = 0x180; // equals 0600
+
+ let localFile;
+ filePath = gCurrDir.path + "/" + filePath;
+ if (this.getOS() == "WINNT") {
+ filePath = filePath.replace(/\//g, "\\");
+ }
+
+ dump("Creating log file: " + filePath + "\n");
+
+ localFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ localFile.initWithPath(filePath);
+
+ if (!localFile.exists()) {
+ localFile.persistentDescriptor = filePath;
+ }
+
+ if (localFile.exists()) {
+
+ if (localFile.isDirectory() || !localFile.isWritable())
+ throw Components.results.NS_ERROR_FAILURE;
+ }
+
+ const flags = NS_WRONLY | NS_CREATE_FILE | NS_TRUNCATE;
+ const fileStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+
+ fileStream.init(localFile, flags, DEFAULT_FILE_PERMS, 0);
+
+ return fileStream;
+ },
+
+ executeScript: function(scriptFile, isAbsolutePath, dontRun) {
+ scriptFile = JSUnit.makeUrl(scriptFile, isAbsolutePath);
+
+ let context = {};
+ Services.scriptloader.loadSubScript("chrome://jsunit/content/modules/jsunit-wrapper.js", context, "UTF-8");
+ Services.scriptloader.loadSubScript(scriptFile, context, "UTF-8");
+ if (!dontRun) {
+ Services.scriptloader.loadSubScript("chrome://jsunit/content/modules/jsunit-exec.js", context, "UTF-8");
+ }
+
+ if (gTestPending) {
+ JSUnit.waitForAsyncTest();
+ }
+ },
+
+ loadScript: function(urlString, context) {
+ try {
+ Services.scriptloader.loadSubScript(urlString, context, "UTF-8");
+ }
+ catch (ex) {
+ JSUnit.printMsg("Failed to load '" + urlString + "' into " + context + "\n");
+ JSUnit.printMsg(ex.toString() + "\n");
+ throw "ERROR while loading script";
+ }
+ },
+
+ abortPendingTests: function() {
+ gTestPending = 0;
+ },
+
+ testPending: function() {
+ ++gTestPending;
+ },
+
+ waitForAsyncTest: function() {
+ var thread = Cc['@mozilla.org/thread-manager;1'].getService(Ci.nsIThreadManager).currentThread;
+ while (gTestPending > 0) {
+ thread.processNextEvent(true);
+ }
+ },
+
+ testFinished: function() {
+ if (gTestPending > 0) --gTestPending;
+ },
+
+ countSucceeded: function() {
+ return gTestSucceed;
+ },
+
+ countFailed: function() {
+ return gTestError;
+ },
+
+ // create empty DOM document
+ createDOMDocument: function() {
+
+ let appShellSvc = Cc["@mozilla.org/appshell/appShellService;1"].getService(Ci.nsIAppShellService);
+
+ let doc = appShellSvc.hiddenDOMWindow.document;
+
+ return doc;
+ },
+
+ // create a non-function nsIDOMWindow object that can be used as stub
+ createStubWindow: function() {
+ var w = {
+ QueryInterface: generateQI(["nsIDOMWindow"]),
+ window: null,
+ self: null,
+ document: null,
+ name: "JSUtil Stub Window",
+ location: null,
+ history: null,
+ locationbar: null,
+ menubar: null,
+ personalbar: null,
+ scrollbars: null,
+ statusbar: null,
+ toolbar: null,
+ status: "",
+ close: function() {},
+ stop: function() {},
+ focus: function() {},
+ blur: function() {},
+ length: 0,
+ top: null,
+ parent: null,
+ opener: null,
+ frameElement: null,
+ navigator: {
+ QueryInterface: generateQI(["nsIDOMNavigator"]),
+ appCodeName: "JSUnit",
+ appName: "JSUnit",
+ appVersion: "1",
+ language: "en",
+ platform: "",
+ oscpu: "",
+ vendor: "",
+ vendorSub: "",
+ product: "",
+ productSub: "",
+ userAgent: "",
+ buildID: "",
+ doNotTrack: ""
+ },
+
+ applicationCache: null,
+ alert: function() {},
+ confirm: function() {},
+ prompt: function() {},
+ print: function() {},
+ showModalDialog: function() {},
+ postMessage: function() {},
+ atob: function(s) {
+ return atob(s);
+ },
+ btoa: function(s) {
+ return btoa(s);
+ },
+ sessionStorage: null,
+ localStorage: null,
+ indexedDB: null,
+ mozIndexedDB: null,
+ getSelection: function() {},
+ matchMedia: function() {},
+ screen: null,
+ innerWidth: 0,
+ innerHeight: 0,
+ scrollX: 0,
+ pageXOffset: 0,
+ scrollY: 0,
+ pageYOffset: 0,
+ scroll: function() {},
+ scrollTo: function() {},
+ scrollBy: function() {},
+ screenX: 0,
+ screenY: 0,
+ outerWidth: 0,
+ outerHeight: 0,
+ getComputedStyle: function() {},
+ getDefaultComputedStyle: function() {},
+ scrollByLines: function() {},
+ scrollByPages: function() {},
+ sizeToContent: function() {},
+ closed: false,
+ crypto: null,
+ mozInnerScreenX: 0.0,
+ mozInnerScreenY: 0.0,
+ devicePixelRatio: 1.0,
+ scrollMaxX: 0,
+ scrollMaxY: 0,
+ fullScreen: false,
+ back: function() {},
+ forward: function() {},
+ home: function() {},
+ moveTo: function() {},
+ moveBy: function() {},
+ resizeTo: function() {},
+ resizeBy: function() {},
+ open: function() {},
+ openDialog: function() {},
+ updateCommands: function() {},
+ find: function() {},
+ mozPaintCount: 0,
+ mozRequestAnimationFrame: function() {},
+ requestAnimationFrame: function() {},
+ mozCancelAnimationFrame: function() {},
+ mozCancelRequestAnimationFrame: function() {},
+ cancelAnimationFrame: function() {},
+ mozAnimationStartTime: 0,
+ onafterprint: null,
+ onbeforeprint: null,
+ onbeforeunload: null,
+ onhashchange: null,
+ onlanguagechange: null,
+ onmessage: null,
+ onoffline: null,
+ ononline: null,
+ onpopstate: null,
+ onpagehide: null,
+ onpageshow: null,
+ // Not supported yet (Gecko 32)
+ onredo: null,
+ onresize: null,
+ // Not supported yet (Gecko 32)
+ onstorage: null,
+ // Not supported yet (Gecko 32)
+ onundo: null,
+ onunload: null,
+ ondevicemotion: null,
+ ondeviceorientation: null,
+ ondeviceproximity: null,
+ onuserproximity: null,
+ ondevicelight: null,
+ onmouseenter: null,
+ onmouseleave: null,
+ console: null,
+ addEventListener: function() {}
+ };
+
+ w.self = w;
+ w.top = w;
+ w.parent = w;
+ return w;
+ }
+};
diff --git a/content/modules/jsunit-service.js b/content/modules/jsunit-service.js
new file mode 100644
index 0000000..f9e78a9
--- /dev/null
+++ b/content/modules/jsunit-service.js
@@ -0,0 +1,61 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+/* global dump: false, Cu: false, Ci: false, Cr: false, Components: false */
+
+var EXPORTED_SYMBOLS = ["JSUnitCommandLine"];
+
+const Cm = Components.manager;
+Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+const JSUnit = ChromeUtils.import("chrome://jsunit/content/modules/jsunit-main.jsm").JSUnit;
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+const {
+ setTimeout,
+ clearTimeout
+} = ChromeUtils.import("resource://gre/modules/Timer.jsm");
+
+
+function DEBUG_LOG(str) {
+ dump("jsunit-service.js: " + str + "\n");
+}
+
+var gStartTime;
+
+
+function startCmdLineTests(fileName, logFileName) {
+ var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
+ gStartTime = Date.now();
+
+ JSUnit.init(false, logFileName);
+ JSUnit.printMsg("Starting JS unit tests " + fileName + "\n");
+
+ try {
+ try {
+ // ensure cache is deleted upon next application start
+ Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).invalidateCachesOnRestart();
+ JSUnit.executeScript(fileName, false, true);
+ } catch (ex) {
+ JSUnit.logTestResult("Exception occurred:\n" + ex.toString(), null, "");
+ dump("** Tests aborted **\n");
+ }
+ JSUnit.printStats();
+ } catch (x) {}
+
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit);
+}
+
+var JSUnitCommandLine = {
+ startup: function() {
+ dump("JSUnit: startup\n");
+ setTimeout(function _f() {
+ dump("JSUnit starting tests\n");
+ startCmdLineTests("main.js", null);
+ }, 3000);
+ }
+}; \ No newline at end of file
diff --git a/content/modules/jsunit-wrapper.js b/content/modules/jsunit-wrapper.js
new file mode 100644
index 0000000..045582e
--- /dev/null
+++ b/content/modules/jsunit-wrapper.js
@@ -0,0 +1,50 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+/*
+ Wrapper file that is inclued for every Test executed
+*/
+
+const JSUnit = ChromeUtils.import("chrome://jsunit/content/modules/jsunit-main.jsm").JSUnit;
+
+var Assert = JSUnit.assert;
+
+// create placeholders for window and document to be used by command
+// line tests
+//
+// an empty document is always available; window is only created on request
+
+
+function do_get_file(filename, allowNonexistent) {
+ var c = Components.stack.caller;
+ return JSUnit.getFile(c, filename, allowNonexistent);
+}
+
+var do_get_cwd = JSUnit.getCwd;
+
+var do_test_pending = JSUnit.testPending;
+
+var do_test_finished = JSUnit.testFinished;
+
+var do_print = JSUnit.printMsg;
+
+function do_subtest(filePath) {
+ JSUnit.printMsg("*** Executing sub-test '" + filePath + "' ***");
+ return JSUnit.executeScript(filePath);
+}
+
+
+function do_load_module(urlString, targetObj) {
+ /* eslint no-invalid-this: 0 */
+ if (targetObj) {
+ JSUnit.loadScript(urlString, targetObj);
+ } else {
+ JSUnit.loadScript(urlString, this);
+ }
+}
+
+function do_open_tinyjsd() {} \ No newline at end of file
diff --git a/content/sample-include.js b/content/sample-include.js
new file mode 100644
index 0000000..e028584
--- /dev/null
+++ b/content/sample-include.js
@@ -0,0 +1,11 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+function timesThree ( n ) {
+ return n * 3;
+}
diff --git a/defaults/preferences/jsunit.js b/defaults/preferences/jsunit.js
new file mode 100644
index 0000000..f253d1e
--- /dev/null
+++ b/defaults/preferences/jsunit.js
@@ -0,0 +1,33 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Default pref values for JSUnit
+ */
+
+// the configured version
+pref("extensions.jsunit.configuredVersion", "0.1");
+
+// enable dumping results to the command line
+pref("browser.dom.window.dump.enabled", true);
+
+// disable XUL cache
+pref("nglayout.debug.disable_xul_cache", true);
+
+
+pref('javascript.options.showInConsole', true);
+pref('devtools.chrome.enabled', true);
+pref('extensions.logging.enabled', true);
+pref('nglayout.debug.disable_xul_fastload', true);
+pref('dom.report_all_js_exceptions', true);
+pref('devtools.errorconsole.deprecation_warnings', true);
+pref('devtools.errorconsole.enabled', true);
+
+pref('browser.cache.disk.enable', false);
+pref('browser.cache.memory.enable', false);
+pref('browser.cache.disk.max_entry_size', 0);
+pref('browser.cache.memory.max_entry_size', 0);
+pref('network.http.use-cache', false);
diff --git a/install.rdf b/install.rdf
new file mode 100644
index 0000000..fee099c
--- /dev/null
+++ b/install.rdf
@@ -0,0 +1 @@
+This file exists as a placeholder until https://bugzilla.mozilla.org/show_bug.cgi?id=1472123 is fixed. \ No newline at end of file
diff --git a/license.txt b/license.txt
new file mode 100644
index 0000000..41dc232
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,3 @@
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at http://mozilla.org/MPL/2.0/.
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..2934a9f
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,17 @@
+{
+ "manifest_version": 2,
+ "name": "JavaScript Unit Test Suite",
+ "description": "A suite to perform unit tests for JavaScript.",
+ "version": "0.2.2",
+ "author": "Patrick Brunschwig",
+ "homepage_url": "https://sourceforge.net/u/pbrunschwig/jsunit/",
+ "legacy": {
+ "type": "bootstrap"
+ },
+ "applications": {
+ "gecko": {
+ "id": "jsunit@enigmail.net", // GUID
+ "strict_min_version": "68.0a1"
+ }
+ }
+} \ No newline at end of file
diff --git a/tests/sample-test.js b/tests/sample-test.js
new file mode 100755
index 0000000..de07e7c
--- /dev/null
+++ b/tests/sample-test.js
@@ -0,0 +1,15 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+do_print("** Performing Subtest 1 **")
+
+do_subtest("tests/subtest1.js");
+
+do_print("** Performing Subtest 2 **")
+
+do_subtest("tests/subtest2.js");
+
diff --git a/tests/subtest1.js b/tests/subtest1.js
new file mode 100755
index 0000000..bc59c7c
--- /dev/null
+++ b/tests/subtest1.js
@@ -0,0 +1,47 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+function runDelayed( callbackFunction, sleepTimeMs ) {
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(callbackFunction, sleepTimeMs, Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+}
+
+function asyncTests() {
+
+ do_print("** Performing async tests **");
+ Assert.ok(true, "true");
+ Assert.ok(false, "false");
+ Assert.equal("3", 3, "equal");
+ Assert.deepEqual('3', 3, "deepEqual");
+ Assert.strictEqual('3', 3, "strictEqual");
+
+ // check if invalid/filename.txt exists
+ do_get_file("invalid/filename.txt");
+ do_test_finished();
+}
+
+function syncTests() {
+ do_print("** Performing Synchronous tests **")
+ Assert.ok(true, "true");
+ Assert.ok(false);
+ Assert.equal(timesThree(5), 15);
+
+ // check if test/sample-test.js exists
+ do_get_file("tests/sample-test.js");
+}
+
+do_load_module("chrome://jsunit/content/sample-include.js");
+
+function run_test() {
+ syncTests();
+
+ do_test_pending(); // should be set before the async call
+ runDelayed(asyncTests, 100);
+}
+
+
diff --git a/tests/subtest2.js b/tests/subtest2.js
new file mode 100755
index 0000000..5e36791
--- /dev/null
+++ b/tests/subtest2.js
@@ -0,0 +1,50 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function runDelayed( callbackFunction, sleepTimeMs ) {
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(callbackFunction, sleepTimeMs, Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+}
+
+function asyncTests() {
+
+ do_print("** Performing async tests **");
+ Assert.ok(true, "true");
+ Assert.ok(false, "false");
+ Assert.equal("3", 3, "equal");
+ Assert.deepEqual('3', 3, "deepEqual");
+ Assert.strictEqual('3', 3, "strictEqual");
+
+ // check if invalid/filename.txt exists
+ do_get_file("invalid/filename.txt");
+ try {
+ // if this is not handled the unit test won't finish!
+ callInvalidFunction();
+ }
+ catch(ex) {}
+
+ do_test_finished();
+}
+
+function syncTests() {
+ do_print("** Performing Synchronous tests **")
+ Assert.ok(true, "true");
+ Assert.ok(false);
+ Assert.equal(timesThree(5), 15);
+
+ // check if test/sample-test.js exists
+ do_get_file("tests/sample-test.js");
+}
+
+do_load_module("chrome://jsunit/content/sample-include.js");
+
+function run_test() {
+ syncTests();
+
+ do_test_pending(); // should be set before the async call
+ runDelayed(asyncTests, 100);
+}