Skip to content

Commit fe5895d

Browse files
authored
feat: support expect.assert for type narrowing (#8695)
1 parent 699bf80 commit fe5895d

File tree

4 files changed

+45
-13
lines changed

4 files changed

+45
-13
lines changed

docs/api/assert.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ test('assert.fail', () => {
3535

3636
## isOk
3737

38-
- **Type:** `<T>(value: T, message?: string) => void`
38+
- **Type:** `<T>(value: T, message?: string) => asserts value`
3939
- **Alias** `ok`
4040

4141
Assert that the given `value` is truthy.
@@ -195,7 +195,7 @@ test('assert.isAtMost', () => {
195195

196196
## isTrue
197197

198-
- **Type:** `<T>(value: T, message?: string) => void`
198+
- **Type:** `<T>(value: T, message?: string) => asserts value is true`
199199

200200
Asserts that `value` is true.
201201

@@ -211,7 +211,7 @@ test('assert.isTrue', () => {
211211

212212
## isNotTrue
213213

214-
- **Type:** `<T>(value: T, message?: string) => void`
214+
- **Type:** `<T>(value: T, message?: string) => asserts value is Exclude<T, true>`
215215

216216
Asserts that `value` is not true.
217217

@@ -227,7 +227,7 @@ test('assert.isNotTrue', () => {
227227

228228
## isFalse
229229

230-
- **Type:** `<T>(value: T, message?: string) => void`
230+
- **Type:** `<T>(value: T, message?: string) => asserts value is false`
231231

232232
Asserts that `value` is false.
233233

@@ -243,7 +243,7 @@ test('assert.isFalse', () => {
243243

244244
## isNotFalse
245245

246-
- **Type:** `<T>(value: T, message?: string) => void`
246+
- **Type:** `<T>(value: T, message?: string) => asserts value is Exclude<T, false>`
247247

248248
Asserts that `value` is not false.
249249

@@ -259,7 +259,7 @@ test('assert.isNotFalse', () => {
259259

260260
## isNull
261261

262-
- **Type:** `<T>(value: T, message?: string) => void`
262+
- **Type:** `<T>(value: T, message?: string) => asserts value is null`
263263

264264
Asserts that `value` is null.
265265

@@ -275,7 +275,7 @@ test('assert.isNull', () => {
275275

276276
## isNotNull
277277

278-
- **Type:** `<T>(value: T, message?: string) => void`
278+
- **Type:** `<T>(value: T, message?: string) => asserts value is Exclude<T, null>`
279279

280280
Asserts that `value` is not null.
281281

@@ -323,7 +323,7 @@ test('assert.isNotNaN', () => {
323323

324324
## exists
325325

326-
- **Type:** `<T>(value: T, message?: string) => void`
326+
- **Type:** `<T>(value: T, message?: string) => asserts value is NonNullable<T>`
327327

328328
Asserts that `value` is neither null nor undefined.
329329

@@ -339,7 +339,7 @@ test('assert.exists', () => {
339339

340340
## notExists
341341

342-
- **Type:** `<T>(value: T, message?: string) => void`
342+
- **Type:** `<T>(value: T, message?: string) => asserts value is null | undefined`
343343

344344
Asserts that `value` is either null nor undefined.
345345

@@ -357,7 +357,7 @@ test('assert.notExists', () => {
357357

358358
## isUndefined
359359

360-
- **Type:** `<T>(value: T, message?: string) => void`
360+
- **Type:** `<T>(value: T, message?: string) => asserts value is undefined`
361361

362362
Asserts that `value` is undefined.
363363

@@ -373,7 +373,7 @@ test('assert.isUndefined', () => {
373373

374374
## isDefined
375375

376-
- **Type:** `<T>(value: T, message?: string) => void`
376+
- **Type:** `<T>(value: T, message?: string) => asserts value is Exclude<T, undefined>`
377377

378378
Asserts that `value` is not undefined.
379379

@@ -631,7 +631,7 @@ test('assert.notTypeOf', () => {
631631

632632
## instanceOf
633633

634-
- **Type:** `<T>(value: T, constructor: Function, message?: string) => void`
634+
- **Type:** `<T>(value: T, constructor: Function, message?: string) => asserts value is T`
635635

636636
Asserts that `value` is an instance of `constructor`.
637637

@@ -656,7 +656,7 @@ test('assert.instanceOf', () => {
656656

657657
## notInstanceOf
658658

659-
- **Type:** `<T>(value: T, constructor: Function, message?: string) => void`
659+
- **Type:** `<T>(value: T, constructor: Function, message?: string) => asserts value is Exclude<T, U>`
660660

661661
Asserts that `value` is not an instance of `constructor`.
662662

docs/api/expect.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,36 @@ Also, `expect` can be used statically to access matcher functions, described lat
3939
`expect` has no effect on testing types, if the expression doesn't have a type error. If you want to use Vitest as [type checker](/guide/testing-types), use [`expectTypeOf`](/api/expect-typeof) or [`assertType`](/api/assert-type).
4040
:::
4141

42+
## assert
43+
44+
- **Type:** `Chai.AssertStatic`
45+
46+
Vitest reexports chai's [`assert` API](https://www.chaijs.com/api/assert/) as `expect.assert` for convenience. You can see the supported methods on the [Assert API page](/api/assert).
47+
48+
This is especially useful if you need to narrow down the type, since `expect.to*` methods do not support that:
49+
50+
```ts
51+
interface Cat {
52+
__type: 'Cat'
53+
mew(): void
54+
}
55+
interface Dog {
56+
__type: 'Dog'
57+
bark(): void
58+
}
59+
type Animal = Cat | Dog
60+
61+
const animal: Animal = { __type: 'Dog', bark: () => {} }
62+
63+
expect.assert(animal.__type === 'Dog')
64+
// does not show a type error!
65+
expect(animal.bark()).toBeUndefined()
66+
```
67+
68+
::: tip
69+
Note that `expect.assert` also supports other type-narrowing methods (like `assert.isDefined`, `assert.exists` and so on).
70+
:::
71+
4272
## soft
4373

4474
- **Type:** `ExpectStatic & (actual: any) => Assertions`

packages/vitest/src/integrations/chai/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export function createExpect(test?: TaskPopulated): ExpectStatic {
5757
expect,
5858
)
5959

60+
expect.assert = chai.assert
6061
// @ts-expect-error untyped
6162
expect.extend = matchers => chai.expect.extend(expect, matchers)
6263
expect.addEqualityTesters = customTesters =>

packages/vitest/src/types/global.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ declare module '@vitest/expect' {
3434
}
3535

3636
interface ExpectStatic {
37+
assert: Chai.AssertStatic
3738
unreachable: (message?: string) => never
3839
soft: <T>(actual: T, message?: string) => Assertion<T>
3940
poll: <T>(

0 commit comments

Comments
 (0)