731 lines
22 KiB
JavaScript
731 lines
22 KiB
JavaScript
/* 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/. */
|
|
|
|
// Originally from narwhal.js (http://narwhaljs.org)
|
|
// Copyright (c) 2009 Thomas Robinson <280north.com>
|
|
// MIT license: http://opensource.org/licenses/MIT
|
|
|
|
import { ObjectUtils } from "resource://gre/modules/ObjectUtils.sys.mjs";
|
|
|
|
/**
|
|
* This module is based on the
|
|
* `CommonJS spec <https://wiki.commonjs.org/wiki/Unit_Testing/1.0>`_
|
|
*
|
|
* When you see a jsdoc comment that contains a number, it's a reference to a
|
|
* specific section of the CommonJS spec.
|
|
*
|
|
* 1. The assert module provides functions that throw AssertionError's when
|
|
* particular conditions are not met.
|
|
*
|
|
* To use the module you may instantiate it first.
|
|
*
|
|
* @param {reporterFunc} reporterFunc
|
|
* Allows consumers to override reporting for this instance.
|
|
* @param {boolean} isDefault
|
|
* Used by test suites to set ``reporterFunc`` as the default
|
|
* used by the global instance, which is called for example
|
|
* by other test-only modules. This is false when the
|
|
* reporter is set by content scripts, because they may still
|
|
* run in the parent process.
|
|
*
|
|
* @class
|
|
*/
|
|
export function Assert(reporterFunc, isDefault) {
|
|
if (reporterFunc) {
|
|
this.setReporter(reporterFunc);
|
|
}
|
|
if (isDefault) {
|
|
Assert.setReporter(reporterFunc);
|
|
}
|
|
}
|
|
|
|
// This allows using the Assert object as an additional global instance.
|
|
Object.setPrototypeOf(Assert, Assert.prototype);
|
|
|
|
function instanceOf(object, type) {
|
|
return Object.prototype.toString.call(object) == "[object " + type + "]";
|
|
}
|
|
|
|
function replacer(key, value) {
|
|
if (value === undefined) {
|
|
return "" + value;
|
|
}
|
|
if (typeof value === "number" && (isNaN(value) || !isFinite(value))) {
|
|
return value.toString();
|
|
}
|
|
if (typeof value === "function" || instanceOf(value, "RegExp")) {
|
|
return value.toString();
|
|
}
|
|
if (
|
|
typeof value === "object" &&
|
|
value !== null &&
|
|
"QueryInterface" in value
|
|
) {
|
|
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);
|
|
}
|
|
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) {
|
|
let truncateLength = error.truncate ? kTruncateLength : Infinity;
|
|
message +=
|
|
(prefix ? " - " : "") +
|
|
truncate(actual, truncateLength) +
|
|
" " +
|
|
error.operator +
|
|
" " +
|
|
truncate(expected, truncateLength);
|
|
}
|
|
return message;
|
|
}
|
|
|
|
/**
|
|
* 2. The AssertionError is defined in assert.
|
|
*
|
|
* At present only the four keys mentioned below are used and
|
|
* understood by the spec. Implementations or sub modules can pass
|
|
* other keys to the AssertionError's constructor - they will be
|
|
* ignored.
|
|
*
|
|
* @example
|
|
*
|
|
* new assert.AssertionError({
|
|
* message: message,
|
|
* actual: actual,
|
|
* expected: expected,
|
|
* operator: operator,
|
|
* truncate: truncate,
|
|
* stack: stack, // Optional, defaults to the current stack.
|
|
* });
|
|
*
|
|
*/
|
|
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, options.truncate);
|
|
// The part of the stack that comes from this module is not interesting.
|
|
let stack = options.stack || Components.stack;
|
|
do {
|
|
stack = stack.asyncCaller || stack.caller;
|
|
} while (
|
|
stack &&
|
|
stack.filename &&
|
|
stack.filename.includes("Assert.sys.mjs")
|
|
);
|
|
this.stack = stack;
|
|
};
|
|
|
|
// assert.AssertionError instanceof Error
|
|
Assert.AssertionError.prototype = Object.create(Error.prototype, {
|
|
constructor: {
|
|
value: Assert.AssertionError,
|
|
enumerable: false,
|
|
writable: true,
|
|
configurable: true,
|
|
},
|
|
});
|
|
|
|
Assert.prototype._reporter = null;
|
|
|
|
/**
|
|
* This callback type is used for custom assertion report handling.
|
|
*
|
|
* @callback reporterFunc
|
|
* @param {AssertionError|null} err
|
|
* An error object when the assertion failed, or null when it passed.
|
|
* @param {String} message
|
|
* Message describing the assertion.
|
|
* @param {Stack} stack
|
|
* Stack trace of the assertion function.
|
|
*/
|
|
|
|
/**
|
|
* Set a custom assertion report handler function.
|
|
*
|
|
* @example
|
|
*
|
|
* 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} reporterFunc
|
|
* Report handler function.
|
|
*/
|
|
Assert.prototype.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
|
|
*
|
|
* // The following will report an assertion failure.
|
|
* this.report(1 != 2, 1, 2, "testing JS number math!", "==");
|
|
*
|
|
* @param {boolean} failed
|
|
* Indicates if the assertion failed or not.
|
|
* @param {*} actual
|
|
* The result of evaluating the assertion.
|
|
* @param {*} [expected]
|
|
* Expected result from the test author.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
* @param {String} [operator]
|
|
* Operation qualifier used by the assertion method (ex: '==').
|
|
* @param {boolean} [truncate=true]
|
|
* Whether or not ``actual`` and ``expected`` should be truncated when printing.
|
|
* @param {nsIStackFrame} [stack]
|
|
* The stack trace including the caller of the assertion method,
|
|
* if this cannot be inferred automatically (e.g. due to async callbacks).
|
|
*/
|
|
Assert.prototype.report = function (
|
|
failed,
|
|
actual,
|
|
expected,
|
|
message,
|
|
operator,
|
|
truncate = true,
|
|
stack = null // Defaults to Components.stack in AssertionError.
|
|
) {
|
|
// Although not ideal, we allow a "null" message due to the way some of the extension tests
|
|
// work.
|
|
if (message !== undefined && message !== null && typeof message != "string") {
|
|
this.ok(
|
|
false,
|
|
`Expected a string or undefined for the error message to Assert.*, got ${typeof message}`
|
|
);
|
|
}
|
|
let err = new Assert.AssertionError({
|
|
message,
|
|
actual,
|
|
expected,
|
|
operator,
|
|
truncate,
|
|
stack,
|
|
});
|
|
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
|
|
* Test subject to be evaluated as truthy.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.ok = function (value, message) {
|
|
if (arguments.length > 2) {
|
|
this.report(
|
|
true,
|
|
false,
|
|
true,
|
|
"Too many arguments passed to `Assert.ok()`",
|
|
"=="
|
|
);
|
|
} else {
|
|
this.report(!value, value, true, message, "==");
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 5. The equality assertion tests shallow, coercive equality with ==.
|
|
* ``assert.equal(actual, expected, message_opt);``
|
|
*
|
|
* @param {*} actual
|
|
* Test subject to be evaluated as equivalent to ``expected``.
|
|
* @param {*} expected
|
|
* Test reference to evaluate against ``actual``.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.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 ``!=``
|
|
*
|
|
* @example
|
|
* assert.notEqual(actual, expected, message_opt);
|
|
*
|
|
* @param {*} actual
|
|
* Test subject to be evaluated as NOT equivalent to ``expected``.
|
|
* @param {*} expected
|
|
* Test reference to evaluate against ``actual``.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.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
|
|
* Test subject to be evaluated as equivalent to ``expected``, including nested properties.
|
|
* @param {*} expected
|
|
* Test reference to evaluate against ``actual``.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.deepEqual = function deepEqual(actual, expected, message) {
|
|
this.report(
|
|
!ObjectUtils.deepEqual(actual, expected),
|
|
actual,
|
|
expected,
|
|
message,
|
|
"deepEqual",
|
|
false
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 8. The non-equivalence assertion tests for any deep inequality.
|
|
* assert.notDeepEqual(actual, expected, message_opt);
|
|
*
|
|
* @param {*} actual
|
|
* Test subject to be evaluated as NOT equivalent to ``expected``, including nested
|
|
* properties.
|
|
* @param {*} expected
|
|
* Test reference to evaluate against ``actual``.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.notDeepEqual = function notDeepEqual(
|
|
actual,
|
|
expected,
|
|
message
|
|
) {
|
|
this.report(
|
|
ObjectUtils.deepEqual(actual, expected),
|
|
actual,
|
|
expected,
|
|
message,
|
|
"notDeepEqual",
|
|
false
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 9. The strict equality assertion tests strict equality, as determined by ===.
|
|
* ``assert.strictEqual(actual, expected, message_opt);``
|
|
*
|
|
* @param {*} actual
|
|
* Test subject to be evaluated as strictly equivalent to ``expected``.
|
|
* @param {*} expected
|
|
* Test reference to evaluate against ``actual``.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.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
|
|
* Test subject to be evaluated as NOT strictly equivalent to ``expected``.
|
|
* @param {*} expected
|
|
* Test reference to evaluate against ``actual``.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.notStrictEqual = function notStrictEqual(
|
|
actual,
|
|
expected,
|
|
message
|
|
) {
|
|
this.report(actual === expected, actual, expected, message, "!==");
|
|
};
|
|
|
|
function checkExpectedArgument(instance, funcName, expected) {
|
|
if (!expected) {
|
|
instance.ok(
|
|
false,
|
|
`Error: The 'expected' argument was not supplied to Assert.${funcName}()`
|
|
);
|
|
}
|
|
|
|
if (
|
|
!instanceOf(expected, "RegExp") &&
|
|
typeof expected !== "function" &&
|
|
typeof expected !== "object"
|
|
) {
|
|
instance.ok(
|
|
false,
|
|
`Error: The 'expected' argument to Assert.${funcName}() must be a RegExp, function or an object`
|
|
);
|
|
}
|
|
}
|
|
|
|
function expectedException(actual, expected) {
|
|
if (!actual || !expected) {
|
|
return false;
|
|
}
|
|
|
|
if (instanceOf(expected, "RegExp")) {
|
|
return expected.test(actual);
|
|
// We need to guard against the right hand parameter of "instanceof" lacking
|
|
// the "prototype" property, which is true of arrow functions in particular.
|
|
} else if (
|
|
!(typeof expected === "function" && !expected.prototype) &&
|
|
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);
|
|
*
|
|
* Example:
|
|
* ```js
|
|
* // The following will verify that an error of type TypeError was thrown:
|
|
* Assert.throws(() => testBody(), TypeError);
|
|
* // The following will verify that an error was thrown with an error message matching "hello":
|
|
* Assert.throws(() => testBody(), /hello/);
|
|
* ```
|
|
*
|
|
* @param {Function} block
|
|
* Function to evaluate and catch eventual thrown errors.
|
|
* @param {RegExp|Function} expected
|
|
* This parameter can be either a RegExp or a function. The function is
|
|
* either the error type's constructor, or it's a method that returns
|
|
* a boolean that describes the test outcome.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.throws = function (block, expected, message) {
|
|
checkExpectedArgument(this, "throws", expected);
|
|
|
|
// `true` if we realize that we have added an
|
|
// error to `ChromeUtils.recentJSDevError` and
|
|
// that we probably need to clean it up.
|
|
let cleanupRecentJSDevError = false;
|
|
if ("recentJSDevError" in ChromeUtils) {
|
|
// Check that we're in a build of Firefox that supports
|
|
// the `recentJSDevError` mechanism (i.e. Nightly build).
|
|
if (ChromeUtils.recentJSDevError === undefined) {
|
|
// There was no previous error, so if we throw
|
|
// an error here, we may need to clean it up.
|
|
cleanupRecentJSDevError = true;
|
|
}
|
|
}
|
|
|
|
let actual;
|
|
|
|
try {
|
|
block();
|
|
} catch (e) {
|
|
actual = e;
|
|
}
|
|
|
|
message =
|
|
(expected.name ? " (" + expected.name + ")." : ".") +
|
|
(message ? " " + message : ".");
|
|
|
|
if (!actual) {
|
|
this.report(true, actual, expected, "Missing expected exception" + message);
|
|
}
|
|
|
|
if (actual && !expectedException(actual, expected)) {
|
|
throw actual;
|
|
}
|
|
|
|
this.report(false, expected, expected, message);
|
|
|
|
// Make sure that we don't cause failures for JS Dev Errors that
|
|
// were expected, typically for tests that attempt to check
|
|
// that we react properly to TypeError, ReferenceError, SyntaxError.
|
|
if (cleanupRecentJSDevError) {
|
|
let recentJSDevError = ChromeUtils.recentJSDevError;
|
|
if (recentJSDevError) {
|
|
if (expectedException(recentJSDevError)) {
|
|
ChromeUtils.clearRecentJSDevError();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A promise that is expected to reject:
|
|
* assert.rejects(promise, expected, message);
|
|
*
|
|
* @param {Promise} promise
|
|
* A promise that is expected to reject.
|
|
* @param {?} [expected]
|
|
* Test reference to evaluate against the rejection result.
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.rejects = function (promise, expected, message) {
|
|
checkExpectedArgument(this, "rejects", expected);
|
|
const operator = undefined; // Should we use "rejects" here?
|
|
const stack = Components.stack;
|
|
return new Promise((resolve, reject) => {
|
|
return promise
|
|
.then(
|
|
() => {
|
|
this.report(
|
|
true,
|
|
null,
|
|
expected,
|
|
"Missing expected exception " + message,
|
|
operator,
|
|
true,
|
|
stack
|
|
);
|
|
// this.report() above should raise an AssertionError. If _reporter
|
|
// has been overridden and doesn't throw an error, just resolve.
|
|
// Otherwise we'll have a never-resolving promise that got stuck.
|
|
resolve();
|
|
},
|
|
err => {
|
|
if (!expectedException(err, expected)) {
|
|
// TODO bug 1480075: Should report error instead of rejecting.
|
|
reject(err);
|
|
return;
|
|
}
|
|
this.report(false, err, expected, message, operator, truncate, stack);
|
|
resolve();
|
|
}
|
|
)
|
|
.catch(reject);
|
|
});
|
|
};
|
|
|
|
function compareNumbers(expression, lhs, rhs, message, operator) {
|
|
let lhsIsNumber = typeof lhs == "number" && !Number.isNaN(lhs);
|
|
let rhsIsNumber = typeof rhs == "number" && !Number.isNaN(rhs);
|
|
|
|
if (lhsIsNumber && rhsIsNumber) {
|
|
this.report(expression, lhs, rhs, message, operator);
|
|
return;
|
|
}
|
|
let lhsIsDate =
|
|
typeof lhs == "object" && lhs.constructor.name == "Date" && !isNaN(lhs);
|
|
let rhsIsDate =
|
|
typeof rhs == "object" && rhs.constructor.name == "Date" && !isNaN(rhs);
|
|
if (lhsIsDate && rhsIsDate) {
|
|
this.report(expression, lhs, rhs, message, operator);
|
|
return;
|
|
}
|
|
|
|
let errorMessage;
|
|
if (!lhsIsNumber && !rhsIsNumber && !lhsIsDate && !rhsIsDate) {
|
|
errorMessage = `Neither '${lhs}' nor '${rhs}' are numbers or dates.`;
|
|
} else if ((lhsIsNumber && rhsIsDate) || (lhsIsDate && rhsIsNumber)) {
|
|
errorMessage = `'${lhsIsNumber ? lhs : rhs}' is a number and '${
|
|
rhsIsDate ? rhs : lhs
|
|
}' is a date.`;
|
|
} else {
|
|
errorMessage = `'${
|
|
lhsIsNumber || lhsIsDate ? rhs : lhs
|
|
}' is not a number or date.`;
|
|
}
|
|
this.report(true, lhs, rhs, errorMessage);
|
|
}
|
|
|
|
/**
|
|
* The lhs must be greater than the rhs.
|
|
* assert.greater(lhs, rhs, message_opt);
|
|
*
|
|
* @param {Number} lhs
|
|
* The left-hand side value.
|
|
* @param {Number} rhs
|
|
* The right-hand side value.
|
|
* @param {String} [message]
|
|
* Short explanation of the comparison result.
|
|
*/
|
|
Assert.prototype.greater = function greater(lhs, rhs, message) {
|
|
compareNumbers.call(this, lhs <= rhs, lhs, rhs, message, ">");
|
|
};
|
|
|
|
/**
|
|
* The lhs must be greater than or equal to the rhs.
|
|
* assert.greaterOrEqual(lhs, rhs, message_opt);
|
|
*
|
|
* @param {Number} [lhs]
|
|
* The left-hand side value.
|
|
* @param {Number} [rhs]
|
|
* The right-hand side value.
|
|
* @param {String} [message]
|
|
* Short explanation of the comparison result.
|
|
*/
|
|
Assert.prototype.greaterOrEqual = function greaterOrEqual(lhs, rhs, message) {
|
|
compareNumbers.call(this, lhs < rhs, lhs, rhs, message, ">=");
|
|
};
|
|
|
|
/**
|
|
* The lhs must be less than the rhs.
|
|
* assert.less(lhs, rhs, message_opt);
|
|
*
|
|
* @param {Number} [lhs]
|
|
* The left-hand side value.
|
|
* @param {Number} [rhs]
|
|
* The right-hand side value.
|
|
* @param {String} [message]
|
|
* Short explanation of the comparison result.
|
|
*/
|
|
Assert.prototype.less = function less(lhs, rhs, message) {
|
|
compareNumbers.call(this, lhs >= rhs, lhs, rhs, message, "<");
|
|
};
|
|
|
|
/**
|
|
* The lhs must be less than or equal to the rhs.
|
|
* assert.lessOrEqual(lhs, rhs, message_opt);
|
|
*
|
|
* @param {Number} [lhs]
|
|
* The left-hand side value.
|
|
* @param {Number} [rhs]
|
|
* The right-hand side value.
|
|
* @param {String} [message]
|
|
* Short explanation of the comparison result.
|
|
*/
|
|
Assert.prototype.lessOrEqual = function lessOrEqual(lhs, rhs, message) {
|
|
compareNumbers.call(this, lhs > rhs, lhs, rhs, message, "<=");
|
|
};
|
|
|
|
/**
|
|
* The lhs must be a string that matches the rhs regular expression.
|
|
* rhs can be specified either as a string or a RegExp object. If specified as a
|
|
* string it will be interpreted as a regular expression so take care to escape
|
|
* special characters such as "?" or "(" if you need the actual characters.
|
|
*
|
|
* @param {String} lhs
|
|
* The string to be tested.
|
|
* @param {String|RegExp} rhs
|
|
* The regular expression that the string will be tested with.
|
|
* Note that if passed as a string, this will be interpreted.
|
|
* as a regular expression.
|
|
* @param {String} [message]
|
|
* Short explanation of the comparison result.
|
|
*/
|
|
Assert.prototype.stringMatches = function stringMatches(lhs, rhs, message) {
|
|
if (typeof rhs != "string" && !instanceOf(rhs, "RegExp")) {
|
|
this.report(
|
|
true,
|
|
lhs,
|
|
String(rhs),
|
|
`Expected a string or a RegExp for rhs, but "${rhs}" isn't a string or a RegExp object.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (typeof lhs != "string") {
|
|
this.report(
|
|
true,
|
|
lhs,
|
|
String(rhs),
|
|
`Expected a string for lhs, but "${lhs}" isn't a string.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (typeof rhs == "string") {
|
|
try {
|
|
rhs = new RegExp(rhs);
|
|
} catch {
|
|
this.report(
|
|
true,
|
|
lhs,
|
|
rhs,
|
|
`Expected a valid regular expression for rhs, but "${rhs}" isn't one.`
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
|
|
const isCorrect = rhs.test(lhs);
|
|
this.report(!isCorrect, lhs, rhs.toString(), message, "matches");
|
|
};
|
|
|
|
/**
|
|
* The lhs must be a string that contains the rhs string.
|
|
*
|
|
* @param {String} lhs
|
|
* The string to be tested (haystack).
|
|
* @param {String} rhs
|
|
* The string to be found (needle).
|
|
* @param {String} [message]
|
|
* Short explanation of the expected result.
|
|
*/
|
|
Assert.prototype.stringContains = function stringContains(lhs, rhs, message) {
|
|
if (typeof lhs != "string" || typeof rhs != "string") {
|
|
this.report(
|
|
true,
|
|
lhs,
|
|
rhs,
|
|
`Expected a string for both lhs and rhs, but either "${lhs}" or "${rhs}" is not a string.`
|
|
);
|
|
}
|
|
|
|
const isCorrect = lhs.includes(rhs);
|
|
this.report(!isCorrect, lhs, rhs, message, "includes");
|
|
};
|