Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/guide/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,23 @@ test.for([{ id: 'a1' }])('case $id', ({ id }) => { /* ... */ })

- The length limit for interpolated values is now controlled by the new [`taskTitleValueFormatTruncate`](/config/tasktitlevalueformattruncate) option (default `40`).

### Inspected Errors Include Their Extra Properties

`Error` instances (including subclasses like `AssertionError` and custom error classes) are now formatted with their own properties — `message`, `cause`, and any custom fields — instead of the bracketed `[Name: message]` form that only showed the message.

```ts
const err = Object.assign(new Error('boom'), { code: 123 })
expect(err).toMatchInlineSnapshot(`[Error: boom]`) // v4 [!code --]
expect(err).toMatchInlineSnapshot(` // v5 [!code ++]
Error { // v5 [!code ++]
"message": "boom", // v5 [!code ++]
"code": 123, // v5 [!code ++]
} // v5 [!code ++]
`) // v5 [!code ++]
```

Custom properties on an error were previously lost when it was printed, so two errors with the same message but different extra properties looked identical in a diff. Snapshots or assertions on inspected errors (`toMatchInlineSnapshot`, `toThrowErrorMatchingInlineSnapshot`, etc.) may need updating to the new format, and — since `AssertionError` carries its own `actual`/`expected`/`operator` bookkeeping — assertions that throw an `AssertionError` now also show those fields.

### Removed `test.sequential`, `describe.sequential`, and `sequential` Options

Vitest 5.0 removes the deprecated `test.sequential`, `describe.sequential`, and `sequential` test options. Use `concurrent: false` when you need a test or suite to opt out of inherited or globally configured concurrency.
Expand Down
9 changes: 7 additions & 2 deletions packages/pretty-format/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,19 @@ function printComplexValue(
}

const ErrorPlugin: NewPlugin = {
test: val => val && val instanceof Error,
// `instanceof` is realm-bound, so it fails for errors created inside a VM
// context (the `vmThreads` pool). Fall back to the brand check used above.
test: val => val && (val instanceof Error || toString.call(val) === '[object 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
// `stack` is a non-enumerable own property in V8, but an enumerable one in
// SpiderMonkey and JavaScriptCore, where it would otherwise be spread into
// the output as a machine-specific list of absolute URLs.
const { message, cause, stack: _stack, ...rest } = val
const entries = {
message,
...typeof cause !== 'undefined' ? { cause } : {},
Expand Down
2 changes: 2 additions & 0 deletions packages/snapshot/src/port/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import MockSerializer from './mockSerializer'
const {
DOMCollection,
DOMElement,
Error: ErrorPlugin,
Immutable,
ReactElement,
ReactTestComponent,
Expand All @@ -29,6 +30,7 @@ let PLUGINS: PrettyFormatPlugins = [
DOMCollection,
Immutable,
AsymmetricMatcher,
ErrorPlugin,
MockSerializer,
]

Expand Down
3 changes: 3 additions & 0 deletions packages/utils/src/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
AsymmetricMatcher,
DOMCollection,
DOMElement,
Error: ErrorPlugin,
Immutable,
ReactElement,
ReactTestComponent,
Expand All @@ -21,6 +22,7 @@ const PLUGINS = [
DOMCollection,
Immutable,
AsymmetricMatcher,
ErrorPlugin,
]

export interface StringifyOptions extends PrettyFormatOptions {
Expand Down Expand Up @@ -49,6 +51,7 @@ export function stringify(
DOMCollection,
Immutable,
AsymmetricMatcher,
ErrorPlugin,
]
: PLUGINS

Expand Down
2 changes: 1 addition & 1 deletion test/browser/specs/errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ test('print unhandled non error', async () => {
const { testTree, stderr } = await runBrowserTests({
root: './fixtures/unhandled-non-error',
})
expect(stderr).toContain('[Error: ResizeObserver loop completed with undelivered notifications.]')
expect(stderr).toContain('ResizeObserver loop completed with undelivered notifications.')
expect(testTree()).toMatchInlineSnapshot(`
{
"basic.test.ts": {
Expand Down
31 changes: 19 additions & 12 deletions test/browser/test/findElement.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ test('locator.findElement fails if there are multiple elements by default', asyn
await expect(
() => page.getByRole('button').findElement(),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[Error: strict mode violation: getByRole('button') resolved to 2 elements:
Error {
"message": "strict mode violation: getByRole('button') resolved to 2 elements:
1) <button></button> aka getByRole('button').first()
2) <button></button> aka getByRole('button').nth(1)
]
",
}
`)
})

Expand All @@ -58,10 +60,12 @@ test('locator.findElement fails if there are multiple elements if strict mode is
await expect(
() => page.getByRole('button').findElement({ strict: true }),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[Error: strict mode violation: getByRole('button') resolved to 2 elements:
Error {
"message": "strict mode violation: getByRole('button') resolved to 2 elements:
1) <button></button> aka getByRole('button').first()
2) <button></button> aka getByRole('button').nth(1)
]
",
}
`)
})

Expand All @@ -74,10 +78,12 @@ test('locator.findElement fails if multiple elements appear later with strict mo
await expect(
() => page.getByRole('button').findElement(),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[Error: strict mode violation: getByRole('button') resolved to 2 elements:
Error {
"message": "strict mode violation: getByRole('button') resolved to 2 elements:
1) <button></button> aka getByRole('button').first()
2) <button></button> aka getByRole('button').nth(1)
]
",
}
`)
})

Expand Down Expand Up @@ -112,12 +118,13 @@ function createButton() {
test('expect.element is strict', async () => {
createButton()
createButton()
// Asserted on the message rather than the whole error: the polling matcher
// attaches a "Matcher did not succeed in time." cause in Chromium and Firefox
// but not in WebKit, and a single inline snapshot cannot cover both shapes.
await expect(
() => expect.element(page.getByRole('button'), { timeout: 50 }).toBeVisible(),
).rejects.toThrowErrorMatchingInlineSnapshot(`
[Error: strict mode violation: getByRole('button') resolved to 2 elements:
1) <button></button> aka getByRole('button').first()
2) <button></button> aka getByRole('button').nth(1)
]
`)
).rejects.toThrowError(`strict mode violation: getByRole('button') resolved to 2 elements:
1) <button></button> aka getByRole('button').first()
2) <button></button> aka getByRole('button').nth(1)
`)
})
6 changes: 5 additions & 1 deletion test/coverage-test/test/threshold-auto-update.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,11 @@ import config from "./some-path"
export default config
`)

await expect(updateThresholds(config)).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Failed to update coverage thresholds. Configuration file is too complex.]`)
await expect(updateThresholds(config)).rejects.toThrowErrorMatchingInlineSnapshot(`
Error {
"message": "Failed to update coverage thresholds. Configuration file is too complex.",
}
`)
})

