Ten skrypt nie powinien być instalowany bezpośrednio. Jest to biblioteka dla innych skyptów do włączenia dyrektywą meta // @require https://update.greasyfork.org/scripts/478188/1332408/NH_xunit.js
- // ==UserScript==
- // ==UserLibrary==
- // @name NH_xunit
- // @description xUnit style testing.
- // @version 53
- // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0-standalone.html
- // @homepageURL https://github.com/nexushoratio/userscripts
- // @supportURL https://github.com/nexushoratio/userscripts/issues
- // @match https://www.example.com/*
- // ==/UserLibrary==
- // ==/UserScript==
-
- window.NexusHoratio ??= {};
-
- window.NexusHoratio.xunit = (function xunit() {
- 'use strict';
-
- /** @type {number} - Bumped per release. */
- const version = 53;
-
- /**
- * @type {object} - For testing support.
- */
- const testing = {
- enabled: false,
- missingDescriptionsAreErrors: true,
- testCases: [],
- };
-
- /** Data about a test execution. */
- class TestExecution {
-
- start = 0;
- stop = 0;
-
- }
-
- /** Accumulated results from running a TestCase. */
- class TestResult {
-
- /** Unexpected exceptions. */
- errors = [];
-
- /** Explicit test failures (typically failed asserts). */
- failures = [];
-
- /** Skipped tests. */
- skipped = [];
-
- /** Successes. */
- successes = [];
-
- /** All test executions. */
- tests = new Map();
-
- /**
- * Record an unexpected exception from a execution.
- * @param {string} name - Name of the TestCase.testMethod.
- * @param {Error} exception - Exception caught.
- */
- addError(name, exception) {
- this.errors.push({
- name: name,
- error: exception.name,
- message: exception.message,
- });
- }
-
- /**
- * Record a test failure.
- * @param {string} name - Name of the TestCase.testMethod.
- * @param {string} message - Message from the test or framework.
- */
- addFailure(name, message) {
- this.failures.push({
- name: name,
- message: message,
- });
- }
-
- /**
- * Record a test skipped.
- * @param {string} name - Name of the TestCase.testMethod.
- * @param {string} message - Reason the test was skipped.
- */
- addSkip(name, message) {
- this.skipped.push({
- name: name,
- message: message,
- });
- }
-
- /**
- * Record a successful execution.
- * @param {string} name - Name of the TestCase.testMethod.
- */
- addSuccess(name) {
- this.successes.push(name);
- }
-
- /**
- * Record the start of a test execution.
- * @param {string} name - Name of the TestCase.testMethod.
- */
- startTest(name) {
- const execution = new TestExecution();
- execution.start = Date.now();
- this.tests.set(name, execution);
- }
-
- /**
- * Record the stop of a test execution.
- * @param {string} name - Name of the TestCase.testMethod.
- */
- stopTest(name) {
- this.tests.get(name).stop = Date.now();
- }
-
- /** @returns {boolean} - Indicates success so far. */
- wasSuccessful() {
- return this.errors.length === 0 && this.failures.length === 0;
- }
-
- /**
- * Text summary of the results.
- *
- * Useful for test runners.
- * @param {boolean} [formatted=false] - Try to line things up columns.
- * @returns {string[]} - Summary, one line per entry in the array.
- */
- summary(formatted = false) {
- const fields = ['total', 'successes', 'skipped', 'errors', 'failures'];
- const numbers = new Map();
- const results = [];
- let maxFieldLength = 0;
- let maxCountLength = 0;
- for (const field of fields) {
- if (field === 'total') {
- // Double duty: renaming 'tests' to 'total', and using '.size'
- numbers.set(field, this.tests.size);
- } else {
- numbers.set(field, this[field].length);
- }
- }
-
- if (formatted) {
- maxFieldLength = Math.max(...Array.from(numbers.keys())
- .map(x => x.length));
- maxCountLength = String(Math.max(...numbers.values())).length;
- }
-
- for (const field of fields) {
- const f = field.padEnd(maxFieldLength);
- const v = `${numbers.get(field)}`.padStart(maxCountLength);
- results.push(`${f} : ${v}`);
- }
- return results;
- }
-
- }
-
- /**
- * Attempt to get the type of item.
- *
- * This is internal to xunit, so no need to make it equivalent to the
- * built-in `typeof` operator. Hence, results are explicitly NOT
- * lower-cased in order to reduce chances of conflicts.
- *
- * This just needs to be good enough to find a comparator function.
- * @param {*} item - Item to inspect.
- * @returns {string} - The likely type of item.
- */
- function getType(item) {
- const builtInClasses = [
- 'Array',
- 'Date',
- 'Error',
- 'Map',
- 'Set',
- ];
- let type = Object.prototype.toString.call(item)
- .replace(/^\[object (?<type>.*)\]$/u, '$<type>');
- if (type === 'Function') {
- if (String(item)
- .startsWith('class ')) {
- type = 'class';
- } else if (builtInClasses.includes(item.name)) {
- type = 'class';
- }
- }
- if (type === 'Object') {
- if (typeof item.constructor.name === 'string') {
- type = item.constructor.name;
- }
- }
- return type;
- }
-
- /**
- * An xUnit style test framework.
- *
- * Many expected methods exist, such as setUp, setUpClass, addCleanup,
- * addClassCleanup, etc. No tearDown methods, however; use addCleanup.
- *
- * Assertion methods should always take a plain text string, typically named
- * `msg`, as the last parameter. This string should be added to the
- * assertion specific error message in case of a failure.
- *
- * JavaScript does not have portable access to things like line numbers and
- * stack traces (and in the case of userscripts, those may be inaccurate
- * anyway). So it can be difficult to track down a particular test failure.
- * The failure messages do include the name of the test class and test
- * method, but, if the method happens to have several assertions in it, it
- * may not be obvious which one failed. These extra descriptive messages
- * can help with differentiation. This system will emit a debug message if
- * any test method calls more than one assert method without a descriptive
- * message.
- *
- * While the *assertEqual()* method will handle many cases by looking up
- * special functions comparing by type. There may be times when what it can
- * handle needs to be enhanced. There are currently two ways to make such
- * enhancements.
- *
- * First, the method *addEqualFunc()* will allow the test method to register
- * an additional function for comparing two identical instances.
- *
- * Second, the property *defaultEqual* points to whatever *equalX()*
- * function should be used if one cannot be found, or if instances differ by
- * type. This fallback defaults to *equalEqEqEq()* which uses the strict
- * equality (`===`) operator. This can be explicitly set in the test
- * method. The method *equalValueOf()* will use the instance's *valueOf()*
- * method to get comparable values, and may be useful in such cases.
- *
- * In many languages, order does not matter for some built-in container
- * types (e.g., Map, Set). The JavaScript standard explicitly specifies
- * that order DOES matter for these types. However, for this test library,
- * the default *equalX()* functions explicitly IGNORE order.
- *
- * Some built-in types (e.g., Map, Set), do not have good string
- * representations when showing up in error messages. While user classes
- * can provide a *toString()* method, sometimes they may not be available.
- * To help with this situation, this class provides a registration system
- * similar to the one used for equality functions.
- *
- * The property *defaultRepr* points to *String()*, but may be overridden
- * for an invocation.
- *
- * The method *addReprFunc()* can allow users to register their own.
- *
- * Implementations for built-in types will be added as needed.
- *
- * All *assertX()* and *equalX()* methods should use *this.repr()* to turn
- * values into strings for these error messages.
- *
- * TestCases should run only one test method per instance. The name of the
- * method is registered during instantiation and invoked by calling
- * *instance.run().*. Generally, a system, like {@link TestRunner} is used
- * to register a number of TestCases, discover the test methods, and invoke
- * all of them in turn.
- *
- * @example
- * class FooTestCase extends TestCase {
- * testMethod() {
- * // Assemble - Act
- *
- * // Assert
- * this.assertEqual(actual, expected, 'extra message');
- * }
- * }
- *
- * const test = new FooTestCase('testMethod');
- * const result = test.run();
- */
- class TestCase {
-
- /**
- * Instantiate a TestCase.
- * @param {string} methodName - The method to run on this instantiation.
- */
- constructor(methodName) {
- if (new.target === TestCase) {
- throw new TypeError('Abstract class; do not instantiate directly.');
- }
-
- this.#methodName = methodName;
-
- this.defaultRepr = String;
- this.addReprFunc('String', this.reprString);
- this.addReprFunc('Array', this.reprArray);
- this.addReprFunc('Object', this.reprObject);
- this.addReprFunc('Map', this.reprMap);
- this.addReprFunc('Set', this.reprSet);
-
- this.defaultEqual = this.equalEqEqEq;
- this.addEqualFunc('String', this.equalString);
- this.addEqualFunc('Array', this.equalArray);
- this.addEqualFunc('Object', this.equalObject);
- this.addEqualFunc('Map', this.equalMap);
- this.addEqualFunc('Set', this.equalSet);
-
- this.addCleanup(this.#checkAssertionCounts);
- }
-
- static Error = class extends Error {
-
- /** @inheritdoc */
- constructor(...rest) {
- super(...rest);
- this.name = `TestCase.${this.constructor.name}`;
- }
-
- };
-
- static Fail = class extends this.Error {}
- static Skip = class extends this.Error {}
-
- static classCleanups = [];
-
- /** Called once before any instances are created. */
- static setUpClass() {
- // Empty.
- }
-
- /**
- * Register a function with arguments to run after all tests in the class
- * have ran.
- * @param {function} func - Function to call.
- * @param {...*} rest - Arbitrary arguments to func.
- */
- static addClassCleanup(func, ...rest) {
- this.classCleanups.push([func, rest]);
- }
-
- /** Execute all functions registered with addClassCleanup. */
- static doClassCleanups() {
- while (this.classCleanups.length) {
- const [func, rest] = this.classCleanups.pop();
- func.call(this, ...rest);
- }
- }
-
- /** @type {string} */
- get id() {
- const methodName = this.#methodName;
- return `${this.constructor.name}.${methodName}`;
- }
-
- /**
- * Execute the test method registered upon instantiation.
- * @param {TestResult} [result] - Instance for accumulating results.
- * Typically, a test runner will pass in one of these to gather results
- * across multiple tests.
- * @returns {TestResult} - Accumulated results (one is created if not
- * passed in).
- */
- run(result) {
- const localResult = result ?? new TestResult();
- const klass = this.constructor.name;
-
- localResult.startTest(this.id);
-
- let stage = null;
- try {
- stage = `${klass}.setUp`;
- this.setUp();
-
- stage = this.id;
- this[this.#methodName]();
-
- stage = `${klass}.doCleanups`;
- this.doCleanups();
-
- localResult.addSuccess(this.id);
- } catch (e) {
- const inCleanup = stage.includes('.doCleanups');
- if (e instanceof TestCase.Skip && !inCleanup) {
- localResult.addSkip(stage, e.message);
- } else if (e instanceof TestCase.Fail && !inCleanup) {
- localResult.addFailure(stage, e.message);
- } else {
- localResult.addError(stage, e);
- }
- }
-
- localResult.stopTest(this.id);
-
- return localResult;
- }
-
- /** Called once before each test method. */
- setUp() { // eslint-disable-line class-methods-use-this
- // Empty.
- }
-
- /**
- * Register a function with arguments to run after a test.
- * @param {function} func - Function to call.
- * @param {...*} rest - Arbitrary arguments to func.
- */
- addCleanup(func, ...rest) {
- this.#cleanups.push([func, rest]);
- }
-
- /** Execute all functions registered with addCleanup. */
- doCleanups() {
- while (this.#cleanups.length) {
- const [func, rest] = this.#cleanups.pop();
- func.call(this, ...rest);
- }
- }
-
- /**
- * Immediately skips a test method.
- * @param {string} [msg=''] - Reason for skipping.
- * @throws {TestCase.Skip}
- */
- skip(msg = '') {
- throw new this.constructor.Skip(msg);
- }
-
- /**
- * Immediately fail a test method.
- * @param {string} [msg=''] - Reason for the failure.
- * @throws {TestCase.Fail}
- */
- fail(msg = '') {
- throw new this.constructor.Fail(msg);
- }
-
- /**
- * Asserts that two arguments are equal.
- * @param {*} first - First argument.
- * @param {*} second - Second argument.
- * @param {string} [msg=''] - Text to complement the failure message.
- */
- assertEqual(first, second, msg = '') {
- this.#countAsserts(msg);
- this.#assertBase(first, second, true, msg);
- }
-
- /**
- * Asserts that two arguments are NOT equal.
- * @param {*} first - First argument.
- * @param {*} second - Second argument.
- * @param {string} [msg=''] - Text to complement the failure message.
- */
- assertNotEqual(first, second, msg = '') {
- this.#countAsserts(msg);
- this.#assertBase(first, second, false, msg);
- }
-
- /**
- * Asserts that the argument is a boolean true.
- * @param {*} arg - Argument to test.
- * @param {string} [msg=''] - Text to complement the failure message.
- */
- assertTrue(arg, msg = '') {
- this.#countAsserts(msg);
- if (!arg) {
- const failMsg = `${arg} is not true`;
- this.#failMsgs(failMsg, msg);
- }
- }
-
- /**
- * Asserts that the argument is a boolean false.
- * @param {*} arg - Argument to test.
- * @param {string} [msg=''] - Text to complement the failure message.
- */
- assertFalse(arg, msg = '') {
- this.#countAsserts(msg);
- if (arg) {
- const s1 = this.repr(arg);
- const failMsg = `${s1} is not false`;
- this.#failMsgs(failMsg, msg);
- }
- }
-
- /**
- * Asserts the expected exception is raised.
- * @param {function(): Error} exc - Expected Error class.
- * @param {function} func - Function to call.
- * @param {string} [msg=''] - Text to complement the failure message.
- */
- assertRaises(exc, func, msg = '') {
- this.assertRaisesRegExp(exc, /.*/u, func, msg);
- }
-
- /**
- * Asserts that no exception is raised.
- *
- * Useful for supplying descriptive text when verifying an error does not
- * occur.
- * @param {function} func - Function to call.
- * @param {string} [msg=''] - Text to complement the failure message.
- */
- assertNoRaises(func, msg = '') {
- this.#countAsserts(msg);
- try {
- func();
- } catch (e) {
- const failMsg = `Unexpected exception: ${e.name}: ${e.message}`;
- this.#failMsgs(failMsg, msg);
- }
- }
-
- /**
- * Asserts the expected exception is raised and the message matches the
- * regular expression.
- * @param {function(): Error} exc - Expected Error class.
- * @param {RegExp} regexp - Regular expression to match.
- * @param {function} func - Function to call.
- * @param {string} [msg=''] - Text to complement the failure message.
- */
- assertRaisesRegExp(exc, regexp, func, msg = '') { // eslint-disable-line max-params
- this.#countAsserts(msg);
- let failMsg = `Expected ${exc.name}, caught nothing`;
- try {
- func();
- } catch (e) {
- if (e instanceof exc) {
- if (regexp.test(e.message)) {
- return;
- }
- failMsg = `Exception message:\n"${e.message}"\ndid not match ` +
- `regular expression:\n"${regexp}"`;
- } else {
- failMsg = `Expected ${exc.name}, caught ${e.name}`;
- }
- }
- this.#failMsgs(failMsg, msg);
- }
-
- /**
- * Asserts the target matches the regular expression.
- * @param {string} target - Target string to check.
- * @param {RegExp} regexp - Regular expression to match.
- * @param {string} [msg=''] - Text to complement the failure message.
- */
- assertRegExp(target, regexp, msg = '') {
- this.#countAsserts(msg);
- if (!regexp.test(target)) {
- const failMsg = `Target "${target}" did not match ` +
- `regular expression "${regexp}"`;
- this.#failMsgs(failMsg, msg);
- }
- }
-
- // TODO: Add assertions as needed.
-
- /**
- * Returns a string representation of the item using the registration
- * system.
- *
- * @param {*} item - Anything.
- * @returns {string} - String version of item.
- */
- repr(item) {
- const reprFunc = this.getReprFunc(item);
- return reprFunc(item);
- }
-
- /**
- * @callback ReprFunc
- * @param {*} item - Anything.
- * @returns {string} - String version of item.
- */
-
- /**
- * Find a ReprFunc for the given item.
- * @param {*} item - Item of interest.
- * @returns {ReprFunc} - Function for this item.
- */
- getReprFunc(item) {
- const type = getType(item);
- return this.#reprFuncs.get(type) ?? this.defaultRepr;
- }
-
- /**
- * @param {string} type - Type of interest.
- * @param {ReprFunc} func - Function for this type.
- */
- addReprFunc(type, func) {
- this.#reprFuncs.set(type, func);
- }
-
- /**
- * @implements {ReprFunc}
- * @param {string} item - String to wrap.
- * @returns {string} - Wrapped version of item.
- */
- reprString = (item) => {
- const str = `"${item}"`;
- return str;
- }
-
- /**
- * @implements {ReprFunc}
- * @param {[*]} array - Array of anything.
- * @returns {string} - String version of item.
- */
- reprArray = (array) => {
- const items = array.map(this.repr.bind(this));
- return `[${items.join(', ')}]`;
- }
-
- /**
- * @implements {ReprFunc}
- * @param {object} obj - Any object.
- * @returns {string} - String version of obj.
- */
- reprObject = (obj) => {
- const items = [];
- for (const [key, value] of Object.entries(obj)) {
- const strKey = this.repr(key);
- const strValue = this.repr(value);
- items.push(`${strKey}: ${strValue}`);
- }
- return `{${items.join(', ')}}`;
- }
-
- /**
- * @implements {ReprFunc}
- * @param {Map<*,*>} map - Any Map.
- * @returns {string} - String version of map.
- */
- reprMap = (map) => {
- const items = [];
- for (const [key, value] of map.entries()) {
- const strKey = this.repr(key);
- const strValue = this.repr(value);
- items.push(`[${strKey}, ${strValue}]`);
- }
- return `Map([${items.join(', ')}])`;
- }
-
- /**
- * @implements {ReprFunc}
- * @param {Set<*>} set - Any Set.
- * @returns {string} - String version of set.
- */
- reprSet = (set) => {
- const items = [];
- for (const value of set.values()) {
- const strValue = this.repr(value);
- items.push(`${strValue}`);
- }
- return `Set([${items.join(', ')}])`;
- }
-
- /**
- * @typedef {object} EqualOutput
- * @property {boolean} equal - Result of equality test.
- * @property {string} detail - Details appropriate to the test (e.g.,
- * where items differed).
- */
-
- /**
- * @callback EqualFunc
- * @param {*} first - First argument.
- * @param {*} second - Second argument.
- * @returns {EqualOutput} - Results of testing equality.
- */
-
- /**
- * Find an equality function appropriate for the arguments.
- * @param {*} first - First argument.
- * @param {*} second - Second argument.
- * @returns {EqualFunc} - Function that should be used to test equality.
- */
- getEqualFunc(first, second) {
- let equal = this.defaultEqual;
- const t1 = getType(first);
- const t2 = getType(second);
- if (t1 === t2) {
- equal = this.#equalFuncs.get(t1) ?? equal;
- }
- return equal;
- }
-
- /**
- * @param {string} type - As returned from {@link getType}.
- * @param {EqualFunc} func - Function to call to compare that type.
- */
- addEqualFunc(type, func) {
- this.#equalFuncs.set(type, func);
- }
-
- /**
- * @implements {EqualFunc}
- * @param {*} first - First argument.
- * @param {*} second - Second argument.
- * @returns {EqualOutput} - Results of testing equality.
- */
- equalEqEqEq = (first, second) => {
- const equal = first === second;
- return {
- equal: equal,
- details: '',
- };
- }
-
- /**
- * For those cases when '===' is too strict.
- * @implements {EqualFunc}
- * @param {*} first - First argument.
- * @param {*} second - Second argument.
- * @returns {EqualOutput} - Results of testing equality.
- */
- equalValueOf = (first, second) => {
- const val1 = first?.valueOf() ?? first;
- const val2 = second?.valueOf() ?? second;
- const equal = val1 === val2;
- return {
- equal: equal,
- details: 'Using valueOf()',
- };
- }
-
- /**
- * @implements {EqualFunc}
- * @param {*} first - First argument.
- * @param {*} second - Second argument.
- * @returns {EqualOutput} - Results of testing equality.
- */
- equalString = (first, second) => {
- let details = '';
- const equal = first === second;
- if (!equal) {
- let indicator = '';
- const len = Math.min(first.length, second.length);
- for (let idx = 0; idx < len; idx += 1) {
- const c1 = first.at(idx);
- const c2 = second.at(idx);
- if (c1 === c2) {
- indicator += ' ';
- } else {
- break;
- }
- }
- indicator += '|';
- details = `\n 1: ${first}\ndiff: ${indicator}\n 2: ${second}\n`;
- }
- return {
- equal: equal,
- details: details,
- };
- }
-
- /**
- * This currently only tests Object.entries().
- *
- * Order is ignored.
- *
- * Other tests, like frozen and sealed states may be implemented later.
- * @implements {EqualFunc}
- * @param {object} first - First argument.
- * @param {object} second - Second argument.
- * @returns {EqualOutput} - Results of testing equality.
- */
- equalObject = (first, second) => {
- const m1 = new Map(Object.entries(first));
- const m2 = new Map(Object.entries(second));
- return this.equalMap(m1, m2);
- }
-
- /**
- * @implements {EqualFunc}
- * @param {[*]} first - First argument.
- * @param {[*]} second - Second argument.
- * @returns {EqualOutput} - Results of testing equality.
- */
- equalArray = (first, second) => {
- let equal = true;
- const details = [];
-
- const len = Math.min(first.length, second.length);
- for (let idx = 0; idx < len; idx += 1) {
- const i1 = first.at(idx);
- const i2 = second.at(idx);
- const equalFunc = this.getEqualFunc(i1, i2);
- const result = equalFunc(i1, i2);
- if (!result.equal) {
- equal = false;
- details.push(
- '',
- `First difference at element ${idx}:`,
- this.repr(i1),
- this.repr(i2)
- );
- break;
- }
- }
-
- if (first.length !== second.length) {
- equal = false;
- const diff = Math.abs(first.length - second.length);
- const longest = first.length > second.length ? 'First' : 'Second';
- details.push(
- '',
- `${longest} array contains ${diff} more elements.`,
- `First additional element is at position ${len}:`,
- this.repr(first.at(len) ?? second.at(len))
- );
- }
-
- return {
- equal: equal,
- details: details.join('\n'),
- };
- }
-
- /**
- * Order is ignored.
- *
- * @implements {EqualFunc}
- * @param {Map<*,*>} first - First argument.
- * @param {Map<*,*>} second - Second argument.
- * @returns {EqualOutput} - Results of testing equality.
- */
- equalMap = (first, second) => {
- const m1 = this.#normalizeContainer(first);
- const m2 = this.#normalizeContainer(second);
- let equal = true;
- const differences = [];
- const missingFirst = [];
- const missingSecond = [];
-
- for (const [key, val1] of m1.entries()) {
- if (m2.has(key)) {
- const val2 = m2.get(key);
- if (val1 !== val2) {
- equal = false;
- differences.push(
- '',
- `Difference with key: ${key}`,
- `Value in first : ${val1}`,
- `Value in second: ${val2}`,
- );
- }
- } else {
- equal = false;
- missingSecond.push(
- '',
- `Key missing from second: ${key}`,
- `Value in first : ${val1}`,
- );
- }
- }
- for (const [key, val2] of m2.entries()) {
- if (!m1.has(key)) {
- equal = false;
- missingFirst.push(
- '',
- `Key missing from first : ${key}`,
- `Value in second: ${val2}`,
- );
- }
- }
- const details = [
- ...differences,
- ...missingFirst,
- ...missingSecond,
- ];
-
- return {
- equal: equal,
- details: details.join('\n'),
- };
- }
-
- /**
- * Order is ignored.
- *
- * @implements {EqualFunc}
- * @param {Set<*>} first - First argument.
- * @param {Set<*>} second - Second argument.
- * @returns {EqualOutput} - Results of testing equality.
- */
- equalSet = (first, second) => {
- const s1 = this.#normalizeContainer(first);
- const s2 = this.#normalizeContainer(second);
-
- let equal = true;
- const missingFirst = [];
- const missingSecond = [];
-
- for (const val of s1.values()) {
- if (!s2.has(val)) {
- equal = false;
- missingSecond.push(
- '',
- `Value missing from second: ${val}`,
- );
- }
- }
- for (const val of s2.values()) {
- if (!s1.has(val)) {
- equal = false;
- missingFirst.push(
- '',
- `Value missing from first : ${val}`,
- );
- }
- }
- const details = [
- ...missingFirst,
- ...missingSecond,
- ];
-
- return {
- equal: equal,
- details: details.join('\n'),
- };
- }
-
- #assertsCalled = 0;
- #assertsWithNoMsg = 0;
- #cleanups = [];
- #equalFuncs = new Map();
- #methodName
- #reprFuncs = new Map();
-
- /**
- * Count how many asserts are called per test method.
- *
- * Each *assertX()* method should call this first this along with its
- * optional *msg* parameter.
- * @param {string} msg - The message the assert method was given.
- */
- #countAsserts = (msg) => {
- this.#assertsCalled += 1;
- if (!msg) {
- this.#assertsWithNoMsg += 1;
- }
- }
-
- #checkAssertionCounts = () => {
- // How many asserts must exist in the test method before caring.
- const MIN_ASSERTS = 2;
- // How many asserts are allowed to be missing descriptions.
- const MAX_MISSING = 1;
- if (this.#assertsCalled >= MIN_ASSERTS &&
- this.#assertsWithNoMsg > MAX_MISSING) {
- // eslint-disable-next-line no-console
- console.debug('Too many asserts without descriptions!',
- this.id,
- this.#assertsWithNoMsg);
- if (testing.missingDescriptionsAreErrors) {
- this.fail(`Too many asserts without descriptions: ${this.id}`);
- }
- }
- }
-
- /**
- * Asserts that two arguments have the expected equality
- *
- * @param {*} first - First argument.
- * @param {*} second - Second argument.
- * @param {boolean} expected - Expectation of equality.
- * @param {string} [msg=''] - Text to complement the failure message.
- */
- #assertBase = (first, second, expected, msg) => { // eslint-disable-line max-params
- const equal = this.getEqualFunc(first, second);
- const results = equal(first, second);
- const passed = results.equal === expected;
- if (!passed) {
- const badCmp = expected ? '!==' : '===';
- const s1 = this.repr(first);
- const s2 = this.repr(second);
- const failMsg = `${s1} ${badCmp} ${s2}`;
- if (!expected) {
- results.details = '';
- }
- this.#failMsgs(failMsg, results.details, msg);
- }
- }
-
- /**
- * Turn all keys and values in a container into a string via *repr()*.
- *
- * Containers must meet the following criteria:
- * + Have an *entries()* method that returns [key, value] pairs.
- * + Have at least one of *set(key, value)* or *add(value)* method.
- * + Support a constructor taking own type that results in a copy.
- *
- * @param {Map<*,*>|Set<*>} container - Or any type with similar
- * signatures.
- * @returns {Map<*,*>|Set<*>} - Clone of container with all keys and
- * values transformed into a string.
- */
- #normalizeContainer = (container) => {
- const clone = new (Object.getPrototypeOf(container)
- .constructor)(container);
- if (!clone.set) {
- clone.set = (k, v) => {
- clone.add(v);
- };
- }
- clone.clear();
- for (const [k, v] of container.entries()) {
- const newK = this.repr(k);
- const newV = this.repr(v);
- clone.set(newK, newV);
- }
- return clone;
- }
-
- /**
- * Immediately fail while combining messages.
- * @param {...string} messages - Messages to join.
- */
- #failMsgs = (...messages) => {
- const filtered = messages
- .filter(x => x)
- .map(x => String(x))
- .join(' : ');
- this.fail(filtered);
- }
-
- }
-
- /* eslint-disable no-array-constructor */
- /* eslint-disable no-new-wrappers */
- /* eslint-disable no-undef */
- /* eslint-disable no-undefined */
- /* eslint-disable require-jsdoc */
- class GetTypeTestCase extends TestCase {
-
- testPrimitives() {
- this.assertEqual(getType(0), 'Number', 'zero');
- this.assertEqual(getType(NaN), 'Number', 'Nan');
- this.assertEqual(getType('0'), 'String', '"0"');
- this.assertEqual(getType(true), 'Boolean', 'true');
- this.assertEqual(getType(false), 'Boolean', 'false');
- this.assertEqual(getType(BigInt('123')), 'BigInt', 'BigInt()');
- this.assertEqual(getType(456n), 'BigInt', '456n');
- this.assertEqual(getType(undefined), 'Undefined', 'undefined');
- this.assertEqual(getType(null), 'Null', 'null');
- }
-
- testBuiltInFunctionLike() {
- this.assertEqual(getType(String('xyzzy')), 'String', 'string-xyzzy');
- this.assertEqual(getType(new String('abc')), 'String', 'string-abc');
- this.assertEqual(getType(String), 'Function', 'bare String');
- this.assertEqual(getType(Symbol('xyzzy')), 'Symbol', 'symbol');
- this.assertEqual(getType(Symbol), 'Function', 'bare Symbol');
- this.assertEqual(getType(/abc123/u), 'RegExp', '/regexp/');
- this.assertEqual(getType(new Date()), 'Date', 'new Date');
- this.assertEqual(getType(Date()), 'String', 'Date()');
- this.assertEqual(getType(Date), 'class', 'bare Date');
- this.assertEqual(getType(Math.min), 'Function', 'math.min');
- this.assertEqual(getType(Math), 'Math', 'Math');
- }
-
- testBuiltinClasses() {
- this.assertEqual(getType({}), 'Object', '{}');
- this.assertEqual(getType([]), 'Array', '[]');
- this.assertEqual(getType(new Array()), 'Array', 'new array');
- this.assertEqual(getType(Array), 'class', 'bare Array');
- this.assertEqual(getType(new Map()), 'Map', 'map');
- this.assertEqual(getType(Map), 'class', 'bare Map');
- this.assertEqual(getType(new Set()), 'Set', 'set');
- this.assertEqual(getType(Set), 'class', 'bare Set');
- this.assertEqual(getType(new Error()), 'Error', 'error');
- this.assertEqual(getType(Error), 'class', 'bare Error');
- }
-
- testRegularClasses() {
- this.assertEqual(getType(TestCase), 'class', 'bare TestCase');
- this.assertEqual(getType(this), 'GetTypeTestCase', 'this');
- this.assertEqual(getType(getType), 'Function', 'bare getType');
- this.assertEqual(getType(TestCase.Skip), 'class', 'nested class');
- }
-
- }
- /* eslint-enable */
-
- testing.testCases.push(GetTypeTestCase);
-
- /* eslint-disable class-methods-use-this */
- /* eslint-disable no-magic-numbers */
- /* eslint-disable require-jsdoc */
- /**
- * For testing TestCase basic features.
- *
- * Do not use directly, but rather inside `TestCaseTestCase`.
- */
- class BasicFeaturesTestCase extends TestCase {
-
- static classCalls = [];
-
- /** Register cleanup functions.. */
- static setUpClassCleanups() {
- this.classCalls = [];
- this.addClassCleanup(this.one);
- this.addClassCleanup(this.two, 3, 4);
- }
-
- /** Capture that it was called. */
- static one() {
- this.classCalls.push('one');
- }
-
- /**
- * Capture that it was called with arguments.
- * @param {*} a - Anything.
- * @param {*} b - Anything.
- */
- static two(a, b) {
- this.classCalls.push('two', a, b);
- }
-
- testInstanceCleanups() {
- this.instanceCalls = [];
- this.addCleanup(this.three);
- this.addCleanup(this.four, 5, 6);
- }
-
- /** Capture that it was called. */
- three() {
- this.instanceCalls.push('three');
- }
-
- /**
- * Capture that it was called with arguments.
- * @param {*} a - Anything.
- * @param {*} b - Anything.
- */
- four(a, b) {
- this.instanceCalls.push('four', a, b);
- }
-
- testInstanceCleanupsWithError() {
- this.addCleanup(this.willError);
- }
-
- testInstanceCleanupsWithSkip() {
- this.addCleanup(this.willSkip);
- }
-
- testInstanceCleanupsWithFail() {
- this.addCleanup(this.willFail);
- }
-
- willError() {
- throw new Error('from willError');
- }
-
- willSkip() {
- this.skip('from willSkip');
- }
-
- willFail() {
- this.fail('from willFail');
- }
-
- }
- /* eslint-enable */
-
- /* eslint-disable max-lines-per-function */
- /* eslint-disable max-statements */
- /* eslint-disable no-array-constructor */
- /* eslint-disable no-empty-function */
- /* eslint-disable no-magic-numbers */
- /* eslint-disable no-new */
- /* eslint-disable no-new-wrappers */
- /* eslint-disable no-undef */
- /* eslint-disable no-undefined */
- /* eslint-disable no-unused-vars */
- /* eslint-disable require-jsdoc */
- class TestCaseTestCase extends TestCase {
-
- testCannotInstantiateDirectly() {
- this.assertRaises(TypeError, () => {
- new TestCase();
- });
- }
-
- testStaticSetUpClassExists() {
- this.assertNoRaises(() => {
- TestCase.setUpClass();
- });
- }
-
- testDoClassCleanups() {
- // Assemble
- BasicFeaturesTestCase.setUpClassCleanups();
-
- // Act
- BasicFeaturesTestCase.doClassCleanups();
-
- // Assert
- const actual = BasicFeaturesTestCase.classCalls;
- const expected = ['two', 3, 4, 'one'];
- this.assertEqual(actual, expected);
- }
-
- testId() {
- // Assemble
- const instance = new BasicFeaturesTestCase('testSomething');
-
- // Assert
- const actual = instance.id;
- const expected = 'BasicFeaturesTestCase.testSomething';
- this.assertEqual(actual, expected);
- }
-
- testDoInstanceCleanups() {
- // Assemble
- const method = 'testInstanceCleanups';
- const instance = new BasicFeaturesTestCase(method);
-
- // Act
- const result = instance.run();
-
- // Assert
- this.assertTrue(result.wasSuccessful(), 'success');
- // Next assert has timestamps in it.
- this.assertEqual(result.tests.size, 1, 'tests collected');
- const actual = instance.instanceCalls;
- const expected = ['four', 5, 6, 'three'];
- this.assertEqual(actual, expected, 'calls');
- }
-
- testDoInstanceCleanupsWithError() {
- // Assemble
- const method = 'testInstanceCleanupsWithError';
- const instance = new BasicFeaturesTestCase(method);
-
- // Act
- const result = instance.run();
-
- // Assert
- this.assertFalse(result.wasSuccessful(), 'success');
- // Next assert has timestamps in it.
- this.assertEqual(result.tests.size, 1, 'tests collected');
- this.assertEqual(
- result.errors,
- [
- {
- name: 'BasicFeaturesTestCase.doCleanups',
- error: 'Error',
- message: 'from willError',
- },
- ],
- 'errors'
- );
- }
-
- testDoInstanceCleanupsWithSkip() {
- // Assemble
- const method = 'testInstanceCleanupsWithSkip';
- const instance = new BasicFeaturesTestCase(method);
-
- // Act
- const result = instance.run();
-
- // Assert
- this.assertFalse(result.wasSuccessful(), 'success');
- // Next assert has timestamps in it.
- this.assertEqual(result.tests.size, 1, 'tests collected');
- this.assertEqual(
- result.errors,
- [
- {
- name: 'BasicFeaturesTestCase.doCleanups',
- error: 'TestCase.Skip',
- message: 'from willSkip',
- },
- ],
- 'errors'
- );
- }
-
- testDoInstanceCleanupsWithFail() {
- // Assemble
- const method = 'testInstanceCleanupsWithFail';
- const instance = new BasicFeaturesTestCase(method);
-
- // Act
- const result = instance.run();
-
- // Assert
- this.assertFalse(result.wasSuccessful(), 'success');
- // Next assert has timestamps in it.
- this.assertEqual(result.tests.size, 1, 'tests collected');
- this.assertEqual(
- result.errors,
- [
- {
- name: 'BasicFeaturesTestCase.doCleanups',
- error: 'TestCase.Fail',
- message: 'from willFail',
- },
- ],
- 'errors'
- );
- }
-
- testCollectTests() {
- // Assemble
- const result = new TestResult();
- const methods = [
- 'testInstanceCleanups',
- 'testInstanceCleanupsWithError',
- 'testInstanceCleanupsWithSkip',
- 'testInstanceCleanupsWithFail',
- ];
-
- // Act
- for (const method of methods) {
- const instance = new BasicFeaturesTestCase(method);
- instance.run(result);
- }
-
- // Assert
- // Next assert has timestamps in it.
- this.assertEqual(result.tests.size, 4);
- }
-
- testSkip() {
- // Act/Assert
- this.assertRaisesRegExp(
- TestCase.Skip,
- /^$/u,
- () => {
- this.skip();
- },
- 'basic skip'
- );
-
- // Act/Assert
- this.assertRaisesRegExp(
- TestCase.Skip,
- /a message/u,
- () => {
- this.skip('a message');
- },
- 'with a message'
- );
- }
-
- testFail() {
- // Act/Assert
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^$/u,
- () => {
- this.fail();
- },
- 'no description'
- );
-
- // Act/Assert
- this.assertRaisesRegExp(
- TestCase.Fail,
- /for the masses/u,
- () => {
- this.fail('for the masses');
- },
- 'with description'
- );
- }
-
- testGetReprFunc() {
- this.assertEqual(this.getReprFunc(null), String, 'null');
- this.assertEqual(this.getReprFunc(undefined), String, 'undefined');
- this.assertEqual(this.getReprFunc(1), String, 'number');
- this.assertEqual(this.getReprFunc(''), this.reprString, 'string');
- this.assertEqual(this.getReprFunc([]), this.reprArray, 'array');
- this.assertEqual(this.getReprFunc({}), this.reprObject, 'object');
- this.assertEqual(this.getReprFunc(new Map()), this.reprMap, 'map');
- this.assertEqual(this.getReprFunc(new Set()), this.reprSet, 'set');
- }
-
- testChangingDefaultRepr() {
- // Assemble
- function x() {}
-
- // Act
- this.defaultRepr = x;
-
- // Assert
- this.assertEqual(this.getReprFunc(null), x, 'null');
- this.assertEqual(this.getReprFunc(undefined), x, 'undefined');
- this.assertEqual(this.getReprFunc(1), x, 'number');
- this.assertEqual(this.getReprFunc(''), this.reprString, 'string');
- this.assertEqual(this.getReprFunc([]), this.reprArray, 'array');
- this.assertEqual(this.getReprFunc({}), this.reprObject, 'object');
- this.assertEqual(this.getReprFunc(new Map()), this.reprMap, 'map');
- this.assertEqual(this.getReprFunc(new Set()), this.reprSet, 'set');
- }
-
- testAddReprFunc() {
- // Assemble
- class C {}
- const c = new C();
- function reprC(item) {}
-
- this.assertNotEqual(this.getReprFunc(c), reprC, 'no reprC');
-
- // Act
- this.addReprFunc('C', reprC);
-
- // Assert
- this.assertEqual(this.getReprFunc(c), reprC, 'found reprC');
- this.assertEqual(this.getReprFunc(null), String, 'null');
- this.assertEqual(this.getReprFunc(undefined), String, 'undefined');
- this.assertEqual(this.getReprFunc(1), String, 'number');
- this.assertEqual(this.getReprFunc(''), this.reprString, 'string');
- this.assertEqual(
- this.getReprFunc(new String('str')), this.reprString, 'new string'
- );
- this.assertEqual(this.getReprFunc([]), this.reprArray, 'array');
- this.assertEqual(
- this.getReprFunc(new Array(1, 2, 3)), this.reprArray, 'new array'
- );
- this.assertEqual(this.getReprFunc({}), this.reprObject, 'object');
- this.assertEqual(this.getReprFunc(new Map()), this.reprMap, 'map');
- this.assertEqual(this.getReprFunc(new Set()), this.reprSet, 'set');
- }
-
- testReprPrimitives() {
- this.assertEqual(this.repr(1), '1', 'number');
- this.assertEqual(this.repr(null), 'null', 'null');
- this.assertEqual(this.repr(undefined), 'undefined', 'undefined');
- this.assertEqual(
- this.repr(Symbol('qwerty')),
- 'Symbol(qwerty)',
- 'symbol'
- );
- }
-
- testReprString() {
- this.assertEqual(this.repr('a. b'), '"a. b"', 'string');
- this.assertEqual(this.repr(new String('xyz')), '"xyz"', 'new string');
- }
-
- testReprArray() {
- this.assertEqual(this.repr(['b', 2]), '["b", 2]', 'mixed array');
- this.assertEqual(
- this.repr(['b', [1, '2']]),
- '["b", [1, "2"]]',
- 'nested array'
- );
- this.assertEqual(
- this.repr(new Array(1, '2', 'three')),
- '[1, "2", "three"]',
- 'new array'
- );
- }
-
- testReprObject() {
- this.assertEqual(this.repr({a: '1'}), '{"a": "1"}', 'simple');
- this.assertEqual(
- this.repr({b: {c: 'd', e: 1}}),
- '{"b": {"c": "d", "e": 1}}',
- 'nested'
- );
- }
-
- testReprMap() {
- this.assertEqual(this.repr(new Map()), 'Map([])', 'empty');
- this.assertEqual(
- this.repr(new Map([])),
- 'Map([])',
- 'empty init'
- );
- this.assertEqual(
- this.repr(new Map([[1, 'one'], ['two', 2]])),
- 'Map([[1, "one"], ["two", 2]])',
- 'with items'
- );
- this.assertEqual(
- this.repr(new Map([[1, 'one'], ['map', new Map([['x', 3]])]])),
- 'Map([[1, "one"], ["map", Map([["x", 3]])]])',
- 'nested'
- );
- }
-
- testReprSet() {
- this.assertEqual(this.repr(new Set()), 'Set([])', 'empty');
- this.assertEqual(
- this.repr(new Set([])),
- 'Set([])',
- 'empty init'
- );
- this.assertEqual(
- this.repr(new Set([1, 'b', 'b', 'xyz', 99])),
- 'Set([1, "b", "xyz", 99])',
- 'with items'
- );
- this.assertEqual(
- this.repr(new Set([1, new Set(['x', 3]), 'qqq'])),
- 'Set([1, Set(["x", 3]), "qqq"])',
- 'nested'
- );
- }
-
- testGetEqualFunc() {
- this.assertEqual(
- this.getEqualFunc({}, []),
- this.equalEqEqEq,
- 'obj vs array'
- );
- this.assertEqual(
- this.getEqualFunc('a', 'b'),
- this.equalString,
- 'str vs str'
- );
- this.assertEqual(
- this.getEqualFunc('a', new String('b')),
- this.equalString,
- 'str vs new str'
- );
- }
-
- testChangingDefaultEqual() {
- // Assemble
- this.assertEqual(this.getEqualFunc({}, []), this.equalEqEqEq, '===');
- this.defaultEqual = this.equalValueOf;
-
- // Act/Assert
- this.assertEqual(
- this.getEqualFunc({}, []),
- this.equalValueOf,
- 'valueOf'
- );
- }
-
- testAddEqualFunc() {
- // Assemble
- class C {}
- const c = new C();
- function equalC(first, second) {}
-
- this.assertNotEqual(this.getEqualFunc(c, c), equalC, 'not equalC');
-
- // Act
- this.addEqualFunc(getType(c), equalC);
-
- // Assert
- this.assertEqual(this.getEqualFunc(c, c), equalC, 'found equalC');
- }
-
- testAssertEqualPrimitives() {
- this.assertEqual(0, 0, '0 vs 0');
- this.assertEqual(42, 42, 'number vs number');
- this.assertEqual(true, true, 'true vs true');
- this.assertEqual(false, false, 'false vs false');
- this.assertEqual(
- BigInt('123456789'),
- BigInt('123456789'),
- 'bigint vs bigint'
- );
- this.assertEqual(undefined, {}.undef, 'undefined vs undef');
- this.assertEqual(null, null, 'null vs null');
-
- const bar = Symbol('bar');
- this.assertEqual(bar, bar, 'same symbol');
-
- // Equivalent Symbols cannot be equal.
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^Symbol.foo. !== Symbol.foo.$/u,
- () => {
- this.assertEqual(Symbol('foo'), Symbol('foo'));
- },
- 'different, but equiv symbols'
- );
- }
-
- testAssertEqualValueOf() {
- // Assemble
- class Silly extends Number {}
-
- const n = new Number(3);
- const s = new Silly(3);
-
- this.assertNotEqual(n, s, 'before');
-
- // Act
- this.defaultEqual = this.equalValueOf;
-
- // Assert
- this.assertEqual(n, s, 'after');
- }
-
- testAssertEqualPrimitivesWithValueOf() {
- this.defaultEqual = this.equalValueOf;
-
- this.assertEqual(0, 0, '0 vs 0');
- this.assertEqual(42, 42, 'number vs number');
- this.assertEqual(true, true, 'true vs true');
- this.assertEqual(false, false, 'false vs false');
- this.assertEqual(
- BigInt('123456789'),
- BigInt('123456789'),
- 'bigint vs bigint'
- );
- this.assertEqual(undefined, {}.undef, 'undefined vs undef');
- this.assertEqual(null, null, 'null vs null');
-
- const bar = Symbol('bar');
- this.assertEqual(bar, bar, 'same symbol');
-
- // Equivalent Symbols cannot be equal, even with valueOf().
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^Symbol.foo. !== Symbol.foo. : Using valueOf/u,
- () => {
- this.assertEqual(Symbol('foo'), Symbol('foo'));
- },
- 'different, but equiv symbols'
- );
- }
-
- testAssertEqualFailureMessages() {
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^\{\} !== \[\] :/u,
- () => {
- this.assertEqual({}, [], 'assert under test');
- },
- 'obj vs array'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^undefined !== null :/u,
- () => {
- this.assertEqual(undefined, null, 'assert under test');
- },
- 'undefined vs null'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^0 !== "0" :/u,
- () => {
- this.assertEqual(0, '0', 'assert under test');
- },
- 'number vs string of same'
- );
- }
-
- // Old version of eslint does not know BigInt.
- /* eslint-disable no-undef */
- testAssertNotEqualPrimitives() {
- this.assertNotEqual(NaN, NaN, 'NaN');
- this.assertNotEqual(true, false, 'true/false');
- this.assertNotEqual(false, true, 'false/true');
- this.assertNotEqual(BigInt('12345678'), BigInt('123456789'), 'BigInt');
- this.assertNotEqual(undefined, null, 'undef/null');
- this.assertNotEqual(Symbol('foo'), Symbol('foo'), 'symbols');
- }
-
- testAssertNotEqualFailureMessages() {
- this.assertRaisesRegExp(TestCase.Fail,
- /^0 === 0 : assert under test$/u,
- () => {
- this.assertNotEqual(0, 0, 'assert under test');
- }, '0 vs 0');
-
- this.assertRaisesRegExp(TestCase.Fail,
- /^undefined === undefined :/u,
- () => {
- this.assertNotEqual(undefined, undefined, 'assert under test');
- }, 'undefined vs undefined');
-
- this.assertRaisesRegExp(TestCase.Fail,
- /^null === null :/u,
- () => {
- this.assertNotEqual(null, null, 'assert under test');
- }, 'null vs null');
-
- this.assertRaisesRegExp(TestCase.Fail,
- /^Symbol\(sym\) === Symbol\(sym\) :/u,
- () => {
- const sym = Symbol('sym');
- this.assertNotEqual(sym, sym, 'assert under test');
- }, 'symbol vs self');
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /"a" === "a" :/u,
- () => {
- this.assertNotEqual('a', 'a', 'assert under test');
- },
- 'str vs str'
- );
- }
-
- testEqualString() {
- let expected = '';
-
- this.assertEqual(this.getEqualFunc('a', 'b'),
- this.equalString,
- 'equalFunc');
- this.assertEqual('string', 'string', 'str === str');
- this.assertNotEqual('string 1', 'string 2', 'str !== str');
-
- expected = `"abc1234" !== "abc123" :[ ]
- 1: abc1234
- diff: |
- 2: abc123`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual('abc1234', 'abc123', 'assert under test');
- },
- 'first longer',
- );
-
- expected = `"abcd" !== "abxd" :[ ]
- 1: abcd
- diff: |
- 2: abxd`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual('abcd', 'abxd', 'assert under test');
- },
- 'diff in middle'
- );
-
- expected += '\n : extra';
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual('abcd', 'abxd', 'extra');
- },
- 'diff in middle with description'
- );
- }
-
- testEqualObject() {
- let o1 = {};
- let o2 = {};
- let expected = '';
- const sym = Symbol('xyzzy');
-
- this.assertEqual(this.getEqualFunc({}, {a: 1}),
- this.equalObject,
- 'equalFunc');
-
- o1 = {};
- o2 = {};
- this.assertEqual(o1, o2, 'empty');
-
- o1 = {1: 'a', b: 2};
- o2 = {b: 2, 1: 'a'};
- this.assertEqual(o1, o2, 'different order');
-
- o1 = {1: 1, 2: {a: 42}};
- o2 = {1: 1, 2: {a: 42}};
- this.assertEqual(o1, o2, 'nested');
-
- o1 = {sym: 'foo'};
- o2 = {sym: 'foo'};
- this.assertEqual(o1, o2, 'symbol');
-
- o1 = {1: 'a', b: 2};
- o2 = {b: 2};
- this.assertNotEqual(o1, o2, 'first has more');
- this.assertNotEqual(o2, o1, 'second has more');
-
- o1 = {1: 'a'};
- o2 = {1: 'b'};
- this.assertNotEqual(o1, o2, 'different values');
-
- o1 = {21: 'b'};
- o2 = {21: 'a'};
- expected = `\\{"21": "b"\\} !== \\{"21": "a"\\} :[ ]
- Difference with key: "21"
- Value in first : "b"
- Value in second: "a"`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(o1, o2, 'assert under test');
- },
- 'same key, different values'
- );
-
- o1 = {1: 'a', 3: 'c', q: 86, null: 'abc', sym: 'x'};
- o2 = {1: 'a', 2: 'b', q: 99, null: 54321, sym: 'y'};
- expected = ` :[ ]
- Difference with key: "q"
- Value in first : 86
- Value in second: 99
-
- Difference with key: "null"
- Value in first : "abc"
- Value in second: 54321
-
- Difference with key: "sym"
- Value in first : "x"
- Value in second: "y"
-
- Key missing from first : "2"
- Value in second: "b"
-
- Key missing from second: "3"
- Value in first : "c"`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(o1, o2, 'assert under test');
- },
- 'bit of everything',
- );
-
- o1 = {1: 1, 2: {a: 42}};
- o2 = {1: 1, 2: {a: 43}};
- expected = ` :[ ]
- Difference with key: "2"
- Value in first : \\{"a": 42\\}
- Value in second: \\{"a": 43\\}`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(o1, o2, 'assert under test');
- },
- 'nested, different',
- );
- }
-
- testEqualArray() {
- let expected = '';
-
- this.assertEqual(this.getEqualFunc([1], [2, 3]),
- this.equalArray,
- 'equalFunc');
- this.assertEqual([], [], 'empty');
- this.assertEqual([1, 'a'], [1, 'a'], 'mixed');
- this.assertEqual([1, [2, 3]], [1, [2, 3]], 'nested');
- this.assertNotEqual([0], [1], 'simple notequal');
- this.assertNotEqual([], [1], 'different lengths');
-
- expected = ` !== .* :[ ]
- First difference at element 1:
- 1
- 2`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual([0, 1], [0, 2], 'assert under test');
- },
- 'simple unequal'
- );
-
- expected = ` :[ ]
- First array contains 1 more elements.
- First additional element is at position 1:
- "xyzzy"`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual([3, 'xyzzy'], [3], 'assert under test');
- },
- 'first longer'
- );
-
- expected = ` :[ ]
- Second array contains 1 more elements.
- First additional element is at position 2:
- "asdf"`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual([1, 2], [1, 2, 'asdf'], 'assert under test');
- },
- 'second longer'
- );
-
- expected = ` :[ ]
- First difference at element 1:
- 3
- 2
-
- Second array contains 1 more elements.
- First additional element is at position 2:
- "asdf"`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual([1, 3], [1, 2, 'asdf'], 'assert under test');
- },
- 'different element and sizes'
- );
-
- expected = ` :[ ]
- First difference at element 2:
- \\[1, 2\\]
- \\[1, 3\\]`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(
- [-1, 0, [1, 2]], [-1, 0, [1, 3]], 'assert under test'
- );
- },
- 'nested unequal'
- );
- }
-
- testEqualMap() {
- let m1 = new Map();
- let m2 = new Map();
- let expected = '';
-
- this.assertEqual(this.getEqualFunc(m1, m2), this.equalMap, 'equalFunc');
- this.assertEqual(m1, m2, 'empty');
-
- m1 = new Map([[1, 'a'], ['b', 2]]);
- m2 = new Map([['b', 2], [1, 'a']]);
- this.assertEqual(m1, m2, 'different order');
-
- m1 = new Map([[1, 'a'], [2, new Map([['a', 42]])]]);
- m2 = new Map([[1, 'a'], [2, new Map([['a', 42]])]]);
- this.assertEqual(m1, m2, 'nested');
-
- m1 = new Map([[1, 'a'], ['b', 2]]);
- m2 = new Map([[1, 'a']]);
- this.assertNotEqual(m1, m2, 'first has more');
- this.assertNotEqual(m2, m1, 'second has more');
-
- m1 = new Map([[1, 'b']]);
- m2 = new Map([[1, 'a']]);
- this.assertNotEqual(m1, m2, 'different values');
-
- m1 = new Map([[19, 'a']]);
- m2 = new Map([[19, 'b']]);
- expected = ` !== .* :[ ]
- Difference with key: 19
- Value in first : "a"
- Value in second: "b"`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(m1, m2, 'assert under test');
- },
- 'same key, different values'
- );
-
- m1 = new Map([[1, 'a']]);
- m2 = new Map([[1, 'a'], ['b', 42]]);
- expected = ` :[ ]
- Key missing from first : "b"
- Value in second: 42`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(m1, m2, 'assert under test');
- },
- 'second has extra key'
- );
-
- m1 = new Map([[1, 'a'], ['b', 42]]);
- m2 = new Map([[1, 'a']]);
- expected = ` :[ ]
- Key missing from second: "b"
- Value in first : 42`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(m1, m2, 'assert under test');
- },
- 'first has extra key'
- );
-
- m1 = new Map([[1, 'a'], [3, 'c'], ['q', 86], [null, 'abc'], [{}, 'x']]);
- m2 = new Map([[1, 'a'], [2, 'b'], ['q', 99], [null, 54321], [{}, 'y']]);
- expected = ` !== .* :[ ]
- Difference with key: "q"
- Value in first : 86
- Value in second: 99
-
- Difference with key: null
- Value in first : "abc"
- Value in second: 54321
-
- Difference with key: \\{\\}
- Value in first : "x"
- Value in second: "y"
-
- Key missing from first : 2
- Value in second: "b"
-
- Key missing from second: 3
- Value in first : "c"`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(m1, m2, 'assert under test');
- },
- 'bit of everything'
- );
-
- expected = ` :[ ]
- Difference with key: 2
- Value in first : Map\\(\\[\\["a", 42\\]\\]\\)
- Value in second: Map\\(\\[\\["a", 43\\]\\]\\)`;
- m1 = new Map([[1, 'a'], [2, new Map([['a', 42]])]]);
- m2 = new Map([[1, 'a'], [2, new Map([['a', 43]])]]);
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(m1, m2, 'assert under test');
- },
- 'nested different'
- );
- }
-
- testEqualSet() {
- let s1 = new Set();
- let s2 = new Set();
- let expected = '';
-
- this.assertEqual(this.getEqualFunc(s1, s2), this.equalSet, 'equalFunc');
- this.assertEqual(s1, s2, 'empty');
-
- s1 = new Set([1, 'a']);
- s2 = new Set(['a', 1]);
- this.assertEqual(s1, s2, 'different order');
-
- s1 = new Set([1, new Set(['a', 42])]);
- s2 = new Set([1, new Set(['a', 42])]);
- this.assertEqual(s1, s2, 'nested');
-
- s1 = new Set([1, 'a', 'xyz']);
- s2 = new Set([1, 'a']);
- this.assertNotEqual(s1, s2, 'first has more');
- this.assertNotEqual(s2, s1, 'second has more');
-
- s1 = new Set([1]);
- s2 = new Set([2]);
- this.assertNotEqual(s1, s2, 'different values');
-
- s1 = new Set([1, 2]);
- s2 = new Set([2, 'three']);
- expected = `Set\\(\\[1, 2\\]\\) !== Set\\(\\[2, "three"\\]\\) :[ ]
- Value missing from first : "three"
-
- Value missing from second: 1`;
- this.assertRaisesRegExp(
- TestCase.Fail,
- RegExp(expected, 'u'),
- () => {
- this.assertEqual(s1, s2);
- },
- 'bit of everything'
- );
- }
-
- testAssertTrue() {
- this.assertTrue(true, 'boolean');
- this.assertTrue(1, 'one');
- this.assertTrue(' ', 'single space');
- this.assertTrue({}, 'empty object');
- this.assertTrue([], 'empty array');
- this.assertTrue(Symbol('true'), 'symbol');
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /false is not true/u,
- () => {
- this.assertTrue(false, 'assert under test');
- }, 'testing false'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /0 is not true/u,
- () => {
- this.assertTrue(0, 'assert under test');
- }, 'testing zero'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^0 is not true : xyzzy$/u,
- () => {
- this.assertTrue(0, 'xyzzy');
- },
- 'testing with description as string'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^undefined is not true : Symbol\(xyzzy\)$/u,
- () => {
- this.assertTrue(undefined, Symbol('xyzzy'));
- },
- 'testing with description as symbol'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^null is not true$/u,
- () => {
- this.assertTrue(null, false);
- },
- 'testing with description as boolean'
- );
- }
-
- testAssertFalse() {
- this.assertFalse(false, 'boolean');
- this.assertFalse(0, 'zero');
- this.assertFalse('', 'empty string');
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /true is not false/u,
- () => {
- this.assertFalse(true, 'assert under test');
- },
- 'testing true'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /-1 is not false/u,
- () => {
- this.assertFalse(-1, 'assert under test');
- },
- 'testing -1'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /\{\} is not false/u,
- () => {
- this.assertFalse({}, 'assert under test');
- },
- 'testing Boolean({})'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^\[\] is not false : abc123$/u,
- () => {
- this.assertFalse([], 'abc123');
- },
- 'testing array'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /Symbol\(bar\) is not false/u,
- () => {
- this.assertFalse(Symbol('bar'), 'assert under test');
- },
- 'testing symbol'
- );
- }
-
- testAssertRaises() {
- this.assertRaises(
- Error,
- () => {
- throw new Error();
- },
- 'empty Error'
- );
-
- this.assertRaises(
- Error,
- () => {
- throw new Error('with a message');
- },
- 'Error with message'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /caught nothing/u,
- () => {
- this.assertRaises(Error, () => {}, 'assert under test');
- },
- 'caught nothing'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /TypeError.* Error/u,
- () => {
- this.assertRaises(
- TypeError,
- () => {
- throw new Error();
- },
- 'assert under test'
- );
- },
- 'regexp, empty Error, no description'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- / : hovercraft/u,
- () => {
- this.assertRaises(TypeError,
- () => {
- throw new Error();
- },
- 'hovercraft full of eels');
- },
- 'regexp, empty error, with eels'
- );
- }
-
- testAssertNoRaises() {
- this.assertNoRaises(() => {
- });
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^Unexpected exception:.*threw an error/u,
- () => {
- this.assertNoRaises(() => {
- throw new Error('This function threw an error');
- }, 'assert under test');
- },
- 'basic error'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /^Unexpected exception:.*: custom text/u,
- () => {
- this.assertNoRaises(() => {
- throw new Error('Bad function. No cookie.');
- }, 'custom text');
- },
- 'with descriptive text'
- );
- }
-
- testAssertRaisesRegExp() {
- this.assertRaisesRegExp(
- Error,
- /xyzzy/u,
- () => {
- throw new Error('xyzzy');
- },
- 'match text in exception'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /caught nothing/u,
- () => {
- this.assertRaisesRegExp(
- Error,
- /.*/u,
- () => {},
- 'assert under test'
- );
- },
- 'no error raise, caught nothing'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- / : my message/u,
- () => {
- this.assertRaisesRegExp(
- Error,
- /.*/u,
- () => {},
- 'my message'
- );
- },
- 'matched description'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /Expected TypeError/u,
- () => {
- this.assertRaisesRegExp(
- TypeError,
- /message/u,
- () => {
- throw new Error('message');
- },
- 'assert under test'
- );
- },
- 'wrong exception thrown'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /did not match regular expression/u,
- () => {
- this.assertRaisesRegExp(
- Error,
- /message/u,
- () => {
- throw new Error('xyzzy');
- },
- 'assert under test'
- );
- },
- 'wrong regexp'
- );
- }
-
- testAssertRegExp() {
- this.assertRegExp('abc', /ab./u, 'basic match');
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- /Target.*did not match regular expression/u,
- () => {
- this.assertRegExp('abc', /ab.d/u, 'assert under test');
- },
- 'should not match'
- );
-
- this.assertRaisesRegExp(
- TestCase.Fail,
- / : what do you expect/u,
- () => {
- this.assertRegExp('abc', /xyz/u, 'what do you expect');
- },
- 'testing descriptive message'
- );
- }
-
- }
- /* eslint-enable */
-
- testing.testCases.push(TestCaseTestCase);
-
- /* eslint-disable max-lines-per-function */
- /* eslint-disable no-magic-numbers */
- /* eslint-disable require-jsdoc */
- class TestResultTestCase extends TestCase {
-
- setUp() {
- this.result = new TestResult();
- }
-
- testAddSuccess() {
- this.assertEqual(this.result.successes, [], 'paranoia check');
-
- // Act
- this.result.addSuccess('TestClass.testMethod');
- this.result.addSuccess('TestClass.testMethod');
-
- // Assert
- this.assertEqual(
- this.result.successes,
- ['TestClass.testMethod', 'TestClass.testMethod'],
- 'real check'
- );
- }
-
- testAddError() {
- this.assertEqual(this.result.errors, [], 'paranoia check');
-
- // Act
- this.result.addError('name1', new Error('first message'));
- this.result.addError('name2', new TypeError('second message'));
- this.result.addError('name3', new Error('third message'));
-
- // Assert
- const actual = this.result.errors;
- const expected = [
- {name: 'name1', error: 'Error', message: 'first message'},
- {name: 'name2', error: 'TypeError', message: 'second message'},
- {name: 'name3', error: 'Error', message: 'third message'},
- ];
- this.assertEqual(actual, expected, 'real check');
- }
-
- testAddFailure() {
- this.assertEqual(this.result.failures, [], 'paranoia check');
-
- // Act
- this.result.addFailure('method1', 'a message');
- this.result.addFailure('method2', 'another message');
-
- // Assert
- const actual = this.result.failures;
- const expected = [
- {name: 'method1', message: 'a message'},
- {name: 'method2', message: 'another message'},
- ];
- this.assertEqual(actual, expected, 'real check');
- }
-
- testAddSkip() {
- this.assertEqual(this.result.skipped, [], 'paranoia check');
-
- // Act
- this.result.addSkip('Skip.Skip', 'skip to my lou');
- this.result.addSkip('Skip.Skip', 'skip to my lou');
- this.result.addSkip('Skip.ToMyLou', 'my darling');
-
- // Assert
- const actual = this.result.skipped;
- const expected = [
- {name: 'Skip.Skip', message: 'skip to my lou'},
- {name: 'Skip.Skip', message: 'skip to my lou'},
- {name: 'Skip.ToMyLou', message: 'my darling'},
- ];
- this.assertEqual(actual, expected, 'real check');
- }
-
- testStartStop() {
- // Act
- this.result.startTest('Foo.testSomething');
- this.result.startTest('Foo.testOrTheOther');
- this.result.stopTest('Foo.testSomething');
-
- // Assert
- this.assertEqual(this.result.tests.size, 2, 'tests ran');
- this.assertTrue(this.result.tests.get('Foo.testSomething').start,
- 'first start');
- this.assertTrue(this.result.tests.get('Foo.testSomething').stop,
- 'first stop');
- this.assertTrue(this.result.tests.get('Foo.testOrTheOther').start,
- 'second start');
- this.assertFalse(this.result.tests.get('Foo.testOrTheOther').stop,
- 'second stop');
- }
-
- testWasSuccessful() {
- this.assertTrue(this.result.wasSuccessful(), 'no results is a pass');
-
- this.result.addSuccess('Class.method');
- this.assertTrue(this.result.wasSuccessful(), 'new success is a pass');
-
- this.result.addSkip('Class.differentMethod', 'rocks');
- this.assertTrue(this.result.wasSuccessful(), 'a skip is a pass');
-
- this.result.addError('NewClass.method', new Error());
- this.assertFalse(this.result.wasSuccessful(), 'an error is not a pass');
-
- const result = new TestResult();
-
- this.assertTrue(result.wasSuccessful(), 'paranoia check');
-
- result.addFailure('NewClass.failedMethod', 'oops');
- this.assertFalse(result.wasSuccessful(), 'a failure is not a pass');
- }
-
- testSummary() {
- const result = new TestResult();
-
- this.assertEqual(
- result.summary(),
- [
- 'total : 0',
- 'successes : 0',
- 'skipped : 0',
- 'errors : 0',
- 'failures : 0',
- ],
- 'empty, no formatting'
- );
-
- this.assertEqual(
- result.summary(true),
- [
- 'total : 0',
- 'successes : 0',
- 'skipped : 0',
- 'errors : 0',
- 'failures : 0',
- ],
- 'empty, with formatting'
- );
-
- for (let i = 0; i < 100; i += 1) {
- const currentMethod = `test-${i}`;
- result.startTest(currentMethod);
- if (i % 17 === 0) {
- result.addError(currentMethod, new Error(`oops-${i}`));
- } else if (i % 19 === 0) {
- result.addFailure(currentMethod, `failed ${i}`);
- } else if (i % 37 === 0) {
- result.addSkip(currentMethod, `skip ${i}`);
- } else {
- result.addSuccess(currentMethod);
- }
- result.stopTest(currentMethod);
- }
-
- this.assertEqual(
- result.summary(),
- [
- 'total : 100',
- 'successes : 87',
- 'skipped : 2',
- 'errors : 6',
- 'failures : 5',
- ],
- 'full, no formatting'
- );
-
- this.assertEqual(
- result.summary(true),
- [
- 'total : 100',
- 'successes : 87',
- 'skipped : 2',
- 'errors : 6',
- 'failures : 5',
- ],
- 'full, with formatting'
- );
-
- for (let i = 0; i < 1000; i += 1) {
- const currentMethod = `test-${i}`;
- result.addFailure(currentMethod, `failed group 2 ${i}`);
- }
-
- this.assertEqual(
- result.summary(),
- [
- 'total : 100',
- 'successes : 87',
- 'skipped : 2',
- 'errors : 6',
- 'failures : 1005',
- ],
- 'extra failures, no formatting'
- );
-
- this.assertEqual(
- result.summary(true),
- [
- 'total : 100',
- 'successes : 87',
- 'skipped : 2',
- 'errors : 6',
- 'failures : 1005',
- ],
- 'extra, with formatting'
- );
- }
-
- }
- /* eslint-enable */
-
- testing.testCases.push(TestResultTestCase);
-
- /** Assembles and drives execution of {@link TestCase}s. */
- class TestRunner {
-
- /** @param {function(): TestCase} tests - TestCases to execute. */
- constructor(tests) {
- const badKlasses = [];
- const testMethods = [];
- for (const klass of tests) {
- if (klass.prototype instanceof TestCase) {
- testMethods.push(...this.#extractTestMethods(klass));
- } else {
- badKlasses.push(klass);
- }
- }
- if (badKlasses.length) {
- const msg = `Bad class count: ${badKlasses.length}`;
- for (const klass of badKlasses) {
- // eslint-disable-next-line no-console
- console.error('Not a TestCase:', klass);
- }
- throw new TypeError(`Bad classes: ${msg}`);
- }
-
- this.#tests = testMethods;
- }
-
- /**
- * Run each test method in turn.
- * @returns {TestResult} - Collected results.
- */
- runTests() {
- const result = new TestResult();
-
- let lastKlass = null;
- let doRunTests = true;
- for (const {klass, method} of this.#tests) {
- if (klass !== lastKlass) {
- this.#doClassCleanups(lastKlass, result);
- doRunTests = this.#doSetUpClass(klass, result);
- }
- lastKlass = klass;
-
- if (doRunTests) {
- this.#doRunTestMethod(klass, method, result);
- }
- }
-
- this.#doClassCleanups(lastKlass, result);
-
- return result;
- }
-
- #tests
-
- /** @param {function(): TestCase} klass - TestCase to process. */
- #extractTestMethods = function *extractTestMethods(klass) {
- let obj = klass;
- while (obj) {
- if (obj.prototype instanceof TestCase) {
- for (const prop of Object.getOwnPropertyNames(obj.prototype)) {
- if (prop.startsWith('test')) {
- yield {klass: klass, method: prop};
- }
- }
- }
- obj = Object.getPrototypeOf(obj);
- }
- }
-
- /**
- * @param {function(): TestCase} klass - TestCase to process.
- * @param {TestResult} result - Result to use if any errors.
- */
- #doClassCleanups = (klass, result) => {
- if (klass) {
- const currentMethod = `${klass.name}.doClassCleanups`;
- try {
- klass.doClassCleanups();
- } catch (e) {
- result.addError(currentMethod, e);
- }
- }
- }
-
- /**
- * @param {function(): TestCase} klass - TestCase to process.
- * @param {TestResult} result - Result to use if any errors.
- * @returns {boolean} - Indicates success of calling setUpClass().
- */
- #doSetUpClass = (klass, result) => {
- const currentMethod = `${klass.name}.setUpClass`;
- try {
- klass.setUpClass();
- } catch (e) {
- if (e instanceof TestCase.Skip) {
- result.addSkip(currentMethod, e.message);
- } else {
- result.addError(currentMethod, e);
- }
- return false;
- }
- return true;
- }
-
- /**
- * @param {function(): TestCase} Klass - TestCase to process.
- * @param {string} methodName - Name of the test method to execute.
- * @param {TestResult} result - Result of the execution.
- */
- #doRunTestMethod = (Klass, methodName, result) => {
- const instance = new Klass(methodName);
- instance.run(result);
- }
-
- }
-
- /* eslint-disable class-methods-use-this */
- /* eslint-disable no-empty-function */
- /* eslint-disable require-jsdoc */
- /**
- * TestCases require at least one test method to get instantiated by {@link
- * TestRunner}
- */
- class DummyMethodTestCase extends TestCase {
-
- testDummy() {}
-
- }
- /* eslint-enable */
-
- /* eslint-disable class-methods-use-this */
- /* eslint-disable max-lines-per-function */
- /* eslint-disable no-empty-function */
- /* eslint-disable no-magic-numbers */
- /* eslint-disable no-new */
- /* eslint-disable require-jsdoc */
- class TestRunnerTestCase extends TestCase {
-
- testNoClasses() {
- // Assemble
- const runner = new TestRunner([]);
-
- // Act
- const result = runner.runTests();
-
- // Assert
- this.assertTrue(result.wasSuccessful());
- }
-
- testBadClasses() {
- this.assertRaisesRegExp(TypeError, /Bad class count: 2$/u, () => {
- new TestRunner([Error, TestRunnerTestCase, TypeError]);
- });
- }
-
- testStrangeClassSetup() {
- // Assemble
- class ClassSetupErrorTestCase extends DummyMethodTestCase {
-
- static setUpClass() {
- throw new Error('erroring');
- }
-
- }
-
- class ClassSetupFailTestCase extends DummyMethodTestCase {
-
- static setUpClass() {
- throw new this.Fail('failing');
- }
-
- }
-
- class ClassSetupSkipTestCase extends DummyMethodTestCase {
-
- static setUpClass() {
- throw new this.Skip('skipping');
- }
-
- }
-
- const classes = [
- DummyMethodTestCase,
- ClassSetupErrorTestCase,
- ClassSetupFailTestCase,
- ClassSetupSkipTestCase,
- ];
- const runner = new TestRunner(classes);
-
- // Act
- const result = runner.runTests();
-
- // Assert
- this.assertFalse(result.wasSuccessful());
-
- // In setUpClass, TestCase.Fail should count as an error
- this.assertEqual(
- result.successes,
- ['DummyMethodTestCase.testDummy'],
- 'successes'
- );
- this.assertEqual(
- result.errors,
- [
- {
- name: 'ClassSetupErrorTestCase.setUpClass',
- error: 'Error',
- message: 'erroring',
- },
- {
- name: 'ClassSetupFailTestCase.setUpClass',
- error: 'TestCase.Fail',
- message: 'failing',
- },
- ],
- 'errors'
- );
- this.assertEqual(result.failures, [], 'failures');
- this.assertEqual(
- result.skipped,
- [{name: 'ClassSetupSkipTestCase.setUpClass', message: 'skipping'}],
- 'skipped'
- );
- }
-
- testStrangeClassCleanups() {
- // Assemble
- class BaseClassCleanupTestCase extends DummyMethodTestCase {
-
- static setUpClass() {
- this.addClassCleanup(this.cleanupFunc);
- }
-
- static cleanupFunc() {}
-
- }
- class CleanupErrorTestCase extends BaseClassCleanupTestCase {
-
- static cleanupFunc() {
- throw new Error('cleanup error');
- }
-
- }
-
- class CleanupFailTestCase extends BaseClassCleanupTestCase {
-
- static cleanupFunc() {
- throw new this.Fail('cleanup fail');
- }
-
- }
- class CleanupSkipTestCase extends BaseClassCleanupTestCase {
-
- static cleanupFunc() {
- throw new this.Skip('cleanup skip');
- }
-
- }
-
- const classes = [
- BaseClassCleanupTestCase,
- CleanupErrorTestCase,
- CleanupFailTestCase,
- CleanupSkipTestCase,
- ];
- const runner = new TestRunner(classes);
-
- // Act
- const result = runner.runTests();
-
- // Assert
- this.assertFalse(result.wasSuccessful());
-
- // In doClassCleanups, TestCase.{Fail,Skip} should count as errors,
- // however, the test *also* passed already, so we get extra counts. Not
- // sure if this is a bug or a feature.
- this.assertEqual(
- result.successes,
- [
- 'BaseClassCleanupTestCase.testDummy',
- 'CleanupErrorTestCase.testDummy',
- 'CleanupFailTestCase.testDummy',
- 'CleanupSkipTestCase.testDummy',
- ],
- 'successes'
- );
- this.assertEqual(
- result.errors,
- [
- {
- name: 'CleanupErrorTestCase.doClassCleanups',
- error: 'Error',
- message: 'cleanup error',
- },
- {
- name: 'CleanupFailTestCase.doClassCleanups',
- error: 'TestCase.Fail',
- message: 'cleanup fail',
- },
- {
- name: 'CleanupSkipTestCase.doClassCleanups',
- error: 'TestCase.Skip',
- message: 'cleanup skip',
- },
- ],
- 'errors'
- );
- this.assertEqual(result.failures, [], 'failures');
- this.assertEqual(result.skipped, [], 'skipped');
- }
-
- testFindsTestMethods() {
- // Assemble
- class One extends TestCase {
-
- test() {}
-
- test_() {}
-
- _test() {
- this.fail('_test');
- }
-
- testOne() {
- this.skip('One');
- }
-
- notATest() {
- this.fail('notATest');
- }
-
- }
- class Two extends TestCase {
-
- alsoNotATest() {
- this.fail('alsoNotATest');
- }
-
- testTwo() {
- this.skip('Two');
- }
-
- }
- const runner = new TestRunner([One, Two]);
-
- // Act
- const result = runner.runTests();
-
- // Assert
- this.assertTrue(result.wasSuccessful());
- this.assertEqual(
- result.successes, ['One.test', 'One.test_'], 'successes'
- );
- this.assertEqual(result.errors, [], 'errors');
- this.assertEqual(result.failures, [], 'failures');
- this.assertEqual(
- result.skipped,
- [
- {name: 'One.testOne', message: 'One'},
- {name: 'Two.testTwo', message: 'Two'},
- ],
- 'skipped'
- );
- }
-
- testAccumulatesResults() {
- class FooTestCase extends TestCase {
-
- testFail() {
- this.fail('Fail failed');
- }
-
- testNotEqual() {
- this.assertEqual(1, 2);
- }
-
- testPass() {}
-
- testError() {
- throw new Error('Oh, dear!');
- }
-
- testSkip() {
- this.skip('Skip skipped');
- }
-
- }
- const runner = new TestRunner([FooTestCase]);
-
- // Act
- const result = runner.runTests();
-
- // Assert
- this.assertFalse(result.wasSuccessful(), 'had failures');
-
- this.assertEqual(
- result.errors,
- [
- {
- name: 'FooTestCase.testError',
- error: 'Error',
- message: 'Oh, dear!',
- },
- ],
- 'errors'
- );
- this.assertEqual(
- result.failures,
- [
- {name: 'FooTestCase.testFail', message: 'Fail failed'},
- {name: 'FooTestCase.testNotEqual', message: '1 !== 2'},
- ],
- 'failures'
- );
- this.assertEqual(
- result.skipped,
- [{name: 'FooTestCase.testSkip', message: 'Skip skipped'}],
- 'skipped'
- );
- this.assertEqual(
- result.successes,
- ['FooTestCase.testPass'],
- 'successes'
- );
- }
-
- }
- /* eslint-enable */
-
- testing.testCases.push(TestRunnerTestCase);
-
- /**
- * Run registered TestCases.
- * @returns {TestResult} - Accumulated results of these tests.
- */
- function runTests() {
- const runner = new TestRunner(testing.testCases);
- return runner.runTests();
- }
-
- return {
- version: version,
- testing: testing,
- TestCase: TestCase,
- runTests: runTests,
- };
-
- }());