From f6d436bae25d69c49d70197b7188b9d086e72a65 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 12 Jun 2024 11:20:14 +0900 Subject: [PATCH 01/33] fix(expect)!: check more properties for error equality --- packages/expect/src/jest-utils.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index 03d6f063ff57..a33559ff5766 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -95,7 +95,7 @@ function eq( } if (a instanceof Error && b instanceof Error) - return a.message === b.message + return isErrorEqual(a, b, customTesters) if (typeof URL === 'function' && a instanceof URL && b instanceof URL) return a.href === b.href @@ -194,6 +194,22 @@ function eq( return result } +function isErrorEqual(a: Error, b: Error, customTesters: Tester[]) { + // assert.deepStrictEqual https://nodejs.org/docs/latest-v22.x/api/assert.html#comparison-details + // - [[Prototype]] of objects are compared using the === operator. + // - Only enumerable "own" properties are considered. + // - Error names, messages, causes, and errors are always compared, even if these are not enumerable properties. errors is also compared. + // (NOTE: causes and errors are added in v22) + + // TODO: causes and errors + return ( + Object.getPrototypeOf(a) === Object.getPrototypeOf(b) + && a.name === b.name + && a.message === b.message + && equals({ ...a }, { ...b }, customTesters) + ) +} + function keys(obj: object, hasKey: (obj: object, key: string) => boolean) { const keys = [] From a768f59d7d4d6d46f0ff2d82056cc92e7a7d33dc Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 12 Jun 2024 11:26:00 +0900 Subject: [PATCH 02/33] test: update --- test/core/test/expect.test.ts | 6 +++--- test/core/test/jest-expect.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/test/core/test/expect.test.ts b/test/core/test/expect.test.ts index 116328dd7824..41a46d471b70 100644 --- a/test/core/test/expect.test.ts +++ b/test/core/test/expect.test.ts @@ -295,8 +295,8 @@ describe('Error equality', () => { // different custom property const e1 = new MyError('hi', 'a') const e2 = new MyError('hi', 'b') - expect(e1).toEqual(e2) - expect(e1).toStrictEqual(e2) + expect(e1).not.toEqual(e2) + expect(e1).not.toStrictEqual(e2) assert.deepEqual(e1, e2) nodeAssert.notDeepStrictEqual(e1, e2) } @@ -315,7 +315,7 @@ describe('Error equality', () => { // different class const e1 = new MyError('hello', 'a') const e2 = new YourError('hello', 'a') - expect(e1).toEqual(e2) + expect(e1).not.toEqual(e2) expect(e1).not.toStrictEqual(e2) // toStrictEqual checks constructor already assert.deepEqual(e1, e2) nodeAssert.notDeepStrictEqual(e1, e2) diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 71f55c483d65..92ac7bb67bde 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -845,7 +845,7 @@ describe('async expect', () => { expect.unreachable() } catch (error) { - expect(error).toEqual(new Error('promise resolved "+0" instead of rejecting')) + expect(error).toMatchObject({ message: 'promise resolved "+0" instead of rejecting' }) } try { @@ -853,7 +853,7 @@ describe('async expect', () => { expect.unreachable() } catch (error) { - expect(error).toEqual(new Error('promise rejected "+0" instead of resolving')) + expect(error).toMatchObject({ message: 'promise rejected "+0" instead of resolving' }) } }) }) From 7f3c0c2a34c723f781f24e16ce80535b7d95c216 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 12 Jun 2024 11:27:46 +0900 Subject: [PATCH 03/33] chore: comment --- packages/expect/src/jest-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index a33559ff5766..bc9d49703c05 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -195,7 +195,7 @@ function eq( } function isErrorEqual(a: Error, b: Error, customTesters: Tester[]) { - // assert.deepStrictEqual https://nodejs.org/docs/latest-v22.x/api/assert.html#comparison-details + // https://nodejs.org/docs/latest-v22.x/api/assert.html#comparison-details // - [[Prototype]] of objects are compared using the === operator. // - Only enumerable "own" properties are considered. // - Error names, messages, causes, and errors are always compared, even if these are not enumerable properties. errors is also compared. From 79818e5b2327fdaf71402f4bdc9c337a31985f25 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 12 Jun 2024 12:02:46 +0900 Subject: [PATCH 04/33] wip: check cause and errors --- packages/expect/src/jest-utils.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index bc9d49703c05..ae2702b40e7d 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -201,12 +201,22 @@ function isErrorEqual(a: Error, b: Error, customTesters: Tester[]) { // - Error names, messages, causes, and errors are always compared, even if these are not enumerable properties. errors is also compared. // (NOTE: causes and errors are added in v22) - // TODO: causes and errors + // TODO: handle cyclic objects + // TODO: check how NodeJs prints error diff + return ( Object.getPrototypeOf(a) === Object.getPrototypeOf(b) && a.name === b.name && a.message === b.message - && equals({ ...a }, { ...b }, customTesters) + // check Error.cause asymmetrically + && (typeof b.cause !== 'undefined' + ? equals(a.cause, b.cause, customTesters) + : true) + // AggregateError.errors + && (a instanceof AggregateError && b instanceof AggregateError + ? equals(a.errors, b.errors, customTesters) + : true) + && equals({ ...a }, { ...b }, customTesters) ) } From 33e71aa749cfc9afe8bb318738867a5175932d6e Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 12 Jun 2024 12:47:43 +0900 Subject: [PATCH 05/33] test: error diff --- .../__snapshots__/jest-expect.test.ts.snap | 40 ++++++++++++++++++ test/core/test/jest-expect.test.ts | 42 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 3e92bdac56d4..7bdd2a5e9949 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -311,6 +311,46 @@ exports[`asymmetric matcher error 23`] = ` } `; +exports[`error equality 1`] = ` +{ + "actual": "[Error: hi]", + "diff": "Compared values have no visual difference.", + "expected": "[Error: hi]", + "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hi { custom: 'b' }", +} +`; + +exports[`error equality 2`] = ` +{ + "actual": "[Error: hi]", + "diff": "- Expected ++ Received + +- [Error: hello] ++ [Error: hi]", + "expected": "[Error: hello]", + "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", +} +`; + +exports[`error equality 3`] = ` +{ + "actual": "[Error: hello]", + "diff": "Compared values have no visual difference.", + "expected": "[Error: hello]", + "message": "expected Error: hello { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", +} +`; + +exports[`error equality 4`] = ` +{ + "actual": "[Error: hello]", + "diff": "Compared values have no visual difference.", + "expected": "[Error: hello]", + "message": "expected Error: hello { cause: 'x' } to deeply equal Error: hello { cause: 'y' }", +} +`; + exports[`toHaveBeenNthCalledWith error 1`] = ` { "actual": "Array [ diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 92ac7bb67bde..4e00d229b3d4 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1232,6 +1232,48 @@ it('asymmetric matcher error', () => { }).toThrow(MyError1)) }) +it('error equality', () => { + class MyError extends Error { + constructor(message: string, public custom: string) { + super(message) + } + } + + class YourError extends Error { + constructor(message: string, public custom: string) { + super(message) + } + } + + { + // different custom property + const e1 = new MyError('hi', 'a') + const e2 = new MyError('hi', 'b') + snapshotError(() => expect(e1).toEqual(e2)) + } + + { + // different message + const e1 = new MyError('hi', 'a') + const e2 = new MyError('hello', 'a') + snapshotError(() => expect(e1).toEqual(e2)) + } + + { + // different class + const e1 = new MyError('hello', 'a') + const e2 = new YourError('hello', 'a') + snapshotError(() => expect(e1).toEqual(e2)) + } + + { + // different cause + const e1 = new Error('hello', { cause: 'x' }) + const e2 = new Error('hello', { cause: 'y' }) + snapshotError(() => expect(e1).toEqual(e2)) + } +}) + it('toHaveBeenNthCalledWith error', () => { const fn = vi.fn() fn('World') From 7091ecfceb1a85923da220aaa2d34388e14be869 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 12 Jun 2024 12:50:33 +0900 Subject: [PATCH 06/33] test: move test --- .../test/__snapshots__/expect.test.ts.snap | 41 +++++++++++++++++++ test/core/test/expect.test.ts | 29 +++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 test/core/test/__snapshots__/expect.test.ts.snap diff --git a/test/core/test/__snapshots__/expect.test.ts.snap b/test/core/test/__snapshots__/expect.test.ts.snap new file mode 100644 index 000000000000..facc3064ac87 --- /dev/null +++ b/test/core/test/__snapshots__/expect.test.ts.snap @@ -0,0 +1,41 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Error equality > basic 1`] = ` +{ + "actual": "[Error: hi]", + "diff": "Compared values have no visual difference.", + "expected": "[Error: hi]", + "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hi { custom: 'b' }", +} +`; + +exports[`Error equality > basic 2`] = ` +{ + "actual": "[Error: hi]", + "diff": "- Expected ++ Received + +- [Error: hello] ++ [Error: hi]", + "expected": "[Error: hello]", + "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", +} +`; + +exports[`Error equality > basic 3`] = ` +{ + "actual": "[Error: hello]", + "diff": "Compared values have no visual difference.", + "expected": "[Error: hello]", + "message": "expected Error: hello { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", +} +`; + +exports[`Error equality > basic 4`] = ` +{ + "actual": "[Error: hello]", + "diff": "Compared values have no visual difference.", + "expected": "[Error: hello]", + "message": "expected Error: hello { cause: 'x' } to deeply equal Error: hello { cause: 'y' }", +} +`; diff --git a/test/core/test/expect.test.ts b/test/core/test/expect.test.ts index 41a46d471b70..63f7f0fe760d 100644 --- a/test/core/test/expect.test.ts +++ b/test/core/test/expect.test.ts @@ -2,6 +2,7 @@ import nodeAssert from 'node:assert' import type { Tester } from '@vitest/expect' import { getCurrentTest } from '@vitest/runner' import { assert, describe, expect, expectTypeOf, test, vi } from 'vitest' +import { processError } from '@vitest/utils/error' describe('expect.soft', () => { test('types', () => { @@ -273,6 +274,23 @@ describe('recursive custom equality tester', () => { }) }) +function snapshotError(f: () => unknown) { + try { + f() + } + catch (error) { + const e = processError(error) + expect({ + message: e.message, + diff: e.diff, + expected: e.expected, + actual: e.actual, + }).toMatchSnapshot() + return + } + expect.unreachable() +} + describe('Error equality', () => { class MyError extends Error { constructor(message: string, public custom: string) { @@ -295,6 +313,7 @@ describe('Error equality', () => { // different custom property const e1 = new MyError('hi', 'a') const e2 = new MyError('hi', 'b') + snapshotError(() => expect(e1).toEqual(e2)) expect(e1).not.toEqual(e2) expect(e1).not.toStrictEqual(e2) assert.deepEqual(e1, e2) @@ -305,6 +324,7 @@ describe('Error equality', () => { // different message const e1 = new MyError('hi', 'a') const e2 = new MyError('hello', 'a') + snapshotError(() => expect(e1).toEqual(e2)) expect(e1).not.toEqual(e2) expect(e1).not.toStrictEqual(e2) assert.notDeepEqual(e1, e2) @@ -315,6 +335,7 @@ describe('Error equality', () => { // different class const e1 = new MyError('hello', 'a') const e2 = new YourError('hello', 'a') + snapshotError(() => expect(e1).toEqual(e2)) expect(e1).not.toEqual(e2) expect(e1).not.toStrictEqual(e2) // toStrictEqual checks constructor already assert.deepEqual(e1, e2) @@ -331,6 +352,14 @@ describe('Error equality', () => { nodeAssert.deepStrictEqual(e1, e2) } + { + // different cause + const e1 = new Error('hello', { cause: 'x' }) + const e2 = new Error('hello', { cause: 'y' }) + snapshotError(() => expect(e1).toEqual(e2)) + expect(e1).not.toEqual(e2) + } + // // stricter behavior with custom equality tester // From fb1b9fea70f288e9b8436b43111818a6efa53830 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 12 Jun 2024 12:50:47 +0900 Subject: [PATCH 07/33] Revert "test: error diff" This reverts commit 33e71aa749cfc9afe8bb318738867a5175932d6e. --- .../__snapshots__/jest-expect.test.ts.snap | 40 ------------------ test/core/test/jest-expect.test.ts | 42 ------------------- 2 files changed, 82 deletions(-) diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 7bdd2a5e9949..3e92bdac56d4 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -311,46 +311,6 @@ exports[`asymmetric matcher error 23`] = ` } `; -exports[`error equality 1`] = ` -{ - "actual": "[Error: hi]", - "diff": "Compared values have no visual difference.", - "expected": "[Error: hi]", - "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hi { custom: 'b' }", -} -`; - -exports[`error equality 2`] = ` -{ - "actual": "[Error: hi]", - "diff": "- Expected -+ Received - -- [Error: hello] -+ [Error: hi]", - "expected": "[Error: hello]", - "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", -} -`; - -exports[`error equality 3`] = ` -{ - "actual": "[Error: hello]", - "diff": "Compared values have no visual difference.", - "expected": "[Error: hello]", - "message": "expected Error: hello { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", -} -`; - -exports[`error equality 4`] = ` -{ - "actual": "[Error: hello]", - "diff": "Compared values have no visual difference.", - "expected": "[Error: hello]", - "message": "expected Error: hello { cause: 'x' } to deeply equal Error: hello { cause: 'y' }", -} -`; - exports[`toHaveBeenNthCalledWith error 1`] = ` { "actual": "Array [ diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 4e00d229b3d4..92ac7bb67bde 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1232,48 +1232,6 @@ it('asymmetric matcher error', () => { }).toThrow(MyError1)) }) -it('error equality', () => { - class MyError extends Error { - constructor(message: string, public custom: string) { - super(message) - } - } - - class YourError extends Error { - constructor(message: string, public custom: string) { - super(message) - } - } - - { - // different custom property - const e1 = new MyError('hi', 'a') - const e2 = new MyError('hi', 'b') - snapshotError(() => expect(e1).toEqual(e2)) - } - - { - // different message - const e1 = new MyError('hi', 'a') - const e2 = new MyError('hello', 'a') - snapshotError(() => expect(e1).toEqual(e2)) - } - - { - // different class - const e1 = new MyError('hello', 'a') - const e2 = new YourError('hello', 'a') - snapshotError(() => expect(e1).toEqual(e2)) - } - - { - // different cause - const e1 = new Error('hello', { cause: 'x' }) - const e2 = new Error('hello', { cause: 'y' }) - snapshotError(() => expect(e1).toEqual(e2)) - } -}) - it('toHaveBeenNthCalledWith error', () => { const fn = vi.fn() fn('World') From 1dfcafcc659ffc774b8512cbbdfdaf590d5b5ef8 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 12 Jun 2024 12:52:08 +0900 Subject: [PATCH 08/33] test: fix colors --- test/core/test/__snapshots__/expect.test.ts.snap | 14 +++++++------- test/core/test/expect.test.ts | 4 ++++ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/test/core/test/__snapshots__/expect.test.ts.snap b/test/core/test/__snapshots__/expect.test.ts.snap index facc3064ac87..5bb384d097d4 100644 --- a/test/core/test/__snapshots__/expect.test.ts.snap +++ b/test/core/test/__snapshots__/expect.test.ts.snap @@ -3,7 +3,7 @@ exports[`Error equality > basic 1`] = ` { "actual": "[Error: hi]", - "diff": "Compared values have no visual difference.", + "diff": "Compared values have no visual difference.", "expected": "[Error: hi]", "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hi { custom: 'b' }", } @@ -12,11 +12,11 @@ exports[`Error equality > basic 1`] = ` exports[`Error equality > basic 2`] = ` { "actual": "[Error: hi]", - "diff": "- Expected -+ Received + "diff": "- Expected ++ Received -- [Error: hello] -+ [Error: hi]", +- [Error: hello] ++ [Error: hi]", "expected": "[Error: hello]", "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", } @@ -25,7 +25,7 @@ exports[`Error equality > basic 2`] = ` exports[`Error equality > basic 3`] = ` { "actual": "[Error: hello]", - "diff": "Compared values have no visual difference.", + "diff": "Compared values have no visual difference.", "expected": "[Error: hello]", "message": "expected Error: hello { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", } @@ -34,7 +34,7 @@ exports[`Error equality > basic 3`] = ` exports[`Error equality > basic 4`] = ` { "actual": "[Error: hello]", - "diff": "Compared values have no visual difference.", + "diff": "Compared values have no visual difference.", "expected": "[Error: hello]", "message": "expected Error: hello { cause: 'x' } to deeply equal Error: hello { cause: 'y' }", } diff --git a/test/core/test/expect.test.ts b/test/core/test/expect.test.ts index 63f7f0fe760d..fc10da1e656c 100644 --- a/test/core/test/expect.test.ts +++ b/test/core/test/expect.test.ts @@ -3,6 +3,8 @@ import type { Tester } from '@vitest/expect' import { getCurrentTest } from '@vitest/runner' import { assert, describe, expect, expectTypeOf, test, vi } from 'vitest' import { processError } from '@vitest/utils/error' +import { setupColors } from '@vitest/expect' +import { getDefaultColors } from '@vitest/utils' describe('expect.soft', () => { test('types', () => { @@ -305,6 +307,8 @@ describe('Error equality', () => { } test('basic', () => { + setupColors(getDefaultColors()) + // // default behavior // From 47e749684f5f40396ab14f8eb6f7fdf01ce05258 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 3 Jul 2024 12:53:05 +0900 Subject: [PATCH 09/33] chore: revert format --- packages/coverage-v8/src/provider.ts | 10 +++++----- packages/vitest/src/node/cli/cli-config.ts | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/coverage-v8/src/provider.ts b/packages/coverage-v8/src/provider.ts index 38050f235791..ee431ef3d5f2 100644 --- a/packages/coverage-v8/src/provider.ts +++ b/packages/coverage-v8/src/provider.ts @@ -443,11 +443,11 @@ export class V8CoverageProvider transformResults: TransformResults, functions: Profiler.FunctionCoverage[] = [], ): Promise<{ - source: string - originalSource: string - sourceMap?: { sourcemap: EncodedSourceMap } - isExecuted: boolean - }> { + source: string + originalSource: string + sourceMap?: { sourcemap: EncodedSourceMap } + isExecuted: boolean + }> { const filePath = normalize(fileURLToPath(url)) let isExecuted = true diff --git a/packages/vitest/src/node/cli/cli-config.ts b/packages/vitest/src/node/cli/cli-config.ts index f8fa04c5dbd9..c6b28c0785d5 100644 --- a/packages/vitest/src/node/cli/cli-config.ts +++ b/packages/vitest/src/node/cli/cli-config.ts @@ -9,9 +9,9 @@ import type { import type { CliOptions } from './cli-api' type NestedOption>> = V extends - | never - | RegExp - | unknown[] +| never +| RegExp +| unknown[] ? never : V From 3609c3efc8d7af4fb9b58f17fb0fd76276178635 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 09:18:03 +0900 Subject: [PATCH 10/33] wip: format error --- packages/expect/src/jest-utils.ts | 1 + packages/pretty-format/src/index.ts | 24 ++++++++++++++++++++++-- test/core/test/expect.test.ts | 11 +++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index 3f41821ddf02..6d1be84c9e47 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -240,6 +240,7 @@ function isErrorEqual(a: Error, b: Error, customTesters: Tester[]) { && a.name === b.name && a.message === b.message // check Error.cause asymmetrically + // TODO: update replaceAsymmetricMatcher? && (typeof b.cause !== 'undefined' ? equals(a.cause, b.cause, customTesters) : true) diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index 0d7c04920530..3904c1b89bad 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -178,7 +178,7 @@ function printBasicValue( return Number.isNaN(+val) ? 'Date { NaN }' : toISOString.call(val) } if (toStringed === '[object Error]') { - return printError(val) + // return printError(val) } if (toStringed === '[object RegExp]') { if (escapeRegex) { @@ -189,7 +189,7 @@ function printBasicValue( } if (val instanceof Error) { - return printError(val) + // return printError(val) } return null @@ -275,6 +275,26 @@ function printComplexValue( printer, )}}` } + // TODO: enable only for diff format + if (val instanceof Error) { + const { message, cause, ...rest } = val; + const entries = { + message, + ...typeof cause !== 'undefined' ? { cause } : {}, + ...val instanceof AggregateError ? { errors: val.errors } : {}, + ...rest, + } + return hitMaxDepth + ? `[${val.name}]` + : `${val.name} {${printIteratorEntries( + Object.entries(entries).values(), + config, + indentation, + depth, + refs, + printer, + )}}` + } // Avoid failure to serialize global window object in jsdom test environment. // For example, not even relevant if window is prop of React element. diff --git a/test/core/test/expect.test.ts b/test/core/test/expect.test.ts index 88a1e77b62c2..fcbff6a4feac 100644 --- a/test/core/test/expect.test.ts +++ b/test/core/test/expect.test.ts @@ -433,6 +433,17 @@ describe('Error equality', () => { }) }) +// test.only('repro', () => { +// const e1 = new Error('hello', { cause: 'x' }) +// const e2 = new Error('hello', { cause: 'y' }) +// expect(e1).toEqual(e2) +// // try { +// // } catch (e) { +// // // console.error(e); +// // // console.log(processError(e)); +// // } +// }) + describe('iterator', () => { test('returns true when given iterator within equal objects', () => { const a = { From fdaa6147fea570b4e1547d0b0c5eb22f56a33d4f Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 09:27:26 +0900 Subject: [PATCH 11/33] test: update --- packages/pretty-format/src/index.ts | 4 +- .../test/__snapshots__/expect.test.ts.snap | 65 +++++++++++++++---- test/core/test/expect.test.ts | 25 ++----- 3 files changed, 61 insertions(+), 33 deletions(-) diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index 3904c1b89bad..91c96be62e4f 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -276,8 +276,8 @@ function printComplexValue( )}}` } // TODO: enable only for diff format - if (val instanceof Error) { - const { message, cause, ...rest } = val; + if (toStringed === '[object Error]' || val instanceof Error) { + const { message, cause, ...rest } = val const entries = { message, ...typeof cause !== 'undefined' ? { cause } : {}, diff --git a/test/core/test/__snapshots__/expect.test.ts.snap b/test/core/test/__snapshots__/expect.test.ts.snap index 5bb384d097d4..9c60c7e38747 100644 --- a/test/core/test/__snapshots__/expect.test.ts.snap +++ b/test/core/test/__snapshots__/expect.test.ts.snap @@ -2,40 +2,81 @@ exports[`Error equality > basic 1`] = ` { - "actual": "[Error: hi]", - "diff": "Compared values have no visual difference.", - "expected": "[Error: hi]", + "actual": "Error { + "message": "hi", + "custom": "a", +}", + "diff": "- Expected ++ Received + + Error { + "message": "hi", +- "custom": "b", ++ "custom": "a", + }", + "expected": "Error { + "message": "hi", + "custom": "b", +}", "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hi { custom: 'b' }", } `; exports[`Error equality > basic 2`] = ` { - "actual": "[Error: hi]", + "actual": "Error { + "message": "hi", + "custom": "a", +}", "diff": "- Expected + Received -- [Error: hello] -+ [Error: hi]", - "expected": "[Error: hello]", + Error { +- "message": "hello", ++ "message": "hi", + "custom": "a", + }", + "expected": "Error { + "message": "hello", + "custom": "a", +}", "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", } `; exports[`Error equality > basic 3`] = ` { - "actual": "[Error: hello]", + "actual": "Error { + "message": "hello", + "custom": "a", +}", "diff": "Compared values have no visual difference.", - "expected": "[Error: hello]", + "expected": "Error { + "message": "hello", + "custom": "a", +}", "message": "expected Error: hello { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", } `; exports[`Error equality > basic 4`] = ` { - "actual": "[Error: hello]", - "diff": "Compared values have no visual difference.", - "expected": "[Error: hello]", + "actual": "Error { + "message": "hello", + "cause": "x", +}", + "diff": "- Expected ++ Received + + Error { + "message": "hello", +- "cause": "y", ++ "cause": "x", + }", + "expected": "Error { + "message": "hello", + "cause": "y", +}", "message": "expected Error: hello { cause: 'x' } to deeply equal Error: hello { cause: 'y' }", } `; diff --git a/test/core/test/expect.test.ts b/test/core/test/expect.test.ts index fcbff6a4feac..3ad9e7c7840a 100644 --- a/test/core/test/expect.test.ts +++ b/test/core/test/expect.test.ts @@ -1,10 +1,9 @@ import nodeAssert from 'node:assert' +import { stripVTControlCharacters } from 'node:util' import type { Tester } from '@vitest/expect' import { getCurrentTest } from '@vitest/runner' import { assert, describe, expect, expectTypeOf, test, vi } from 'vitest' import { processError } from '@vitest/utils/error' -import { setupColors } from '@vitest/expect' -import { getDefaultColors } from '@vitest/utils' describe('expect.soft', () => { test('types', () => { @@ -291,10 +290,10 @@ function snapshotError(f: () => unknown) { catch (error) { const e = processError(error) expect({ - message: e.message, - diff: e.diff, - expected: e.expected, - actual: e.actual, + message: stripVTControlCharacters(e.message), + diff: stripVTControlCharacters(e.diff), + expected: stripVTControlCharacters(e.expected), + actual: stripVTControlCharacters(e.actual), }).toMatchSnapshot() return } @@ -315,8 +314,6 @@ describe('Error equality', () => { } test('basic', () => { - setupColors(getDefaultColors()) - // // default behavior // @@ -376,6 +373,7 @@ describe('Error equality', () => { // stricter behavior with custom equality tester // + // TODO: remove expect.addEqualityTesters( [ function tester(a, b, customTesters) { @@ -433,17 +431,6 @@ describe('Error equality', () => { }) }) -// test.only('repro', () => { -// const e1 = new Error('hello', { cause: 'x' }) -// const e2 = new Error('hello', { cause: 'y' }) -// expect(e1).toEqual(e2) -// // try { -// // } catch (e) { -// // // console.error(e); -// // // console.log(processError(e)); -// // } -// }) - describe('iterator', () => { test('returns true when given iterator within equal objects', () => { const a = { From 6e5fe191c8c9d4c8a4f861dd8f368868f21c51bf Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 09:36:40 +0900 Subject: [PATCH 12/33] test: more --- packages/pretty-format/src/index.ts | 2 +- .../test/__snapshots__/expect.test.ts.snap | 41 +++++++++++++++++++ test/core/test/expect.test.ts | 23 +++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index 91c96be62e4f..d6a6aac5dd6d 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -276,7 +276,7 @@ function printComplexValue( )}}` } // TODO: enable only for diff format - if (toStringed === '[object Error]' || val instanceof Error) { + if (val instanceof Error) { const { message, cause, ...rest } = val const entries = { message, diff --git a/test/core/test/__snapshots__/expect.test.ts.snap b/test/core/test/__snapshots__/expect.test.ts.snap index 9c60c7e38747..9a531c4c5a22 100644 --- a/test/core/test/__snapshots__/expect.test.ts.snap +++ b/test/core/test/__snapshots__/expect.test.ts.snap @@ -80,3 +80,44 @@ exports[`Error equality > basic 4`] = ` "message": "expected Error: hello { cause: 'x' } to deeply equal Error: hello { cause: 'y' }", } `; + +exports[`Error equality > basic 5`] = ` +{ + "actual": "Error { + "message": "hello", +}", + "diff": "- Expected ++ Received + + Error { + "message": "hello", +- "cause": "y", + }", + "expected": "Error { + "message": "hello", + "cause": "y", +}", + "message": "expected Error: hello to deeply equal Error: hello { cause: 'y' }", +} +`; + +exports[`Error equality > basic 6`] = ` +{ + "actual": "Error { + "message": "hello", + "cause": "x", +}", + "diff": "- Expected ++ Received + + Error { +- "message": "world", ++ "message": "hello", ++ "cause": "x", + }", + "expected": "Error { + "message": "world", +}", + "message": "expected Error: hello { cause: 'x' } to deeply equal Error: world", +} +`; diff --git a/test/core/test/expect.test.ts b/test/core/test/expect.test.ts index 3ad9e7c7840a..9b2b637389a8 100644 --- a/test/core/test/expect.test.ts +++ b/test/core/test/expect.test.ts @@ -369,6 +369,29 @@ describe('Error equality', () => { expect(e1).not.toEqual(e2) } + { + // different cause (asymmetric fail) + const e1 = new Error('hello') + const e2 = new Error('hello', { cause: 'y' }) + snapshotError(() => expect(e1).toEqual(e2)) + expect(e1).not.toEqual(e2) + } + + { + // different cause (asymmetric pass) + const e1 = new Error('hello', { cause: 'x' }) + const e2 = new Error('hello') + expect(e1).toEqual(e2) + } + + { + // different cause (fail by other props) + // TODO: strip `cause` on actual side from diff + const e1 = new Error('hello', { cause: 'x' }) + const e2 = new Error('world') + snapshotError(() => expect(e1).toEqual(e2)) + } + // // stricter behavior with custom equality tester // From bff22de758eb3ad9058bf94c7e211fb984022933 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 10:04:13 +0900 Subject: [PATCH 13/33] fix: enable error format plugin only during diff --- packages/pretty-format/src/index.ts | 50 +++++++++------- packages/utils/src/diff/index.ts | 2 + .../test/__snapshots__/expect.test.ts.snap | 58 ++++--------------- 3 files changed, 42 insertions(+), 68 deletions(-) diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index d6a6aac5dd6d..e784d6b49151 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -178,7 +178,7 @@ function printBasicValue( return Number.isNaN(+val) ? 'Date { NaN }' : toISOString.call(val) } if (toStringed === '[object Error]') { - // return printError(val) + return printError(val) } if (toStringed === '[object RegExp]') { if (escapeRegex) { @@ -189,7 +189,7 @@ function printBasicValue( } if (val instanceof Error) { - // return printError(val) + return printError(val) } return null @@ -275,26 +275,6 @@ function printComplexValue( printer, )}}` } - // TODO: enable only for diff format - if (val instanceof Error) { - const { message, cause, ...rest } = val - const entries = { - message, - ...typeof cause !== 'undefined' ? { cause } : {}, - ...val instanceof AggregateError ? { errors: val.errors } : {}, - ...rest, - } - return hitMaxDepth - ? `[${val.name}]` - : `${val.name} {${printIteratorEntries( - Object.entries(entries).values(), - config, - indentation, - depth, - refs, - printer, - )}}` - } // Avoid failure to serialize global window object in jsdom test environment. // For example, not even relevant if window is prop of React element. @@ -316,6 +296,30 @@ function printComplexValue( )}}` } +const ErrorPlugin: NewPlugin = { + test: val => val && val instanceof Error, + serialize(val, config, indentation, depth, refs, printer) { + const hitMaxDepth = ++depth > config.maxDepth + const { message, cause, ...rest } = val + const entries = { + message, + ...typeof cause !== 'undefined' ? { cause } : {}, + ...val instanceof AggregateError ? { errors: val.errors } : {}, + ...rest, + } + return hitMaxDepth + ? `[${val.name}]` + : `${val.name} {${printIteratorEntries( + Object.entries(entries).values(), + config, + indentation, + depth, + refs, + printer, + )}}` + }, +} + function isNewPlugin(plugin: Plugin): plugin is NewPlugin { return (plugin as NewPlugin).serialize != null } @@ -555,6 +559,7 @@ export const plugins: { Immutable: NewPlugin ReactElement: NewPlugin ReactTestComponent: NewPlugin + Error: NewPlugin } = { AsymmetricMatcher, DOMCollection, @@ -562,4 +567,5 @@ export const plugins: { Immutable, ReactElement, ReactTestComponent, + Error: ErrorPlugin, } diff --git a/packages/utils/src/diff/index.ts b/packages/utils/src/diff/index.ts index d6dc8a084a83..adb121de462e 100644 --- a/packages/utils/src/diff/index.ts +++ b/packages/utils/src/diff/index.ts @@ -50,6 +50,7 @@ const PLUGINS = [ DOMCollection, Immutable, AsymmetricMatcher, + prettyFormatPlugins.Error, ] const FORMAT_OPTIONS = { plugins: PLUGINS, @@ -271,6 +272,7 @@ export function printDiffOrStringify( // if (isLineDiffable(expected, received)) { const clonedExpected = deepClone(expected, { forceWritable: true }) const clonedReceived = deepClone(received, { forceWritable: true }) + // TODO: strip `Error.cause` like asymmetric matcher const { replacedExpected, replacedActual } = replaceAsymmetricMatcher(clonedExpected, clonedReceived) const difference = diff(replacedExpected, replacedActual, options) diff --git a/test/core/test/__snapshots__/expect.test.ts.snap b/test/core/test/__snapshots__/expect.test.ts.snap index 9a531c4c5a22..8a66899b6f0f 100644 --- a/test/core/test/__snapshots__/expect.test.ts.snap +++ b/test/core/test/__snapshots__/expect.test.ts.snap @@ -2,10 +2,7 @@ exports[`Error equality > basic 1`] = ` { - "actual": "Error { - "message": "hi", - "custom": "a", -}", + "actual": "[Error: hi]", "diff": "- Expected + Received @@ -14,20 +11,14 @@ exports[`Error equality > basic 1`] = ` - "custom": "b", + "custom": "a", }", - "expected": "Error { - "message": "hi", - "custom": "b", -}", + "expected": "[Error: hi]", "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hi { custom: 'b' }", } `; exports[`Error equality > basic 2`] = ` { - "actual": "Error { - "message": "hi", - "custom": "a", -}", + "actual": "[Error: hi]", "diff": "- Expected + Received @@ -36,35 +27,23 @@ exports[`Error equality > basic 2`] = ` + "message": "hi", "custom": "a", }", - "expected": "Error { - "message": "hello", - "custom": "a", -}", + "expected": "[Error: hello]", "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", } `; exports[`Error equality > basic 3`] = ` { - "actual": "Error { - "message": "hello", - "custom": "a", -}", + "actual": "[Error: hello]", "diff": "Compared values have no visual difference.", - "expected": "Error { - "message": "hello", - "custom": "a", -}", + "expected": "[Error: hello]", "message": "expected Error: hello { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", } `; exports[`Error equality > basic 4`] = ` { - "actual": "Error { - "message": "hello", - "cause": "x", -}", + "actual": "[Error: hello]", "diff": "- Expected + Received @@ -73,19 +52,14 @@ exports[`Error equality > basic 4`] = ` - "cause": "y", + "cause": "x", }", - "expected": "Error { - "message": "hello", - "cause": "y", -}", + "expected": "[Error: hello]", "message": "expected Error: hello { cause: 'x' } to deeply equal Error: hello { cause: 'y' }", } `; exports[`Error equality > basic 5`] = ` { - "actual": "Error { - "message": "hello", -}", + "actual": "[Error: hello]", "diff": "- Expected + Received @@ -93,20 +67,14 @@ exports[`Error equality > basic 5`] = ` "message": "hello", - "cause": "y", }", - "expected": "Error { - "message": "hello", - "cause": "y", -}", + "expected": "[Error: hello]", "message": "expected Error: hello to deeply equal Error: hello { cause: 'y' }", } `; exports[`Error equality > basic 6`] = ` { - "actual": "Error { - "message": "hello", - "cause": "x", -}", + "actual": "[Error: hello]", "diff": "- Expected + Received @@ -115,9 +83,7 @@ exports[`Error equality > basic 6`] = ` + "message": "hello", + "cause": "x", }", - "expected": "Error { - "message": "world", -}", + "expected": "[Error: world]", "message": "expected Error: hello { cause: 'x' } to deeply equal Error: world", } `; From 46f3f9cd9a85673b2c5fa2274d2b8f6e8ceb9464 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 10:06:21 +0900 Subject: [PATCH 14/33] test: more snapshot --- .../core/test/__snapshots__/jest-expect.test.ts.snap | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 85b30fbf13c6..d2942977d1fe 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -279,7 +279,9 @@ exports[`asymmetric matcher error 21`] = ` StringContaining "ll" + Received: -[Error: hello]", +Error { + "message": "hello", +}", "expected": "StringContaining "ll"", "message": "expected error to match asymmetric matcher", } @@ -292,7 +294,9 @@ exports[`asymmetric matcher error 22`] = ` stringContainingCustom + Received: -[Error: hello]", +Error { + "message": "hello", +}", "expected": "stringContainingCustom", "message": "expected error to match asymmetric matcher", } @@ -305,7 +309,9 @@ exports[`asymmetric matcher error 23`] = ` [Function MyError1] + Received: -[Error: hello]", +Error { + "message": "hello", +}", "expected": "[Function MyError1]", "message": "expected error to be instance of MyError1", } From 23ca80add1db9628a8c468b64358c6cac344ab28 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 10:08:33 +0900 Subject: [PATCH 15/33] chore: comment --- packages/expect/src/jest-utils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index 6d1be84c9e47..3fa4eef2f031 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -232,15 +232,11 @@ function isErrorEqual(a: Error, b: Error, customTesters: Tester[]) { // - Error names, messages, causes, and errors are always compared, even if these are not enumerable properties. errors is also compared. // (NOTE: causes and errors are added in v22) - // TODO: handle cyclic objects - // TODO: check how NodeJs prints error diff - return ( Object.getPrototypeOf(a) === Object.getPrototypeOf(b) && a.name === b.name && a.message === b.message // check Error.cause asymmetrically - // TODO: update replaceAsymmetricMatcher? && (typeof b.cause !== 'undefined' ? equals(a.cause, b.cause, customTesters) : true) From b9d9204f12be763b4e2cbbbe3f03fc4c66360aca Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 10:53:48 +0900 Subject: [PATCH 16/33] test: move code --- .../test/__snapshots__/expect.test.ts.snap | 89 --------- .../__snapshots__/jest-expect.test.ts.snap | 88 +++++++++ test/core/test/expect.test.ts | 176 +----------------- test/core/test/jest-expect.test.ts | 105 ++++++++++- 4 files changed, 192 insertions(+), 266 deletions(-) delete mode 100644 test/core/test/__snapshots__/expect.test.ts.snap diff --git a/test/core/test/__snapshots__/expect.test.ts.snap b/test/core/test/__snapshots__/expect.test.ts.snap deleted file mode 100644 index 8a66899b6f0f..000000000000 --- a/test/core/test/__snapshots__/expect.test.ts.snap +++ /dev/null @@ -1,89 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Error equality > basic 1`] = ` -{ - "actual": "[Error: hi]", - "diff": "- Expected -+ Received - - Error { - "message": "hi", -- "custom": "b", -+ "custom": "a", - }", - "expected": "[Error: hi]", - "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hi { custom: 'b' }", -} -`; - -exports[`Error equality > basic 2`] = ` -{ - "actual": "[Error: hi]", - "diff": "- Expected -+ Received - - Error { -- "message": "hello", -+ "message": "hi", - "custom": "a", - }", - "expected": "[Error: hello]", - "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", -} -`; - -exports[`Error equality > basic 3`] = ` -{ - "actual": "[Error: hello]", - "diff": "Compared values have no visual difference.", - "expected": "[Error: hello]", - "message": "expected Error: hello { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", -} -`; - -exports[`Error equality > basic 4`] = ` -{ - "actual": "[Error: hello]", - "diff": "- Expected -+ Received - - Error { - "message": "hello", -- "cause": "y", -+ "cause": "x", - }", - "expected": "[Error: hello]", - "message": "expected Error: hello { cause: 'x' } to deeply equal Error: hello { cause: 'y' }", -} -`; - -exports[`Error equality > basic 5`] = ` -{ - "actual": "[Error: hello]", - "diff": "- Expected -+ Received - - Error { - "message": "hello", -- "cause": "y", - }", - "expected": "[Error: hello]", - "message": "expected Error: hello to deeply equal Error: hello { cause: 'y' }", -} -`; - -exports[`Error equality > basic 6`] = ` -{ - "actual": "[Error: hello]", - "diff": "- Expected -+ Received - - Error { -- "message": "world", -+ "message": "hello", -+ "cause": "x", - }", - "expected": "[Error: world]", - "message": "expected Error: hello { cause: 'x' } to deeply equal Error: world", -} -`; diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index d2942977d1fe..43d82244f605 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -317,6 +317,94 @@ Error { } `; +exports[`error equality 1`] = ` +{ + "actual": "[Error: hi]", + "diff": "- Expected ++ Received + + Error { + "message": "hi", +- "custom": "b", ++ "custom": "a", + }", + "expected": "[Error: hi]", + "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hi { custom: 'b' }", +} +`; + +exports[`error equality 2`] = ` +{ + "actual": "[Error: hi]", + "diff": "- Expected ++ Received + + Error { +- "message": "hello", ++ "message": "hi", + "custom": "a", + }", + "expected": "[Error: hello]", + "message": "expected Error: hi { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", +} +`; + +exports[`error equality 3`] = ` +{ + "actual": "[Error: hello]", + "diff": "Compared values have no visual difference.", + "expected": "[Error: hello]", + "message": "expected Error: hello { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", +} +`; + +exports[`error equality 4`] = ` +{ + "actual": "[Error: hello]", + "diff": "- Expected ++ Received + + Error { + "message": "hello", +- "cause": "y", ++ "cause": "x", + }", + "expected": "[Error: hello]", + "message": "expected Error: hello { cause: 'x' } to deeply equal Error: hello { cause: 'y' }", +} +`; + +exports[`error equality 5`] = ` +{ + "actual": "[Error: hello]", + "diff": "- Expected ++ Received + + Error { + "message": "hello", +- "cause": "y", + }", + "expected": "[Error: hello]", + "message": "expected Error: hello to deeply equal Error: hello { cause: 'y' }", +} +`; + +exports[`error equality 6`] = ` +{ + "actual": "[Error: hello]", + "diff": "- Expected ++ Received + + Error { +- "message": "world", ++ "message": "hello", ++ "cause": "x", + }", + "expected": "[Error: world]", + "message": "expected Error: hello { cause: 'x' } to deeply equal Error: world", +} +`; + exports[`toHaveBeenNthCalledWith error 1`] = ` { "actual": "Array [ diff --git a/test/core/test/expect.test.ts b/test/core/test/expect.test.ts index 9b2b637389a8..e857465832f5 100644 --- a/test/core/test/expect.test.ts +++ b/test/core/test/expect.test.ts @@ -1,9 +1,6 @@ -import nodeAssert from 'node:assert' -import { stripVTControlCharacters } from 'node:util' import type { Tester } from '@vitest/expect' import { getCurrentTest } from '@vitest/runner' -import { assert, describe, expect, expectTypeOf, test, vi } from 'vitest' -import { processError } from '@vitest/utils/error' +import { describe, expect, expectTypeOf, test, vi } from 'vitest' describe('expect.soft', () => { test('types', () => { @@ -283,177 +280,6 @@ describe('recursive custom equality tester', () => { }) }) -function snapshotError(f: () => unknown) { - try { - f() - } - catch (error) { - const e = processError(error) - expect({ - message: stripVTControlCharacters(e.message), - diff: stripVTControlCharacters(e.diff), - expected: stripVTControlCharacters(e.expected), - actual: stripVTControlCharacters(e.actual), - }).toMatchSnapshot() - return - } - expect.unreachable() -} - -describe('Error equality', () => { - class MyError extends Error { - constructor(message: string, public custom: string) { - super(message) - } - } - - class YourError extends Error { - constructor(message: string, public custom: string) { - super(message) - } - } - - test('basic', () => { - // - // default behavior - // - - { - // different custom property - const e1 = new MyError('hi', 'a') - const e2 = new MyError('hi', 'b') - snapshotError(() => expect(e1).toEqual(e2)) - expect(e1).not.toEqual(e2) - expect(e1).not.toStrictEqual(e2) - assert.deepEqual(e1, e2) - nodeAssert.notDeepStrictEqual(e1, e2) - } - - { - // different message - const e1 = new MyError('hi', 'a') - const e2 = new MyError('hello', 'a') - snapshotError(() => expect(e1).toEqual(e2)) - expect(e1).not.toEqual(e2) - expect(e1).not.toStrictEqual(e2) - assert.notDeepEqual(e1, e2) - nodeAssert.notDeepStrictEqual(e1, e2) - } - - { - // different class - const e1 = new MyError('hello', 'a') - const e2 = new YourError('hello', 'a') - snapshotError(() => expect(e1).toEqual(e2)) - expect(e1).not.toEqual(e2) - expect(e1).not.toStrictEqual(e2) // toStrictEqual checks constructor already - assert.deepEqual(e1, e2) - nodeAssert.notDeepStrictEqual(e1, e2) - } - - { - // same - const e1 = new MyError('hi', 'a') - const e2 = new MyError('hi', 'a') - expect(e1).toEqual(e2) - expect(e1).toStrictEqual(e2) - assert.deepEqual(e1, e2) - nodeAssert.deepStrictEqual(e1, e2) - } - - { - // different cause - const e1 = new Error('hello', { cause: 'x' }) - const e2 = new Error('hello', { cause: 'y' }) - snapshotError(() => expect(e1).toEqual(e2)) - expect(e1).not.toEqual(e2) - } - - { - // different cause (asymmetric fail) - const e1 = new Error('hello') - const e2 = new Error('hello', { cause: 'y' }) - snapshotError(() => expect(e1).toEqual(e2)) - expect(e1).not.toEqual(e2) - } - - { - // different cause (asymmetric pass) - const e1 = new Error('hello', { cause: 'x' }) - const e2 = new Error('hello') - expect(e1).toEqual(e2) - } - - { - // different cause (fail by other props) - // TODO: strip `cause` on actual side from diff - const e1 = new Error('hello', { cause: 'x' }) - const e2 = new Error('world') - snapshotError(() => expect(e1).toEqual(e2)) - } - - // - // stricter behavior with custom equality tester - // - - // TODO: remove - expect.addEqualityTesters( - [ - function tester(a, b, customTesters) { - const aOk = a instanceof Error - const bOk = b instanceof Error - if (aOk && bOk) { - // cf. assert.deepStrictEqual https://nodejs.org/api/assert.html#comparison-details_1 - // > [[Prototype]] of objects are compared using the === operator. - // > Only enumerable "own" properties are considered. - // > Error names and messages are always compared, even if these are not enumerable properties. - return ( - Object.getPrototypeOf(a) === Object.getPrototypeOf(b) - && a.name === b.name - && a.message === b.message - && this.equals({ ...a }, { ...b }, customTesters) - ) - } - return aOk !== bOk ? false : undefined - }, - ], - ) - - { - // different custom property - const e1 = new MyError('hi', 'a') - const e2 = new MyError('hi', 'b') - expect(e1).not.toEqual(e2) // changed - expect(e1).not.toStrictEqual(e2) // changed - assert.deepEqual(e1, e2) // chai assert is still same - } - - { - // different message - const e1 = new MyError('hi', 'a') - const e2 = new MyError('hello', 'a') - expect(e1).not.toEqual(e2) - expect(e1).not.toStrictEqual(e2) - } - - { - // different class - const e1 = new MyError('hello', 'a') - const e2 = new YourError('hello', 'a') - expect(e1).not.toEqual(e2) // changed - expect(e1).not.toStrictEqual(e2) - } - - { - // same - const e1 = new MyError('hi', 'a') - const e2 = new MyError('hi', 'a') - expect(e1).toEqual(e2) - expect(e1).toStrictEqual(e2) - } - }) -}) - describe('iterator', () => { test('returns true when given iterator within equal objects', () => { const a = { diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index bcabc72f175b..f59f110c28fa 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1,7 +1,7 @@ /* eslint-disable no-sparse-arrays */ -import { AssertionError } from 'node:assert' +import nodeAssert, { AssertionError } from 'node:assert' import { stripVTControlCharacters } from 'node:util' -import { describe, expect, it, vi } from 'vitest' +import { assert, describe, expect, it, vi } from 'vitest' import { generateToBeMessage } from '@vitest/expect' import { processError } from '@vitest/utils/error' @@ -1339,6 +1339,107 @@ it('asymmetric matcher error', () => { }).toThrow(MyError1)) }) +it('error equality', () => { + class MyError extends Error { + constructor(message: string, public custom: string) { + super(message) + } + } + + class YourError extends Error { + constructor(message: string, public custom: string) { + super(message) + } + } + + { + // different custom property + const e1 = new MyError('hi', 'a') + const e2 = new MyError('hi', 'b') + snapshotError(() => expect(e1).toEqual(e2)) + expect(e1).not.toEqual(e2) + expect(e1).not.toStrictEqual(e2) + assert.deepEqual(e1, e2) + nodeAssert.notDeepStrictEqual(e1, e2) + } + + { + // different message + const e1 = new MyError('hi', 'a') + const e2 = new MyError('hello', 'a') + snapshotError(() => expect(e1).toEqual(e2)) + expect(e1).not.toEqual(e2) + expect(e1).not.toStrictEqual(e2) + assert.notDeepEqual(e1, e2) + nodeAssert.notDeepStrictEqual(e1, e2) + } + + { + // different class + const e1 = new MyError('hello', 'a') + const e2 = new YourError('hello', 'a') + snapshotError(() => expect(e1).toEqual(e2)) + expect(e1).not.toEqual(e2) + expect(e1).not.toStrictEqual(e2) // toStrictEqual checks constructor already + assert.deepEqual(e1, e2) + nodeAssert.notDeepStrictEqual(e1, e2) + } + + { + // same + const e1 = new MyError('hi', 'a') + const e2 = new MyError('hi', 'a') + expect(e1).toEqual(e2) + expect(e1).toStrictEqual(e2) + assert.deepEqual(e1, e2) + nodeAssert.deepStrictEqual(e1, e2) + } + + { + // same + const e1 = new MyError('hi', 'a') + const e2 = new MyError('hi', 'a') + expect(e1).toEqual(e2) + expect(e1).toStrictEqual(e2) + assert.deepEqual(e1, e2) + nodeAssert.deepStrictEqual(e1, e2) + } + + { + // different cause + const e1 = new Error('hello', { cause: 'x' }) + const e2 = new Error('hello', { cause: 'y' }) + snapshotError(() => expect(e1).toEqual(e2)) + expect(e1).not.toEqual(e2) + } + + { + // different cause (asymmetric fail) + const e1 = new Error('hello') + const e2 = new Error('hello', { cause: 'y' }) + snapshotError(() => expect(e1).toEqual(e2)) + expect(e1).not.toEqual(e2) + } + + { + // different cause (asymmetric pass) + const e1 = new Error('hello', { cause: 'x' }) + const e2 = new Error('hello') + expect(e1).toEqual(e2) + } + + { + // different cause (fail by other props) + // TODO: strip `cause` on actual side from diff + const e1 = new Error('hello', { cause: 'x' }) + const e2 = new Error('world') + snapshotError(() => expect(e1).toEqual(e2)) + } + + // TODO: AggregateError + // TODO: cyclic +}) + it('toHaveBeenNthCalledWith error', () => { const fn = vi.fn() fn('World') From 898ce93b98a77055975f14965364e911c6c3876e Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 11:00:28 +0900 Subject: [PATCH 17/33] test: more test --- .../__snapshots__/jest-expect.test.ts.snap | 22 +++++++++++++++++++ test/core/test/jest-expect.test.ts | 15 ++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 43d82244f605..2bb85bf85c74 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -405,6 +405,28 @@ exports[`error equality 6`] = ` } `; +exports[`error equality 7`] = ` +{ + "actual": "[AggregateError: outer]", + "diff": "- Expected ++ Received + + AggregateError { + "message": "outer", + "cause": "x", + "errors": Array [ + Error { + "message": "inner", +- "cause": "y", ++ "cause": "x", + }, + ], + }", + "expected": "[AggregateError: outer]", + "message": "expected AggregateError: outer { …(2) } to deeply equal AggregateError: outer { …(2) }", +} +`; + exports[`toHaveBeenNthCalledWith error 1`] = ` { "actual": "Array [ diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index f59f110c28fa..539d05a40ce9 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1436,7 +1436,20 @@ it('error equality', () => { snapshotError(() => expect(e1).toEqual(e2)) } - // TODO: AggregateError + { + // AggregateError (pass) + const e1 = new AggregateError([new Error('inner')], 'outer', { cause: 'x' }) + const e2 = new AggregateError([new Error('inner')], 'outer', { cause: 'x' }) + expect(e1).toEqual(e2) + } + + { + // AggregateError (fail) + const e1 = new AggregateError([new Error('inner', { cause: 'x' })], 'outer', { cause: 'x' }) + const e2 = new AggregateError([new Error('inner', { cause: 'y' })], 'outer', { cause: 'x' }) + snapshotError(() => expect(e1).toEqual(e2)) + } + // TODO: cyclic }) From 755cfb86533e2c0ede7eb3c1e146af1d0777aba6 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 11:15:01 +0900 Subject: [PATCH 18/33] fix: handle asymmetric cause diff --- packages/utils/src/diff/index.ts | 14 +++++++++++++- .../test/__snapshots__/jest-expect.test.ts.snap | 1 - test/core/test/jest-expect.test.ts | 7 ++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/diff/index.ts b/packages/utils/src/diff/index.ts index adb121de462e..ad5a6e0a1e7a 100644 --- a/packages/utils/src/diff/index.ts +++ b/packages/utils/src/diff/index.ts @@ -272,7 +272,6 @@ export function printDiffOrStringify( // if (isLineDiffable(expected, received)) { const clonedExpected = deepClone(expected, { forceWritable: true }) const clonedReceived = deepClone(received, { forceWritable: true }) - // TODO: strip `Error.cause` like asymmetric matcher const { replacedExpected, replacedActual } = replaceAsymmetricMatcher(clonedExpected, clonedReceived) const difference = diff(replacedExpected, replacedActual, options) @@ -299,6 +298,19 @@ export function replaceAsymmetricMatcher( replacedActual: any replacedExpected: any } { + // handle asymmetric Error.cause diff + if ( + actual instanceof Error + && expected instanceof Error + && typeof actual.cause !== 'undefined' + && typeof expected.cause === 'undefined' + ) { + delete actual.cause + return { + replacedActual: actual, + replacedExpected: expected, + } + } if (!isReplaceable(actual, expected)) { return { replacedActual: actual, replacedExpected: expected } } diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 2bb85bf85c74..33396c4261d1 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -398,7 +398,6 @@ exports[`error equality 6`] = ` Error { - "message": "world", + "message": "hello", -+ "cause": "x", }", "expected": "[Error: world]", "message": "expected Error: hello { cause: 'x' } to deeply equal Error: world", diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 539d05a40ce9..5e813fcd5c84 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1430,7 +1430,6 @@ it('error equality', () => { { // different cause (fail by other props) - // TODO: strip `cause` on actual side from diff const e1 = new Error('hello', { cause: 'x' }) const e2 = new Error('world') snapshotError(() => expect(e1).toEqual(e2)) @@ -1453,6 +1452,12 @@ it('error equality', () => { // TODO: cyclic }) +it.skip('repro', () => { + const e1 = new Error('hello', { cause: 'x' }) + const e2 = new Error('world') + expect(e1).toEqual(e2) +}) + it('toHaveBeenNthCalledWith error', () => { const fn = vi.fn() fn('World') From b9aab2b579515e391635bd81baf1500d61700ee6 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 11:28:08 +0900 Subject: [PATCH 19/33] fix: print error constructor --- packages/pretty-format/src/index.ts | 7 ++++--- .../test/__snapshots__/jest-expect.test.ts.snap | 13 ++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index e784d6b49151..650aa6297348 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -298,7 +298,7 @@ function printComplexValue( const ErrorPlugin: NewPlugin = { test: val => val && val instanceof Error, - serialize(val, config, indentation, depth, refs, printer) { + serialize(val: Error, config, indentation, depth, refs, printer) { const hitMaxDepth = ++depth > config.maxDepth const { message, cause, ...rest } = val const entries = { @@ -307,9 +307,10 @@ const ErrorPlugin: NewPlugin = { ...val instanceof AggregateError ? { errors: val.errors } : {}, ...rest, } + const name = val.name !== 'Error' ? val.name : getConstructorName(val as any) return hitMaxDepth - ? `[${val.name}]` - : `${val.name} {${printIteratorEntries( + ? `[${name}]` + : `${name} {${printIteratorEntries( Object.entries(entries).values(), config, indentation, diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 33396c4261d1..966e53c3c099 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -323,7 +323,7 @@ exports[`error equality 1`] = ` "diff": "- Expected + Received - Error { + MyError { "message": "hi", - "custom": "b", + "custom": "a", @@ -339,7 +339,7 @@ exports[`error equality 2`] = ` "diff": "- Expected + Received - Error { + MyError { - "message": "hello", + "message": "hi", "custom": "a", @@ -352,7 +352,14 @@ exports[`error equality 2`] = ` exports[`error equality 3`] = ` { "actual": "[Error: hello]", - "diff": "Compared values have no visual difference.", + "diff": "- Expected ++ Received + +- YourError { ++ MyError { + "message": "hello", + "custom": "a", + }", "expected": "[Error: hello]", "message": "expected Error: hello { custom: 'a' } to deeply equal Error: hello { custom: 'a' }", } From 45062b68acd9bdc62b4730b676f8c355571b2fe8 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 11:33:33 +0900 Subject: [PATCH 20/33] fix: handle cyclic during format --- packages/pretty-format/src/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/pretty-format/src/index.ts b/packages/pretty-format/src/index.ts index 650aa6297348..93e6f10c508d 100644 --- a/packages/pretty-format/src/index.ts +++ b/packages/pretty-format/src/index.ts @@ -299,6 +299,10 @@ function printComplexValue( const ErrorPlugin: NewPlugin = { test: val => val && val instanceof Error, serialize(val: Error, config, indentation, depth, refs, printer) { + if (refs.includes(val)) { + return '[Circular]' + } + refs = [...refs, val] const hitMaxDepth = ++depth > config.maxDepth const { message, cause, ...rest } = val const entries = { From 86a0b5b0648c4b8b8cf69edf141577f06c3bdacf Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 11:35:57 +0900 Subject: [PATCH 21/33] test: update snapshot --- test/core/test/__snapshots__/jest-expect.test.ts.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 966e53c3c099..e11c5b40c805 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -309,7 +309,7 @@ exports[`asymmetric matcher error 23`] = ` [Function MyError1] + Received: -Error { +MyError2 { "message": "hello", }", "expected": "[Function MyError1]", From de9a4de6dd56ec933ebb5c47774b7dac691c4d5c Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 12:40:24 +0900 Subject: [PATCH 22/33] wip: handle cycle --- packages/expect/src/jest-utils.ts | 54 ++++++++++++++++++++++++++++++ test/core/test/jest-expect.test.ts | 26 +++++++++++--- 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index 3fa4eef2f031..1e4786046e25 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -225,6 +225,60 @@ function eq( return result } +// TODO: how to setup it as a default tester? +export function createErrorEqualityTester(): Tester { + const aStack: Error[] = [] + const bStack: Error[] = [] + + const tester: Tester = function (a_: unknown, b_: unknown, customTesters) { + const aOk = a_ instanceof Error + const bOk = b_ instanceof Error + if (!aOk && !bOk) { + return undefined + } + if (aOk !== bOk) { + return false + } + const a = a_ as Error + const b = b_ as Error + + // check circular + let length = aStack.length + while (length--) { + if (aStack[length] === a) { + return bStack[length] === b + } + else if (bStack[length] === b) { + return false + } + } + aStack.push(a) + aStack.push(b) + + const result = ( + Object.getPrototypeOf(a) === Object.getPrototypeOf(b) + && a.name === b.name + && a.message === b.message + // check Error.cause asymmetrically + && (typeof b.cause !== 'undefined' + ? equals(a.cause, b.cause, customTesters) + : true) + // AggregateError.errors + && (a instanceof AggregateError && b instanceof AggregateError + ? equals(a.errors, b.errors, customTesters) + : true) + // spread to compare enumerable properties + && equals({ ...a }, { ...b }, customTesters) + ) + + aStack.pop() + aStack.pop() + return result + } + + return tester +} + function isErrorEqual(a: Error, b: Error, customTesters: Tester[]) { // https://nodejs.org/docs/latest-v22.x/api/assert.html#comparison-details // - [[Prototype]] of objects are compared using the === operator. diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 5e813fcd5c84..8564c98b587b 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1449,12 +1449,30 @@ it('error equality', () => { snapshotError(() => expect(e1).toEqual(e2)) } - // TODO: cyclic + // { + // // cyclic (pass) + // const e1 = new Error("hi"); + // e1.cause = e1; + // const e2 = new Error("hi"); + // e2.cause = e2; + // expect(e1).toEqual(e2) + // } + + // { + // // cyclic (fail) + // const e1 = new Error("hello"); + // e1.cause = e1; + // const e2 = new Error("world"); + // e2.cause = e2; + // snapshotError(() => expect(e1).toEqual(e2)) + // } }) -it.skip('repro', () => { - const e1 = new Error('hello', { cause: 'x' }) - const e2 = new Error('world') +it.only('repro', () => { + const e1 = new Error('hi') + e1.cause = e1 + const e2 = new Error('hi') + e2.cause = e2 expect(e1).toEqual(e2) }) From dbbe1375fb69d5cfb5ca90fd2fdeff16dc3a10d9 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 13:30:54 +0900 Subject: [PATCH 23/33] fix: handle cyclic --- packages/expect/src/jest-utils.ts | 82 ++++++------------- .../__snapshots__/jest-expect.test.ts.snap | 9 ++ test/core/test/jest-expect.test.ts | 43 +++++----- 3 files changed, 54 insertions(+), 80 deletions(-) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index 1e4786046e25..5c505ee56b64 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -115,7 +115,7 @@ function eq( } if (a instanceof Error && b instanceof Error) { - return isErrorEqual(a, b, customTesters) + return isErrorEqual(a, b, aStack, bStack, customTesters, hasKey) } if (typeof URL === 'function' && a instanceof URL && b instanceof URL) { @@ -225,81 +225,51 @@ function eq( return result } -// TODO: how to setup it as a default tester? -export function createErrorEqualityTester(): Tester { - const aStack: Error[] = [] - const bStack: Error[] = [] - - const tester: Tester = function (a_: unknown, b_: unknown, customTesters) { - const aOk = a_ instanceof Error - const bOk = b_ instanceof Error - if (!aOk && !bOk) { - return undefined +function isErrorEqual( + a: Error, + b: Error, + aStack: Array, + bStack: Array, + customTesters: Array, + hasKey: any, +) { + // check circular + let length = aStack.length + while (length--) { + if (aStack[length] === a) { + return bStack[length] === b } - if (aOk !== bOk) { + else if (bStack[length] === b) { return false } - const a = a_ as Error - const b = b_ as Error - - // check circular - let length = aStack.length - while (length--) { - if (aStack[length] === a) { - return bStack[length] === b - } - else if (bStack[length] === b) { - return false - } - } - aStack.push(a) - aStack.push(b) - - const result = ( - Object.getPrototypeOf(a) === Object.getPrototypeOf(b) - && a.name === b.name - && a.message === b.message - // check Error.cause asymmetrically - && (typeof b.cause !== 'undefined' - ? equals(a.cause, b.cause, customTesters) - : true) - // AggregateError.errors - && (a instanceof AggregateError && b instanceof AggregateError - ? equals(a.errors, b.errors, customTesters) - : true) - // spread to compare enumerable properties - && equals({ ...a }, { ...b }, customTesters) - ) - - aStack.pop() - aStack.pop() - return result } + aStack.push(a) + bStack.push(b) - return tester -} - -function isErrorEqual(a: Error, b: Error, customTesters: Tester[]) { // https://nodejs.org/docs/latest-v22.x/api/assert.html#comparison-details // - [[Prototype]] of objects are compared using the === operator. // - Only enumerable "own" properties are considered. // - Error names, messages, causes, and errors are always compared, even if these are not enumerable properties. errors is also compared. // (NOTE: causes and errors are added in v22) - - return ( + const result = ( Object.getPrototypeOf(a) === Object.getPrototypeOf(b) && a.name === b.name && a.message === b.message // check Error.cause asymmetrically && (typeof b.cause !== 'undefined' - ? equals(a.cause, b.cause, customTesters) + ? eq(a.cause, b.cause, aStack, bStack, customTesters, hasKey) : true) // AggregateError.errors && (a instanceof AggregateError && b instanceof AggregateError - ? equals(a.errors, b.errors, customTesters) + ? eq(a.errors, b.errors, aStack, bStack, customTesters, hasKey) : true) - && equals({ ...a }, { ...b }, customTesters) + // spread to compare enumerable properties + && eq({ ...a }, { ...b }, aStack, bStack, customTesters, hasKey) ) + + aStack.pop() + bStack.pop() + return result } function keys(obj: object, hasKey: (obj: object, key: string) => boolean) { diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index e11c5b40c805..4e3b41ed33b1 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -433,6 +433,15 @@ exports[`error equality 7`] = ` } `; +exports[`error equality 8`] = ` +{ + "actual": "undefined", + "diff": undefined, + "expected": "undefined", + "message": "Maximum call stack size exceeded", +} +`; + exports[`toHaveBeenNthCalledWith error 1`] = ` { "actual": "Array [ diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 8564c98b587b..550e4d17f238 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1449,31 +1449,26 @@ it('error equality', () => { snapshotError(() => expect(e1).toEqual(e2)) } - // { - // // cyclic (pass) - // const e1 = new Error("hi"); - // e1.cause = e1; - // const e2 = new Error("hi"); - // e2.cause = e2; - // expect(e1).toEqual(e2) - // } - - // { - // // cyclic (fail) - // const e1 = new Error("hello"); - // e1.cause = e1; - // const e2 = new Error("world"); - // e2.cause = e2; - // snapshotError(() => expect(e1).toEqual(e2)) - // } -}) + { + // cyclic (pass) + const e1 = new Error('hi') + e1.cause = e1 + const e2 = new Error('hi') + e2.cause = e2 + expect(e1).toEqual(e2) + } -it.only('repro', () => { - const e1 = new Error('hi') - e1.cause = e1 - const e2 = new Error('hi') - e2.cause = e2 - expect(e1).toEqual(e2) + { + // cyclic (fail) + // currently it fails by 'Maximum call stack size exceeded' + // due to chai's error formatting + // https://github.com/chaijs/chai/issues/1645 + const e1 = new Error('hello') + e1.cause = e1 + const e2 = new Error('world') + e2.cause = e2 + snapshotError(() => expect(e1).toEqual(e2)) + } }) it('toHaveBeenNthCalledWith error', () => { From e2a1c3233a3bcd5f80082821e1e2b93f06dd1f44 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 13:33:09 +0900 Subject: [PATCH 24/33] refactor: minor --- packages/expect/src/jest-utils.ts | 33 +++++++++++-------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index 5c505ee56b64..38781a268503 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -114,10 +114,6 @@ function eq( } } - if (a instanceof Error && b instanceof Error) { - return isErrorEqual(a, b, aStack, bStack, customTesters, hasKey) - } - if (typeof URL === 'function' && a instanceof URL && b instanceof URL) { return a.href === b.href } @@ -196,6 +192,16 @@ function eq( return false } + if (a instanceof Error && b instanceof Error) { + try { + return isErrorEqual(a, b, aStack, bStack, customTesters, hasKey) + } + finally { + aStack.pop() + bStack.pop() + } + } + // Deep compare objects. const aKeys = keys(a, hasKey) let key @@ -233,25 +239,12 @@ function isErrorEqual( customTesters: Array, hasKey: any, ) { - // check circular - let length = aStack.length - while (length--) { - if (aStack[length] === a) { - return bStack[length] === b - } - else if (bStack[length] === b) { - return false - } - } - aStack.push(a) - bStack.push(b) - // https://nodejs.org/docs/latest-v22.x/api/assert.html#comparison-details // - [[Prototype]] of objects are compared using the === operator. // - Only enumerable "own" properties are considered. // - Error names, messages, causes, and errors are always compared, even if these are not enumerable properties. errors is also compared. // (NOTE: causes and errors are added in v22) - const result = ( + return ( Object.getPrototypeOf(a) === Object.getPrototypeOf(b) && a.name === b.name && a.message === b.message @@ -266,10 +259,6 @@ function isErrorEqual( // spread to compare enumerable properties && eq({ ...a }, { ...b }, aStack, bStack, customTesters, hasKey) ) - - aStack.pop() - bStack.pop() - return result } function keys(obj: object, hasKey: (obj: object, key: string) => boolean) { From 87031b5a97e03e6f5b33bdf8291eb9a68a794117 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 13:48:44 +0900 Subject: [PATCH 25/33] docs: update `toEqual` --- docs/api/expect.md | 12 +++++++++++- packages/expect/src/jest-expect.ts | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/api/expect.md b/docs/api/expect.md index 1103ee6a15cb..8567878bb242 100644 --- a/docs/api/expect.md +++ b/docs/api/expect.md @@ -431,7 +431,17 @@ test('stocks are not the same', () => { ``` :::warning -A _deep equality_ will not be performed for `Error` objects. Only the `message` property of an Error is considered for equality. To customize equality to check properties other than `message`, use [`expect.addEqualityTesters`](#expect-addequalitytesters). To test if something was thrown, use [`toThrowError`](#tothrowerror) assertion. +For `Error` objects, non-enumerable properties such as `name`, `message`, `cause` and `AggregateError.errors` are also compared. For `Error.cause`, the comparison is done asymmetrically: + +```ts +// success +expect(new Error('hi', { cause: 'x' })).toEqual(new Error('hi')) + +// fail +expect(new Error('hi')).toEqual(new Error('hi', { cause: 'x' })) +``` + +To test if something was thrown, use [`toThrowError`](#tothrowerror) assertion. ::: ## toStrictEqual diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index e925e3c3316e..731fee9961c5 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -682,6 +682,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { ) } + // TODO: update this too? if (expected instanceof Error) { return this.assert( thrown && expected.message === thrown.message, From 82b59718f9ea1ff65815256d7da1f343c7da2a15 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 14:54:34 +0900 Subject: [PATCH 26/33] fix: do the same for `toThrowError` --- docs/api/expect.md | 12 ++++++-- packages/expect/src/jest-expect.ts | 15 ++++++---- .../__snapshots__/jest-expect.test.ts.snap | 28 +++++++++++++++---- test/core/test/jest-expect.test.ts | 11 ++++++++ 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/docs/api/expect.md b/docs/api/expect.md index 8567878bb242..133c3d7417f4 100644 --- a/docs/api/expect.md +++ b/docs/api/expect.md @@ -659,8 +659,9 @@ test('the number of elements must match exactly', () => { You can provide an optional argument to test that a specific error is thrown: -- regular expression: error message matches the pattern -- string: error message includes the substring +- `RegExp`: error message matches the pattern +- `string`: error message includes the substring +- `Error`, `AsymmetricMatcher`: compare with a received object similar to `toEqual(received)` :::tip You must wrap the code in a function, otherwise the error will not be caught, and test will fail. @@ -688,6 +689,13 @@ test('throws on pineapples', () => { expect(() => getFruitStock('pineapples')).toThrowError( /^Pineapples are not in stock$/, ) + + expect(() => getFruitStock('pineapples')).toThrowError( + new Error('Pineapples are not in stock'), + ) + expect(() => getFruitStock('pineapples')).toThrowError(expect.objectContaining({ + message: 'Pineapples are not in stock', + })) }) ``` diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index 731fee9961c5..d0fe77b1cac3 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -682,14 +682,17 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { ) } - // TODO: update this too? if (expected instanceof Error) { + const equal = jestEquals(thrown, expected, [ + ...customTesters, + iterableEquality, + ]) return this.assert( - thrown && expected.message === thrown.message, - `expected error to have message: ${expected.message}`, - `expected error not to have message: ${expected.message}`, - expected.message, - thrown && thrown.message, + equal, + 'expected error to be #{exp}', + 'expected error not to be #{exp}', + expected, + thrown, ) } diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 4e3b41ed33b1..5fc2918694d2 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -334,6 +334,22 @@ exports[`error equality 1`] = ` `; exports[`error equality 2`] = ` +{ + "actual": "[Error: hi]", + "diff": "- Expected ++ Received + + MyError { + "message": "hi", +- "custom": "b", ++ "custom": "a", + }", + "expected": "[Error: hi]", + "message": "expected error to be Error: hi { custom: 'b' }", +} +`; + +exports[`error equality 3`] = ` { "actual": "[Error: hi]", "diff": "- Expected @@ -349,7 +365,7 @@ exports[`error equality 2`] = ` } `; -exports[`error equality 3`] = ` +exports[`error equality 4`] = ` { "actual": "[Error: hello]", "diff": "- Expected @@ -365,7 +381,7 @@ exports[`error equality 3`] = ` } `; -exports[`error equality 4`] = ` +exports[`error equality 5`] = ` { "actual": "[Error: hello]", "diff": "- Expected @@ -381,7 +397,7 @@ exports[`error equality 4`] = ` } `; -exports[`error equality 5`] = ` +exports[`error equality 6`] = ` { "actual": "[Error: hello]", "diff": "- Expected @@ -396,7 +412,7 @@ exports[`error equality 5`] = ` } `; -exports[`error equality 6`] = ` +exports[`error equality 7`] = ` { "actual": "[Error: hello]", "diff": "- Expected @@ -411,7 +427,7 @@ exports[`error equality 6`] = ` } `; -exports[`error equality 7`] = ` +exports[`error equality 8`] = ` { "actual": "[AggregateError: outer]", "diff": "- Expected @@ -433,7 +449,7 @@ exports[`error equality 7`] = ` } `; -exports[`error equality 8`] = ` +exports[`error equality 9`] = ` { "actual": "undefined", "diff": undefined, diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 550e4d17f238..d72fcf2f4fbf 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1361,6 +1361,13 @@ it('error equality', () => { expect(e1).not.toStrictEqual(e2) assert.deepEqual(e1, e2) nodeAssert.notDeepStrictEqual(e1, e2) + + // toThrowError also compare errors similar to toEqual + snapshotError(() => + expect(() => { + throw e1 + }).toThrowError(e2), + ) } { @@ -1393,6 +1400,10 @@ it('error equality', () => { expect(e1).toStrictEqual(e2) assert.deepEqual(e1, e2) nodeAssert.deepStrictEqual(e1, e2) + + expect(() => { + throw e1 + }).toThrowError(e2) } { From 646f81f9af7430618ddb7cfa9c0212a0e7775319 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 14:55:48 +0900 Subject: [PATCH 27/33] chore: tweak --- packages/expect/src/jest-expect.ts | 4 ++-- test/core/test/__snapshots__/jest-expect.test.ts.snap | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index d0fe77b1cac3..3e8965da7f31 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -689,8 +689,8 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { ]) return this.assert( equal, - 'expected error to be #{exp}', - 'expected error not to be #{exp}', + 'expected a thrown error to be #{exp}', + 'expected a thrown error not to be #{exp}', expected, thrown, ) diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 5fc2918694d2..766beb81ce3b 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -345,7 +345,7 @@ exports[`error equality 2`] = ` + "custom": "a", }", "expected": "[Error: hi]", - "message": "expected error to be Error: hi { custom: 'b' }", + "message": "expected a thrown error to be Error: hi { custom: 'b' }", } `; From 5de6d0a23196e7b4a76836100613814f8172f246 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 7 Oct 2024 18:56:47 +0900 Subject: [PATCH 28/33] refactor: many if --- packages/expect/src/jest-utils.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/expect/src/jest-utils.ts b/packages/expect/src/jest-utils.ts index 38781a268503..72bca9dacb1f 100644 --- a/packages/expect/src/jest-utils.ts +++ b/packages/expect/src/jest-utils.ts @@ -243,22 +243,23 @@ function isErrorEqual( // - [[Prototype]] of objects are compared using the === operator. // - Only enumerable "own" properties are considered. // - Error names, messages, causes, and errors are always compared, even if these are not enumerable properties. errors is also compared. - // (NOTE: causes and errors are added in v22) - return ( + + let result = ( Object.getPrototypeOf(a) === Object.getPrototypeOf(b) && a.name === b.name && a.message === b.message - // check Error.cause asymmetrically - && (typeof b.cause !== 'undefined' - ? eq(a.cause, b.cause, aStack, bStack, customTesters, hasKey) - : true) - // AggregateError.errors - && (a instanceof AggregateError && b instanceof AggregateError - ? eq(a.errors, b.errors, aStack, bStack, customTesters, hasKey) - : true) - // spread to compare enumerable properties - && eq({ ...a }, { ...b }, aStack, bStack, customTesters, hasKey) ) + // check Error.cause asymmetrically + if (typeof b.cause !== 'undefined') { + result &&= eq(a.cause, b.cause, aStack, bStack, customTesters, hasKey) + } + // AggregateError.errors + if (a instanceof AggregateError && b instanceof AggregateError) { + result &&= eq(a.errors, b.errors, aStack, bStack, customTesters, hasKey) + } + // spread to compare enumerable properties + result &&= eq({ ...a }, { ...b }, aStack, bStack, customTesters, hasKey) + return result } function keys(obj: object, hasKey: (obj: object, key: string) => boolean) { From 1908bd6822770b84fe207738de59ea3bcd680609 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 8 Oct 2024 14:45:40 +0900 Subject: [PATCH 29/33] fix: tweak asymmetric error.cause diff --- packages/utils/src/diff/index.ts | 6 ++---- .../__snapshots__/jest-expect.test.ts.snap | 20 ++++++++++++++++++- test/core/test/jest-expect.test.ts | 8 ++++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/packages/utils/src/diff/index.ts b/packages/utils/src/diff/index.ts index ad5a6e0a1e7a..5c19fda1784b 100644 --- a/packages/utils/src/diff/index.ts +++ b/packages/utils/src/diff/index.ts @@ -300,10 +300,8 @@ export function replaceAsymmetricMatcher( } { // handle asymmetric Error.cause diff if ( - actual instanceof Error - && expected instanceof Error - && typeof actual.cause !== 'undefined' - && typeof expected.cause === 'undefined' + (actual instanceof Error && typeof actual.cause !== 'undefined') + && !(expected instanceof Error && typeof expected.cause !== 'undefined') ) { delete actual.cause return { diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 766beb81ce3b..1f1c073dea3d 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -428,6 +428,24 @@ exports[`error equality 7`] = ` `; exports[`error equality 8`] = ` +{ + "actual": "[Error: hello]", + "diff": "- Expected ++ Received + +- Object { +- "something": "else", ++ Error { ++ "message": "hello", + }", + "expected": "Object { + "something": "else", +}", + "message": "expected Error: hello { cause: 'x' } to deeply equal { something: 'else' }", +} +`; + +exports[`error equality 9`] = ` { "actual": "[AggregateError: outer]", "diff": "- Expected @@ -449,7 +467,7 @@ exports[`error equality 8`] = ` } `; -exports[`error equality 9`] = ` +exports[`error equality 10`] = ` { "actual": "undefined", "diff": undefined, diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index d72fcf2f4fbf..7b5caab60902 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1446,6 +1446,14 @@ it('error equality', () => { snapshotError(() => expect(e1).toEqual(e2)) } + { + // different cause + const e1 = new Error('hello', { cause: 'x' }) + const e2 = { something: 'else' } + expect(e1).toEqual(e2) + snapshotError(() => expect(e1).toEqual(e2)) + } + { // AggregateError (pass) const e1 = new AggregateError([new Error('inner')], 'outer', { cause: 'x' }) From d0b805eedd6e59830f9916fa3541b0c31ee7ee79 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 8 Oct 2024 14:53:44 +0900 Subject: [PATCH 30/33] chore: remove debug --- test/core/test/jest-expect.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 7b5caab60902..7b1569980687 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1450,7 +1450,6 @@ it('error equality', () => { // different cause const e1 = new Error('hello', { cause: 'x' }) const e2 = { something: 'else' } - expect(e1).toEqual(e2) snapshotError(() => expect(e1).toEqual(e2)) } From 78edae31d075f067ff5b7061936368f4eecb2f06 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 9 Oct 2024 10:14:36 +0900 Subject: [PATCH 31/33] fix: simplify back --- packages/utils/src/diff/index.ts | 6 +- .../__snapshots__/jest-expect.test.ts.snap | 62 +++++++++++++++++++ test/core/test/jest-expect.test.ts | 21 +++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/diff/index.ts b/packages/utils/src/diff/index.ts index 5c19fda1784b..ad5a6e0a1e7a 100644 --- a/packages/utils/src/diff/index.ts +++ b/packages/utils/src/diff/index.ts @@ -300,8 +300,10 @@ export function replaceAsymmetricMatcher( } { // handle asymmetric Error.cause diff if ( - (actual instanceof Error && typeof actual.cause !== 'undefined') - && !(expected instanceof Error && typeof expected.cause !== 'undefined') + actual instanceof Error + && expected instanceof Error + && typeof actual.cause !== 'undefined' + && typeof expected.cause === 'undefined' ) { delete actual.cause return { diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index 1f1c073dea3d..8167611c594b 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -437,6 +437,7 @@ exports[`error equality 8`] = ` - "something": "else", + Error { + "message": "hello", ++ "cause": "x", }", "expected": "Object { "something": "else", @@ -476,6 +477,67 @@ exports[`error equality 10`] = ` } `; +exports[`error equality 11`] = ` +{ + "actual": "[Error: hello]", + "diff": "- Expected ++ Received + +- ObjectContaining { +- "cause": "y", ++ Error { + "message": "hello", ++ "cause": "x", + }", + "expected": "ObjectContaining { + "cause": "y", + "message": "hello", +}", + "message": "expected Error: hello { cause: 'x' } to deeply equal ObjectContaining{…}", +} +`; + +exports[`error equality 12`] = ` +{ + "actual": "[Error: hello]", + "diff": "- Expected ++ Received + +- ObjectContaining { ++ Error { ++ "message": "hello", + "cause": "x", +- "message": "world", + }", + "expected": "ObjectContaining { + "cause": "x", + "message": "world", +}", + "message": "expected Error: hello { cause: 'x' } to deeply equal ObjectContaining{…}", +} +`; + +exports[`error equality 13`] = ` +{ + "actual": "[Error: hello]", + "diff": "- Expected ++ Received + +- ObjectContaining { +- "cause": "y", +- "message": "world", ++ Error { ++ "message": "hello", ++ "cause": "x", + }", + "expected": "ObjectContaining { + "cause": "y", + "message": "world", +}", + "message": "expected Error: hello { cause: 'x' } to deeply equal ObjectContaining{…}", +} +`; + exports[`toHaveBeenNthCalledWith error 1`] = ` { "actual": "Array [ diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 7b1569980687..5de05dd9fb70 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1487,6 +1487,27 @@ it('error equality', () => { e2.cause = e2 snapshotError(() => expect(e1).toEqual(e2)) } + + { + // asymmetric matcher + const e1 = new Error('hello', { cause: 'x' }) + expect(e1).toEqual(expect.objectContaining({ + message: 'hello', + cause: 'x', + })) + snapshotError(() => expect(e1).toEqual(expect.objectContaining({ + message: 'hello', + cause: 'y', + }))) + snapshotError(() => expect(e1).toEqual(expect.objectContaining({ + message: 'world', + cause: 'x', + }))) + snapshotError(() => expect(e1).toEqual(expect.objectContaining({ + message: 'world', + cause: 'y', + }))) + } }) it('toHaveBeenNthCalledWith error', () => { From ee17d0c06864b2b0882a24c0a51acbe2a7cbd1cd Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sat, 7 Dec 2024 11:49:48 +0900 Subject: [PATCH 32/33] chore: stale comment --- test/core/test/jest-expect.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/core/test/jest-expect.test.ts b/test/core/test/jest-expect.test.ts index 5e0876e2c4db..48b06f042852 100644 --- a/test/core/test/jest-expect.test.ts +++ b/test/core/test/jest-expect.test.ts @@ -1707,9 +1707,6 @@ it('error equality', () => { { // cyclic (fail) - // currently it fails by 'Maximum call stack size exceeded' - // due to chai's error formatting - // https://github.com/chaijs/chai/issues/1645 const e1 = new Error('hello') e1.cause = e1 const e2 = new Error('world') From baf414bdc0c023ec67e4696d334fcf2e27457c54 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 10 Dec 2024 09:47:02 +0900 Subject: [PATCH 33/33] test: update snapshot --- test/core/test/__snapshots__/jest-expect.test.ts.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/core/test/__snapshots__/jest-expect.test.ts.snap b/test/core/test/__snapshots__/jest-expect.test.ts.snap index aacb5337817c..2921503d6ccd 100644 --- a/test/core/test/__snapshots__/jest-expect.test.ts.snap +++ b/test/core/test/__snapshots__/jest-expect.test.ts.snap @@ -514,7 +514,7 @@ exports[`error equality 8`] = ` "diff": "- Expected + Received -- Object { +- { - "something": "else", + Error { + "message": "hello", @@ -536,7 +536,7 @@ exports[`error equality 9`] = ` AggregateError { "message": "outer", "cause": "x", - "errors": Array [ + "errors": [ Error { "message": "inner", - "cause": "y",