Skip to content

Commit c61511d

Browse files
authored
fix: preserve stack trace of resolves/rejects chained assertion error (#9679)
1 parent 1d9392c commit c61511d

File tree

2 files changed

+141
-13
lines changed

2 files changed

+141
-13
lines changed

packages/expect/src/jest-expect.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,7 +1098,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
10981098

10991099
return (...args: any[]) => {
11001100
utils.flag(this, '_name', key)
1101-
const promise = obj.then(
1101+
const promise = Promise.resolve(obj).then(
11021102
(value: any) => {
11031103
utils.flag(this, 'object', value)
11041104
return result.call(this, ...args)
@@ -1111,13 +1111,17 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
11111111
{ showDiff: false },
11121112
) as Error
11131113
_error.cause = err
1114-
_error.stack = (error.stack as string).replace(
1115-
error.message,
1116-
_error.message,
1117-
)
11181114
throw _error
11191115
},
1120-
)
1116+
).catch((err: any) => {
1117+
if (isError(err) && error.stack) {
1118+
err.stack = error.stack.replace(
1119+
error.message,
1120+
err.message,
1121+
)
1122+
}
1123+
throw err
1124+
})
11211125

11221126
return recordAsyncExpect(
11231127
test,
@@ -1166,7 +1170,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
11661170

11671171
return (...args: any[]) => {
11681172
utils.flag(this, '_name', key)
1169-
const promise = wrapper.then(
1173+
const promise = Promise.resolve(wrapper).then(
11701174
(value: any) => {
11711175
const _error = new AssertionError(
11721176
`promise resolved "${utils.inspect(
@@ -1178,17 +1182,21 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
11781182
actual: value,
11791183
},
11801184
) as any
1181-
_error.stack = (error.stack as string).replace(
1182-
error.message,
1183-
_error.message,
1184-
)
11851185
throw _error
11861186
},
11871187
(err: any) => {
11881188
utils.flag(this, 'object', err)
11891189
return result.call(this, ...args)
11901190
},
1191-
)
1191+
).catch((err: any) => {
1192+
if (isError(err) && error.stack) {
1193+
err.stack = error.stack.replace(
1194+
error.message,
1195+
err.message,
1196+
)
1197+
}
1198+
throw err
1199+
})
11921200

11931201
return recordAsyncExpect(
11941202
test,

test/cli/test/stacktraces.test.ts

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { resolve } from 'pathe'
22
import { glob } from 'tinyglobby'
33
import { describe, expect, it } from 'vitest'
4-
import { runVitest } from '../../test-utils'
4+
import { runInlineTests, runVitest } from '../../test-utils'
55

66
// To prevent the warning coming up in snapshots
77
process.setMaxListeners(20)
@@ -193,3 +193,123 @@ it('custom helper with captureStackTrace', async () => {
193193
}
194194
`)
195195
})
196+
197+
it('resolves/rejects', async () => {
198+
const { stderr, errorTree } = await runInlineTests({
199+
'repro.test.ts': `
200+
import { test, expect } from 'vitest'
201+
202+
test('resolves: resolved promise with mismatched value', async () => {
203+
await expect(Promise.resolve(3)).resolves.toBe(4)
204+
})
205+
206+
test('rejects: rejected promise with mismatched value', async () => {
207+
await expect(Promise.reject(3)).rejects.toBe(4)
208+
})
209+
210+
test('rejects: resolves when rejection expected', async () => {
211+
await expect(Promise.resolve(3)).rejects.toBe(4)
212+
})
213+
214+
test('resolves: rejects when resolve expected', async () => {
215+
await expect(Promise.reject(3)).resolves.toBe(4)
216+
})
217+
`,
218+
})
219+
220+
expect(stderr).toMatchInlineSnapshot(`
221+
"
222+
⎯⎯⎯⎯⎯⎯⎯ Failed Tests 4 ⎯⎯⎯⎯⎯⎯⎯
223+
224+
FAIL repro.test.ts > resolves: resolved promise with mismatched value
225+
AssertionError: expected 3 to be 4 // Object.is equality
226+
227+
- Expected
228+
+ Received
229+
230+
- 4
231+
+ 3
232+
233+
❯ repro.test.ts:5:40
234+
3|
235+
4| test('resolves: resolved promise with mismatched value', async (…
236+
5| await expect(Promise.resolve(3)).resolves.toBe(4)
237+
| ^
238+
6| })
239+
7|
240+
241+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/4]⎯
242+
243+
FAIL repro.test.ts > rejects: rejected promise with mismatched value
244+
AssertionError: expected 3 to be 4 // Object.is equality
245+
246+
- Expected
247+
+ Received
248+
249+
- 4
250+
+ 3
251+
252+
❯ repro.test.ts:9:39
253+
7|
254+
8| test('rejects: rejected promise with mismatched value', async ()…
255+
9| await expect(Promise.reject(3)).rejects.toBe(4)
256+
| ^
257+
10| })
258+
11|
259+
260+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/4]⎯
261+
262+
FAIL repro.test.ts > rejects: resolves when rejection expected
263+
AssertionError: promise resolved "3" instead of rejecting
264+
265+
- Expected:
266+
Error {
267+
"message": "rejected promise",
268+
}
269+
270+
+ Received:
271+
3
272+
273+
❯ repro.test.ts:13:40
274+
11|
275+
12| test('rejects: resolves when rejection expected', async () => {
276+
13| await expect(Promise.resolve(3)).rejects.toBe(4)
277+
| ^
278+
14| })
279+
15|
280+
281+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/4]⎯
282+
283+
FAIL repro.test.ts > resolves: rejects when resolve expected
284+
AssertionError: promise rejected "3" instead of resolving
285+
❯ repro.test.ts:17:39
286+
15|
287+
16| test('resolves: rejects when resolve expected', async () => {
288+
17| await expect(Promise.reject(3)).resolves.toBe(4)
289+
| ^
290+
18| })
291+
19|
292+
293+
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[4/4]⎯
294+
295+
"
296+
`)
297+
expect(errorTree()).toMatchInlineSnapshot(`
298+
{
299+
"repro.test.ts": {
300+
"rejects: rejected promise with mismatched value": [
301+
"expected 3 to be 4 // Object.is equality",
302+
],
303+
"rejects: resolves when rejection expected": [
304+
"promise resolved "3" instead of rejecting",
305+
],
306+
"resolves: rejects when resolve expected": [
307+
"promise rejected "3" instead of resolving",
308+
],
309+
"resolves: resolved promise with mismatched value": [
310+
"expected 3 to be 4 // Object.is equality",
311+
],
312+
},
313+
}
314+
`)
315+
})

0 commit comments

Comments
 (0)