diff --git a/lib/api.js b/lib/api.js index 202a127ea..39a3c435c 100644 --- a/lib/api.js +++ b/lib/api.js @@ -305,13 +305,7 @@ export default class Api extends Emittery { const files = scheduler.storeFailedTestFiles(runStatus, this.options.cacheEnabled === false ? null : this._createCacheDir()); runStatus.emitStateChange({type: 'touched-files', files}); } catch (error) { - if (error?.name === 'AggregateError') { - for (const error_ of error.errors) { - runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error_)}); - } - } else { - runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error)}); - } + runStatus.emitStateChange({type: 'internal-error', err: serializeError(error)}); } timeoutTrigger.discard(); diff --git a/lib/assert.js b/lib/assert.js index a1cd1c661..4638c42f9 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -34,54 +34,49 @@ const notImplemented = () => { }; export class AssertionError extends Error { - constructor(options) { - super(options.message ?? ''); + constructor(message = '', { + assertion, + assertionStack = getAssertionStack(AssertionError), + formattedDetails = [], + improperUsage = null, + cause, + } = {}) { + super(message, {cause}); this.name = 'AssertionError'; - this.assertion = options.assertion; - this.fixedSource = options.fixedSource; - this.improperUsage = options.improperUsage ?? false; - this.actualStack = options.actualStack; - this.operator = options.operator; - this.values = options.values ?? []; - - // Raw expected and actual objects are stored for custom reporters - // (such as wallaby.js), that manage worker processes directly and - // use the values for custom diff views - this.raw = options.raw; - - this.savedError = options.savedError ?? getErrorWithLongStackTrace(); + this.assertion = assertion; + this.assertionStack = assertionStack; + this.improperUsage = improperUsage; + this.formattedDetails = formattedDetails; } } -export function checkAssertionMessage(assertion, message) { +export function checkAssertionMessage(message, assertion) { if (message === undefined || typeof message === 'string') { return true; } - return new AssertionError({ + return new AssertionError('The assertion message must be a string', { assertion, - improperUsage: true, - message: 'The assertion message must be a string', - values: [formatWithLabel('Called with:', message)], + formattedDetails: [formatWithLabel('Called with:', message)], }); } -function getErrorWithLongStackTrace() { - const limitBefore = Error.stackTraceLimit; +export function getAssertionStack(constructorOpt = getAssertionStack) { + const {stackTraceLimit: limitBefore} = Error; Error.stackTraceLimit = Number.POSITIVE_INFINITY; - const error = new Error(); // eslint-disable-line unicorn/error-message + const temporary = {}; + Error.captureStackTrace(temporary, constructorOpt); Error.stackTraceLimit = limitBefore; - return error; + return temporary.stack; } function validateExpectations(assertion, expectations, numberArgs) { // eslint-disable-line complexity if (numberArgs === 1 || expectations === null || expectations === undefined) { if (expectations === null) { - throw new AssertionError({ + throw new AssertionError(`The second argument to \`${assertion}\` must be an expectation object or \`undefined\``, { assertion, - message: `The second argument to \`t.${assertion}()\` must be an expectation object or \`undefined\``, - values: [formatWithLabel('Called with:', expectations)], + formattedDetails: [formatWithLabel('Called with:', expectations)], }); } @@ -94,17 +89,15 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d || Array.isArray(expectations) || Object.keys(expectations).length === 0 ) { - throw new AssertionError({ + throw new AssertionError(`The second argument to \`${assertion}\` must be an expectation object, \`null\` or \`undefined\``, { assertion, - message: `The second argument to \`t.${assertion}()\` must be an expectation object, \`null\` or \`undefined\``, - values: [formatWithLabel('Called with:', expectations)], + formattedDetails: [formatWithLabel('Called with:', expectations)], }); } else { if (Object.hasOwn(expectations, 'instanceOf') && typeof expectations.instanceOf !== 'function') { - throw new AssertionError({ + throw new AssertionError(`The \`instanceOf\` property of the second argument to \`${assertion}\` must be a function`, { assertion, - message: `The \`instanceOf\` property of the second argument to \`t.${assertion}()\` must be a function`, - values: [formatWithLabel('Called with:', expectations)], + formattedDetails: [formatWithLabel('Called with:', expectations)], }); } @@ -114,26 +107,23 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d && !(expectations.message instanceof RegExp) && !(typeof expectations.message === 'function') ) { - throw new AssertionError({ + throw new AssertionError(`The \`message\` property of the second argument to \`${assertion}\` must be a string, regular expression or a function`, { assertion, - message: `The \`message\` property of the second argument to \`t.${assertion}()\` must be a string, regular expression or a function`, - values: [formatWithLabel('Called with:', expectations)], + formattedDetails: [formatWithLabel('Called with:', expectations)], }); } if (Object.hasOwn(expectations, 'name') && typeof expectations.name !== 'string') { - throw new AssertionError({ + throw new AssertionError(`The \`name\` property of the second argument to \`${assertion}\` must be a string`, { assertion, - message: `The \`name\` property of the second argument to \`t.${assertion}()\` must be a string`, - values: [formatWithLabel('Called with:', expectations)], + formattedDetails: [formatWithLabel('Called with:', expectations)], }); } if (Object.hasOwn(expectations, 'code') && typeof expectations.code !== 'string' && typeof expectations.code !== 'number') { - throw new AssertionError({ + throw new AssertionError(`The \`code\` property of the second argument to \`${assertion}\` must be a string or number`, { assertion, - message: `The \`code\` property of the second argument to \`t.${assertion}()\` must be a string or number`, - values: [formatWithLabel('Called with:', expectations)], + formattedDetails: [formatWithLabel('Called with:', expectations)], }); } @@ -148,10 +138,9 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d } default: { - throw new AssertionError({ + throw new AssertionError(`The second argument to \`${assertion}\` contains unexpected properties`, { assertion, - message: `The second argument to \`t.${assertion}()\` contains unexpected properties`, - values: [formatWithLabel('Called with:', expectations)], + formattedDetails: [formatWithLabel('Called with:', expectations)], }); } } @@ -163,25 +152,22 @@ function validateExpectations(assertion, expectations, numberArgs) { // eslint-d // Note: this function *must* throw exceptions, since it can be used // as part of a pending assertion for promises. -function assertExpectations({assertion, actual, expectations, message, prefix, savedError}) { +function assertExpectations({actual, expectations, message, prefix, assertion, assertionStack}) { if (!isNativeError(actual)) { - throw new AssertionError({ + throw new AssertionError(message, { assertion, - message, - savedError, - values: [formatWithLabel(`${prefix} exception that is not an error:`, actual)], + assertionStack, + cause: actual, + formattedDetails: [formatWithLabel(`${prefix} exception that is not an error:`, actual)], }); } - const actualStack = actual.stack; - if (Object.hasOwn(expectations, 'is') && actual !== expectations.is) { - throw new AssertionError({ + throw new AssertionError(message, { assertion, - message, - savedError, - actualStack, - values: [ + assertionStack, + cause: actual, + formattedDetails: [ formatWithLabel(`${prefix} unexpected exception:`, actual), formatWithLabel('Expected to be strictly equal to:', expectations.is), ], @@ -189,12 +175,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s } if (expectations.instanceOf && !(actual instanceof expectations.instanceOf)) { - throw new AssertionError({ + throw new AssertionError(message, { assertion, - message, - savedError, - actualStack, - values: [ + assertionStack, + cause: actual, + formattedDetails: [ formatWithLabel(`${prefix} unexpected exception:`, actual), formatWithLabel('Expected instance of:', expectations.instanceOf), ], @@ -202,12 +187,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s } if (typeof expectations.name === 'string' && actual.name !== expectations.name) { - throw new AssertionError({ + throw new AssertionError(message, { assertion, - message, - savedError, - actualStack, - values: [ + assertionStack, + cause: actual, + formattedDetails: [ formatWithLabel(`${prefix} unexpected exception:`, actual), formatWithLabel('Expected name to equal:', expectations.name), ], @@ -215,12 +199,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s } if (typeof expectations.message === 'string' && actual.message !== expectations.message) { - throw new AssertionError({ + throw new AssertionError(message, { assertion, - message, - savedError, - actualStack, - values: [ + assertionStack, + cause: actual, + formattedDetails: [ formatWithLabel(`${prefix} unexpected exception:`, actual), formatWithLabel('Expected message to equal:', expectations.message), ], @@ -228,12 +211,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s } if (expectations.message instanceof RegExp && !expectations.message.test(actual.message)) { - throw new AssertionError({ + throw new AssertionError(message, { assertion, - message, - savedError, - actualStack, - values: [ + assertionStack, + cause: actual, + formattedDetails: [ formatWithLabel(`${prefix} unexpected exception:`, actual), formatWithLabel('Expected message to match:', expectations.message), ], @@ -241,12 +223,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s } if (typeof expectations.message === 'function' && expectations.message(actual.message) === false) { - throw new AssertionError({ + throw new AssertionError(message, { assertion, - message, - savedError, - actualStack, - values: [ + assertionStack, + cause: actual, + formattedDetails: [ formatWithLabel(`${prefix} unexpected exception:`, actual), formatWithLabel('Expected message to return true:', expectations.message), ], @@ -254,12 +235,11 @@ function assertExpectations({assertion, actual, expectations, message, prefix, s } if (expectations.code !== undefined && actual.code !== expectations.code) { - throw new AssertionError({ + throw new AssertionError(message, { assertion, - message, - savedError, - actualStack, - values: [ + assertionStack, + cause: actual, + formattedDetails: [ formatWithLabel(`${prefix} unexpected exception:`, actual), formatWithLabel('Expected code to equal:', expectations.code), ], @@ -282,8 +262,8 @@ export class Assertions { return assertionFn; }; - const checkMessage = (assertion, message) => { - const result = checkAssertionMessage(assertion, message); + const checkMessage = (message, assertion) => { + const result = checkAssertionMessage(message, assertion); if (result === true) { return true; } @@ -298,20 +278,19 @@ export class Assertions { }); this.fail = withSkip(message => { - if (!checkMessage('fail', message)) { + if (!checkMessage(message, 't.fail()')) { return false; } - fail(new AssertionError({ - assertion: 'fail', - message: message ?? 'Test failed via `t.fail()`', + fail(new AssertionError(message ?? 'Test failed via `t.fail()`', { + assertion: 't.fail()', })); return false; }); this.is = withSkip((actual, expected, message) => { - if (!checkMessage('is', message)) { + if (!checkMessage(message, 't.is()')) { return false; } @@ -325,18 +304,14 @@ export class Assertions { const expectedDescriptor = result.expected ?? concordance.describe(expected, concordanceOptions); if (result.pass) { - fail(new AssertionError({ - assertion: 'is', - message, - raw: {actual, expected}, - values: [formatDescriptorWithLabel('Values are deeply equal to each other, but they are not the same:', actualDescriptor)], + fail(new AssertionError(message, { + assertion: 't.is()', + formattedDetails: [formatDescriptorWithLabel('Values are deeply equal to each other, but they are not the same:', actualDescriptor)], })); } else { - fail(new AssertionError({ - assertion: 'is', - message, - raw: {actual, expected}, - values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)], + fail(new AssertionError(message, { + assertion: 't.is()', + formattedDetails: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)], })); } @@ -344,16 +319,14 @@ export class Assertions { }); this.not = withSkip((actual, expected, message) => { - if (!checkMessage('not', message)) { + if (!checkMessage(message, 't.not()')) { return false; } if (Object.is(actual, expected)) { - fail(new AssertionError({ - assertion: 'not', - message, - raw: {actual, expected}, - values: [formatWithLabel('Value is the same as:', actual)], + fail(new AssertionError(message, { + assertion: 't.not()', + formattedDetails: [formatWithLabel('Value is the same as:', actual)], })); return false; } @@ -363,7 +336,7 @@ export class Assertions { }); this.deepEqual = withSkip((actual, expected, message) => { - if (!checkMessage('deepEqual', message)) { + if (!checkMessage(message, 't.deepEqual()')) { return false; } @@ -375,28 +348,24 @@ export class Assertions { const actualDescriptor = result.actual ?? concordance.describe(actual, concordanceOptions); const expectedDescriptor = result.expected ?? concordance.describe(expected, concordanceOptions); - fail(new AssertionError({ - assertion: 'deepEqual', - message, - raw: {actual, expected}, - values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)], + fail(new AssertionError(message, { + assertion: 't.deepEqual()', + formattedDetails: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)], })); return false; }); this.notDeepEqual = withSkip((actual, expected, message) => { - if (!checkMessage('notDeepEqual', message)) { + if (!checkMessage(message, 't.notDeepEqual()')) { return false; } const result = concordance.compare(actual, expected, concordanceOptions); if (result.pass) { const actualDescriptor = result.actual ?? concordance.describe(actual, concordanceOptions); - fail(new AssertionError({ - assertion: 'notDeepEqual', - message, - raw: {actual, expected}, - values: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)], + fail(new AssertionError(message, { + assertion: 't.notDeepEqual()', + formattedDetails: [formatDescriptorWithLabel('Value is deeply equal:', actualDescriptor)], })); return false; } @@ -406,16 +375,14 @@ export class Assertions { }); this.like = withSkip((actual, selector, message) => { - if (!checkMessage('like', message)) { + if (!checkMessage(message, 't.like()')) { return false; } if (!isLikeSelector(selector)) { - fail(new AssertionError({ - assertion: 'like', - improperUsage: true, - message: '`t.like()` selector must be a non-empty object', - values: [formatWithLabel('Called with:', selector)], + fail(new AssertionError('`t.like()` selector must be a non-empty object', { + assertion: 't.like()', + formattedDetails: [formatWithLabel('Called with:', selector)], })); return false; } @@ -425,11 +392,9 @@ export class Assertions { comparable = selectComparable(actual, selector); } catch (error) { if (error === CIRCULAR_SELECTOR) { - fail(new AssertionError({ - assertion: 'like', - improperUsage: true, - message: '`t.like()` selector must not contain circular references', - values: [formatWithLabel('Called with:', selector)], + fail(new AssertionError('`t.like()` selector must not contain circular references', { + assertion: 't.like()', + formattedDetails: [formatWithLabel('Called with:', selector)], })); return false; } @@ -445,10 +410,9 @@ export class Assertions { const actualDescriptor = result.actual ?? concordance.describe(comparable, concordanceOptions); const expectedDescriptor = result.expected ?? concordance.describe(selector, concordanceOptions); - fail(new AssertionError({ - assertion: 'like', - message, - values: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)], + fail(new AssertionError(message, { + assertion: 't.like()', + formattedDetails: [formatDescriptorDiff(actualDescriptor, expectedDescriptor)], })); return false; @@ -460,22 +424,21 @@ export class Assertions { // to the function. let [fn, expectations, message] = args; - if (!checkMessage('throws', message)) { + if (!checkMessage(message, 't.throws()')) { return; } if (typeof fn !== 'function') { - fail(new AssertionError({ - assertion: 'throws', - improperUsage: true, - message: '`t.throws()` must be called with a function', - values: [formatWithLabel('Called with:', fn)], + fail(new AssertionError('`t.throws()` must be called with a function', { + assertion: 't.throws()', + improperUsage: {assertion: 'throws'}, + formattedDetails: [formatWithLabel('Called with:', fn)], })); return; } try { - expectations = validateExpectations('throws', expectations, args.length, experiments); + expectations = validateExpectations('t.throws()', expectations, args.length, experiments); } catch (error) { fail(error); return; @@ -488,10 +451,9 @@ export class Assertions { if (isPromise(retval)) { // Here isPromise() checks if something is "promise like". Cast to an actual promise. Promise.resolve(retval).catch(noop); - fail(new AssertionError({ - assertion: 'throws', - message, - values: [formatWithLabel('Function returned a promise. Use `t.throwsAsync()` instead:', retval)], + fail(new AssertionError(message, { + assertion: 't.throws()', + formattedDetails: [formatWithLabel('Function returned a promise. Use `t.throwsAsync()` instead:', retval)], })); return; } @@ -500,17 +462,16 @@ export class Assertions { } if (!actual) { - fail(new AssertionError({ - assertion: 'throws', - message, - values: [formatWithLabel('Function returned:', retval)], + fail(new AssertionError(message, { + assertion: 't.throws()', + formattedDetails: [formatWithLabel('Function returned:', retval)], })); return; } try { assertExpectations({ - assertion: 'throws', + assertion: 't.throws()', actual, expectations, message, @@ -526,46 +487,43 @@ export class Assertions { this.throwsAsync = withSkip(async (...args) => { let [thrower, expectations, message] = args; - if (!checkMessage('throwsAsync', message)) { + if (!checkMessage(message, 't.throwsAsync()')) { return; } if (typeof thrower !== 'function' && !isPromise(thrower)) { - fail(new AssertionError({ - assertion: 'throwsAsync', - improperUsage: true, - message: '`t.throwsAsync()` must be called with a function or promise', - values: [formatWithLabel('Called with:', thrower)], + fail(new AssertionError('`t.throwsAsync()` must be called with a function or promise', { + assertion: 't.throwsAsync()', + formattedDetails: [formatWithLabel('Called with:', thrower)], })); return; } try { - expectations = validateExpectations('throwsAsync', expectations, args.length, experiments); + expectations = validateExpectations('t.throwsAsync()', expectations, args.length, experiments); } catch (error) { fail(error); return; } const handlePromise = async (promise, wasReturned) => { - // Create an error object to record the stack before it gets lost in the promise chain. - const savedError = getErrorWithLongStackTrace(); + // Record the stack before it gets lost in the promise chain. + const assertionStack = getAssertionStack(); // Handle "promise like" objects by casting to a real Promise. const intermediate = Promise.resolve(promise).then(value => { - throw new AssertionError({ - assertion: 'throwsAsync', - message, - savedError, - values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} resolved with:`, value)], + throw new AssertionError(message, { + assertion: 't.throwsAsync()', + assertionStack, + formattedDetails: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} resolved with:`, value)], }); }, error => { assertExpectations({ - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', actual: error, expectations, message, prefix: `${wasReturned ? 'Returned promise' : 'Promise'} rejected with`, - savedError, + assertionStack, }); return error; }); @@ -591,11 +549,10 @@ export class Assertions { } if (actual) { - fail(new AssertionError({ - assertion: 'throwsAsync', - message, - actualStack: actual.stack, - values: [formatWithLabel('Function threw synchronously. Use `t.throws()` instead:', actual)], + fail(new AssertionError(message, { + assertion: 't.throwsAsync()', + cause: actual, + formattedDetails: [formatWithLabel('Function threw synchronously. Use `t.throws()` instead:', actual)], })); return; } @@ -604,24 +561,22 @@ export class Assertions { return handlePromise(retval, true); } - fail(new AssertionError({ - assertion: 'throwsAsync', - message, - values: [formatWithLabel('Function returned:', retval)], + fail(new AssertionError(message, { + assertion: 't.throwsAsync()', + formattedDetails: [formatWithLabel('Function returned:', retval)], })); }); this.notThrows = withSkip((fn, message) => { - if (!checkMessage('notThrows', message)) { + if (!checkMessage(message, 't.notThrows()')) { return; } if (typeof fn !== 'function') { - fail(new AssertionError({ - assertion: 'notThrows', - improperUsage: true, - message: '`t.notThrows()` must be called with a function', - values: [formatWithLabel('Called with:', fn)], + fail(new AssertionError('`t.notThrows()` must be called with a function', { + assertion: 't.notThrows()', + improperUsage: {assertion: 'notThrows'}, + formattedDetails: [formatWithLabel('Called with:', fn)], })); return; } @@ -629,11 +584,10 @@ export class Assertions { try { fn(); } catch (error) { - fail(new AssertionError({ - assertion: 'notThrows', - message, - actualStack: error.stack, - values: [formatWithLabel('Function threw:', error)], + fail(new AssertionError(message, { + assertion: 't.notThrows()', + cause: error, + formattedDetails: [formatWithLabel('Function threw:', error)], })); return; } @@ -642,30 +596,27 @@ export class Assertions { }); this.notThrowsAsync = withSkip((nonThrower, message) => { - if (!checkMessage('notThrowsAsync', message)) { + if (!checkMessage(message, 't.notThrowsAsync()')) { return Promise.resolve(); } if (typeof nonThrower !== 'function' && !isPromise(nonThrower)) { - fail(new AssertionError({ - assertion: 'notThrowsAsync', - improperUsage: true, - message: '`t.notThrowsAsync()` must be called with a function or promise', - values: [formatWithLabel('Called with:', nonThrower)], + fail(new AssertionError('`t.notThrowsAsync()` must be called with a function or promise', { + assertion: 't.notThrowsAsync()', + formattedDetails: [formatWithLabel('Called with:', nonThrower)], })); return Promise.resolve(); } const handlePromise = async (promise, wasReturned) => { // Create an error object to record the stack before it gets lost in the promise chain. - const savedError = getErrorWithLongStackTrace(); + const assertionStack = getAssertionStack(); // Handle "promise like" objects by casting to a real Promise. const intermediate = Promise.resolve(promise).then(noop, error => { - throw new AssertionError({ - assertion: 'notThrowsAsync', - message, - savedError, - values: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} rejected with:`, error)], + throw new AssertionError(message, { + assertion: 't.notThrowsAsync()', + assertionStack, + formattedDetails: [formatWithLabel(`${wasReturned ? 'Returned promise' : 'Promise'} rejected with:`, error)], }); }); pending(intermediate); @@ -685,20 +636,18 @@ export class Assertions { try { retval = nonThrower(); } catch (error) { - fail(new AssertionError({ - assertion: 'notThrowsAsync', - message, - actualStack: error.stack, - values: [formatWithLabel('Function threw:', error)], + fail(new AssertionError(message, { + assertion: 't.notThrowsAsync()', + cause: error, + formattedDetails: [formatWithLabel('Function threw:', error)], })); return Promise.resolve(); } if (!isPromise(retval)) { - fail(new AssertionError({ - assertion: 'notThrowsAsync', - message, - values: [formatWithLabel('Function did not return a promise. Use `t.notThrows()` instead:', retval)], + fail(new AssertionError(message, { + assertion: 't.notThrowsAsync()', + formattedDetails: [formatWithLabel('Function did not return a promise. Use `t.notThrows()` instead:', retval)], })); return Promise.resolve(); } @@ -708,34 +657,28 @@ export class Assertions { this.snapshot = withSkip((expected, message) => { if (disableSnapshots) { - fail(new AssertionError({ - assertion: 'snapshot', - message: '`t.snapshot()` can only be used in tests', - improperUsage: true, + fail(new AssertionError('`t.snapshot()` can only be used in tests', { + assertion: 't.snapshot()', })); return false; } if (message?.id !== undefined) { - fail(new AssertionError({ - assertion: 'snapshot', - message: 'AVA 4 no longer supports snapshot IDs', - improperUsage: true, - values: [formatWithLabel('Called with id:', message.id)], + fail(new AssertionError('Since AVA 4, snapshot IDs are no longer supported', { + assertion: 't.snapshot()', + formattedDetails: [formatWithLabel('Called with id:', message.id)], })); return false; } - if (!checkMessage('snapshot', message)) { + if (!checkMessage(message, 't.snapshot()')) { return false; } if (message === '') { - fail(new AssertionError({ - assertion: 'snapshot', - improperUsage: true, - message: 'The snapshot assertion message must be a non-empty string', - values: [formatWithLabel('Called with:', message)], + fail(new AssertionError('The snapshot assertion message must be a non-empty string', { + assertion: 't.snapshot()', + formattedDetails: [formatWithLabel('Called with:', message)], })); return false; } @@ -748,15 +691,14 @@ export class Assertions { throw error; } - const improperUsage = {name: error.name, snapPath: error.snapPath}; + const improperUsage = {assertion: 'snapshot', name: error.name, snapPath: error.snapPath}; if (error instanceof VersionMismatchError) { improperUsage.snapVersion = error.snapVersion; improperUsage.expectedVersion = error.expectedVersion; } - fail(new AssertionError({ - assertion: 'snapshot', - message: message ?? 'Could not compare snapshot', + fail(new AssertionError(message ?? 'Could not compare snapshot', { + asssertion: 't.snapshot()', improperUsage, })); return false; @@ -768,16 +710,14 @@ export class Assertions { } if (result.actual) { - fail(new AssertionError({ - assertion: 'snapshot', - message: message ?? 'Did not match snapshot', - values: [formatDescriptorDiff(result.actual, result.expected, {invert: true})], + fail(new AssertionError(message ?? 'Did not match snapshot', { + assertion: 't.snapshot()', + formattedDetails: [formatDescriptorDiff(result.actual, result.expected, {invert: true})], })); } else { // This can only occur in CI environments. - fail(new AssertionError({ - assertion: 'snapshot', - message: message ?? 'No snapshot available — new snapshots are not created in CI environments', + fail(new AssertionError(message ?? 'No snapshot available — new snapshots are not created in CI environments', { + assertion: 't.snapshot()', })); } @@ -785,7 +725,7 @@ export class Assertions { }); this.truthy = withSkip((actual, message) => { - if (!checkMessage('truthy', message)) { + if (!checkMessage(message, 't.truthy()')) { return false; } @@ -794,26 +734,22 @@ export class Assertions { return true; } - fail(new AssertionError({ - assertion: 'truthy', - message, - operator: '!!', - values: [formatWithLabel('Value is not truthy:', actual)], + fail(new AssertionError(message, { + assertion: 't.truthy()', + formattedDetails: [formatWithLabel('Value is not truthy:', actual)], })); return false; }); this.falsy = withSkip((actual, message) => { - if (!checkMessage('falsy', message)) { + if (!checkMessage(message, 't.falsy()')) { return false; } if (actual) { - fail(new AssertionError({ - assertion: 'falsy', - message, - operator: '!', - values: [formatWithLabel('Value is not falsy:', actual)], + fail(new AssertionError(message, { + assertion: 't.falsy()', + formattedDetails: [formatWithLabel('Value is not falsy:', actual)], })); return false; } @@ -823,7 +759,7 @@ export class Assertions { }); this.true = withSkip((actual, message) => { - if (!checkMessage('true', message)) { + if (!checkMessage(message, 't.true()')) { return false; } @@ -832,16 +768,15 @@ export class Assertions { return true; } - fail(new AssertionError({ - assertion: 'true', - message, - values: [formatWithLabel('Value is not `true`:', actual)], + fail(new AssertionError(message, { + assertion: 't.true()', + formattedDetails: [formatWithLabel('Value is not `true`:', actual)], })); return false; }); this.false = withSkip((actual, message) => { - if (!checkMessage('false', message)) { + if (!checkMessage(message, 't.false()')) { return false; } @@ -850,44 +785,38 @@ export class Assertions { return true; } - fail(new AssertionError({ - assertion: 'false', - message, - values: [formatWithLabel('Value is not `false`:', actual)], + fail(new AssertionError(message, { + assertion: 't.false()', + formattedDetails: [formatWithLabel('Value is not `false`:', actual)], })); return false; }); this.regex = withSkip((string, regex, message) => { - if (!checkMessage('regex', message)) { + if (!checkMessage(message, 't.regex()')) { return false; } if (typeof string !== 'string') { - fail(new AssertionError({ - assertion: 'regex', - improperUsage: true, - message: '`t.regex()` must be called with a string', - values: [formatWithLabel('Called with:', string)], + fail(new AssertionError('`t.regex()` must be called with a string', { + assertion: 't.regex()', + formattedDetails: [formatWithLabel('Called with:', string)], })); return false; } if (!(regex instanceof RegExp)) { - fail(new AssertionError({ - assertion: 'regex', - improperUsage: true, - message: '`t.regex()` must be called with a regular expression', - values: [formatWithLabel('Called with:', regex)], + fail(new AssertionError('`t.regex()` must be called with a regular expression', { + assertion: 't.regex()', + formattedDetails: [formatWithLabel('Called with:', regex)], })); return false; } if (!regex.test(string)) { - fail(new AssertionError({ - assertion: 'regex', - message, - values: [ + fail(new AssertionError(message, { + assertion: 't.regex()', + formattedDetails: [ formatWithLabel('Value must match expression:', string), formatWithLabel('Regular expression:', regex), ], @@ -900,35 +829,30 @@ export class Assertions { }); this.notRegex = withSkip((string, regex, message) => { - if (!checkMessage('notRegex', message)) { + if (!checkMessage(message, 't.notRegex()')) { return false; } if (typeof string !== 'string') { - fail(new AssertionError({ - assertion: 'notRegex', - improperUsage: true, - message: '`t.notRegex()` must be called with a string', - values: [formatWithLabel('Called with:', string)], + fail(new AssertionError('`t.notRegex()` must be called with a string', { + assertion: 't.notRegex()', + formattedDetails: [formatWithLabel('Called with:', string)], })); return false; } if (!(regex instanceof RegExp)) { - fail(new AssertionError({ - assertion: 'notRegex', - improperUsage: true, - message: '`t.notRegex()` must be called with a regular expression', - values: [formatWithLabel('Called with:', regex)], + fail(new AssertionError('`t.notRegex()` must be called with a regular expression', { + assertion: 't.notRegex()', + formattedDetails: [formatWithLabel('Called with:', regex)], })); return false; } if (regex.test(string)) { - fail(new AssertionError({ - assertion: 'notRegex', - message, - values: [ + fail(new AssertionError(message, { + assertion: 't.notRegex()', + formattedDetails: [ formatWithLabel('Value must not match expression:', string), formatWithLabel('Regular expression:', regex), ], @@ -941,16 +865,14 @@ export class Assertions { }); this.assert = withSkip((actual, message) => { - if (!checkMessage('assert', message)) { + if (!checkMessage(message, 't.assert()')) { return false; } if (!actual) { - fail(new AssertionError({ - assertion: 'assert', - message, - operator: '!!', - values: [formatWithLabel('Value is not truthy:', actual)], + fail(new AssertionError(message, { + assertion: 't.assert()', + formattedDetails: [formatWithLabel('Value is not truthy:', actual)], })); return false; } diff --git a/lib/fork.js b/lib/fork.js index 554ec343a..5ddf294e8 100644 --- a/lib/fork.js +++ b/lib/fork.js @@ -134,7 +134,7 @@ export default function loadFork(file, options, execArgv = process.execArgv) { }); worker.on('error', error => { - emitStateChange({type: 'worker-failed', err: serializeError('Worker error', false, tagWorkerError(error), file)}); + emitStateChange({type: 'worker-failed', err: serializeError(tagWorkerError(error))}); finish(); }); diff --git a/lib/plugin-support/shared-workers.js b/lib/plugin-support/shared-workers.js index 6b92ff4c5..827ea52cb 100644 --- a/lib/plugin-support/shared-workers.js +++ b/lib/plugin-support/shared-workers.js @@ -97,7 +97,7 @@ export async function observeWorkerProcess(fork, runStatus) { launched.statePromises.error.then(error => { launched.worker.off('message', handleWorkerMessage); removeAllInstances(); - runStatus.emitStateChange({type: 'shared-worker-error', err: serializeError('Shared worker error', true, error)}); + runStatus.emitStateChange({type: 'shared-worker-error', err: serializeError(error)}); signalError(); }); diff --git a/lib/reporters/beautify-stack.js b/lib/reporters/beautify-stack.js index cb65eedb5..423400dc2 100644 --- a/lib/reporters/beautify-stack.js +++ b/lib/reporters/beautify-stack.js @@ -4,7 +4,6 @@ const stackUtils = new StackUtils({ ignoredPackages: [ '@ava/typescript', 'ava', - 'nyc', ], internals: [ // AVA internals, which ignoredPackages don't ignore when we run our own unit tests. diff --git a/lib/reporters/default.js b/lib/reporters/default.js index 80f0263d8..acf34e2bc 100644 --- a/lib/reporters/default.js +++ b/lib/reporters/default.js @@ -208,9 +208,7 @@ export default class Reporter { this.write(colors.error(`${figures.cross} Internal error`)); } - this.lineWriter.writeLine(colors.stack(event.err.summary)); - this.lineWriter.writeLine(colors.errorStack(event.err.stack)); - this.lineWriter.writeLine(); + this.writeSerializedError(event.err); this.lineWriter.writeLine(); break; @@ -221,8 +219,7 @@ export default class Reporter { this.write(colors.information(`${figures.warning} Could not parse ${this.relativeFile(event.testFile)} for line number selection`)); this.lineWriter.writeLine(); - this.lineWriter.writeLine(colors.errorStack(event.err.stack)); - this.lineWriter.writeLine(); + this.writeSerializedError(event.err); break; } @@ -268,7 +265,7 @@ export default class Reporter { this.lineWriter.ensureEmptyLine(); this.lineWriter.writeLine(colors.error(`${figures.cross} Error in shared worker`)); this.lineWriter.writeLine(); - this.writeErr(event); + this.writeSerializedError(event.err); break; } @@ -279,7 +276,7 @@ export default class Reporter { this.lineWriter.ensureEmptyLine(); this.lineWriter.writeLine(colors.title(`Uncaught exception in ${this.relativeFile(event.testFile)}`)); this.lineWriter.writeLine(); - this.writeErr(event); + this.writeSerializedError(event.err); break; } @@ -290,7 +287,7 @@ export default class Reporter { this.lineWriter.ensureEmptyLine(); this.lineWriter.writeLine(colors.title(`Unhandled rejection in ${this.relativeFile(event.testFile)}`)); this.lineWriter.writeLine(); - this.writeErr(event); + this.writeSerializedError(event.err); break; } @@ -304,7 +301,7 @@ export default class Reporter { if (event.err) { this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(event.testFile)} exited due to an error:`)); this.lineWriter.writeLine(); - this.writeErr(event); + this.writeSerializedError(event.err); } else if (event.nonZeroExitCode) { this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(event.testFile)} exited with a non-zero exit code: ${event.nonZeroExitCode}`)); } else { @@ -427,16 +424,32 @@ export default class Reporter { this.lineWriter.writeLine(string); } - writeErr(event) { - if (event.err.name === 'TSError' && event.err.object?.diagnosticText) { - this.lineWriter.writeLine(colors.errorStack(event.err.object.diagnosticText)); + writeSerializedError(error) { // eslint-disable-line complexity + if (error.type === 'aggregate') { + for (const error_ of error.errors) { + this.writeSerializedError(error_); + } + + return; + } + + if (error.type === 'unknown') { + this.lineWriter.writeLine(error.formattedError); this.lineWriter.writeLine(); return; } - if (event.err.source) { - this.lineWriter.writeLine(colors.errorSource(`${this.relativeFile(event.err.source.file)}:${event.err.source.line}`)); - const excerpt = codeExcerpt(event.err.source, {maxWidth: this.reportStream.columns - 2}); + if (error.type === 'native' && error.name === 'TSError' && error.originalError.diagnosticText) { + this.lineWriter.writeLine(colors.errorStack(error.originalError.diagnosticText)); + this.lineWriter.writeLine(); + return; + } + + const hasSource = error.source !== null; + if (hasSource) { + const {source} = error; + this.lineWriter.writeLine(colors.errorSource(`${this.relativeFile(source.file)}:${source.line}`)); + const excerpt = codeExcerpt(source, {maxWidth: this.reportStream.columns - 2}); if (excerpt) { this.lineWriter.writeLine(); this.lineWriter.writeLine(excerpt); @@ -444,10 +457,47 @@ export default class Reporter { } } - if (event.err.avaAssertionError) { - const result = formatSerializedError(event.err); + let summary = ''; + let printStack = true; + if (error.type === 'native') { + const lines = error.stack.split('\n'); + + // SyntaxError stacks may begin with the offending code. Write all stack + // lines up to and including one that begins with SyntaxError. + if (error.name === 'SyntaxError') { + for (const line of lines) { + summary += line + '\n'; + if (line.startsWith('SyntaxError')) { + break; + } + } + + printStack = summary === ''; + } else { + // Handle multi-line error messages. + for (let index = 0; index < lines.length; index++) { + if (/^\s+at/.test(lines[index])) { + break; + } + + const next = index + 1; + const end = next === lines.length || /^\s+at/.test(lines[next]); + summary += end ? lines[index] : lines[index] + '\n'; + } + } + + if (summary !== '') { + this.lineWriter.writeLine(summary.trim()); + this.lineWriter.writeLine(); + } + } + + if (error.type === 'ava') { + const {formattedDetails, improperUsage, message} = error; + + const result = formatSerializedError(formattedDetails, message); if (result.printMessage) { - this.lineWriter.writeLine(event.err.message); + this.lineWriter.writeLine(message); this.lineWriter.writeLine(); } @@ -456,33 +506,29 @@ export default class Reporter { this.lineWriter.writeLine(); } - const message = improperUsageMessage(event.err); - if (message) { - this.lineWriter.writeLine(message); + const usageMessage = improperUsageMessage(improperUsage); + if (usageMessage) { + this.lineWriter.writeLine(usageMessage); this.lineWriter.writeLine(); } - } else if (event.err.nonErrorObject) { - this.lineWriter.writeLine(event.err.formatted); - this.lineWriter.writeLine(); - } else { - this.lineWriter.writeLine(event.err.summary); - this.lineWriter.writeLine(); } - const formatted = this.formatErrorStack(event.err); - if (formatted.length > 0) { - this.lineWriter.writeLine(formatted.join('\n')); - this.lineWriter.writeLine(); + if (printStack) { + const formattedStack = this.formatErrorStack(error.stack, hasSource); + if (formattedStack.length > 0) { + this.lineWriter.writeLine(formattedStack.join('\n')); + this.lineWriter.writeLine(); + } } } - formatErrorStack(error) { - if (!error.stack) { + formatErrorStack(stack, hasSource) { + if (stack === '') { return []; } - if (error.shouldBeautifyStack) { - return beautifyStack(error.stack).map(line => { + if (hasSource) { + return beautifyStack(stack).map(line => { if (nodeInternals.some(internal => internal.test(line))) { return colors.errorStackInternal(`${figures.pointerSmall} ${line}`); } @@ -491,7 +537,7 @@ export default class Reporter { }); } - return [error.stack]; + return [colors.errorStack(stack)]; } writeLogs(event, surroundLines) { @@ -551,7 +597,7 @@ export default class Reporter { this.lineWriter.writeLine(); } - this.writeErr(event); + this.writeSerializedError(event.err); } endRun() {// eslint-disable-line complexity diff --git a/lib/reporters/format-serialized-error.js b/lib/reporters/format-serialized-error.js index 617f65857..83c033a4f 100644 --- a/lib/reporters/format-serialized-error.js +++ b/lib/reporters/format-serialized-error.js @@ -1,14 +1,14 @@ -export default function formatSerializedError(error) { - const printMessage = error.values.length === 0 - ? Boolean(error.message) - : !error.values[0].label.startsWith(error.message); +export default function formatSerializedError(formattedDetails, message) { + const printMessage = formattedDetails.length === 0 + ? Boolean(message) + : !formattedDetails[0].label.startsWith(message); - if (error.values.length === 0) { + if (formattedDetails.length === 0) { return {formatted: null, printMessage}; } let formatted = ''; - for (const value of error.values) { + for (const value of formattedDetails) { formatted += `${value.label}\n\n${value.formatted}\n\n`; } diff --git a/lib/reporters/improper-usage-messages.js b/lib/reporters/improper-usage-messages.js index 7c66e3ca9..b5b9f6ce2 100644 --- a/lib/reporters/improper-usage-messages.js +++ b/lib/reporters/improper-usage-messages.js @@ -1,12 +1,12 @@ import {chalk} from '../chalk.js'; import pkg from '../pkg.cjs'; -export default function buildMessage(error) { - if (!error.improperUsage) { +export default function buildMessage(improperUsage) { + if (!improperUsage) { return null; } - const {assertion} = error; + const {assertion} = improperUsage; if (assertion === 'throws' || assertion === 'notThrows') { return `Try wrapping the first argument to \`t.${assertion}()\` in a function: @@ -18,7 +18,7 @@ Visit the following URL for more details: } if (assertion === 'snapshot') { - const {name, snapPath} = error.improperUsage; + const {name, snapPath} = improperUsage; if (name === 'ChecksumError' || name === 'InvalidSnapshotError') { return `The snapshot file is corrupted. @@ -37,7 +37,7 @@ Please run AVA again with the ${chalk.cyan('--update-snapshots')} flag to upgrad } if (name === 'VersionMismatchError') { - const {snapVersion, expectedVersion} = error.improperUsage; + const {snapVersion, expectedVersion} = improperUsage; const upgradeMessage = snapVersion < expectedVersion ? `Please run AVA again with the ${chalk.cyan('--update-snapshots')} flag to upgrade.` : 'You should upgrade AVA.'; diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js index 27e14a77d..f76ac47c1 100644 --- a/lib/reporters/tap.js +++ b/lib/reporters/tap.js @@ -6,43 +6,42 @@ import plur from 'plur'; import stripAnsi from 'strip-ansi'; import * as supertap from 'supertap'; -import beautifyStack from './beautify-stack.js'; import prefixTitle from './prefix-title.js'; -function dumpError(error) { - const object = {...error.object}; - if (error.name) { - object.name = error.name; +function dumpError({ + assertion, + formattedDetails, + formattedError, + name, + originalError, // A structured clone, so some details are missing. + stack, + type, +}, sanitizeStackOutput) { + if (type === 'unknown') { + return { + message: 'Non-native error', + formatted: stripAnsi(formattedError), + }; } - if (error.message) { - object.message = error.message; - } - - if (error.avaAssertionError) { - if (error.assertion) { - object.assertion = error.assertion; - } + originalError.name = name; // Restore the original name. - if (error.operator) { - object.operator = error.operator; + if (type === 'ava') { + if (assertion) { + originalError.assertion = assertion; } - if (error.values.length > 0) { - object.values = Object.fromEntries(error.values.map(({label, formatted}) => [stripAnsi(label), stripAnsi(formatted)])); + if (formattedDetails.length > 0) { + originalError.details = Object.fromEntries(formattedDetails.map(({label, formatted}) => [ + stripAnsi(label), + stripAnsi(formatted), + ])); } } - if (error.nonErrorObject) { - object.message = 'Non-error object'; - object.formatted = stripAnsi(error.formatted); - } - - if (error.stack) { - object.at = error.shouldBeautifyStack ? beautifyStack(error.stack).join('\n') : error.stack; - } + originalError.stack = sanitizeStackOutput?.(stack || originalError.stack) ?? (stack || originalError.stack); - return object; + return originalError; } export default class TapReporter { @@ -52,6 +51,7 @@ export default class TapReporter { this.extensions = options.extensions; this.stdStream = options.stdStream; this.reportStream = options.reportStream; + this.sanitizeStackOutput = options.sanitizeStackOutput; this.crashCount = 0; this.filesWithMissingAvaImports = new Set(); @@ -98,7 +98,7 @@ export default class TapReporter { writeTest(evt, flags) { this.reportStream.write(supertap.test(this.prefixTitle(evt.testFile, evt.title), { comment: evt.logs, - error: evt.err ? dumpError(evt.err) : null, + error: evt.err ? dumpError(evt.err, this.sanitizeStackOutput) : null, index: ++this.i, passed: flags.passed, skip: flags.skip, @@ -108,9 +108,9 @@ export default class TapReporter { writeCrash(evt, title) { this.crashCount++; - this.reportStream.write(supertap.test(title ?? evt.err.summary ?? evt.type, { + this.reportStream.write(supertap.test(title ?? evt.err.stack?.split('\n')[0].trim() ?? evt.err.message ?? evt.type, { comment: evt.logs, - error: evt.err ? dumpError(evt.err) : null, + error: evt.err ? dumpError(evt.err, this.sanitizeStackOutput) : null, index: ++this.i, passed: false, skip: false, diff --git a/lib/runner.js b/lib/runner.js index 0ef356f7f..d81c11ad3 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -319,7 +319,7 @@ export default class Runner extends Emittery { this.emit('stateChange', { type: 'hook-failed', title: result.title, - err: serializeError('Hook failure', true, result.error), + err: serializeError(result.error, {testFile: this.file}), duration: result.duration, logs: result.logs, }); @@ -387,7 +387,7 @@ export default class Runner extends Emittery { this.emit('stateChange', { type: 'test-failed', title: result.title, - err: serializeError('Test failure', true, result.error, this.file), + err: serializeError(result.error, {testFile: this.file}), duration: result.duration, knownFailing: result.metadata.failing, logs: result.logs, diff --git a/lib/serialize-error.js b/lib/serialize-error.js index f89cee06e..ca47a6350 100644 --- a/lib/serialize-error.js +++ b/lib/serialize-error.js @@ -1,9 +1,7 @@ import path from 'node:path'; -import process from 'node:process'; -import {fileURLToPath, pathToFileURL} from 'node:url'; +import {pathToFileURL} from 'node:url'; import {isNativeError} from 'node:util/types'; -import cleanYamlObject from 'clean-yaml-object'; import concordance from 'concordance'; import StackUtils from 'stack-utils'; @@ -14,10 +12,6 @@ function isAvaAssertionError(source) { return source instanceof AssertionError; } -function filter(propertyName, isRoot) { - return !isRoot || (propertyName !== 'message' && propertyName !== 'name' && propertyName !== 'stack'); -} - function normalizeFile(file, ...base) { return file.startsWith('file://') ? file : pathToFileURL(path.resolve(...base, file)).toString(); } @@ -45,106 +39,6 @@ function extractSource(stack, testFile) { return null; } -function buildSource(source) { - if (!source) { - return null; - } - - // Assume the CWD is the project directory. This holds since this function - // is only called in test workers, which are created with their working - // directory set to the project directory. - const projectDir = process.cwd(); - - const file = normalizeFile(source.file.trim(), projectDir); - const rel = path.relative(projectDir, fileURLToPath(file)); - - const [segment] = rel.split(path.sep); - const isWithinProject = segment !== '..' && (process.platform !== 'win32' || !segment.includes(':')); - const isDependency = isWithinProject && path.dirname(rel).split(path.sep).includes('node_modules'); - - return { - isDependency, - isWithinProject, - file, - line: source.line, - }; -} - -function trySerializeError(error, shouldBeautifyStack, testFile) { - const stack = error.savedError ? error.savedError.stack : error.stack; - - const retval = { - avaAssertionError: isAvaAssertionError(error), - nonErrorObject: false, - source: extractSource(stack, testFile), - stack, - shouldBeautifyStack, - }; - - if (error.actualStack) { - retval.stack = error.actualStack; - } - - if (retval.avaAssertionError) { - retval.improperUsage = error.improperUsage; - retval.message = error.message; - retval.name = error.name; - retval.values = error.values; - - if (error.fixedSource) { - const source = buildSource(error.fixedSource); - if (source) { - retval.source = source; - } - } - - if (error.assertion) { - retval.assertion = error.assertion; - } - - if (error.operator) { - retval.operator = error.operator; - } - } else { - retval.object = cleanYamlObject(error, filter); // Cleanly copy non-standard properties - if (typeof error.message === 'string') { - retval.message = error.message; - } - - if (typeof error.name === 'string') { - retval.name = error.name; - } - } - - if (typeof error.stack === 'string') { - const lines = error.stack.split('\n'); - if (error.name === 'SyntaxError' && !lines[0].startsWith('SyntaxError')) { - retval.summary = ''; - for (const line of lines) { - retval.summary += line + '\n'; - if (line.startsWith('SyntaxError')) { - break; - } - } - - retval.summary = retval.summary.trim(); - } else { - retval.summary = ''; - for (let index = 0; index < lines.length; index++) { - if (lines[index].startsWith(' at')) { - break; - } - - const next = index + 1; - const end = next === lines.length || lines[next].startsWith(' at'); - retval.summary += end ? lines[index] : lines[index] + '\n'; - } - } - } - - return retval; -} - const workerErrors = new WeakSet(); export function tagWorkerError(error) { // Track worker errors, which aren't native due to https://github.com/nodejs/node/issues/48716. @@ -158,26 +52,47 @@ export function tagWorkerError(error) { const isWorkerError = error => workerErrors.has(error); -export default function serializeError(origin, shouldBeautifyStack, error, testFile) { +export default function serializeError(error, {testFile = null} = {}) { if (!isNativeError(error) && !isWorkerError(error)) { return { - avaAssertionError: false, - nonErrorObject: true, - formatted: concordance.formatDescriptor(concordance.describe(error, concordanceOptions), concordanceOptions), + type: 'unknown', + originalError: error, // Note that the main process receives a structured clone. + formattedError: concordance.formatDescriptor(concordance.describe(error, concordanceOptions), concordanceOptions), }; } - try { - return trySerializeError(error, shouldBeautifyStack, testFile); - } catch { - const replacement = new Error(`${origin}: Could not serialize error`); + const {message, name, stack} = error; + const base = { + message, + name, + originalError: error, // Note that the main process receives a structured clone. + stack, + }; + + if (!isAvaAssertionError(error)) { + if (name === 'AggregateError') { + return { + ...base, + type: 'aggregate', + errors: error.errors.map(error => serializeError(error, {testFile})), + }; + } + return { - avaAssertionError: false, - nonErrorObject: false, - name: replacement.name, - message: replacement.message, - stack: replacement.stack, - summary: replacement.message, + ...base, + type: 'native', + source: extractSource(error.stack, testFile), }; } + + return { + ...base, + type: 'ava', + assertion: error.assertion, + improperUsage: error.improperUsage, + formattedCause: error.cause ? concordance.formatDescriptor(concordance.describe(error.cause, concordanceOptions), concordanceOptions) : null, + formattedDetails: error.formattedDetails, + source: extractSource(error.assertionStack, testFile), + stack: isNativeError(error.cause) ? error.cause.stack : error.assertionStack, + }; } diff --git a/lib/test.js b/lib/test.js index 40782171f..1879bcdd2 100644 --- a/lib/test.js +++ b/lib/test.js @@ -2,7 +2,7 @@ import concordance from 'concordance'; import isPromise from 'is-promise'; import plur from 'plur'; -import {AssertionError, Assertions, checkAssertionMessage} from './assert.js'; +import {AssertionError, Assertions, checkAssertionMessage, getAssertionStack} from './assert.js'; import concordanceOptions from './concordance-options.js'; import nowAndTimers from './now-and-timers.cjs'; import parseTestArgs from './parse-test-args.js'; @@ -26,14 +26,6 @@ function formatErrorValue(label, error) { return {label, formatted}; } -const captureSavedError = () => { - const limitBefore = Error.stackTraceLimit; - Error.stackTraceLimit = 1; - const error = new Error(); // eslint-disable-line unicorn/error-message - Error.stackTraceLimit = limitBefore; - return error; -}; - const testMap = new WeakMap(); class ExecutionContext extends Assertions { constructor(test) { @@ -70,7 +62,7 @@ class ExecutionContext extends Assertions { }; this.plan = count => { - test.plan(count, captureSavedError()); + test.plan(count, getAssertionStack()); }; this.plan.skip = () => {}; @@ -396,7 +388,7 @@ export default class Test { } } - plan(count, planError) { + plan(count, planAssertionStack) { if (typeof count !== 'number') { throw new TypeError('Expected a number'); } @@ -405,11 +397,11 @@ export default class Test { // In case the `planCount` doesn't match `assertCount, we need the stack of // this function to throw with a useful stack. - this.planError = planError; + this.planAssertionStack = planAssertionStack; } timeout(ms, message) { - const result = checkAssertionMessage('timeout', message); + const result = checkAssertionMessage(message, 't.timeout()'); if (result !== true) { this.saveFirstError(result); // Allow the timeout to be set even when the message is invalid. @@ -473,11 +465,9 @@ export default class Test { verifyPlan() { if (!this.assertError && this.planCount !== null && this.planCount !== this.assertCount) { - this.saveFirstError(new AssertionError({ - assertion: 'plan', - message: `Planned for ${this.planCount} ${plur('assertion', this.planCount)}, but got ${this.assertCount}.`, - operator: '===', - savedError: this.planError, + this.saveFirstError(new AssertionError(`Planned for ${this.planCount} ${plur('assertion', this.planCount)}, but got ${this.assertCount}.`, { + assertion: 't.plan()', + assertionStack: this.planAssertionStack, })); } } @@ -528,16 +518,17 @@ export default class Test { const result = this.callFn(); if (!result.ok) { if (isExternalAssertError(result.error)) { - this.saveFirstError(new AssertionError({ - message: 'Assertion failed', - savedError: result.error instanceof Error && result.error, - values: [{label: 'Assertion failed: ', formatted: result.error.message}], + this.saveFirstError(new AssertionError('Assertion failed', { + cause: result.error, + formattedDetails: [{label: 'Assertion failed: ', formatted: result.error.message}], })); } else { - this.saveFirstError(new AssertionError({ - message: 'Error thrown in test', - savedError: result.error instanceof Error && result.error, - values: [formatErrorValue('Error thrown in test:', result.error)], + this.saveFirstError(new AssertionError('Error thrown in test', { + // TODO: Provide an assertion stack that traces to the test declaration, + // rather than AVA internals. + assertionStack: '', + cause: result.error, + formattedDetails: [formatErrorValue('Error thrown in test:', result.error)], })); } @@ -581,16 +572,14 @@ export default class Test { promise .catch(error => { if (isExternalAssertError(error)) { - this.saveFirstError(new AssertionError({ - message: 'Assertion failed', - savedError: error instanceof Error && error, - values: [{label: 'Assertion failed: ', formatted: error.message}], + this.saveFirstError(new AssertionError('Assertion failed', { + cause: error, + formattedDetails: [{label: 'Assertion failed: ', formatted: error.message}], })); } else { - this.saveFirstError(new AssertionError({ - message: 'Rejected promise returned by test', - savedError: error instanceof Error && error, - values: [formatErrorValue('Rejected promise returned by test. Reason:', error)], + this.saveFirstError(new AssertionError('Rejected promise returned by test', { + cause: error, + formattedDetails: [formatErrorValue('Rejected promise returned by test. Reason:', error)], })); } }) @@ -617,7 +606,11 @@ export default class Test { if (this.metadata.failing) { passed = !passed; - error = passed ? null : new Error('Test was expected to fail, but succeeded, you should stop marking the test as failing'); + error = passed ? null : new AssertionError('Test was expected to fail, but succeeded, you should stop marking the test as failing', { + // TODO: Provide an assertion stack that traces to the test declaration, + // rather than AVA internals. + assertionStack: '', + }); } return { diff --git a/lib/worker/base.js b/lib/worker/base.js index d67e965ee..576da720c 100644 --- a/lib/worker/base.js +++ b/lib/worker/base.js @@ -43,8 +43,7 @@ async function exit(code, forceSync = false) { const handleProcessExit = (fn, receiver, args) => { const error = new Error('Unexpected process.exit()'); Error.captureStackTrace(error, handleProcessExit); - const {stack} = serializeError('', true, error); - channel.send({type: 'process-exit', stack}); + channel.send({type: 'process-exit', stack: error.stack}); // Make sure to extract the code only from `args` rather than e.g. `Array.prototype`. // This level of paranoia is usually unwarranted, but we're dealing with test code @@ -75,7 +74,7 @@ const run = async options => { lineNumbers: options.lineNumbers, }); } catch (error) { - channel.send({type: 'line-number-selection-error', err: serializeError('Line number selection error', false, error, options.file)}); + channel.send({type: 'line-number-selection-error', err: serializeError(error)}); checkSelectedByLineNumbers = () => false; } @@ -108,7 +107,7 @@ const run = async options => { runner.on('stateChange', state => channel.send(state)); runner.on('error', error => { - channel.send({type: 'internal-error', err: serializeError('Internal runner error', false, error, runner.file)}); + channel.send({type: 'internal-error', err: serializeError(error)}); exit(1); }); @@ -119,7 +118,7 @@ const run = async options => { channel.send({type: 'touched-files', files: touchedFiles}); } } catch (error) { - channel.send({type: 'internal-error', err: serializeError('Internal runner error', false, error, runner.file)}); + channel.send({type: 'internal-error', err: serializeError(error)}); exit(1); return; } @@ -127,14 +126,14 @@ const run = async options => { try { await Promise.all(sharedWorkerTeardowns.map(fn => fn())); } catch (error) { - channel.send({type: 'uncaught-exception', err: serializeError('Shared worker teardown error', false, error, runner.file)}); + channel.send({type: 'uncaught-exception', err: serializeError(error)}); exit(1); return; } nowAndTimers.setImmediate(() => { for (const rejection of currentlyUnhandled()) { - channel.send({type: 'unhandled-rejection', err: serializeError('Unhandled rejection', true, rejection.reason, runner.file)}); + channel.send({type: 'unhandled-rejection', err: serializeError(rejection.reason, {testFile: options.file})}); } exit(0); @@ -142,7 +141,7 @@ const run = async options => { }); process.on('uncaughtException', error => { - channel.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)}); + channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})}); exit(1); }); @@ -265,7 +264,7 @@ const run = async options => { exit(1); } } catch (error) { - channel.send({type: 'uncaught-exception', err: serializeError('Uncaught exception', true, error, runner.file)}); + channel.send({type: 'uncaught-exception', err: serializeError(error, {testFile: options.file})}); exit(1); } }; diff --git a/package-lock.json b/package-lock.json index 66ac94158..c344a438d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,6 @@ "chunkd": "^2.0.1", "ci-info": "^3.8.0", "ci-parallel-vars": "^1.0.1", - "clean-yaml-object": "^0.1.0", "cli-truncate": "^3.1.0", "code-excerpt": "^4.0.0", "common-path-prefix": "^3.0.0", @@ -2192,6 +2191,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz", "integrity": "sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -10677,9 +10677,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 0ed60e733..5f124ff64 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ "chunkd": "^2.0.1", "ci-info": "^3.8.0", "ci-parallel-vars": "^1.0.1", - "clean-yaml-object": "^0.1.0", "cli-truncate": "^3.1.0", "code-excerpt": "^4.0.0", "common-path-prefix": "^3.0.0", diff --git a/test-tap/assert.js b/test-tap/assert.js index 2cec483ad..a333717df 100644 --- a/test-tap/assert.js +++ b/test-tap/assert.js @@ -47,20 +47,15 @@ function assertFailure(t, subset) { t.equal(lastFailure.assertion, subset.assertion); t.equal(lastFailure.message, subset.message); t.equal(lastFailure.name, 'AssertionError'); - t.equal(lastFailure.operator, subset.operator); - if (subset.raw) { - t.equal(lastFailure.raw.expected, subset.raw.expected); - t.equal(lastFailure.raw.actual, subset.raw.actual); - } - if (subset.values) { - t.equal(lastFailure.values.length, subset.values.length); - for (const [i, s] of lastFailure.values.entries()) { - t.equal(stripAnsi(s.label), subset.values[i].label); - t.match(stripAnsi(s.formatted), subset.values[i].formatted); + 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.values, []); + t.same(lastFailure.formattedDetails, []); } } @@ -172,25 +167,24 @@ test('.pass()', t => { test('.fail()', t => { failsWith(t, () => assertions.fail(), { - assertion: 'fail', + assertion: 't.fail()', message: 'Test failed via `t.fail()`', }); failsWith(t, () => assertions.fail('my message'), { - assertion: 'fail', + assertion: 't.fail()', message: 'my message', }); failsWith(t, () => assertions.fail(), { - assertion: 'fail', + assertion: 't.fail()', message: 'Test failed via `t.fail()`', }); failsWith(t, () => assertions.fail(null), { - assertion: 'fail', - improperUsage: true, + assertion: 't.fail()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -264,64 +258,62 @@ test('.is()', t => { fails(t, () => assertions.is('foo', Number.NaN)); failsWith(t, () => assertions.is({foo: 'bar'}, {foo: 'bar'}), { - assertion: 'is', + assertion: 't.is()', message: '', actual: {foo: 'bar'}, expected: {foo: 'bar'}, - values: [{ + formattedDetails: [{ label: 'Values are deeply equal to each other, but they are not the same:', formatted: /foo/, }], }); failsWith(t, () => assertions.is('foo', 'bar'), { - assertion: 'is', + assertion: 't.is()', message: '', - raw: {actual: 'foo', expected: 'bar'}, - values: [ + formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 'bar'/}, ], }); failsWith(t, () => assertions.is('foo', 42), { actual: 'foo', - assertion: 'is', + assertion: 't.is()', expected: 42, message: '', - values: [ + formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}, ], }); failsWith(t, () => assertions.is('foo', 42, 'my message'), { - assertion: 'is', + assertion: 't.is()', message: 'my message', - values: [ + formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}, ], }); failsWith(t, () => assertions.is(0, -0, 'my message'), { - assertion: 'is', + assertion: 't.is()', message: 'my message', - values: [ + formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- 0\n\+ -0/}, ], }); failsWith(t, () => assertions.is(-0, 0, 'my message'), { - assertion: 'is', + assertion: 't.is()', message: 'my message', - values: [ + formattedDetails: [ {label: 'Difference (- actual, + expected):', formatted: /- -0\n\+ 0/}, ], }); failsWith(t, () => assertions.is(0, 0, null), { - assertion: 'is', - improperUsage: true, + assertion: 't.is()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -340,23 +332,21 @@ test('.not()', t => { fails(t, () => assertions.not(0 / 0, Number.NaN)); failsWith(t, () => assertions.not('foo', 'foo'), { - assertion: 'not', + assertion: 't.not()', message: '', - raw: {actual: 'foo', expected: 'foo'}, - values: [{label: 'Value is the same as:', formatted: /foo/}], + formattedDetails: [{label: 'Value is the same as:', formatted: /foo/}], }); failsWith(t, () => assertions.not('foo', 'foo', 'my message'), { - assertion: 'not', + assertion: 't.not()', message: 'my message', - values: [{label: 'Value is the same as:', formatted: /foo/}], + formattedDetails: [{label: 'Value is the same as:', formatted: /foo/}], }); failsWith(t, () => assertions.not(0, 1, null), { - assertion: 'not', - improperUsage: true, + assertion: 't.not()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -532,30 +522,27 @@ test('.deepEqual()', t => { }); failsWith(t, () => assertions.deepEqual('foo', 'bar'), { - assertion: 'deepEqual', + assertion: 't.deepEqual()', message: '', - raw: {actual: 'foo', expected: 'bar'}, - values: [{label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 'bar'/}], + formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 'bar'/}], }); failsWith(t, () => assertions.deepEqual('foo', 42), { - assertion: 'deepEqual', + assertion: 't.deepEqual()', message: '', - raw: {actual: 'foo', expected: 42}, - values: [{label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}], + formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}], }); failsWith(t, () => assertions.deepEqual('foo', 42, 'my message'), { - assertion: 'deepEqual', + assertion: 't.deepEqual()', message: 'my message', - values: [{label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}], + formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: /- 'foo'\n\+ 42/}], }); failsWith(t, () => assertions.deepEqual({}, {}, null), { - assertion: 'deepEqual', - improperUsage: true, + assertion: 't.deepEqual()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -575,24 +562,22 @@ test('.notDeepEqual()', t => { const expected = {a: 'a'}; failsWith(t, () => assertions.notDeepEqual(actual, expected), { actual, - assertion: 'notDeepEqual', + assertion: 't.notDeepEqual()', expected, message: '', - raw: {actual, expected}, - values: [{label: 'Value is deeply equal:', formatted: /.*{.*\n.*a: 'a'/}], + formattedDetails: [{label: 'Value is deeply equal:', formatted: /.*{.*\n.*a: 'a'/}], }); failsWith(t, () => assertions.notDeepEqual(['a', 'b'], ['a', 'b'], 'my message'), { - assertion: 'notDeepEqual', + assertion: 't.notDeepEqual()', message: 'my message', - values: [{label: 'Value is deeply equal:', formatted: /.*\[.*\n.*'a',\n.*'b',/}], + formattedDetails: [{label: 'Value is deeply equal:', formatted: /.*\[.*\n.*'a',\n.*'b',/}], }); failsWith(t, () => assertions.notDeepEqual({}, [], null), { - assertion: 'notDeepEqual', - improperUsage: true, + assertion: 't.notDeepEqual()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -721,15 +706,15 @@ test('.like()', t => { }); failsWith(t, () => assertions.like({a: 'a'}, Object.defineProperties({}, {ignored: {}})), { - assertion: 'like', + assertion: 't.like()', message: '`t.like()` selector must be a non-empty object', - values: [{label: 'Called with:', formatted: '{}'}], + formattedDetails: [{label: 'Called with:', formatted: '{}'}], }); failsWith(t, () => assertions.like('foo', 'bar'), { - assertion: 'like', + assertion: 't.like()', message: '`t.like()` selector must be a non-empty object', - values: [{label: 'Called with:', formatted: '\'bar\''}], + formattedDetails: [{label: 'Called with:', formatted: '\'bar\''}], }); passes(t, () => { @@ -754,25 +739,24 @@ test('.like()', t => { return assertions.like({}, likePattern); }, { - assertion: 'like', + assertion: 't.like()', message: '`t.like()` selector must not contain circular references', - values: [{label: 'Called with:', formatted: '{\n a: \'a\',\n circular: [Circular],\n}'}], + formattedDetails: [{label: 'Called with:', formatted: '{\n a: \'a\',\n circular: [Circular],\n}'}], }); failsWith(t, () => assertions.like({}, {}, null), { - assertion: 'like', - improperUsage: true, + assertion: 't.like()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); failsWith(t, () => assertions.like({a: 'foo', b: 'irrelevant'}, {a: 'bar'}), { - assertion: 'like', + assertion: 't.like()', message: '', - values: [{label: 'Difference (- actual, + expected):', formatted: /{\n-\s*a: 'foo',\n\+\s*a: 'bar',\n\s*}/}], + 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}]})); @@ -795,30 +779,30 @@ test('.like()', t => { test('.throws()', gather(t => { // Fails because function doesn't throw. failsWith(t, () => assertions.throws(() => {}), { - assertion: 'throws', + assertion: 't.throws()', message: '', - values: [{label: 'Function returned:', formatted: /undefined/}], + formattedDetails: [{label: 'Function returned:', formatted: /undefined/}], }); failsWith(t, () => assertions.throws(() => {}), { - assertion: 'throws', + assertion: 't.throws()', message: '', - values: [{label: 'Function returned:', formatted: /undefined/}], + 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: 'throws', + assertion: 't.throws()', message: 'my message', - values: [{label: 'Function returned:', formatted: /undefined/}], + formattedDetails: [{label: 'Function returned:', formatted: /undefined/}], }); // Fails because the function returned a promise. failsWith(t, () => assertions.throws(() => Promise.resolve()), { - assertion: 'throws', + assertion: 't.throws()', message: '', - values: [{label: 'Function returned a promise. Use `t.throwsAsync()` instead:', formatted: /Promise/}], + formattedDetails: [{label: 'Function returned a promise. Use `t.throwsAsync()` instead:', formatted: /Promise/}], }); // Fails because thrown exception is not an error @@ -826,9 +810,9 @@ test('.throws()', gather(t => { const error = 'foo'; throw error; }), { - assertion: 'throws', + assertion: 't.throws()', message: '', - values: [ + formattedDetails: [ {label: 'Function threw exception that is not an error:', formatted: /'foo'/}, ], }); @@ -919,10 +903,9 @@ test('.throws()', gather(t => { }); failsWith(t, () => assertions.throws(() => {}, undefined, null), { - assertion: 'throws', - improperUsage: true, + assertion: 't.throws()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -939,9 +922,9 @@ test('.throws()', gather(t => { {message: 'my error'}, ), { - assertion: 'throws', + assertion: 't.throws()', message: '', - values: [ + formattedDetails: [ {label: 'Function threw unexpected exception:', formatted: /error/}, {label: 'Expected message to equal:', formatted: /my error/}, ], @@ -963,9 +946,9 @@ test('.throws()', gather(t => { {message: /my error/}, ), { - assertion: 'throws', + assertion: 't.throws()', message: '', - values: [ + formattedDetails: [ {label: 'Function threw unexpected exception:', formatted: /error/}, {label: 'Expected message to match:', formatted: /my error/}, ], @@ -987,9 +970,9 @@ test('.throws()', gather(t => { {message: () => false}, ), { - assertion: 'throws', + assertion: 't.throws()', message: '', - values: [ + formattedDetails: [ {label: 'Function threw unexpected exception:', formatted: /error/}, {label: 'Expected message to return true:', formatted: /Function/}, ], @@ -1015,29 +998,29 @@ test('.throws() returns the thrown error', t => { test('.throwsAsync()', gather(t => { // Fails because the promise is resolved, not rejected. throwsAsyncFails(t, () => assertions.throwsAsync(Promise.resolve('foo')), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: '', - values: [{label: 'Promise resolved with:', formatted: /'foo'/}], + formattedDetails: [{label: 'Promise resolved with:', formatted: /'foo'/}], }); throwsAsyncFails(t, () => assertions.throwsAsync(Promise.resolve('foo')), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: '', - values: [{label: 'Promise resolved with:', formatted: /'foo'/}], + 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: 'throwsAsync', + assertion: 't.throwsAsync()', message: '', - values: [{label: 'Promise resolved with:', formatted: /Error/}], + 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: 'throwsAsync', + assertion: 't.throwsAsync()', message: '', - values: [{label: 'Returned promise resolved with:', formatted: /'foo'/}], + formattedDetails: [{label: 'Returned promise resolved with:', formatted: /'foo'/}], }); // Passes because the promise was rejected with an error. @@ -1050,27 +1033,26 @@ test('.throwsAsync()', gather(t => { throwsAsyncFails(t, () => assertions.throwsAsync(() => { throw new Error('sync'); }, undefined, 'message'), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'message', - values: [ + 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: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'message', - values: [ + formattedDetails: [ {label: 'Function returned:', formatted: /undefined/}, ], }); throwsAsyncFails(t, () => assertions.throwsAsync(Promise.resolve(), undefined, null), { - assertion: 'throwsAsync', - improperUsage: true, + assertion: 't.throwsAsync()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -1097,9 +1079,9 @@ test('.throwsAsync() returns the rejection reason of a promise returned by the f test('.throws() fails if passed a bad value', t => { failsWith(t, () => assertions.throws('not a function'), { - assertion: 'throws', + assertion: 't.throws()', message: '`t.throws()` must be called with a function', - values: [{label: 'Called with:', formatted: /not a function/}], + formattedDetails: [{label: 'Called with:', formatted: /not a function/}], }); t.end(); @@ -1107,9 +1089,9 @@ test('.throws() fails if passed a bad value', t => { test('.throwsAsync() fails if passed a bad value', t => { failsWith(t, () => assertions.throwsAsync('not a function'), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: '`t.throwsAsync()` must be called with a function or promise', - values: [{label: 'Called with:', formatted: /not a function/}], + formattedDetails: [{label: 'Called with:', formatted: /not a function/}], }, {expectBoolean: false}); t.end(); @@ -1117,69 +1099,69 @@ test('.throwsAsync() fails if passed a bad value', t => { test('.throws() fails if passed a bad expectation', t => { failsWith(t, () => assertions.throws(() => {}, true), { - assertion: 'throws', + assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /true/}], + formattedDetails: [{label: 'Called with:', formatted: /true/}], }); failsWith(t, () => assertions.throws(() => {}, 'foo'), { - assertion: 'throws', + assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /foo/}], + formattedDetails: [{label: 'Called with:', formatted: /foo/}], }); failsWith(t, () => assertions.throws(() => {}, /baz/), { - assertion: 'throws', + assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /baz/}], + formattedDetails: [{label: 'Called with:', formatted: /baz/}], }); failsWith(t, () => assertions.throws(() => {}, class Bar {}), { - assertion: 'throws', + assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /Bar/}], + formattedDetails: [{label: 'Called with:', formatted: /Bar/}], }); failsWith(t, () => assertions.throws(() => {}, {}), { - assertion: 'throws', + assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /{}/}], + formattedDetails: [{label: 'Called with:', formatted: /{}/}], }); failsWith(t, () => assertions.throws(() => {}, []), { - assertion: 'throws', + assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /\[]/}], + formattedDetails: [{label: 'Called with:', formatted: /\[]/}], }); failsWith(t, () => assertions.throws(() => {}, {code: {}}), { - assertion: 'throws', + assertion: 't.throws()', message: 'The `code` property of the second argument to `t.throws()` must be a string or number', - values: [{label: 'Called with:', formatted: /code: {}/}], + formattedDetails: [{label: 'Called with:', formatted: /code: {}/}], }); failsWith(t, () => assertions.throws(() => {}, {instanceOf: null}), { - assertion: 'throws', + assertion: 't.throws()', message: 'The `instanceOf` property of the second argument to `t.throws()` must be a function', - values: [{label: 'Called with:', formatted: /instanceOf: null/}], + formattedDetails: [{label: 'Called with:', formatted: /instanceOf: null/}], }); failsWith(t, () => assertions.throws(() => {}, {message: null}), { - assertion: 'throws', + assertion: 't.throws()', message: 'The `message` property of the second argument to `t.throws()` must be a string, regular expression or a function', - values: [{label: 'Called with:', formatted: /message: null/}], + formattedDetails: [{label: 'Called with:', formatted: /message: null/}], }); failsWith(t, () => assertions.throws(() => {}, {name: null}), { - assertion: 'throws', + assertion: 't.throws()', message: 'The `name` property of the second argument to `t.throws()` must be a string', - values: [{label: 'Called with:', formatted: /name: null/}], + formattedDetails: [{label: 'Called with:', formatted: /name: null/}], }); failsWith(t, () => assertions.throws(() => {}, {is: {}, message: '', name: '', of() {}, foo: null}), { - assertion: 'throws', + assertion: 't.throws()', message: 'The second argument to `t.throws()` contains unexpected properties', - values: [{label: 'Called with:', formatted: /foo: null/}], + formattedDetails: [{label: 'Called with:', formatted: /foo: null/}], }); t.end(); @@ -1187,69 +1169,69 @@ test('.throws() fails if passed a bad expectation', t => { test('.throwsAsync() fails if passed a bad expectation', t => { failsWith(t, () => assertions.throwsAsync(() => {}, true), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /true/}], + formattedDetails: [{label: 'Called with:', formatted: /true/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, 'foo'), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /foo/}], + formattedDetails: [{label: 'Called with:', formatted: /foo/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, /baz/), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /baz/}], + formattedDetails: [{label: 'Called with:', formatted: /baz/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, class Bar {}), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /Bar/}], + formattedDetails: [{label: 'Called with:', formatted: /Bar/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, {}), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /{}/}], + formattedDetails: [{label: 'Called with:', formatted: /{}/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, []), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object, `null` or `undefined`', - values: [{label: 'Called with:', formatted: /\[]/}], + formattedDetails: [{label: 'Called with:', formatted: /\[]/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, {code: {}}), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The `code` property of the second argument to `t.throwsAsync()` must be a string or number', - values: [{label: 'Called with:', formatted: /code: {}/}], + formattedDetails: [{label: 'Called with:', formatted: /code: {}/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, {instanceOf: null}), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The `instanceOf` property of the second argument to `t.throwsAsync()` must be a function', - values: [{label: 'Called with:', formatted: /instanceOf: null/}], + formattedDetails: [{label: 'Called with:', formatted: /instanceOf: null/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, {message: null}), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The `message` property of the second argument to `t.throwsAsync()` must be a string, regular expression or a function', - values: [{label: 'Called with:', formatted: /message: null/}], + formattedDetails: [{label: 'Called with:', formatted: /message: null/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, {name: null}), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The `name` property of the second argument to `t.throwsAsync()` must be a string', - values: [{label: 'Called with:', formatted: /name: null/}], + formattedDetails: [{label: 'Called with:', formatted: /name: null/}], }, {expectBoolean: false}); failsWith(t, () => assertions.throwsAsync(() => {}, {is: {}, message: '', name: '', of() {}, foo: null}), { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` contains unexpected properties', - values: [{label: 'Called with:', formatted: /foo: null/}], + formattedDetails: [{label: 'Called with:', formatted: /foo: null/}], }, {expectBoolean: false}); t.end(); @@ -1261,9 +1243,9 @@ test('.throws() fails if passed null expectation', t => { failsWith(t, () => { asserter.throws(() => {}, null); }, { - assertion: 'throws', + assertion: 't.throws()', message: 'The second argument to `t.throws()` must be an expectation object or `undefined`', - values: [{label: 'Called with:', formatted: /null/}], + formattedDetails: [{label: 'Called with:', formatted: /null/}], }); t.end(); @@ -1275,9 +1257,9 @@ test('.throwsAsync() fails if passed null', t => { failsWith(t, () => { asserter.throwsAsync(() => {}, null); }, { - assertion: 'throwsAsync', + assertion: 't.throwsAsync()', message: 'The second argument to `t.throwsAsync()` must be an expectation object or `undefined`', - values: [{label: 'Called with:', formatted: /null/}], + formattedDetails: [{label: 'Called with:', formatted: /null/}], }); t.end(); @@ -1293,9 +1275,9 @@ test('.notThrows()', gather(t => { failsWith(t, () => assertions.notThrows(() => { throw new Error('foo'); }), { - assertion: 'notThrows', + assertion: 't.notThrows()', message: '', - values: [{label: 'Function threw:', formatted: /foo/}], + formattedDetails: [{label: 'Function threw:', formatted: /foo/}], }, {expectBoolean: false}); // Fails because the function throws. Asserts that message is used for the @@ -1303,16 +1285,15 @@ test('.notThrows()', gather(t => { failsWith(t, () => assertions.notThrows(() => { throw new Error('foo'); }, 'my message'), { - assertion: 'notThrows', + assertion: 't.notThrows()', message: 'my message', - values: [{label: 'Function threw:', formatted: /foo/}], + formattedDetails: [{label: 'Function threw:', formatted: /foo/}], }, {expectBoolean: false}); failsWith(t, () => assertions.notThrows(() => {}, null), { - assertion: 'notThrows', - improperUsage: true, + assertion: 't.notThrows()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -1327,9 +1308,9 @@ test('.notThrowsAsync()', gather(t => { // Fails because the promise is rejected notThrowsAsyncFails(t, () => assertions.notThrowsAsync(Promise.reject(new Error())), { - assertion: 'notThrowsAsync', + assertion: 't.notThrowsAsync()', message: '', - values: [{label: 'Promise rejected with:', formatted: /Error/}], + formattedDetails: [{label: 'Promise rejected with:', formatted: /Error/}], }); // Passes because the function returned a resolved promise @@ -1337,36 +1318,35 @@ test('.notThrowsAsync()', gather(t => { // Fails because the function returned a rejected promise notThrowsAsyncFails(t, () => assertions.notThrowsAsync(() => Promise.reject(new Error())), { - assertion: 'notThrowsAsync', + assertion: 't.notThrowsAsync()', message: '', - values: [{label: 'Returned promise rejected with:', formatted: /Error/}], + formattedDetails: [{label: 'Returned promise rejected with:', formatted: /Error/}], }); // Fails because the function throws synchronously notThrowsAsyncFails(t, () => assertions.notThrowsAsync(() => { throw new Error('sync'); }, 'message'), { - assertion: 'notThrowsAsync', + assertion: 't.notThrowsAsync()', message: 'message', - values: [ + formattedDetails: [ {label: 'Function threw:', formatted: /Error/}, ], }); // Fails because the function did not return a promise notThrowsAsyncFails(t, () => assertions.notThrowsAsync(() => {}, 'message'), { - assertion: 'notThrowsAsync', + assertion: 't.notThrowsAsync()', message: 'message', - values: [ + formattedDetails: [ {label: 'Function did not return a promise. Use `t.notThrows()` instead:', formatted: /undefined/}, ], }); notThrowsAsyncFails(t, () => assertions.notThrowsAsync(Promise.resolve(), null), { - assertion: 'notThrowsAsync', - improperUsage: true, + assertion: 't.notThrowsAsync()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -1383,9 +1363,9 @@ test('.notThrowsAsync() returns undefined for a fulfilled promise returned by th test('.notThrows() fails if passed a bad value', t => { failsWith(t, () => assertions.notThrows('not a function'), { - assertion: 'notThrows', + assertion: 't.notThrows()', message: '`t.notThrows()` must be called with a function', - values: [{label: 'Called with:', formatted: /not a function/}], + formattedDetails: [{label: 'Called with:', formatted: /not a function/}], }); t.end(); @@ -1393,9 +1373,9 @@ test('.notThrows() fails if passed a bad value', t => { test('.notThrowsAsync() fails if passed a bad value', t => { failsWith(t, () => assertions.notThrowsAsync('not a function'), { - assertion: 'notThrowsAsync', + assertion: 't.notThrowsAsync()', message: '`t.notThrowsAsync()` must be called with a function or promise', - values: [{label: 'Called with:', formatted: /not a function/}], + formattedDetails: [{label: 'Called with:', formatted: /not a function/}], }, { expectBoolean: false, }); @@ -1455,9 +1435,9 @@ test('.snapshot()', async t => { } failsWith(t, () => assertions.snapshot({foo: 'not bar'}), { - assertion: 'snapshot', + assertion: 't.snapshot()', message: 'Did not match snapshot', - values: [{label: 'Difference (- actual, + expected):', formatted: ' {\n- foo: \'not bar\',\n+ foo: \'bar\',\n }'}], + formattedDetails: [{label: 'Difference (- actual, + expected):', formatted: ' {\n- foo: \'not bar\',\n+ foo: \'bar\',\n }'}], }); } @@ -1468,29 +1448,27 @@ test('.snapshot()', async t => { } failsWith(t, () => assertions.snapshot({foo: 'not bar'}, 'my message'), { - assertion: 'snapshot', + assertion: 't.snapshot()', message: 'my message', - values: [{label: 'Difference (- actual, + expected):', formatted: ' {\n- foo: \'not bar\',\n+ foo: \'bar\',\n }'}], + 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: 'snapshot', - improperUsage: true, + assertion: 't.snapshot()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], }); failsWith(t, () => assertions.snapshot(null, ''), { - assertion: 'snapshot', - improperUsage: true, + assertion: 't.snapshot()', message: 'The snapshot assertion message must be a non-empty string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: '\'\'', }], @@ -1501,10 +1479,9 @@ test('.snapshot()', async t => { // See https://github.com/avajs/ava/issues/2669 const assertions = setup('id'); failsWith(t, () => assertions.snapshot({foo: 'bar'}, {id: 'an id'}), { - assertion: 'snapshot', - improperUsage: true, - message: 'AVA 4 no longer supports snapshot IDs', - values: [{ + assertion: 't.snapshot()', + message: 'Since AVA 4, snapshot IDs are no longer supported', + formattedDetails: [{ label: 'Called with id:', formatted: '\'an id\'', }], @@ -1517,17 +1494,15 @@ test('.snapshot()', async t => { test('.truthy()', t => { failsWith(t, () => assertions.truthy(0), { - assertion: 'truthy', + assertion: 't.truthy()', message: '', - operator: '!!', - values: [{label: 'Value is not truthy:', formatted: /0/}], + formattedDetails: [{label: 'Value is not truthy:', formatted: /0/}], }); failsWith(t, () => assertions.truthy(false, 'my message'), { - assertion: 'truthy', + assertion: 't.truthy()', message: 'my message', - operator: '!!', - values: [{label: 'Value is not truthy:', formatted: /false/}], + formattedDetails: [{label: 'Value is not truthy:', formatted: /false/}], }); passes(t, () => assertions.truthy(1) @@ -1536,10 +1511,9 @@ test('.truthy()', t => { passes(t, () => assertions.truthy(1) && assertions.truthy(true)); failsWith(t, () => assertions.truthy(true, null), { - assertion: 'truthy', - improperUsage: true, + assertion: 't.truthy()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -1550,17 +1524,15 @@ test('.truthy()', t => { test('.falsy()', t => { failsWith(t, () => assertions.falsy(1), { - assertion: 'falsy', + assertion: 't.falsy()', message: '', - operator: '!', - values: [{label: 'Value is not falsy:', formatted: /1/}], + formattedDetails: [{label: 'Value is not falsy:', formatted: /1/}], }); failsWith(t, () => assertions.falsy(true, 'my message'), { - assertion: 'falsy', + assertion: 't.falsy()', message: 'my message', - operator: '!', - values: [{label: 'Value is not falsy:', formatted: /true/}], + formattedDetails: [{label: 'Value is not falsy:', formatted: /true/}], }); passes(t, () => assertions.falsy(0) @@ -1570,10 +1542,9 @@ test('.falsy()', t => { && assertions.falsy(false)); failsWith(t, () => assertions.falsy(false, null), { - assertion: 'falsy', - improperUsage: true, + assertion: 't.falsy()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -1584,27 +1555,27 @@ test('.falsy()', t => { test('.true()', t => { failsWith(t, () => assertions.true(1), { - assertion: 'true', + assertion: 't.true()', message: '', - values: [{label: 'Value is not `true`:', formatted: /1/}], + formattedDetails: [{label: 'Value is not `true`:', formatted: /1/}], }); failsWith(t, () => assertions.true(0), { - assertion: 'true', + assertion: 't.true()', message: '', - values: [{label: 'Value is not `true`:', formatted: /0/}], + formattedDetails: [{label: 'Value is not `true`:', formatted: /0/}], }); failsWith(t, () => assertions.true(false), { - assertion: 'true', + assertion: 't.true()', message: '', - values: [{label: 'Value is not `true`:', formatted: /false/}], + formattedDetails: [{label: 'Value is not `true`:', formatted: /false/}], }); failsWith(t, () => assertions.true('foo', 'my message'), { - assertion: 'true', + assertion: 't.true()', message: 'my message', - values: [{label: 'Value is not `true`:', formatted: /foo/}], + formattedDetails: [{label: 'Value is not `true`:', formatted: /foo/}], }); passes(t, () => assertions.true(true)); @@ -1615,10 +1586,9 @@ test('.true()', t => { }); failsWith(t, () => assertions.true(true, null), { - assertion: 'true', - improperUsage: true, + assertion: 't.true()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -1629,27 +1599,27 @@ test('.true()', t => { test('.false()', t => { failsWith(t, () => assertions.false(0), { - assertion: 'false', + assertion: 't.false()', message: '', - values: [{label: 'Value is not `false`:', formatted: /0/}], + formattedDetails: [{label: 'Value is not `false`:', formatted: /0/}], }); failsWith(t, () => assertions.false(1), { - assertion: 'false', + assertion: 't.false()', message: '', - values: [{label: 'Value is not `false`:', formatted: /1/}], + formattedDetails: [{label: 'Value is not `false`:', formatted: /1/}], }); failsWith(t, () => assertions.false(true), { - assertion: 'false', + assertion: 't.false()', message: '', - values: [{label: 'Value is not `false`:', formatted: /true/}], + formattedDetails: [{label: 'Value is not `false`:', formatted: /true/}], }); failsWith(t, () => assertions.false('foo', 'my message'), { - assertion: 'false', + assertion: 't.false()', message: 'my message', - values: [{label: 'Value is not `false`:', formatted: /foo/}], + formattedDetails: [{label: 'Value is not `false`:', formatted: /foo/}], }); passes(t, () => assertions.false(false)); @@ -1660,10 +1630,9 @@ test('.false()', t => { }); failsWith(t, () => assertions.false(false, null), { - assertion: 'false', - improperUsage: true, + assertion: 't.false()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -1678,28 +1647,27 @@ test('.regex()', t => { passes(t, () => assertions.regex('abc', /^abc$/)); failsWith(t, () => assertions.regex('foo', /^abc$/), { - assertion: 'regex', + assertion: 't.regex()', message: '', - values: [ + formattedDetails: [ {label: 'Value must match expression:', formatted: /foo/}, {label: 'Regular expression:', formatted: /\/\^abc\$\//}, ], }); failsWith(t, () => assertions.regex('foo', /^abc$/, 'my message'), { - assertion: 'regex', + assertion: 't.regex()', message: 'my message', - values: [ + formattedDetails: [ {label: 'Value must match expression:', formatted: /foo/}, {label: 'Regular expression:', formatted: /\/\^abc\$\//}, ], }); failsWith(t, () => assertions.regex('foo', /^abc$/, null), { - assertion: 'regex', - improperUsage: true, + assertion: 't.regex()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -1710,16 +1678,15 @@ test('.regex()', t => { test('.regex() fails if passed a bad value', t => { failsWith(t, () => assertions.regex(42, /foo/), { - assertion: 'regex', - improperUsage: true, + assertion: 't.regex()', message: '`t.regex()` must be called with a string', - values: [{label: 'Called with:', formatted: /42/}], + formattedDetails: [{label: 'Called with:', formatted: /42/}], }); failsWith(t, () => assertions.regex('42', {}), { - assertion: 'regex', + assertion: 't.regex()', message: '`t.regex()` must be called with a regular expression', - values: [{label: 'Called with:', formatted: /{}/}], + formattedDetails: [{label: 'Called with:', formatted: /{}/}], }); t.end(); @@ -1731,28 +1698,27 @@ test('.notRegex()', t => { passes(t, () => assertions.notRegex('abc', /def/)); failsWith(t, () => assertions.notRegex('abc', /abc/), { - assertion: 'notRegex', + assertion: 't.notRegex()', message: '', - values: [ + formattedDetails: [ {label: 'Value must not match expression:', formatted: /abc/}, {label: 'Regular expression:', formatted: /\/abc\//}, ], }); failsWith(t, () => assertions.notRegex('abc', /abc/, 'my message'), { - assertion: 'notRegex', + assertion: 't.notRegex()', message: 'my message', - values: [ + formattedDetails: [ {label: 'Value must not match expression:', formatted: /abc/}, {label: 'Regular expression:', formatted: /\/abc\//}, ], }); failsWith(t, () => assertions.notRegex('abc', /abc/, null), { - assertion: 'notRegex', - improperUsage: true, + assertion: 't.notRegex()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], @@ -1763,15 +1729,15 @@ test('.notRegex()', t => { test('.notRegex() fails if passed a bad value', t => { failsWith(t, () => assertions.notRegex(42, /foo/), { - assertion: 'notRegex', + assertion: 't.notRegex()', message: '`t.notRegex()` must be called with a string', - values: [{label: 'Called with:', formatted: /42/}], + formattedDetails: [{label: 'Called with:', formatted: /42/}], }); failsWith(t, () => assertions.notRegex('42', {}), { - assertion: 'notRegex', + assertion: 't.notRegex()', message: '`t.notRegex()` must be called with a regular expression', - values: [{label: 'Called with:', formatted: /{}/}], + formattedDetails: [{label: 'Called with:', formatted: /{}/}], }); t.end(); @@ -1779,17 +1745,15 @@ test('.notRegex() fails if passed a bad value', t => { test('.assert()', t => { failsWith(t, () => assertions.assert(0), { - assertion: 'assert', + assertion: 't.assert()', message: '', - operator: '!!', - values: [{label: 'Value is not truthy:', formatted: /0/}], + formattedDetails: [{label: 'Value is not truthy:', formatted: /0/}], }); failsWith(t, () => assertions.assert(false, 'my message'), { - assertion: 'assert', + assertion: 't.assert()', message: 'my message', - operator: '!!', - values: [{label: 'Value is not truthy:', formatted: /false/}], + formattedDetails: [{label: 'Value is not truthy:', formatted: /false/}], }); passes(t, () => assertions.assert(1) @@ -1798,10 +1762,9 @@ test('.assert()', t => { passes(t, () => assertions.assert(1) && assertions.assert(true)); failsWith(t, () => assertions.assert(null, null), { - assertion: 'assert', - improperUsage: true, + assertion: 't.assert()', message: 'The assertion message must be a string', - values: [{ + formattedDetails: [{ label: 'Called with:', formatted: /null/, }], diff --git a/test-tap/promise.js b/test-tap/promise.js index 1fd855445..b12433262 100644 --- a/test-tap/promise.js +++ b/test-tap/promise.js @@ -61,7 +61,7 @@ test('missing assertion will fail the test', t => ava(a => { }); }).run().then(result => { t.equal(result.passed, false); - t.equal(result.error.assertion, 'plan'); + t.equal(result.error.assertion, 't.plan()'); })); test('extra assertion will fail the test', t => ava(a => { @@ -80,7 +80,7 @@ test('extra assertion will fail the test', t => ava(a => { }); }).run().then(result => { t.equal(result.passed, false); - t.equal(result.error.assertion, 'plan'); + t.equal(result.error.assertion, 't.plan()'); })); test('assert pass', t => { @@ -106,9 +106,9 @@ test('reject', t => ava(a => fail().then(() => { t.equal(result.passed, false); t.equal(result.error.name, 'AssertionError'); t.equal(result.error.message, 'Rejected promise returned by test'); - t.equal(result.error.values.length, 1); - t.equal(result.error.values[0].label, 'Rejected promise returned by test. Reason:'); - t.match(result.error.values[0].formatted, /.*Error.*\n.*message: 'unicorn'/); + t.equal(result.error.formattedDetails.length, 1); + t.equal(result.error.formattedDetails[0].label, 'Rejected promise returned by test. Reason:'); + t.match(result.error.formattedDetails[0].formatted, /.*Error.*\n.*message: 'unicorn'/); })); test('reject with non-Error', t => ava(() => @@ -117,7 +117,7 @@ test('reject with non-Error', t => ava(() => t.equal(result.passed, false); t.equal(result.error.name, 'AssertionError'); t.equal(result.error.message, 'Rejected promise returned by test'); - t.equal(result.error.values.length, 1); - t.equal(result.error.values[0].label, 'Rejected promise returned by test. Reason:'); - t.match(result.error.values[0].formatted, /failure/); + t.equal(result.error.formattedDetails.length, 1); + t.equal(result.error.formattedDetails[0].label, 'Rejected promise returned by test. Reason:'); + t.match(result.error.formattedDetails[0].formatted, /failure/); })); diff --git a/test-tap/reporters/default.edgecases.v16.log b/test-tap/reporters/default.edgecases.v16.log index b3c1b8057..8b1c5dcdf 100644 --- a/test-tap/reporters/default.edgecases.v16.log +++ b/test-tap/reporters/default.edgecases.v16.log @@ -2,7 +2,7 @@ ---tty-stream-chunk-separator ⚠ Could not parse ast-syntax-error.cjs for line number selection - SyntaxError: Unexpected token (3:11) + SyntaxError: Unexpected token (3:11) ---tty-stream-chunk-separator Uncaught exception in ast-syntax-error.cjs diff --git a/test-tap/reporters/default.edgecases.v18.log b/test-tap/reporters/default.edgecases.v18.log index b3c1b8057..8b1c5dcdf 100644 --- a/test-tap/reporters/default.edgecases.v18.log +++ b/test-tap/reporters/default.edgecases.v18.log @@ -2,7 +2,7 @@ ---tty-stream-chunk-separator ⚠ Could not parse ast-syntax-error.cjs for line number selection - SyntaxError: Unexpected token (3:11) + SyntaxError: Unexpected token (3:11) ---tty-stream-chunk-separator Uncaught exception in ast-syntax-error.cjs diff --git a/test-tap/reporters/default.edgecases.v20.log b/test-tap/reporters/default.edgecases.v20.log index b3c1b8057..8b1c5dcdf 100644 --- a/test-tap/reporters/default.edgecases.v20.log +++ b/test-tap/reporters/default.edgecases.v20.log @@ -2,7 +2,7 @@ ---tty-stream-chunk-separator ⚠ Could not parse ast-syntax-error.cjs for line number selection - SyntaxError: Unexpected token (3:11) + SyntaxError: Unexpected token (3:11) ---tty-stream-chunk-separator Uncaught exception in ast-syntax-error.cjs diff --git a/test-tap/reporters/default.regular.v16.log b/test-tap/reporters/default.regular.v16.log index 9b9a969d3..77eda7ecd 100644 --- a/test-tap/reporters/default.regular.v16.log +++ b/test-tap/reporters/default.regular.v16.log @@ -199,7 +199,7 @@ test › no longer failing - Error: Test was expected to fail, but succeeded, you should stop marking the test as failing + Test was expected to fail, but succeeded, you should stop marking the test as failing diff --git a/test-tap/reporters/default.regular.v18.log b/test-tap/reporters/default.regular.v18.log index 9b9a969d3..77eda7ecd 100644 --- a/test-tap/reporters/default.regular.v18.log +++ b/test-tap/reporters/default.regular.v18.log @@ -199,7 +199,7 @@ test › no longer failing - Error: Test was expected to fail, but succeeded, you should stop marking the test as failing + Test was expected to fail, but succeeded, you should stop marking the test as failing diff --git a/test-tap/reporters/default.regular.v20.log b/test-tap/reporters/default.regular.v20.log index 9b9a969d3..77eda7ecd 100644 --- a/test-tap/reporters/default.regular.v20.log +++ b/test-tap/reporters/default.regular.v20.log @@ -199,7 +199,7 @@ test › no longer failing - Error: Test was expected to fail, but succeeded, you should stop marking the test as failing + Test was expected to fail, but succeeded, you should stop marking the test as failing diff --git a/test-tap/reporters/improper-usage-messages.js b/test-tap/reporters/improper-usage-messages.js index 0648db207..ace0ad2bc 100644 --- a/test-tap/reporters/improper-usage-messages.js +++ b/test-tap/reporters/improper-usage-messages.js @@ -4,8 +4,8 @@ import improperUsageMessages from '../../lib/reporters/improper-usage-messages.j test('results when nothing is applicable', t => { const error = { - assertion: 'assertion', improperUsage: { + assertion: 'assertion', name: 'VersionMismatchError', snapPath: 'path', snapVersion: 2, diff --git a/test-tap/reporters/tap.edgecases.v16.log b/test-tap/reporters/tap.edgecases.v16.log index 0b7bcf535..ae0f6cb72 100644 --- a/test-tap/reporters/tap.edgecases.v16.log +++ b/test-tap/reporters/tap.edgecases.v16.log @@ -1,14 +1,10 @@ TAP version 13 ---tty-stream-chunk-separator not ok 1 - ~/test-tap/fixture/report/edgecases/ast-syntax-error.cjs:3 -const fn = do { - ^^ - -SyntaxError: Unexpected token 'do' --- name: SyntaxError message: Unexpected token 'do' - at: '' + at: 'const fn = do {' ... ---tty-stream-chunk-separator not ok 2 - ast-syntax-error.cjs exited with a non-zero exit code: 1 @@ -21,7 +17,7 @@ not ok 4 - TypeError: test is not a function message: test is not a function at: >- Object. - (test-tap/fixture/report/edgecases/import-and-use-test-member.cjs:3:1) + (~/test-tap/fixture/report/edgecases/import-and-use-test-member.cjs:3:1) ... ---tty-stream-chunk-separator not ok 5 - import-and-use-test-member.cjs exited with a non-zero exit code: 1 @@ -32,7 +28,7 @@ not ok 7 - Error: throws --- name: Error message: throws - at: 'Object. (test-tap/fixture/report/edgecases/throws.cjs:1:7)' + at: 'Object. (~/test-tap/fixture/report/edgecases/throws.cjs:1:7)' ... ---tty-stream-chunk-separator not ok 8 - throws.cjs exited with a non-zero exit code: 1 diff --git a/test-tap/reporters/tap.edgecases.v18.log b/test-tap/reporters/tap.edgecases.v18.log index 0b7bcf535..ae0f6cb72 100644 --- a/test-tap/reporters/tap.edgecases.v18.log +++ b/test-tap/reporters/tap.edgecases.v18.log @@ -1,14 +1,10 @@ TAP version 13 ---tty-stream-chunk-separator not ok 1 - ~/test-tap/fixture/report/edgecases/ast-syntax-error.cjs:3 -const fn = do { - ^^ - -SyntaxError: Unexpected token 'do' --- name: SyntaxError message: Unexpected token 'do' - at: '' + at: 'const fn = do {' ... ---tty-stream-chunk-separator not ok 2 - ast-syntax-error.cjs exited with a non-zero exit code: 1 @@ -21,7 +17,7 @@ not ok 4 - TypeError: test is not a function message: test is not a function at: >- Object. - (test-tap/fixture/report/edgecases/import-and-use-test-member.cjs:3:1) + (~/test-tap/fixture/report/edgecases/import-and-use-test-member.cjs:3:1) ... ---tty-stream-chunk-separator not ok 5 - import-and-use-test-member.cjs exited with a non-zero exit code: 1 @@ -32,7 +28,7 @@ not ok 7 - Error: throws --- name: Error message: throws - at: 'Object. (test-tap/fixture/report/edgecases/throws.cjs:1:7)' + at: 'Object. (~/test-tap/fixture/report/edgecases/throws.cjs:1:7)' ... ---tty-stream-chunk-separator not ok 8 - throws.cjs exited with a non-zero exit code: 1 diff --git a/test-tap/reporters/tap.edgecases.v20.log b/test-tap/reporters/tap.edgecases.v20.log index 0b7bcf535..ae0f6cb72 100644 --- a/test-tap/reporters/tap.edgecases.v20.log +++ b/test-tap/reporters/tap.edgecases.v20.log @@ -1,14 +1,10 @@ TAP version 13 ---tty-stream-chunk-separator not ok 1 - ~/test-tap/fixture/report/edgecases/ast-syntax-error.cjs:3 -const fn = do { - ^^ - -SyntaxError: Unexpected token 'do' --- name: SyntaxError message: Unexpected token 'do' - at: '' + at: 'const fn = do {' ... ---tty-stream-chunk-separator not ok 2 - ast-syntax-error.cjs exited with a non-zero exit code: 1 @@ -21,7 +17,7 @@ not ok 4 - TypeError: test is not a function message: test is not a function at: >- Object. - (test-tap/fixture/report/edgecases/import-and-use-test-member.cjs:3:1) + (~/test-tap/fixture/report/edgecases/import-and-use-test-member.cjs:3:1) ... ---tty-stream-chunk-separator not ok 5 - import-and-use-test-member.cjs exited with a non-zero exit code: 1 @@ -32,7 +28,7 @@ not ok 7 - Error: throws --- name: Error message: throws - at: 'Object. (test-tap/fixture/report/edgecases/throws.cjs:1:7)' + at: 'Object. (~/test-tap/fixture/report/edgecases/throws.cjs:1:7)' ... ---tty-stream-chunk-separator not ok 8 - throws.cjs exited with a non-zero exit code: 1 diff --git a/test-tap/reporters/tap.failfast.v16.log b/test-tap/reporters/tap.failfast.v16.log index 5b44649f1..7092b0590 100644 --- a/test-tap/reporters/tap.failfast.v16.log +++ b/test-tap/reporters/tap.failfast.v16.log @@ -3,9 +3,9 @@ TAP version 13 not ok 1 - a › fails --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/failfast/a.cjs:3:22' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator diff --git a/test-tap/reporters/tap.failfast.v18.log b/test-tap/reporters/tap.failfast.v18.log index 5b44649f1..7092b0590 100644 --- a/test-tap/reporters/tap.failfast.v18.log +++ b/test-tap/reporters/tap.failfast.v18.log @@ -3,9 +3,9 @@ TAP version 13 not ok 1 - a › fails --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/failfast/a.cjs:3:22' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator diff --git a/test-tap/reporters/tap.failfast.v20.log b/test-tap/reporters/tap.failfast.v20.log index 5b44649f1..7092b0590 100644 --- a/test-tap/reporters/tap.failfast.v20.log +++ b/test-tap/reporters/tap.failfast.v20.log @@ -3,9 +3,9 @@ TAP version 13 not ok 1 - a › fails --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/failfast/a.cjs:3:22' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator diff --git a/test-tap/reporters/tap.failfast2.v16.log b/test-tap/reporters/tap.failfast2.v16.log index c10c8cd9f..9c78edb09 100644 --- a/test-tap/reporters/tap.failfast2.v16.log +++ b/test-tap/reporters/tap.failfast2.v16.log @@ -3,9 +3,9 @@ TAP version 13 not ok 1 - a › fails --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/failfast2/a.cjs:3:22' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator # 1 test remaining in a.cjs diff --git a/test-tap/reporters/tap.failfast2.v18.log b/test-tap/reporters/tap.failfast2.v18.log index c10c8cd9f..9c78edb09 100644 --- a/test-tap/reporters/tap.failfast2.v18.log +++ b/test-tap/reporters/tap.failfast2.v18.log @@ -3,9 +3,9 @@ TAP version 13 not ok 1 - a › fails --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/failfast2/a.cjs:3:22' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator # 1 test remaining in a.cjs diff --git a/test-tap/reporters/tap.failfast2.v20.log b/test-tap/reporters/tap.failfast2.v20.log index c10c8cd9f..9c78edb09 100644 --- a/test-tap/reporters/tap.failfast2.v20.log +++ b/test-tap/reporters/tap.failfast2.v20.log @@ -3,9 +3,9 @@ TAP version 13 not ok 1 - a › fails --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/failfast2/a.cjs:3:22' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator # 1 test remaining in a.cjs diff --git a/test-tap/reporters/tap.js b/test-tap/reporters/tap.js index 47050fad8..034186164 100644 --- a/test-tap/reporters/tap.js +++ b/test-tap/reporters/tap.js @@ -25,6 +25,7 @@ test(async t => { projectDir: report.projectDir(type), reportStream: tty, stdStream: tty, + sanitizeStackOutput: report.sanitizers.cwd, }); return report[type](reporter) .then(() => { diff --git a/test-tap/reporters/tap.regular.v16.log b/test-tap/reporters/tap.regular.v16.log index 3fd8b13c2..08a91e9cc 100644 --- a/test-tap/reporters/tap.regular.v16.log +++ b/test-tap/reporters/tap.regular.v16.log @@ -4,7 +4,7 @@ not ok 1 - TypeError: test.serial.test is not a function --- name: TypeError message: test.serial.test is not a function - at: 'Object. (test-tap/fixture/report/regular/bad-test-chain.cjs:3:13)' + at: 'Object. (~/test-tap/fixture/report/regular/bad-test-chain.cjs:3:13)' ... ---tty-stream-chunk-separator not ok 2 - bad-test-chain.cjs exited with a non-zero exit code: 1 @@ -12,8 +12,8 @@ not ok 2 - bad-test-chain.cjs exited with a non-zero exit code: 1 not ok 3 - nested-objects › format with max depth 4 --- name: AssertionError - assertion: deepEqual - values: + assertion: t.deepEqual() + details: 'Difference (- actual, + expected):': |2- { a: { @@ -29,14 +29,15 @@ not ok 3 - nested-objects › format with max depth 4 + }, + }, } - at: 'test-tap/fixture/report/regular/nested-objects.cjs:29:4' + message: '' + at: 'ExecutionContext.deepEqual (/lib/assert.js:351:9)' ... ---tty-stream-chunk-separator not ok 4 - nested-objects › format like with max depth 4 --- name: AssertionError - assertion: like - values: + assertion: t.like() + details: 'Difference (- actual, + expected):': |2- { a: { @@ -46,7 +47,8 @@ not ok 4 - nested-objects › format like with max depth 4 }, }, } - at: 'test-tap/fixture/report/regular/nested-objects.cjs:55:4' + message: '' + at: 'ExecutionContext.like (/lib/assert.js:413:9)' ... ---tty-stream-chunk-separator # output-in-hook › before hook @@ -68,9 +70,9 @@ ok 5 - output-in-hook › passing test not ok 6 - output-in-hook › failing test --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/regular/output-in-hook.cjs:34:4' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator # output-in-hook › afterEach hook for passing test @@ -98,20 +100,20 @@ ok 9 - test › passes not ok 10 - test › fails --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/regular/test.cjs:9:22' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator ok 11 - test › known failure ---tty-stream-chunk-separator not ok 12 - test › no longer failing --- - name: Error + name: AssertionError message: >- Test was expected to fail, but succeeded, you should stop marking the test as failing - at: '' + at: 'Test.finish (/lib/test.js:609:28)' ... ---tty-stream-chunk-separator not ok 13 - test › logs @@ -119,113 +121,99 @@ not ok 13 - test › logs # world --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/regular/test.cjs:18:4' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator not ok 14 - test › formatted --- name: AssertionError - assertion: deepEqual - values: + assertion: t.deepEqual() + details: 'Difference (- actual, + expected):': |- - 'foo' + 'bar' - at: 'test-tap/fixture/report/regular/test.cjs:22:4' + message: '' + at: 'ExecutionContext.deepEqual (/lib/assert.js:351:9)' ... ---tty-stream-chunk-separator not ok 15 - test › implementation throws non-error --- name: AssertionError - message: Error thrown in test - values: + details: 'Error thrown in test:': 'null' - at: '' + message: Error thrown in test + at: 'Test.run (/lib/test.js:526:25)' ... ---tty-stream-chunk-separator not ok 16 - traces-in-t-throws › throws --- name: AssertionError - assertion: throws - values: + assertion: t.throws() + details: 'Function threw unexpected exception:': |- Error { message: 'uh-oh', } 'Expected instance of:': 'Function TypeError {}' - at: >- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - - t.throws.instanceOf - (test-tap/fixture/report/regular/traces-in-t-throws.cjs:12:17) - - test-tap/fixture/report/regular/traces-in-t-throws.cjs:12:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 17 - traces-in-t-throws › notThrows --- name: AssertionError - assertion: notThrows - values: + assertion: t.notThrows() + details: 'Function threw:': |- Error { message: 'uh-oh', } - at: |- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - test-tap/fixture/report/regular/traces-in-t-throws.cjs:16:20 - test-tap/fixture/report/regular/traces-in-t-throws.cjs:16:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 18 - traces-in-t-throws › notThrowsAsync --- name: AssertionError - assertion: notThrowsAsync - values: + assertion: t.notThrowsAsync() + details: 'Function threw:': |- Error { message: 'uh-oh', } - at: |- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - test-tap/fixture/report/regular/traces-in-t-throws.cjs:20:25 - test-tap/fixture/report/regular/traces-in-t-throws.cjs:20:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 19 - traces-in-t-throws › throwsAsync --- name: AssertionError - assertion: throwsAsync - values: + assertion: t.throwsAsync() + details: 'Function threw synchronously. Use `t.throws()` instead:': |- Error { message: 'uh-oh', } - at: >- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - - t.throwsAsync.instanceOf - (test-tap/fixture/report/regular/traces-in-t-throws.cjs:24:22) - - test-tap/fixture/report/regular/traces-in-t-throws.cjs:24:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 20 - traces-in-t-throws › throwsAsync different error --- name: AssertionError - assertion: throwsAsync - values: + assertion: t.throwsAsync() + details: 'Returned promise rejected with unexpected exception:': |- Error { message: 'uh-oh', } 'Expected instance of:': 'Function TypeError {}' + message: '' at: >- returnRejectedPromise - (test-tap/fixture/report/regular/traces-in-t-throws.cjs:8:24) - - test-tap/fixture/report/regular/traces-in-t-throws.cjs:27:44 + (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:8:24) ... ---tty-stream-chunk-separator ok 21 - uncaught-exception › passes @@ -236,7 +224,7 @@ not ok 22 - Error: Can’t catch me message: Can’t catch me at: >- Immediate. - (test-tap/fixture/report/regular/uncaught-exception.cjs:5:9) + (~/test-tap/fixture/report/regular/uncaught-exception.cjs:5:9) ... ---tty-stream-chunk-separator not ok 23 - uncaught-exception.cjs exited with a non-zero exit code: 1 @@ -249,12 +237,12 @@ not ok 26 - Error: Can’t catch me --- name: Error message: Can’t catch me - at: 'passes (test-tap/fixture/report/regular/unhandled-rejection.cjs:4:17)' + at: 'passes (~/test-tap/fixture/report/regular/unhandled-rejection.cjs:4:17)' ... ---tty-stream-chunk-separator not ok 27 - unhandled-rejection --- - message: Non-error object + message: Non-native error formatted: 'null' ... ---tty-stream-chunk-separator diff --git a/test-tap/reporters/tap.regular.v18.log b/test-tap/reporters/tap.regular.v18.log index 3fd8b13c2..08a91e9cc 100644 --- a/test-tap/reporters/tap.regular.v18.log +++ b/test-tap/reporters/tap.regular.v18.log @@ -4,7 +4,7 @@ not ok 1 - TypeError: test.serial.test is not a function --- name: TypeError message: test.serial.test is not a function - at: 'Object. (test-tap/fixture/report/regular/bad-test-chain.cjs:3:13)' + at: 'Object. (~/test-tap/fixture/report/regular/bad-test-chain.cjs:3:13)' ... ---tty-stream-chunk-separator not ok 2 - bad-test-chain.cjs exited with a non-zero exit code: 1 @@ -12,8 +12,8 @@ not ok 2 - bad-test-chain.cjs exited with a non-zero exit code: 1 not ok 3 - nested-objects › format with max depth 4 --- name: AssertionError - assertion: deepEqual - values: + assertion: t.deepEqual() + details: 'Difference (- actual, + expected):': |2- { a: { @@ -29,14 +29,15 @@ not ok 3 - nested-objects › format with max depth 4 + }, + }, } - at: 'test-tap/fixture/report/regular/nested-objects.cjs:29:4' + message: '' + at: 'ExecutionContext.deepEqual (/lib/assert.js:351:9)' ... ---tty-stream-chunk-separator not ok 4 - nested-objects › format like with max depth 4 --- name: AssertionError - assertion: like - values: + assertion: t.like() + details: 'Difference (- actual, + expected):': |2- { a: { @@ -46,7 +47,8 @@ not ok 4 - nested-objects › format like with max depth 4 }, }, } - at: 'test-tap/fixture/report/regular/nested-objects.cjs:55:4' + message: '' + at: 'ExecutionContext.like (/lib/assert.js:413:9)' ... ---tty-stream-chunk-separator # output-in-hook › before hook @@ -68,9 +70,9 @@ ok 5 - output-in-hook › passing test not ok 6 - output-in-hook › failing test --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/regular/output-in-hook.cjs:34:4' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator # output-in-hook › afterEach hook for passing test @@ -98,20 +100,20 @@ ok 9 - test › passes not ok 10 - test › fails --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/regular/test.cjs:9:22' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator ok 11 - test › known failure ---tty-stream-chunk-separator not ok 12 - test › no longer failing --- - name: Error + name: AssertionError message: >- Test was expected to fail, but succeeded, you should stop marking the test as failing - at: '' + at: 'Test.finish (/lib/test.js:609:28)' ... ---tty-stream-chunk-separator not ok 13 - test › logs @@ -119,113 +121,99 @@ not ok 13 - test › logs # world --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/regular/test.cjs:18:4' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator not ok 14 - test › formatted --- name: AssertionError - assertion: deepEqual - values: + assertion: t.deepEqual() + details: 'Difference (- actual, + expected):': |- - 'foo' + 'bar' - at: 'test-tap/fixture/report/regular/test.cjs:22:4' + message: '' + at: 'ExecutionContext.deepEqual (/lib/assert.js:351:9)' ... ---tty-stream-chunk-separator not ok 15 - test › implementation throws non-error --- name: AssertionError - message: Error thrown in test - values: + details: 'Error thrown in test:': 'null' - at: '' + message: Error thrown in test + at: 'Test.run (/lib/test.js:526:25)' ... ---tty-stream-chunk-separator not ok 16 - traces-in-t-throws › throws --- name: AssertionError - assertion: throws - values: + assertion: t.throws() + details: 'Function threw unexpected exception:': |- Error { message: 'uh-oh', } 'Expected instance of:': 'Function TypeError {}' - at: >- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - - t.throws.instanceOf - (test-tap/fixture/report/regular/traces-in-t-throws.cjs:12:17) - - test-tap/fixture/report/regular/traces-in-t-throws.cjs:12:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 17 - traces-in-t-throws › notThrows --- name: AssertionError - assertion: notThrows - values: + assertion: t.notThrows() + details: 'Function threw:': |- Error { message: 'uh-oh', } - at: |- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - test-tap/fixture/report/regular/traces-in-t-throws.cjs:16:20 - test-tap/fixture/report/regular/traces-in-t-throws.cjs:16:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 18 - traces-in-t-throws › notThrowsAsync --- name: AssertionError - assertion: notThrowsAsync - values: + assertion: t.notThrowsAsync() + details: 'Function threw:': |- Error { message: 'uh-oh', } - at: |- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - test-tap/fixture/report/regular/traces-in-t-throws.cjs:20:25 - test-tap/fixture/report/regular/traces-in-t-throws.cjs:20:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 19 - traces-in-t-throws › throwsAsync --- name: AssertionError - assertion: throwsAsync - values: + assertion: t.throwsAsync() + details: 'Function threw synchronously. Use `t.throws()` instead:': |- Error { message: 'uh-oh', } - at: >- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - - t.throwsAsync.instanceOf - (test-tap/fixture/report/regular/traces-in-t-throws.cjs:24:22) - - test-tap/fixture/report/regular/traces-in-t-throws.cjs:24:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 20 - traces-in-t-throws › throwsAsync different error --- name: AssertionError - assertion: throwsAsync - values: + assertion: t.throwsAsync() + details: 'Returned promise rejected with unexpected exception:': |- Error { message: 'uh-oh', } 'Expected instance of:': 'Function TypeError {}' + message: '' at: >- returnRejectedPromise - (test-tap/fixture/report/regular/traces-in-t-throws.cjs:8:24) - - test-tap/fixture/report/regular/traces-in-t-throws.cjs:27:44 + (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:8:24) ... ---tty-stream-chunk-separator ok 21 - uncaught-exception › passes @@ -236,7 +224,7 @@ not ok 22 - Error: Can’t catch me message: Can’t catch me at: >- Immediate. - (test-tap/fixture/report/regular/uncaught-exception.cjs:5:9) + (~/test-tap/fixture/report/regular/uncaught-exception.cjs:5:9) ... ---tty-stream-chunk-separator not ok 23 - uncaught-exception.cjs exited with a non-zero exit code: 1 @@ -249,12 +237,12 @@ not ok 26 - Error: Can’t catch me --- name: Error message: Can’t catch me - at: 'passes (test-tap/fixture/report/regular/unhandled-rejection.cjs:4:17)' + at: 'passes (~/test-tap/fixture/report/regular/unhandled-rejection.cjs:4:17)' ... ---tty-stream-chunk-separator not ok 27 - unhandled-rejection --- - message: Non-error object + message: Non-native error formatted: 'null' ... ---tty-stream-chunk-separator diff --git a/test-tap/reporters/tap.regular.v20.log b/test-tap/reporters/tap.regular.v20.log index 3fd8b13c2..08a91e9cc 100644 --- a/test-tap/reporters/tap.regular.v20.log +++ b/test-tap/reporters/tap.regular.v20.log @@ -4,7 +4,7 @@ not ok 1 - TypeError: test.serial.test is not a function --- name: TypeError message: test.serial.test is not a function - at: 'Object. (test-tap/fixture/report/regular/bad-test-chain.cjs:3:13)' + at: 'Object. (~/test-tap/fixture/report/regular/bad-test-chain.cjs:3:13)' ... ---tty-stream-chunk-separator not ok 2 - bad-test-chain.cjs exited with a non-zero exit code: 1 @@ -12,8 +12,8 @@ not ok 2 - bad-test-chain.cjs exited with a non-zero exit code: 1 not ok 3 - nested-objects › format with max depth 4 --- name: AssertionError - assertion: deepEqual - values: + assertion: t.deepEqual() + details: 'Difference (- actual, + expected):': |2- { a: { @@ -29,14 +29,15 @@ not ok 3 - nested-objects › format with max depth 4 + }, + }, } - at: 'test-tap/fixture/report/regular/nested-objects.cjs:29:4' + message: '' + at: 'ExecutionContext.deepEqual (/lib/assert.js:351:9)' ... ---tty-stream-chunk-separator not ok 4 - nested-objects › format like with max depth 4 --- name: AssertionError - assertion: like - values: + assertion: t.like() + details: 'Difference (- actual, + expected):': |2- { a: { @@ -46,7 +47,8 @@ not ok 4 - nested-objects › format like with max depth 4 }, }, } - at: 'test-tap/fixture/report/regular/nested-objects.cjs:55:4' + message: '' + at: 'ExecutionContext.like (/lib/assert.js:413:9)' ... ---tty-stream-chunk-separator # output-in-hook › before hook @@ -68,9 +70,9 @@ ok 5 - output-in-hook › passing test not ok 6 - output-in-hook › failing test --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/regular/output-in-hook.cjs:34:4' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator # output-in-hook › afterEach hook for passing test @@ -98,20 +100,20 @@ ok 9 - test › passes not ok 10 - test › fails --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/regular/test.cjs:9:22' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator ok 11 - test › known failure ---tty-stream-chunk-separator not ok 12 - test › no longer failing --- - name: Error + name: AssertionError message: >- Test was expected to fail, but succeeded, you should stop marking the test as failing - at: '' + at: 'Test.finish (/lib/test.js:609:28)' ... ---tty-stream-chunk-separator not ok 13 - test › logs @@ -119,113 +121,99 @@ not ok 13 - test › logs # world --- name: AssertionError + assertion: t.fail() message: Test failed via `t.fail()` - assertion: fail - at: 'test-tap/fixture/report/regular/test.cjs:18:4' + at: 'ExecutionContext.fail (/lib/assert.js:285:9)' ... ---tty-stream-chunk-separator not ok 14 - test › formatted --- name: AssertionError - assertion: deepEqual - values: + assertion: t.deepEqual() + details: 'Difference (- actual, + expected):': |- - 'foo' + 'bar' - at: 'test-tap/fixture/report/regular/test.cjs:22:4' + message: '' + at: 'ExecutionContext.deepEqual (/lib/assert.js:351:9)' ... ---tty-stream-chunk-separator not ok 15 - test › implementation throws non-error --- name: AssertionError - message: Error thrown in test - values: + details: 'Error thrown in test:': 'null' - at: '' + message: Error thrown in test + at: 'Test.run (/lib/test.js:526:25)' ... ---tty-stream-chunk-separator not ok 16 - traces-in-t-throws › throws --- name: AssertionError - assertion: throws - values: + assertion: t.throws() + details: 'Function threw unexpected exception:': |- Error { message: 'uh-oh', } 'Expected instance of:': 'Function TypeError {}' - at: >- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - - t.throws.instanceOf - (test-tap/fixture/report/regular/traces-in-t-throws.cjs:12:17) - - test-tap/fixture/report/regular/traces-in-t-throws.cjs:12:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 17 - traces-in-t-throws › notThrows --- name: AssertionError - assertion: notThrows - values: + assertion: t.notThrows() + details: 'Function threw:': |- Error { message: 'uh-oh', } - at: |- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - test-tap/fixture/report/regular/traces-in-t-throws.cjs:16:20 - test-tap/fixture/report/regular/traces-in-t-throws.cjs:16:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 18 - traces-in-t-throws › notThrowsAsync --- name: AssertionError - assertion: notThrowsAsync - values: + assertion: t.notThrowsAsync() + details: 'Function threw:': |- Error { message: 'uh-oh', } - at: |- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - test-tap/fixture/report/regular/traces-in-t-throws.cjs:20:25 - test-tap/fixture/report/regular/traces-in-t-throws.cjs:20:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 19 - traces-in-t-throws › throwsAsync --- name: AssertionError - assertion: throwsAsync - values: + assertion: t.throwsAsync() + details: 'Function threw synchronously. Use `t.throws()` instead:': |- Error { message: 'uh-oh', } - at: >- - throwError (test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8) - - t.throwsAsync.instanceOf - (test-tap/fixture/report/regular/traces-in-t-throws.cjs:24:22) - - test-tap/fixture/report/regular/traces-in-t-throws.cjs:24:4 + message: '' + at: 'throwError (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:4:8)' ... ---tty-stream-chunk-separator not ok 20 - traces-in-t-throws › throwsAsync different error --- name: AssertionError - assertion: throwsAsync - values: + assertion: t.throwsAsync() + details: 'Returned promise rejected with unexpected exception:': |- Error { message: 'uh-oh', } 'Expected instance of:': 'Function TypeError {}' + message: '' at: >- returnRejectedPromise - (test-tap/fixture/report/regular/traces-in-t-throws.cjs:8:24) - - test-tap/fixture/report/regular/traces-in-t-throws.cjs:27:44 + (~/test-tap/fixture/report/regular/traces-in-t-throws.cjs:8:24) ... ---tty-stream-chunk-separator ok 21 - uncaught-exception › passes @@ -236,7 +224,7 @@ not ok 22 - Error: Can’t catch me message: Can’t catch me at: >- Immediate. - (test-tap/fixture/report/regular/uncaught-exception.cjs:5:9) + (~/test-tap/fixture/report/regular/uncaught-exception.cjs:5:9) ... ---tty-stream-chunk-separator not ok 23 - uncaught-exception.cjs exited with a non-zero exit code: 1 @@ -249,12 +237,12 @@ not ok 26 - Error: Can’t catch me --- name: Error message: Can’t catch me - at: 'passes (test-tap/fixture/report/regular/unhandled-rejection.cjs:4:17)' + at: 'passes (~/test-tap/fixture/report/regular/unhandled-rejection.cjs:4:17)' ... ---tty-stream-chunk-separator not ok 27 - unhandled-rejection --- - message: Non-error object + message: Non-native error formatted: 'null' ... ---tty-stream-chunk-separator diff --git a/test-tap/serialize-error.js b/test-tap/serialize-error.js deleted file mode 100644 index f293988db..000000000 --- a/test-tap/serialize-error.js +++ /dev/null @@ -1,83 +0,0 @@ -import {test} from 'tap'; - -import * as avaAssert from '../lib/assert.js'; -import serializeError from '../lib/serialize-error.js'; -import {set as setOptions} from '../lib/worker/options.cjs'; - -setOptions({}); - -const serialize = error => serializeError('Test', true, error, import.meta.url); - -test('serialize standard props', t => { - const error = new Error('Hello'); - const serializedError = serialize(error); - - t.equal(Object.keys(serializedError).length, 9); - t.equal(serializedError.avaAssertionError, false); - t.equal(serializedError.nonErrorObject, false); - t.same(serializedError.object, {}); - t.equal(serializedError.name, 'Error'); - t.equal(serializedError.stack, error.stack); - t.equal(serializedError.message, 'Hello'); - t.equal(serializedError.summary, 'Error: Hello'); - t.equal(serializedError.shouldBeautifyStack, true); - t.equal(serializedError.source.isWithinProject, true); - t.equal(serializedError.source.isDependency, false); - t.equal(typeof serializedError.source.file, 'string'); - t.equal(typeof serializedError.source.line, 'number'); - t.end(); -}); - -test('additional error properties are preserved', t => { - const serializedError = serialize(Object.assign(new Error(), {foo: 'bar'})); - t.same(serializedError.object, {foo: 'bar'}); - t.end(); -}); - -test('source file is an absolute path', t => { - const error = new Error('Hello'); - const serializedError = serialize(error); - - t.equal(serializedError.source.file, import.meta.url); - t.end(); -}); - -test('sets avaAssertionError to true if indeed an assertion error', t => { - const error = new avaAssert.AssertionError({}); - const serializedError = serialize(error); - t.ok(serializedError.avaAssertionError); - t.end(); -}); - -test('includes values of assertion errors', t => { - const error = new avaAssert.AssertionError({ - assertion: 'is', - values: [{label: 'actual:', formatted: '1'}, {label: 'expected:', formatted: 'a'}], - }); - - const serializedError = serialize(error); - t.equal(serializedError.values, error.values); - t.end(); -}); - -test('remove non-string error properties', t => { - const error = { - name: [42], - stack: /re/g, - }; - const serializedError = serialize(error); - t.equal(serializedError.name, undefined); - t.equal(serializedError.stack, undefined); - t.end(); -}); - -test('creates multiline summaries for syntax errors', t => { - const error = new SyntaxError(); - Object.defineProperty(error, 'stack', { - value: 'Hello\nThere\nSyntaxError here\nIgnore me', - }); - const serializedError = serialize(error); - t.equal(serializedError.name, 'SyntaxError'); - t.equal(serializedError.summary, 'Hello\nThere\nSyntaxError here'); - t.end(); -}); diff --git a/test-tap/test.js b/test-tap/test.js index 261e108e2..e76d21498 100644 --- a/test-tap/test.js +++ b/test-tap/test.js @@ -105,9 +105,9 @@ test('wrap non-assertion errors', t => { t.equal(result.passed, false); t.equal(result.error.message, 'Error thrown in test'); t.equal(result.error.name, 'AssertionError'); - t.equal(result.error.values.length, 1); - t.equal(result.error.values[0].label, 'Error thrown in test:'); - t.match(result.error.values[0].formatted, /Error/); + t.equal(result.error.formattedDetails.length, 1); + t.equal(result.error.formattedDetails[0].label, 'Error thrown in test:'); + t.match(result.error.formattedDetails[0].formatted, /Error/); }); }); @@ -212,9 +212,9 @@ test('fails with the first assertError', t => ava(a => { }).run().then(result => { t.equal(result.passed, false); t.equal(result.error.name, 'AssertionError'); - t.equal(result.error.values.length, 1); - t.equal(result.error.values[0].label, 'Difference (- actual, + expected):'); - t.match(result.error.values[0].formatted, /- 1\n\+ 2/); + t.equal(result.error.formattedDetails.length, 1); + t.equal(result.error.formattedDetails[0].label, 'Difference (- actual, + expected):'); + t.match(result.error.formattedDetails[0].formatted, /- 1\n\+ 2/); })); test('failing pending assertion causes test to fail, not promise rejection', t => ava(a => a.throwsAsync(Promise.resolve()).then(() => { @@ -230,9 +230,9 @@ test('fails with thrown falsy value', t => ava(() => { t.equal(result.passed, false); t.equal(result.error.message, 'Error thrown in test'); t.equal(result.error.name, 'AssertionError'); - t.equal(result.error.values.length, 1); - t.equal(result.error.values[0].label, 'Error thrown in test:'); - t.match(result.error.values[0].formatted, /0/); + t.equal(result.error.formattedDetails.length, 1); + t.equal(result.error.formattedDetails[0].label, 'Error thrown in test:'); + t.match(result.error.formattedDetails[0].formatted, /0/); })); test('fails with thrown non-error object', t => { @@ -243,9 +243,9 @@ test('fails with thrown non-error object', t => { t.equal(result.passed, false); t.equal(result.error.message, 'Error thrown in test'); t.equal(result.error.name, 'AssertionError'); - t.equal(result.error.values.length, 1); - t.equal(result.error.values[0].label, 'Error thrown in test:'); - t.match(result.error.values[0].formatted, /.*{.*\n.*foo: 'bar'/); + t.equal(result.error.formattedDetails.length, 1); + t.equal(result.error.formattedDetails[0].label, 'Error thrown in test:'); + t.match(result.error.formattedDetails[0].formatted, /.*{.*\n.*foo: 'bar'/); }); }); diff --git a/test/builtin-nodejs-assert/test.js b/test/builtin-nodejs-assert/test.js index 3419d4951..10aaf81f3 100644 --- a/test/builtin-nodejs-assert/test.js +++ b/test/builtin-nodejs-assert/test.js @@ -15,12 +15,12 @@ test('node assertion failures are reported to the console when running in a term const result = await t.throwsAsync(fixture(['assert-failure.js'], options)); const error = result.stats.getError(result.stats.failed[0]); - t.true(error.values.every(value => value.label.includes('Assertion failed'))); + t.true(error.formattedDetails.every(value => value.label.includes('Assertion failed'))); }); test('node assertion failures are reported to the console when not running in a terminal', async t => { const result = await t.throwsAsync(fixture(['assert-failure.js'])); const error = result.stats.getError(result.stats.failed[0]); - t.true(error.values.every(value => value.label.includes('Assertion failed'))); + t.true(error.formattedDetails.every(value => value.label.includes('Assertion failed'))); }); diff --git a/test/external-assertions/snapshots/test.js.md b/test/external-assertions/snapshots/test.js.md index a6981b7e0..5db2b6acb 100644 --- a/test/external-assertions/snapshots/test.js.md +++ b/test/external-assertions/snapshots/test.js.md @@ -14,34 +14,33 @@ Generated by [AVA](https://avajs.dev). ─␊ ␊ test␊ - ␊ - assert-failure.js:6␊ - ␊ - 5: test('test', () => {␊ - 6: assert(false); ␊ - 7: }); ␊ ␊ Assertion failed: ␊ ␊ false == true␊ ␊ - › file://assert-failure.js:6:2␊ + AssertionError [ERR_ASSERTION]: false == true␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ ␊ ␊ ␊ test async␊ - ␊ - assert-failure.js:10␊ - ␊ - 9: test('test async', async () => { ␊ - 10: assert(await Promise.resolve(false));␊ - 11: }); ␊ ␊ Assertion failed: ␊ ␊ false == true␊ ␊ - › file://assert-failure.js:10:2␊ + AssertionError [ERR_ASSERTION]: false == true␊ + at ---␊ + at ---␊ ␊ ─␊ ␊ @@ -57,12 +56,6 @@ Generated by [AVA](https://avajs.dev). ─␊ ␊ test␊ - ␊ - expect-failure.js:5␊ - ␊ - 4: test('test', () => { ␊ - 5: expect(false).toBeTruthy();␊ - 6: }); ␊ ␊ Assertion failed: ␊ ␊ @@ -70,18 +63,22 @@ Generated by [AVA](https://avajs.dev). ␊ Received: false␊ ␊ - › Received: false␊ - › file://expect-failure.js:5:16␊ - ␊ + Error: expect(received).toBeTruthy()␊ ␊ + Received: false␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ + at ---␊ ␊ - test async␊ ␊ - expect-failure.js:9␊ ␊ - 8: test('test async', async () => { ␊ - 9: expect(await Promise.resolve(false)).toBeTruthy();␊ - 10: }); ␊ + test async␊ ␊ Assertion failed: ␊ ␊ @@ -89,8 +86,11 @@ Generated by [AVA](https://avajs.dev). ␊ Received: false␊ ␊ - › Received: false␊ - › file://expect-failure.js:9:39␊ + Error: expect(received).toBeTruthy()␊ + ␊ + Received: false␊ + at ---␊ + at ---␊ ␊ ─␊ ␊ diff --git a/test/external-assertions/snapshots/test.js.snap b/test/external-assertions/snapshots/test.js.snap index e9ee42e2a..f9f9d3962 100644 Binary files a/test/external-assertions/snapshots/test.js.snap and b/test/external-assertions/snapshots/test.js.snap differ diff --git a/test/external-assertions/test.js b/test/external-assertions/test.js index 65b2ddfc7..4995d5535 100644 --- a/test/external-assertions/test.js +++ b/test/external-assertions/test.js @@ -4,10 +4,10 @@ import {fixture} from '../helpers/exec.js'; test('node assertion ', async t => { const result = await t.throwsAsync(fixture(['assert-failure.js'])); - t.snapshot(result.stdout.replace(/\r/g, '').replace(/\/{3}/g, '//')); + t.snapshot(result.stdout.replace(/\r/g, '').replace(/\/{3}/g, '//').replace(/at.*\n/g, 'at ---\n')); }); test('expect error ', async t => { const result = await t.throwsAsync(fixture(['expect-failure.js'])); - t.snapshot(result.stdout.replace(/\r/g, '').replace(/\/{3}/g, '//')); + t.snapshot(result.stdout.replace(/\r/g, '').replace(/\/{3}/g, '//').replace(/at.*\n/g, 'at ---\n')); }); diff --git a/test/test-timeouts/snapshots/test.js.md b/test/test-timeouts/snapshots/test.js.md index 5c7c42ab1..cf3a2ab68 100644 --- a/test/test-timeouts/snapshots/test.js.md +++ b/test/test-timeouts/snapshots/test.js.md @@ -10,7 +10,7 @@ Generated by [AVA](https://avajs.dev). 'The assertion message must be a string' -> formatted values +> formatted details [ { diff --git a/test/test-timeouts/snapshots/test.js.snap b/test/test-timeouts/snapshots/test.js.snap index ccf0fc9c6..3f85c7ca5 100644 Binary files a/test/test-timeouts/snapshots/test.js.snap and b/test/test-timeouts/snapshots/test.js.snap differ diff --git a/test/test-timeouts/test.js b/test/test-timeouts/test.js index e63a876e6..4f529e950 100644 --- a/test/test-timeouts/test.js +++ b/test/test-timeouts/test.js @@ -12,5 +12,5 @@ test('timeout messages must be strings', async t => { const result = await t.throwsAsync(fixture(['invalid-message.js'])); const error = result.stats.getError(result.stats.failed[0]); t.snapshot(error.message, 'error message'); - t.snapshot(error.values, 'formatted values'); + t.snapshot(error.formattedDetails, 'formatted details'); });