import path from 'node:path'; import {fileURLToPath} from 'node:url'; import stripAnsi from 'strip-ansi'; import {test} from 'tap'; import * as assert from '../lib/assert.js'; import * as snapshotManager from '../lib/snapshot-manager.js'; import {set as setOptions} from '../lib/worker/options.cjs'; setOptions({chalkOptions: {level: 0}}); let lastFailure = null; let lastPassed = false; class AssertionFailed extends Error { constructor() { super('Assertion failed'); this.name = 'AssertionFailed'; } } const AssertionsBase = class extends assert.Assertions { constructor(overwrites = {}) { super({ pass() { lastPassed = true; // Match behavior in lib/test.js, not great for this test suite but refactoring is a much larger task. return true; }, pending(promise) { promise.then(() => { lastPassed = true; }, error => { if (error.name !== 'AssertionFailed') { lastFailure = error; } }); }, fail(error) { lastFailure = error; return new AssertionFailed(); }, failPending(error) { if (error.name !== 'AssertionFailed') { lastFailure = error; } return new AssertionFailed(); }, skip() {}, experiments: {}, ...overwrites, }); } }; const assertions = new AssertionsBase(); function assertFailure(t, subset) { if (!lastFailure) { t.fail('Expected assertion to fail'); return; } t.equal(lastFailure.assertion, subset.assertion); t.equal(lastFailure.message, subset.message); t.equal(lastFailure.name, 'AssertionError'); if (subset.formattedDetails) { t.equal(lastFailure.formattedDetails.length, subset.formattedDetails.length); for (const [i, s] of lastFailure.formattedDetails.entries()) { t.equal(stripAnsi(s.label), subset.formattedDetails[i].label); t.match(stripAnsi(s.formatted), subset.formattedDetails[i].formatted); } } else { t.same(lastFailure.formattedDetails, []); } } let gathering = false; let gatheringPromise = Promise.resolve(); function gather(run) { return t => { if (gathering) { throw new Error('Cannot nest gather()'); } gathering = true; try { run(t); return gatheringPromise; } finally { gathering = false; gatheringPromise = Promise.resolve(); } }; } function add(fn) { if (!gathering) { throw new Error('Cannot add promise, must be called from gather() callback'); } gatheringPromise = gatheringPromise.then(fn); return gatheringPromise; } function failsWith(t, fn, subset) { lastFailure = null; try { fn(); } catch (error) { if (error.name !== 'AssertionFailed') { throw error; } } assertFailure(t, subset); } async function failsWithAsync(t, fn, subset) { lastFailure = null; try { await fn(); } catch (error) { if (error.name !== 'AssertionFailed') { throw error; } } assertFailure(t, subset); } function throwsAsyncFails(t, fn, subset) { return add(() => { lastFailure = null; return fn().catch(error => { if (error.name === 'AssertionFailed') { assertFailure(t, subset); } else { throw error; } }); }); } function notThrowsAsyncFails(t, fn, subset) { return add(() => { lastFailure = null; return fn().catch(error => { if (error.name === 'AssertionFailed') { assertFailure(t, subset); } else { throw error; } }); }); } function fails(t, fn) { lastFailure = null; let retval; try { retval = fn(); } catch (error) { if (error.name !== 'AssertionFailed') { throw error; } } if (lastFailure) { t.notOk(retval); } else { t.fail('Expected assertion to fail'); } } function passes(t, fn, {expectBoolean = true} = {}) { lastPassed = false; lastFailure = null; const retval = fn(); if (lastPassed) { if (expectBoolean) { t.equal(retval, true); } else { t.pass(); } } else { t.error(lastFailure, 'Expected assertion to pass'); } } function throwsAsyncPasses(t, fn) { return add(() => { lastPassed = false; lastFailure = null; return fn().then(() => { if (lastPassed) { t.pass(); } else { t.error(lastFailure, 'Expected assertion to pass'); } }); }); } const notThrowsAsyncPasses = throwsAsyncPasses; test('.pass()', t => { passes(t, () => assertions.pass()); passes(t, () => assertions.pass()); t.end(); }); test('.fail()', t => { failsWith(t, () => assertions.fail(), { assertion: 't.fail()', message: 'Test failed via `t.fail()`', }); failsWith(t, () => assertions.fail('my message'), { assertion: 't.fail()', message: 'my message', }); failsWith(t, () => assertions.fail(), { assertion: 't.fail()', message: 'Test failed via `t.fail()`', }); failsWith(t, () => assertions.fail(null), { assertion: 't.fail()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.is()', t => { passes(t, () => assertions.is('foo', 'foo')); passes(t, () => assertions.is('foo', 'foo')); passes(t, () => assertions.is('', '')); passes(t, () => assertions.is(true, true)); passes(t, () => assertions.is(false, false)); passes(t, () => assertions.is(null, null)); passes(t, () => assertions.is(undefined, undefined)); passes(t, () => assertions.is(1, 1)); passes(t, () => assertions.is(0, 0)); passes(t, () => assertions.is(-0, -0)); passes(t, () => assertions.is(Number.NaN, Number.NaN)); passes(t, () => assertions.is(0 / 0, Number.NaN)); passes(t, () => { const someRef = {foo: 'bar'}; return assertions.is(someRef, someRef); }); fails(t, () => assertions.is(0, -0)); fails(t, () => assertions.is(0, false)); fails(t, () => assertions.is('', false)); fails(t, () => assertions.is('0', 0)); fails(t, () => assertions.is('17', 17)); fails(t, () => assertions.is([1, 2], '1,2')); fails(t, () => // eslint-disable-next-line no-new-wrappers, unicorn/new-for-builtins assertions.is(new String('foo'), 'foo'), ); fails(t, () => assertions.is(null, undefined)); fails(t, () => assertions.is(null, false)); fails(t, () => assertions.is(undefined, false)); fails(t, () => // eslint-disable-next-line no-new-wrappers, unicorn/new-for-builtins assertions.is(new String('foo'), new String('foo')), ); fails(t, () => assertions.is(0, null)); fails(t, () => assertions.is(0, Number.NaN)); fails(t, () => assertions.is('foo', Number.NaN)); failsWith(t, () => assertions.is({foo: 'bar'}, {foo: 'bar'}), { assertion: 't.is()', message: '', actual: {foo: 'bar'}, expected: {foo: 'bar'}, formattedDetails: [{ label: 'Values are deeply equal to each other, but they are not the same:', formatted: /foo/, }], }); failsWith(t, () => assertions.is('foo', 'bar'), { assertion: 't.is()', message: '', formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 'bar'/}, ], }); failsWith(t, () => assertions.is('foo', 42), { actual: 'foo', assertion: 't.is()', expected: 42, message: '', formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}, ], }); failsWith(t, () => assertions.is('foo', 42, 'my message'), { assertion: 't.is()', message: 'my message', formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}, ], }); failsWith(t, () => assertions.is(0, -0, 'my message'), { assertion: 't.is()', message: 'my message', formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- 0\n\+ -0/}, ], }); failsWith(t, () => assertions.is(-0, 0, 'my message'), { assertion: 't.is()', message: 'my message', formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- -0\n\+ 0/}, ], }); failsWith(t, () => assertions.is(0, 0, null), { assertion: 't.is()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.not()', t => { passes(t, () => assertions.not('foo', 'bar')); passes(t, () => assertions.not('foo', 'bar')); fails(t, () => assertions.not(Number.NaN, Number.NaN)); fails(t, () => assertions.not(0 / 0, Number.NaN)); failsWith(t, () => assertions.not('foo', 'foo'), { assertion: 't.not()', message: '', formattedDetails: [{label: 'Value is the same as:', formatted: /foo/}], }); failsWith(t, () => assertions.not('foo', 'foo', 'my message'), { assertion: 't.not()', message: 'my message', formattedDetails: [{label: 'Value is the same as:', formatted: /foo/}], }); failsWith(t, () => assertions.not(0, 1, null), { assertion: 't.not()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.deepEqual()', t => { // Tests starting here are to detect regressions in the underlying libraries // used to test deep object equality fails(t, () => assertions.deepEqual({a: false}, {a: 0})); passes(t, () => assertions.deepEqual({ a: 'a', b: 'b', }, { b: 'b', a: 'a', })); passes(t, () => assertions.deepEqual({a: 'a', b: 'b'}, {b: 'b', a: 'a'})); passes(t, () => assertions.deepEqual({ a: 'a', b: 'b', c: { d: 'd', }, }, { c: { d: 'd', }, b: 'b', a: 'a', })); fails(t, () => assertions.deepEqual([1, 2, 3], [1, 2, 3, 4])); passes(t, () => assertions.deepEqual([1, 2, 3], [1, 2, 3])); fails(t, () => { const fnA = a => a; const fnB = a => a; return assertions.deepEqual(fnA, fnB); }); passes(t, () => { const x1 = {z: 4}; const y1 = {x: x1}; x1.y = y1; const x2 = {z: 4}; const y2 = {x: x2}; x2.y = y2; return assertions.deepEqual(x1, x2); }); passes(t, () => { function Foo(a) { this.a = a; } const x = new Foo(1); const y = new Foo(1); return assertions.deepEqual(x, y); }); fails(t, () => { function Foo(a) { this.a = a; } function Bar(a) { this.a = a; } const x = new Foo(1); const y = new Bar(1); return assertions.deepEqual(x, y); }); fails(t, () => assertions.deepEqual({ a: 'a', b: 'b', c: { d: false, }, }, { c: { d: 0, }, b: 'b', a: 'a', })); fails(t, () => assertions.deepEqual({}, [])); fails(t, () => assertions.deepEqual({0: 'a', 1: 'b'}, ['a', 'b'])); fails(t, () => assertions.deepEqual({a: 1}, {a: 1, b: undefined})); fails(t, () => assertions.deepEqual(new Date('1972-08-01'), null)); fails(t, () => assertions.deepEqual(new Date('1972-08-01'), undefined)); passes(t, () => assertions.deepEqual(new Date('1972-08-01'), new Date('1972-08-01'))); passes(t, () => assertions.deepEqual({x: new Date('1972-08-01')}, {x: new Date('1972-08-01')})); fails(t, () => assertions.deepEqual(() => {}, () => {})); passes(t, () => assertions.deepEqual(undefined, undefined) && assertions.deepEqual({x: undefined}, {x: undefined}) && assertions.deepEqual({x: [undefined]}, {x: [undefined]})); passes(t, () => assertions.deepEqual(null, null) && assertions.deepEqual({x: null}, {x: null}) && assertions.deepEqual({x: [null]}, {x: [null]})); passes(t, () => assertions.deepEqual(0, 0) && assertions.deepEqual(1, 1) && assertions.deepEqual(3.14, 3.14)); fails(t, () => assertions.deepEqual(0, 1)); fails(t, () => assertions.deepEqual(1, -1)); fails(t, () => assertions.deepEqual(3.14, 2.72)); fails(t, () => assertions.deepEqual({0: 'a', 1: 'b'}, ['a', 'b'])); passes(t, () => assertions.deepEqual( [ {foo: {z: 100, y: 200, x: 300}}, 'bar', 11, { baz: { d: 4, a: 1, b: 2, c: 3, }, }, ], [ {foo: {x: 300, y: 200, z: 100}}, 'bar', 11, { baz: { c: 3, b: 2, a: 1, d: 4, }, }, ], )); passes(t, () => assertions.deepEqual( {x: {a: 1, b: 2}, y: {c: 3, d: 4}}, {y: {d: 4, c: 3}, x: {b: 2, a: 1}}, )); // Regression test end here passes(t, () => assertions.deepEqual({a: 'a'}, {a: 'a'})); passes(t, () => assertions.deepEqual(['a', 'b'], ['a', 'b'])); fails(t, () => assertions.deepEqual({a: 'a'}, {a: 'b'})); fails(t, () => assertions.deepEqual(['a', 'b'], ['a', 'a'])); fails(t, () => assertions.deepEqual([['a', 'b'], 'c'], [['a', 'b'], 'd'])); fails(t, () => { const circular = ['a', 'b']; circular.push(circular); return assertions.deepEqual([circular, 'c'], [circular, 'd']); }); failsWith(t, () => assertions.deepEqual('foo', 'bar'), { assertion: 't.deepEqual()', message: '', formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 'bar'/}], }); failsWith(t, () => assertions.deepEqual('foo', 42), { assertion: 't.deepEqual()', message: '', formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}], }); failsWith(t, () => assertions.deepEqual('foo', 42, 'my message'), { assertion: 't.deepEqual()', message: 'my message', formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}], }); failsWith(t, () => assertions.deepEqual({}, {}, null), { assertion: 't.deepEqual()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.notDeepEqual()', t => { passes(t, () => assertions.notDeepEqual({a: 'a'}, {a: 'b'})); passes(t, () => assertions.notDeepEqual({a: 'a'}, {a: 'b'})); passes(t, () => assertions.notDeepEqual(['a', 'b'], ['c', 'd'])); const actual = {a: 'a'}; const expected = {a: 'a'}; failsWith(t, () => assertions.notDeepEqual(actual, expected), { actual, assertion: 't.notDeepEqual()', expected, message: '', formattedDetails: [{label: 'Value is deeply equal:', formatted: /.*{.*\n.*a: 'a'/}], }); failsWith(t, () => assertions.notDeepEqual(['a', 'b'], ['a', 'b'], 'my message'), { assertion: 't.notDeepEqual()', message: 'my message', formattedDetails: [{label: 'Value is deeply equal:', formatted: /.*\[.*\n.*'a',\n.*'b',/}], }); failsWith(t, () => assertions.notDeepEqual({}, [], null), { assertion: 't.notDeepEqual()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.like()', t => { fails(t, () => assertions.like({a: false}, {a: 0})); passes(t, () => assertions.like({ a: 'a', b: 'b', }, { b: 'b', a: 'a', })); passes(t, () => assertions.like({a: 'a', b: 'b'}, {b: 'b', a: 'a'})); passes(t, () => assertions.like({ a: 'a', b: 'b', c: { d: 'd', x: 'x', }, x: 'x', }, { c: { d: 'd', }, b: 'b', a: 'a', })); fails(t, () => assertions.like([1, 2, 3], [1, 2, 3, 4])); fails(t, () => assertions.like({ a: [1, 2, 3], }, { a: [1, 2, 3, 4], })); passes(t, () => assertions.like({ a: [1, 2, 3], x: 'x', }, { a: [1, 2, 3], })); passes(t, () => { const actual = { a: 'a', extra: 'irrelevant', }; actual.circular = actual; const likePattern = { a: 'a', }; return assertions.like(actual, likePattern); }); fails(t, () => { const fnA = a => a; const fnB = a => a; return assertions.like(fnA, fnB); }); fails(t, () => { const fnA = a => a; const fnB = a => a; return assertions.like({ fn: fnA, }, { fn: fnB, }); }); fails(t, () => { function Foo(a) { this.a = a; } function Bar(a) { this.a = a; } const x = new Foo(1); const y = new Bar(1); return assertions.like(x, y); }); passes(t, () => assertions.like({a: 'a'}, {a: 'a'})); passes(t, () => assertions.like({a: 'a', b: 'b'}, {a: 'a'})); passes(t, () => assertions.like({ab: ['a', 'b']}, {ab: ['a', 'b']})); passes(t, () => assertions.like({ab: ['a', 'b'], c: 'c'}, {ab: ['a', 'b']})); fails(t, () => assertions.like({a: 'a'}, {a: 'b'})); fails(t, () => assertions.like({a: 'a', b: 'b'}, {a: 'b'})); fails(t, () => assertions.like({ab: ['a', 'b']}, {ab: ['a', 'a']})); fails(t, () => assertions.like({ab: ['a', 'b'], c: 'c'}, {ab: ['a', 'a']})); fails(t, () => assertions.like([['a', 'b'], 'c'], [['a', 'b'], 'd'])); fails(t, () => { const circular = ['a', 'b']; circular.push(circular); return assertions.like([circular, 'c'], [circular, 'd']); }); fails(t, () => { const circular = ['a', 'b']; circular.push(circular); return assertions.like({xc: [circular, 'c']}, {xc: [circular, 'd']}); }); failsWith(t, () => assertions.like({a: 'a'}, Object.defineProperties({}, {ignored: {}})), { assertion: 't.like()', message: '`t.like()` selector must be a non-empty object', formattedDetails: [{label: 'Called with:', formatted: '{}'}], }); failsWith(t, () => assertions.like('foo', 'bar'), { assertion: 't.like()', message: '`t.like()` selector must be a non-empty object', formattedDetails: [{label: 'Called with:', formatted: '\'bar\''}], }); passes(t, () => { const specimen = {[Symbol.toStringTag]: 'Custom', extra: true}; const selector = Object.defineProperties( {[Symbol.toStringTag]: 'Custom'}, {ignored: {value: true}}, ); return assertions.like(specimen, selector); }); passes(t, () => { const array = ['c1', 'c2']; return assertions.like({ a: 'a', b: 'b', c: ['c1', 'c2'], d: ['c1', 'c2'], }, {b: 'b', d: array, c: array}); }); failsWith(t, () => { const likePattern = { a: 'a', }; likePattern.circular = likePattern; return assertions.like({}, likePattern); }, { assertion: 't.like()', message: '`t.like()` selector must not contain circular references', formattedDetails: [{label: 'Called with:', formatted: '{\n a: \'a\',\n circular: [Circular],\n}'}], }); failsWith(t, () => assertions.like({}, {}, null), { assertion: 't.like()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); failsWith(t, () => assertions.like({a: 'foo', b: 'irrelevant'}, {a: 'bar'}), { assertion: 't.like()', message: '', formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: /{\n-\s*a: 'foo',\n\+\s*a: 'bar',\n\s*}/}], }); passes(t, () => assertions.like({a: [{a: 1, b: 2}]}, {a: [{a: 1}]})); passes(t, () => assertions.like([{a: 1, b: 2}], [{a: 1}])); passes(t, () => assertions.like([{a: 1, b: 2}, {c: 3}], [{a: 1}])); passes(t, () => assertions.like([1, 2, 3], [1, 2, 3])); passes(t, () => assertions.like([1, 2, 3], [1, 2])); // eslint-disable-next-line no-sparse-arrays passes(t, () => assertions.like([1, 2, 3], [1, , 3])); fails(t, () => assertions.like([1, 2, 3], [3, 2, 1])); // eslint-disable-next-line no-sparse-arrays fails(t, () => assertions.like([1, 2, 3], [1, , 4])); fails(t, () => assertions.like([1, 2], [1, 2, 3])); t.end(); }); test('.throws()', gather(t => { // Fails because function doesn't throw. failsWith(t, () => assertions.throws(() => {}), { assertion: 't.throws()', message: '', formattedDetails: [{label: 'Function returned:', formatted: /undefined/}], }); failsWith(t, () => assertions.throws(() => {}), { assertion: 't.throws()', message: '', formattedDetails: [{label: 'Function returned:', formatted: /undefined/}], }); // Fails because function doesn't throw. Asserts that 'my message' is used // as the assertion message (*not* compared against the error). failsWith(t, () => assertions.throws(() => {}, undefined, 'my message'), { assertion: 't.throws()', message: 'my message', formattedDetails: [{label: 'Function returned:', formatted: /undefined/}], }); // Fails because the function returned a promise. failsWith(t, () => assertions.throws(() => Promise.resolve()), { assertion: 't.throws()', message: '', formattedDetails: [{label: 'Function returned a promise. Use `t.throwsAsync()` instead:', formatted: /Promise/}], }); // Fails because thrown exception is not an error failsWith(t, () => assertions.throws(() => { const error = 'foo'; throw error; }), { assertion: 't.throws()', message: '', formattedDetails: [ {label: 'Function threw exception that is not an error:', formatted: /'foo'/}, ], }); // Passes because an error is thrown. passes(t, () => assertions.throws(() => { throw new Error('foo'); }), {expectBoolean: false}); // Passes when string is thrown and `expectation.any` is true. passes(t, () => assertions.throws(() => { throw 'foo'; // eslint-disable-line no-throw-literal }, {any: true}), {expectBoolean: false}); // Passes when false is thrown and `expectation.any` is true. passes(t, () => assertions.throws(() => { throw false; // eslint-disable-line no-throw-literal }, {any: true}), {expectBoolean: false}); // Passes because the correct error is thrown. passes(t, () => { const error = new Error('foo'); return assertions.throws(() => { throw error; }, {is: error}); }, {expectBoolean: false}); // Fails because the thrown value is not an error fails(t, () => { const object = {}; return assertions.throws(() => { throw object; }, {is: object}); }); // Fails because the thrown value is not the right one fails(t, () => { const error = new Error('foo'); return assertions.throws(() => { throw error; }, {is: {}}); }); // Passes because the correct error is thrown. passes(t, () => assertions.throws(() => { throw new TypeError(); }, {name: 'TypeError'}), {expectBoolean: false}); // Fails because the thrown value is not an error fails(t, () => assertions.throws(() => { const error = {name: 'Bob'}; throw error; }, {name: 'Bob'})); // Fails because the thrown value is not the right one fails(t, () => assertions.throws(() => { throw new Error('foo'); }, {name: 'TypeError'})); // Passes because the correct error is thrown. passes(t, () => assertions.throws(() => { const error = new TypeError(); error.code = 'ERR_TEST'; throw error; }, {code: 'ERR_TEST'}), {expectBoolean: false}); // Passes because the correct error is thrown. passes(t, () => assertions.throws(() => { const error = new TypeError(); error.code = 42; throw error; }, {code: 42}), {expectBoolean: false}); // Fails because the thrown value is not the right one fails(t, () => assertions.throws(() => { const error = new TypeError(); error.code = 'ERR_NOPE'; throw error; }, {code: 'ERR_TEST'})); fails(t, () => assertions.throws(() => { const error = new TypeError(); error.code = 1; throw error; }, {code: 42})); // Regression test for https://github.com/avajs/ava/issues/1676 fails(t, () => assertions.throws(() => { throw new Error('foo'); }, false)); passes(t, () => assertions.throws(() => { throw new Error('foo'); }, undefined), {expectBoolean: false}); passes(t, async () => { await assertions.throwsAsync(() => Promise.reject(new Error('foo')), undefined); }, {expectBoolean: false}); failsWith(t, () => assertions.throws(() => {}, undefined, null), { assertion: 't.throws()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); // Fails because the string in the message is incorrect failsWith( t, () => assertions.throws( () => { throw new Error('error'); }, {message: 'my error'}, ), { assertion: 't.throws()', message: '', formattedDetails: [ {label: 'Function threw unexpected exception:', formatted: /error/}, {label: 'Expected message to equal:', formatted: /my error/}, ], }, ); passes(t, () => assertions.throws(() => { throw new Error('error'); }, {message: 'error'}), {expectBoolean: false}); // Fails because the regular expression in the message is incorrect failsWith( t, () => assertions.throws( () => { throw new Error('error'); }, {message: /my error/}, ), { assertion: 't.throws()', message: '', formattedDetails: [ {label: 'Function threw unexpected exception:', formatted: /error/}, {label: 'Expected message to match:', formatted: /my error/}, ], }, ); passes(t, () => assertions.throws(() => { throw new Error('error'); }, {message: /error/}), {expectBoolean: false}); // Fails because the function in the message returns false failsWith( t, () => assertions.throws( () => { throw new Error('error'); }, {message: () => false}, ), { assertion: 't.throws()', message: '', formattedDetails: [ {label: 'Function threw unexpected exception:', formatted: /error/}, {label: 'Expected message to return true:', formatted: /Function/}, ], }, ); passes(t, () => assertions.throws(() => { throw new Error('error'); }, {message: () => true}), {expectBoolean: false}); })); test('.throws() returns the thrown error', t => { const expected = new Error(); const actual = assertions.throws(() => { throw expected; }); t.equal(actual, expected); t.end(); }); test('.throwsAsync()', gather(t => { // Fails because the promise is resolved, not rejected. throwsAsyncFails(t, () => assertions.throwsAsync(Promise.resolve('foo')), { assertion: 't.throwsAsync()', message: '', formattedDetails: [{label: 'Promise resolved with:', formatted: /'foo'/}], }); throwsAsyncFails(t, () => assertions.throwsAsync(Promise.resolve('foo')), { assertion: 't.throwsAsync()', message: '', formattedDetails: [{label: 'Promise resolved with:', formatted: /'foo'/}], }); // Fails because the promise is resolved with an Error throwsAsyncFails(t, () => assertions.throwsAsync(Promise.resolve(new Error())), { assertion: 't.throwsAsync()', message: '', formattedDetails: [{label: 'Promise resolved with:', formatted: /Error/}], }); // Fails because the function returned a promise that resolved, not rejected. throwsAsyncFails(t, () => assertions.throwsAsync(() => Promise.resolve('foo')), { assertion: 't.throwsAsync()', message: '', formattedDetails: [{label: 'Returned promise resolved with:', formatted: /'foo'/}], }); // Fails because the function returned a promise that rejected, but not with an error. throwsAsyncFails(t, () => assertions.throwsAsync(() => Promise.reject('foo')), { // eslint-disable-line prefer-promise-reject-errors assertion: 't.throwsAsync()', message: '', formattedDetails: [{label: 'Returned promise rejected with exception that is not an error:', formatted: /'foo'/}], }); // Passes because the promise was rejected with an error. throwsAsyncPasses(t, () => assertions.throwsAsync(Promise.reject(new Error()))); // Passes because the promise was rejected with a non-error reason and `expectation.any` is true. throwsAsyncPasses(t, () => assertions.throwsAsync(Promise.reject('foo'), {any: true})); // eslint-disable-line prefer-promise-reject-errors // Passes because the promise was rejected with a falsy non-error reason and `expectation.any` is true. throwsAsyncPasses(t, () => assertions.throwsAsync(Promise.reject(), {any: true})); // Passes because the function returned a promise rejected with an error. throwsAsyncPasses(t, () => assertions.throwsAsync(() => Promise.reject(new Error()))); // Fails because the function throws synchronously throwsAsyncFails(t, () => assertions.throwsAsync(() => { throw new Error('sync'); }, undefined, 'message'), { assertion: 't.throwsAsync()', message: 'message', formattedDetails: [ {label: 'Function threw synchronously. Use `t.throws()` instead:', formatted: /Error/}, ], }); // Fails because the function did not return a promise throwsAsyncFails(t, () => assertions.throwsAsync(() => {}, undefined, 'message'), { assertion: 't.throwsAsync()', message: 'message', formattedDetails: [ {label: 'Function returned:', formatted: /undefined/}, ], }); throwsAsyncFails(t, () => assertions.throwsAsync(Promise.resolve(), undefined, null), { assertion: 't.throwsAsync()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); })); test('.throwsAsync() returns the rejection reason of promise', t => { const expected = new Error(); return assertions.throwsAsync(Promise.reject(expected)).then(actual => { t.equal(actual, expected); t.end(); }); }); test('.throwsAsync() returns the rejection reason of a promise returned by the function', t => { const expected = new Error(); return assertions.throwsAsync(() => Promise.reject(expected)).then(actual => { t.equal(actual, expected); t.end(); }); }); test('.throws() fails if passed a bad value', t => { failsWith(t, () => assertions.throws('not a function'), { assertion: 't.throws()', message: '`t.throws()` must be called with a function', formattedDetails: [{label: 'Called with:', formatted: /not a function/}], }); t.end(); }); test('.throwsAsync() fails if passed a bad value', gather(t => { throwsAsyncFails(t, () => assertions.throwsAsync('not a function'), { assertion: 't.throwsAsync()', message: '`t.throwsAsync()` must be called with a function or promise', formattedDetails: [{label: 'Called with:', formatted: /not a function/}], }); })); test('.throws() fails if passed a bad expectation', t => { failsWith(t, () => assertions.throws(() => {}, true), { assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /true/}], }); failsWith(t, () => assertions.throws(() => {}, 'foo'), { assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /foo/}], }); failsWith(t, () => assertions.throws(() => {}, /baz/), { assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /baz/}], }); failsWith(t, () => assertions.throws(() => {}, class Bar {}), { assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /Bar/}], }); failsWith(t, () => assertions.throws(() => {}, {}), { assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /{}/}], }); failsWith(t, () => assertions.throws(() => {}, []), { assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /\[]/}], }); failsWith(t, () => assertions.throws(() => {}, {any: {}}), { assertion: 't.throws()', message: 'The `any` property of the second argument to `t.throws()` must be a boolean', formattedDetails: [{label: 'Called with:', formatted: /any: {}/}], }); failsWith(t, () => assertions.throws(() => {}, {code: {}}), { assertion: 't.throws()', message: 'The `code` property of the second argument to `t.throws()` must be a string or number', formattedDetails: [{label: 'Called with:', formatted: /code: {}/}], }); failsWith(t, () => assertions.throws(() => {}, {instanceOf: null}), { assertion: 't.throws()', message: 'The `instanceOf` property of the second argument to `t.throws()` must be a function', formattedDetails: [{label: 'Called with:', formatted: /instanceOf: null/}], }); failsWith(t, () => assertions.throws(() => {}, {message: null}), { assertion: 't.throws()', message: 'The `message` property of the second argument to `t.throws()` must be a string, regular expression or a function', formattedDetails: [{label: 'Called with:', formatted: /message: null/}], }); failsWith(t, () => assertions.throws(() => {}, {name: null}), { assertion: 't.throws()', message: 'The `name` property of the second argument to `t.throws()` must be a string', formattedDetails: [{label: 'Called with:', formatted: /name: null/}], }); failsWith(t, () => assertions.throws(() => {}, { is: {}, message: '', name: '', of() {}, foo: null, }), { assertion: 't.throws()', message: 'The second argument to `t.throws()` contains unexpected properties', formattedDetails: [{label: 'Called with:', formatted: /foo: null/}], }); t.end(); }); test('.throwsAsync() fails if passed a bad expectation', async t => { await failsWithAsync(t, () => assertions.throwsAsync(() => {}, true), { assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /true/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, 'foo'), { assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /foo/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, /baz/), { assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /baz/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, class Bar {}), { assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /Bar/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, {}), { assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /{}/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, []), { assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /\[]/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, {any: {}}), { assertion: 't.throwsAsync()', message: 'The `any` property of the second argument to `t.throwsAsync()` must be a boolean', formattedDetails: [{label: 'Called with:', formatted: /any: {}/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, {code: {}}), { assertion: 't.throwsAsync()', message: 'The `code` property of the second argument to `t.throwsAsync()` must be a string or number', formattedDetails: [{label: 'Called with:', formatted: /code: {}/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, {instanceOf: null}), { assertion: 't.throwsAsync()', message: 'The `instanceOf` property of the second argument to `t.throwsAsync()` must be a function', formattedDetails: [{label: 'Called with:', formatted: /instanceOf: null/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, {message: null}), { assertion: 't.throwsAsync()', message: 'The `message` property of the second argument to `t.throwsAsync()` must be a string, regular expression or a function', formattedDetails: [{label: 'Called with:', formatted: /message: null/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, {name: null}), { assertion: 't.throwsAsync()', message: 'The `name` property of the second argument to `t.throwsAsync()` must be a string', formattedDetails: [{label: 'Called with:', formatted: /name: null/}], }); await failsWithAsync(t, () => assertions.throwsAsync(() => {}, { is: {}, message: '', name: '', of() {}, foo: null, }), { assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` contains unexpected properties', formattedDetails: [{label: 'Called with:', formatted: /foo: null/}], }); t.end(); }); test('.throws() fails if passed null expectation', t => { const asserter = new AssertionsBase(); failsWith(t, () => { asserter.throws(() => {}, null); }, { assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /null/}], }); t.end(); }); test('.throwsAsync() fails if passed null', async t => { const asserter = new AssertionsBase(); await failsWithAsync(t, () => asserter.throwsAsync(() => {}, null), { assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object or `undefined`', formattedDetails: [{label: 'Called with:', formatted: /null/}], }); t.end(); }); test('.notThrows()', gather(t => { // Passes because the function doesn't throw passes(t, () => assertions.notThrows(() => {})); passes(t, () => assertions.notThrows(() => {})); // Fails because the function throws. failsWith(t, () => assertions.notThrows(() => { throw new Error('foo'); }), { assertion: 't.notThrows()', message: '', formattedDetails: [{label: 'Function threw:', formatted: /foo/}], }); // Fails because the function throws. Asserts that message is used for the // assertion, not to validate the thrown error. failsWith(t, () => assertions.notThrows(() => { throw new Error('foo'); }, 'my message'), { assertion: 't.notThrows()', message: 'my message', formattedDetails: [{label: 'Function threw:', formatted: /foo/}], }); failsWith(t, () => assertions.notThrows(() => {}, null), { assertion: 't.notThrows()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); })); test('.notThrowsAsync()', gather(t => { // Passes because the promise is resolved notThrowsAsyncPasses(t, () => assertions.notThrowsAsync(Promise.resolve())); notThrowsAsyncPasses(t, () => assertions.notThrowsAsync(Promise.resolve())); // Fails because the promise is rejected notThrowsAsyncFails(t, () => assertions.notThrowsAsync(Promise.reject(new Error())), { assertion: 't.notThrowsAsync()', message: '', formattedDetails: [{label: 'Promise rejected with:', formatted: /Error/}], }); // Passes because the function returned a resolved promise notThrowsAsyncPasses(t, () => assertions.notThrowsAsync(() => Promise.resolve())); // Fails because the function returned a rejected promise notThrowsAsyncFails(t, () => assertions.notThrowsAsync(() => Promise.reject(new Error())), { assertion: 't.notThrowsAsync()', message: '', formattedDetails: [{label: 'Returned promise rejected with:', formatted: /Error/}], }); // Fails because the function throws synchronously notThrowsAsyncFails(t, () => assertions.notThrowsAsync(() => { throw new Error('sync'); }, 'message'), { assertion: 't.notThrowsAsync()', message: 'message', formattedDetails: [ {label: 'Function threw:', formatted: /Error/}, ], }); // Fails because the function did not return a promise notThrowsAsyncFails(t, () => assertions.notThrowsAsync(() => {}, 'message'), { assertion: 't.notThrowsAsync()', message: 'message', formattedDetails: [ {label: 'Function did not return a promise. Use `t.notThrows()` instead:', formatted: /undefined/}, ], }); notThrowsAsyncFails(t, () => assertions.notThrowsAsync(Promise.resolve(), null), { assertion: 't.notThrowsAsync()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); })); test('.notThrows() fails if passed a bad value', t => { failsWith(t, () => assertions.notThrows('not a function'), { assertion: 't.notThrows()', message: '`t.notThrows()` must be called with a function', formattedDetails: [{label: 'Called with:', formatted: /not a function/}], }); t.end(); }); test('.notThrowsAsync() fails if passed a bad value', async t => { await failsWithAsync(t, () => assertions.notThrowsAsync('not a function'), { assertion: 't.notThrowsAsync()', message: '`t.notThrowsAsync()` must be called with a function or promise', formattedDetails: [{label: 'Called with:', formatted: /not a function/}], }); t.end(); }); test('.snapshot()', async t => { // Set to `true` to update the snapshot, then run: // npx tap test-tap/assert.js // // Ignore errors and make sure not to run tests with the `-b` (bail) option. const updating = false; const projectDir = fileURLToPath(new URL('fixture', import.meta.url)); const manager = snapshotManager.load({ file: path.join(projectDir, 'assert.cjs'), projectDir, fixedLocation: null, recordNewSnapshots: updating, updating, }); const setup = _title => new class extends assertions.constructor { constructor(title) { super({ compareWithSnapshot: assertionOptions => { const {record, ...result} = manager.compare({ belongsTo: this.title, expected: assertionOptions.expected, index: this.snapshotInvocationCount++, label: assertionOptions.message ?? `Snapshot ${this.snapshotInvocationCount}`, }); if (record) { record(); } return result; }, }); this.title = title; this.snapshotInvocationCount = 0; } }(_title); { const assertions = setup('passes'); passes(t, () => assertions.snapshot({foo: 'bar'})); passes(t, () => assertions.snapshot({foo: 'bar'})); } { const assertions = setup('fails'); if (updating) { return assertions.snapshot({foo: 'bar'}); } failsWith(t, () => assertions.snapshot({foo: 'not bar'}), { assertion: 't.snapshot()', message: 'Did not match snapshot', formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: ' {\n- foo: \'not bar\',\n+ foo: \'bar\',\n }'}], }); } { const assertions = setup('fails'); if (updating) { return assertions.snapshot({foo: 'bar'}, 'my message'); } failsWith(t, () => assertions.snapshot({foo: 'not bar'}, 'my message'), { assertion: 't.snapshot()', message: 'my message', formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: ' {\n- foo: \'not bar\',\n+ foo: \'bar\',\n }'}], }); } { const assertions = setup('bad message'); failsWith(t, () => assertions.snapshot(null, null), { assertion: 't.snapshot()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); failsWith(t, () => assertions.snapshot(null, ''), { assertion: 't.snapshot()', message: 'The snapshot assertion message must be a non-empty string', formattedDetails: [{ label: 'Called with:', formatted: '\'\'', }], }); } await manager.save(); t.end(); }); test('.truthy()', t => { failsWith(t, () => assertions.truthy(0), { assertion: 't.truthy()', message: '', formattedDetails: [{label: 'Value is not truthy:', formatted: /0/}], }); failsWith(t, () => assertions.truthy(false, 'my message'), { assertion: 't.truthy()', message: 'my message', formattedDetails: [{label: 'Value is not truthy:', formatted: /false/}], }); passes(t, () => assertions.truthy(1) && assertions.truthy(true)); passes(t, () => assertions.truthy(1) && assertions.truthy(true)); failsWith(t, () => assertions.truthy(true, null), { assertion: 't.truthy()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.falsy()', t => { failsWith(t, () => assertions.falsy(1), { assertion: 't.falsy()', message: '', formattedDetails: [{label: 'Value is not falsy:', formatted: /1/}], }); failsWith(t, () => assertions.falsy(true, 'my message'), { assertion: 't.falsy()', message: 'my message', formattedDetails: [{label: 'Value is not falsy:', formatted: /true/}], }); passes(t, () => assertions.falsy(0) && assertions.falsy(false)); passes(t, () => assertions.falsy(0) && assertions.falsy(false)); failsWith(t, () => assertions.falsy(false, null), { assertion: 't.falsy()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.true()', t => { failsWith(t, () => assertions.true(1), { assertion: 't.true()', message: '', formattedDetails: [{label: 'Value is not `true`:', formatted: /1/}], }); failsWith(t, () => assertions.true(0), { assertion: 't.true()', message: '', formattedDetails: [{label: 'Value is not `true`:', formatted: /0/}], }); failsWith(t, () => assertions.true(false), { assertion: 't.true()', message: '', formattedDetails: [{label: 'Value is not `true`:', formatted: /false/}], }); failsWith(t, () => assertions.true('foo', 'my message'), { assertion: 't.true()', message: 'my message', formattedDetails: [{label: 'Value is not `true`:', formatted: /foo/}], }); passes(t, () => assertions.true(true)); passes(t, () => { const {true: trueFn} = assertions; return trueFn(true); }); failsWith(t, () => assertions.true(true, null), { assertion: 't.true()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.false()', t => { failsWith(t, () => assertions.false(0), { assertion: 't.false()', message: '', formattedDetails: [{label: 'Value is not `false`:', formatted: /0/}], }); failsWith(t, () => assertions.false(1), { assertion: 't.false()', message: '', formattedDetails: [{label: 'Value is not `false`:', formatted: /1/}], }); failsWith(t, () => assertions.false(true), { assertion: 't.false()', message: '', formattedDetails: [{label: 'Value is not `false`:', formatted: /true/}], }); failsWith(t, () => assertions.false('foo', 'my message'), { assertion: 't.false()', message: 'my message', formattedDetails: [{label: 'Value is not `false`:', formatted: /foo/}], }); passes(t, () => assertions.false(false)); passes(t, () => { const {false: falseFn} = assertions; return falseFn(false); }); failsWith(t, () => assertions.false(false, null), { assertion: 't.false()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.regex()', t => { passes(t, () => assertions.regex('abc', /^abc$/)); passes(t, () => assertions.regex('abc', /^abc$/)); failsWith(t, () => assertions.regex('foo', /^abc$/), { assertion: 't.regex()', message: '', formattedDetails: [ {label: 'Value must match expression:', formatted: /foo/}, {label: 'Regular expression:', formatted: /\/\^abc\$\//}, ], }); failsWith(t, () => assertions.regex('foo', /^abc$/, 'my message'), { assertion: 't.regex()', message: 'my message', formattedDetails: [ {label: 'Value must match expression:', formatted: /foo/}, {label: 'Regular expression:', formatted: /\/\^abc\$\//}, ], }); failsWith(t, () => assertions.regex('foo', /^abc$/, null), { assertion: 't.regex()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.regex() fails if passed a bad value', t => { failsWith(t, () => assertions.regex(42, /foo/), { assertion: 't.regex()', message: '`t.regex()` must be called with a string', formattedDetails: [{label: 'Called with:', formatted: /42/}], }); failsWith(t, () => assertions.regex('42', {}), { assertion: 't.regex()', message: '`t.regex()` must be called with a regular expression', formattedDetails: [{label: 'Called with:', formatted: /{}/}], }); t.end(); }); test('.notRegex()', t => { passes(t, () => assertions.notRegex('abc', /def/)); passes(t, () => assertions.notRegex('abc', /def/)); failsWith(t, () => assertions.notRegex('abc', /abc/), { assertion: 't.notRegex()', message: '', formattedDetails: [ {label: 'Value must not match expression:', formatted: /abc/}, {label: 'Regular expression:', formatted: /\/abc\//}, ], }); failsWith(t, () => assertions.notRegex('abc', /abc/, 'my message'), { assertion: 't.notRegex()', message: 'my message', formattedDetails: [ {label: 'Value must not match expression:', formatted: /abc/}, {label: 'Regular expression:', formatted: /\/abc\//}, ], }); failsWith(t, () => assertions.notRegex('abc', /abc/, null), { assertion: 't.notRegex()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); }); test('.notRegex() fails if passed a bad value', t => { failsWith(t, () => assertions.notRegex(42, /foo/), { assertion: 't.notRegex()', message: '`t.notRegex()` must be called with a string', formattedDetails: [{label: 'Called with:', formatted: /42/}], }); failsWith(t, () => assertions.notRegex('42', {}), { assertion: 't.notRegex()', message: '`t.notRegex()` must be called with a regular expression', formattedDetails: [{label: 'Called with:', formatted: /{}/}], }); t.end(); }); test('.assert()', t => { failsWith(t, () => assertions.assert(0), { assertion: 't.assert()', message: '', formattedDetails: [{label: 'Value is not truthy:', formatted: /0/}], }); failsWith(t, () => assertions.assert(false, 'my message'), { assertion: 't.assert()', message: 'my message', formattedDetails: [{label: 'Value is not truthy:', formatted: /false/}], }); passes(t, () => assertions.assert(1) && assertions.assert(true)); passes(t, () => assertions.assert(1) && assertions.assert(true)); failsWith(t, () => assertions.assert(null, null), { assertion: 't.assert()', message: 'The assertion message must be a string', formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); t.end(); });