From b557a0e8584ef57ceed87132f01b41e59c661f49 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 17 Feb 2026 12:04:48 +0900 Subject: [PATCH 1/6] fix: preserve stacktrace of `resolves/rejects` chain assertion error --- examples/basic/test/repro.test.ts | 10 ++++++++++ packages/expect/src/jest-expect.ts | 28 ++++++++++++++++++---------- 2 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 examples/basic/test/repro.test.ts diff --git a/examples/basic/test/repro.test.ts b/examples/basic/test/repro.test.ts new file mode 100644 index 000000000000..37387780b6cd --- /dev/null +++ b/examples/basic/test/repro.test.ts @@ -0,0 +1,10 @@ +import { expect, test } from 'vitest' + +test('async failure', async () => { + const p = Promise.resolve(3 + 3) + await expect(p).resolves.toEqual(7) +}) + +test('async failure 2', async () => { + await expect(() => Promise.reject(3 + 3)).rejects.toEqual(7) +}) diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index dad17d6d31d7..a44f538c2b66 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -1111,13 +1111,17 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { { showDiff: false }, ) as Error _error.cause = err - _error.stack = (error.stack as string).replace( - error.message, - _error.message, - ) throw _error }, - ) + ).then(undefined, (err: any) => { + if (isError(err) && error.stack) { + err.stack = error.stack.replace( + error.message, + err.message, + ) + } + throw err + }) return recordAsyncExpect( test, @@ -1178,17 +1182,21 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { actual: value, }, ) as any - _error.stack = (error.stack as string).replace( - error.message, - _error.message, - ) throw _error }, (err: any) => { utils.flag(this, 'object', err) return result.call(this, ...args) }, - ) + ).then(undefined, (err: any) => { + if (isError(err) && error.stack) { + err.stack = error.stack.replace( + error.message, + err.message, + ) + } + throw err + }) return recordAsyncExpect( test, From bb043e980bf2c2b497dfebd1f4aa57e5148cc944 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 17 Feb 2026 12:05:38 +0900 Subject: [PATCH 2/6] chore: cleanup --- examples/basic/test/repro.test.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 examples/basic/test/repro.test.ts diff --git a/examples/basic/test/repro.test.ts b/examples/basic/test/repro.test.ts deleted file mode 100644 index 37387780b6cd..000000000000 --- a/examples/basic/test/repro.test.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { expect, test } from 'vitest' - -test('async failure', async () => { - const p = Promise.resolve(3 + 3) - await expect(p).resolves.toEqual(7) -}) - -test('async failure 2', async () => { - await expect(() => Promise.reject(3 + 3)).rejects.toEqual(7) -}) From 5451e3d5937cece7f36d66433deb80cc6e032137 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 17 Feb 2026 12:06:34 +0900 Subject: [PATCH 3/6] refactor: simplify --- packages/expect/src/jest-expect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index a44f538c2b66..647beefd104e 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -1113,7 +1113,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { _error.cause = err throw _error }, - ).then(undefined, (err: any) => { + ).catch((err: any) => { if (isError(err) && error.stack) { err.stack = error.stack.replace( error.message, @@ -1188,7 +1188,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { utils.flag(this, 'object', err) return result.call(this, ...args) }, - ).then(undefined, (err: any) => { + ).catch((err: any) => { if (isError(err) && error.stack) { err.stack = error.stack.replace( error.message, From 655902760d67e577dbcf076ed51368e4ad6e4e77 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 17 Feb 2026 12:14:03 +0900 Subject: [PATCH 4/6] fix: thenable to promise --- packages/expect/src/jest-expect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/expect/src/jest-expect.ts b/packages/expect/src/jest-expect.ts index 647beefd104e..e483676f1062 100644 --- a/packages/expect/src/jest-expect.ts +++ b/packages/expect/src/jest-expect.ts @@ -1098,7 +1098,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { return (...args: any[]) => { utils.flag(this, '_name', key) - const promise = obj.then( + const promise = Promise.resolve(obj).then( (value: any) => { utils.flag(this, 'object', value) return result.call(this, ...args) @@ -1170,7 +1170,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => { return (...args: any[]) => { utils.flag(this, '_name', key) - const promise = wrapper.then( + const promise = Promise.resolve(wrapper).then( (value: any) => { const _error = new AssertionError( `promise resolved "${utils.inspect( From 0a6143eeb19ff905b9f45733e45d8daf52f5c6cb Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 17 Feb 2026 12:32:11 +0900 Subject: [PATCH 5/6] test: add integration --- test/cli/test/stacktraces.test.ts | 122 +++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/test/cli/test/stacktraces.test.ts b/test/cli/test/stacktraces.test.ts index 042663203c97..451b03ff7b6a 100644 --- a/test/cli/test/stacktraces.test.ts +++ b/test/cli/test/stacktraces.test.ts @@ -1,7 +1,7 @@ import { resolve } from 'pathe' import { glob } from 'tinyglobby' import { describe, expect, it } from 'vitest' -import { runVitest } from '../../test-utils' +import { runInlineTests, runVitest } from '../../test-utils' // To prevent the warning coming up in snapshots process.setMaxListeners(20) @@ -193,3 +193,123 @@ it('custom helper with captureStackTrace', async () => { } `) }) + +it('resolves/rejects', async () => { + const { stderr, errorTree } = await runInlineTests({ + 'repro.test.ts': ` + import { test, expect } from 'vitest' + + test('resolves: resolved promise with mismatched value', async () => { + await expect(Promise.resolve(3)).resolves.toBe(4) + }) + + test('rejects: rejected promise with mismatched value', async () => { + await expect(Promise.reject(3)).rejects.toBe(4) + }) + + test('rejects: resolves when rejection expected', async () => { + await expect(Promise.resolve(3)).rejects.toBe(4) + }) + + test('resolves: rejects when resolve expected', async () => { + await expect(Promise.reject(3)).resolves.toBe(4) + }) + `, + }) + + expect(stderr).toMatchInlineSnapshot(` + " + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 4 ⎯⎯⎯⎯⎯⎯⎯ + + FAIL repro.test.ts > resolves: resolved promise with mismatched value + AssertionError: expected 3 to be 4 // Object.is equality + + - Expected + + Received + + - 4 + + 3 + + ❯ repro.test.ts:5:40 + 3| + 4| test('resolves: resolved promise with mismatched value', async (… + 5| await expect(Promise.resolve(3)).resolves.toBe(4) + | ^ + 6| }) + 7| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/4]⎯ + + FAIL repro.test.ts > rejects: rejected promise with mismatched value + AssertionError: expected 3 to be 4 // Object.is equality + + - Expected + + Received + + - 4 + + 3 + + ❯ repro.test.ts:9:39 + 7| + 8| test('rejects: rejected promise with mismatched value', async ()… + 9| await expect(Promise.reject(3)).rejects.toBe(4) + | ^ + 10| }) + 11| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/4]⎯ + + FAIL repro.test.ts > rejects: resolves when rejection expected + AssertionError: promise resolved "3" instead of rejecting + + - Expected: + Error { + "message": "rejected promise", + } + + + Received: + 3 + + ❯ repro.test.ts:13:40 + 11| + 12| test('rejects: resolves when rejection expected', async () => { + 13| await expect(Promise.resolve(3)).rejects.toBe(4) + | ^ + 14| }) + 15| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/4]⎯ + + FAIL repro.test.ts > resolves: rejects when resolve expected + AssertionError: promise rejected "3" instead of resolving + ❯ repro.test.ts:17:39 + 15| + 16| test('resolves: rejects when resolve expected', async () => { + 17| await expect(Promise.reject(3)).resolves.toBe(4) + | ^ + 18| }) + 19| + + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/4]⎯ + + " + `) + expect(errorTree()).toMatchInlineSnapshot(` + { + "repro.test.ts": { + "rejects: rejected promise with mismatched value": [ + "expected 3 to be 4 // Object.is equality", + ], + "rejects: resolves when rejection expected": [ + "promise resolved "3" instead of rejecting", + ], + "resolves: rejects when resolve expected": [ + "promise rejected "3" instead of resolving", + ], + "resolves: resolved promise with mismatched value": [ + "expected 3 to be 4 // Object.is equality", + ], + }, + } + `) +}) From 8a8391ed86297613633d1679518693b1bf67a178 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 17 Feb 2026 12:43:46 +0900 Subject: [PATCH 6/6] test: trailing space --- test/cli/test/stacktraces.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/cli/test/stacktraces.test.ts b/test/cli/test/stacktraces.test.ts index 451b03ff7b6a..717fcae406e3 100644 --- a/test/cli/test/stacktraces.test.ts +++ b/test/cli/test/stacktraces.test.ts @@ -262,12 +262,12 @@ it('resolves/rejects', async () => { FAIL repro.test.ts > rejects: resolves when rejection expected AssertionError: promise resolved "3" instead of rejecting - - Expected: + - Expected: Error { "message": "rejected promise", } - + Received: + + Received: 3 ❯ repro.test.ts:13:40