"use strict"; // The ext-c-* files are imported into the same scopes. /* import-globals-from ext-c-toolkit.js */ /** * Checks whether the given error matches the given expectations. * * @param {*} error * The error to check. * @param {string|RegExp|function|null} expectedError * The expectation to check against. If this parameter is: * * - a string, the error message must exactly equal the string. * - a regular expression, it must match the error message. * - a function, it is called with the error object and its * return value is returned. * - null, the function always returns true. * @param {BaseContext} context * * @returns {boolean} * True if the error matches the expected error. */ const errorMatches = (error, expectedError, context) => { if (expectedError === null) { return true; } if (typeof expectedError === "function") { return context.runSafeWithoutClone(expectedError, error); } if (typeof error !== "object" || error == null || typeof error.message !== "string") { return false; } if (typeof expectedError === "string") { return error.message === expectedError; } try { return expectedError.test(error.message); } catch (e) { Cu.reportError(e); } return false; }; /** * Calls .toSource() on the given value, but handles null, undefined, * and errors. * * @param {*} value * @returns {string} */ const toSource = value => { if (value === null) { return "null"; } if (value === undefined) { return "undefined"; } if (typeof value === "string") { return JSON.stringify(value); } try { return String(value.toSource()); } catch (e) { return ""; } }; this.test = class extends ExtensionAPI { getAPI(context) { const {extension} = context; function getStack() { return new context.cloneScope.Error().stack.replace(/^/gm, " "); } function assertTrue(value, msg) { extension.emit("test-result", Boolean(value), String(msg), getStack()); } class TestEventManager extends EventManager { addListener(callback, ...args) { super.addListener(function(...args) { try { callback.call(this, ...args); } catch (e) { assertTrue(false, `${e}\n${e.stack}`); } }, ...args); } } return { test: { sendMessage(...args) { extension.emit("test-message", ...args); }, notifyPass(msg) { extension.emit("test-done", true, msg, getStack()); }, notifyFail(msg) { extension.emit("test-done", false, msg, getStack()); }, log(msg) { extension.emit("test-log", true, msg, getStack()); }, fail(msg) { assertTrue(false, msg); }, succeed(msg) { assertTrue(true, msg); }, assertTrue(value, msg) { assertTrue(value, msg); }, assertFalse(value, msg) { assertTrue(!value, msg); }, assertEq(expected, actual, msg) { let equal = expected === actual; expected = String(expected); actual = String(actual); if (!equal && expected === actual) { actual += " (different)"; } extension.emit("test-eq", equal, String(msg), expected, actual, getStack()); }, assertRejects(promise, expectedError, msg) { // Wrap in a native promise for consistency. promise = Promise.resolve(promise); if (msg) { msg = `: ${msg}`; } return promise.then(result => { assertTrue(false, `Promise resolved, expected rejection${msg}`); }, error => { let errorMessage = toSource(error && error.message); assertTrue(errorMatches(error, expectedError, context), `Promise rejected, expecting rejection to match ${toSource(expectedError)}, ` + `got ${errorMessage}${msg}`); }); }, assertThrows(func, expectedError, msg) { if (msg) { msg = `: ${msg}`; } try { func(); assertTrue(false, `Function did not throw, expected error${msg}`); } catch (error) { let errorMessage = toSource(error && error.message); assertTrue(errorMatches(error, expectedError, context), `Function threw, expecting error to match ${toSource(expectedError)}` + `got ${errorMessage}${msg}`); } }, onMessage: new TestEventManager(context, "test.onMessage", fire => { let handler = (event, ...args) => { fire.async(...args); }; extension.on("test-harness-message", handler); return () => { extension.off("test-harness-message", handler); }; }).api(), }, }; } };