test('formats values with custom formatter', async () => {
Expand Down
15 changes: 14 additions & 1 deletion test/e2e/snapshots/domain-poll.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,20 @@ test('signal', async () => {
})
return new Promise(() => {})
}, { timeout: 100, interval: 10 }).toMatchKvSnapshot()
).rejects.toThrowErrorMatchingInlineSnapshot(\`[Error: poll() did not produce a stable snapshot within the timeout]\`)
).rejects.toThrowErrorMatchingInlineSnapshot(\`
JestExtendError {
"message": "poll() did not produce a stable snapshot within the timeout",
"cause": Error {
"message": "Matcher did not succeed in time.",
},
"actual": undefined,
"expected": undefined,
"__vitest_error_context__": {
"assertionName": "toMatchKvSnapshot",
"meta": undefined,
},
}
\`)
expect(aborted).toMatchInlineSnapshot(\`true\`)
})
"
Expand Down
61 changes: 44 additions & 17 deletions test/e2e/snapshots/inline-multiple-calls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,11 @@ test('test.each/for', async () => {
test.for(["hello", "world"])("toThrowErrorMatchingInlineSnapshot %s", (arg) => {
expect(() => {
throw new Error(\`length = \${arg.length}\`);
}).toThrowErrorMatchingInlineSnapshot(\`[Error: length = 5]\`)
}).toThrowErrorMatchingInlineSnapshot(\`
Error {
"message": "length = 5",
}
\`)
});
"
`)
Expand Down Expand Up @@ -528,32 +532,44 @@ test('test.each/for', async () => {
FAIL each.test.ts > toThrowErrorMatchingInlineSnapshot hey
Error: Snapshot \`toThrowErrorMatchingInlineSnapshot hey 1\` mismatched

Expected: "[Error: length = 5]"
Received: "[Error: length = 3]"
- Expected
+ Received

Error {
- "message": "length = 5",
+ "message": "length = 3",
}

❯ each.test.ts:16:6
14| expect(() => {
15| throw new Error(\`length = \${arg.length}\`);
16| }).toThrowErrorMatchingInlineSnapshot(\`[Error: length = 5]\`)
16| }).toThrowErrorMatchingInlineSnapshot(\`
| ^
17| });
18|
17| Error {
18| "message": "length = 5",

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[5/6]⎯

FAIL each.test.ts > toThrowErrorMatchingInlineSnapshot world
Error: toThrowErrorMatchingInlineSnapshot with different snapshots cannot be called at the same location

Expected: "[Error: length = 3]"
Received: "[Error: length = 5]"
- Expected
+ Received


Error {
- "message": "length = 3",
+ "message": "length = 5",
}


❯ each.test.ts:16:6
14| expect(() => {
15| throw new Error(\`length = \${arg.length}\`);
16| }).toThrowErrorMatchingInlineSnapshot(\`[Error: length = 5]\`)
16| }).toThrowErrorMatchingInlineSnapshot(\`
| ^
17| });
18|
17| Error {
18| "message": "length = 5",

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[6/6]⎯

Expand Down Expand Up @@ -647,16 +663,23 @@ test('test.each/for', async () => {
FAIL each.test.ts > toThrowErrorMatchingInlineSnapshot world
Error: toThrowErrorMatchingInlineSnapshot with different snapshots cannot be called at the same location

Expected: "[Error: length = 3]"
Received: "[Error: length = 5]"
- Expected
+ Received


Error {
- "message": "length = 3",
+ "message": "length = 5",
}


❯ each.test.ts:16:6
14| expect(() => {
15| throw new Error(\`length = \${arg.length}\`);
16| }).toThrowErrorMatchingInlineSnapshot(\`[Error: length = 5]\`)
16| }).toThrowErrorMatchingInlineSnapshot(\`
| ^
17| });
18|
17| Error {
18| "message": "length = 5",

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/3]⎯

Expand Down Expand Up @@ -700,7 +723,11 @@ test('test.each/for', async () => {
test.for(["hey", "world"])("toThrowErrorMatchingInlineSnapshot %s", (arg) => {
expect(() => {
throw new Error(\`length = \${arg.length}\`);
}).toThrowErrorMatchingInlineSnapshot(\`[Error: length = 5]\`)
}).toThrowErrorMatchingInlineSnapshot(\`
Error {
"message": "length = 5",
}
\`)
});
"
`)
Expand Down
24 changes: 20 additions & 4 deletions test/e2e/snapshots/soft-inline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ test('soft inline', async () => {
})

test('toThrowErrorMatchingInlineSnapshot', () => {
expect.soft(() => { throw new Error('--error-1--') }).toThrowErrorMatchingInlineSnapshot(\`[Error: --error-1--]\`)
expect.soft(() => { throw new Error('--error-2--') }).toThrowErrorMatchingInlineSnapshot(\`[Error: --error-2--]\`)
expect.soft(() => { throw new Error('--error-1--') }).toThrowErrorMatchingInlineSnapshot(\`
Error {
"message": "--error-1--",
}
\`)
expect.soft(() => { throw new Error('--error-2--') }).toThrowErrorMatchingInlineSnapshot(\`
Error {
"message": "--error-2--",
}
\`)
})
"
`)
Expand Down Expand Up @@ -73,8 +81,16 @@ test('soft inline', async () => {
})

test('toThrowErrorMatchingInlineSnapshot', () => {
expect.soft(() => { throw new Error('--error-1-edit--') }).toThrowErrorMatchingInlineSnapshot(\`[Error: --error-1-edit--]\`)
expect.soft(() => { throw new Error('--error-2-edit--') }).toThrowErrorMatchingInlineSnapshot(\`[Error: --error-2-edit--]\`)
expect.soft(() => { throw new Error('--error-1-edit--') }).toThrowErrorMatchingInlineSnapshot(\`
Error {
"message": "--error-1-edit--",
}
\`)
expect.soft(() => { throw new Error('--error-2-edit--') }).toThrowErrorMatchingInlineSnapshot(\`
Error {
"message": "--error-2-edit--",
}
\`)
})
"
`)
Expand Down
Loading
Loading