Skip to content

Commit cf3afe2

Browse files
authored
feat!: move assertion declarations to expect package (#3294)
1 parent 1f1189b commit cf3afe2

File tree

13 files changed

+162
-153
lines changed

13 files changed

+162
-153
lines changed

.eslintrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"no-only-tests/no-only-tests": "off",
55
// prefer global Buffer to not initialize the whole module
66
"n/prefer-global/buffer": "off",
7+
"@typescript-eslint/no-invalid-this": "off",
78
"no-restricted-imports": [
89
"error",
910
{

docs/api/expect.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,18 +1290,16 @@ If the value in the error message is too truncated, you can increase [chaiConfig
12901290

12911291
This function is compatible with Jest's `expect.extend`, so any library that uses it to create custom matchers will work with Vitest.
12921292

1293-
If you are using TypeScript, you can extend default `Matchers` interface in an ambient declaration file (e.g: `vitest.d.ts`) with the code below:
1293+
If you are using TypeScript, since Vitest 0.31.0 you can extend default `Assertion` interface in an ambient declaration file (e.g: `vitest.d.ts`) with the code below:
12941294

12951295
```ts
12961296
interface CustomMatchers<R = unknown> {
12971297
toBeFoo(): R
12981298
}
12991299

1300-
declare namespace Vi {
1301-
interface Assertion extends CustomMatchers {}
1300+
declare module '@vitest/expect' {
1301+
interface Assertion<T = any> extends CustomMatchers<T> {}
13021302
interface AsymmetricMatchersContaining extends CustomMatchers {}
1303-
1304-
// Note: augmenting jest.Matchers interface will also work.
13051303
}
13061304
```
13071305

docs/guide/extending-matchers.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,16 @@ expect.extend({
2323
})
2424
```
2525

26-
If you are using TypeScript, you can extend default Matchers interface in an ambient declaration file (e.g: `vitest.d.ts`) with the code below:
26+
If you are using TypeScript, since Vitest 0.31.0 you can extend default `Assertion` interface in an ambient declaration file (e.g: `vitest.d.ts`) with the code below:
2727

2828
```ts
2929
interface CustomMatchers<R = unknown> {
3030
toBeFoo(): R
3131
}
3232

33-
declare namespace Vi {
34-
interface Assertion extends CustomMatchers {}
33+
declare module '@vitest/expect' {
34+
interface Assertion<T = any> extends CustomMatchers<T> {}
3535
interface AsymmetricMatchersContaining extends CustomMatchers {}
36-
37-
// Note: augmenting jest.Matchers interface will also work.
3836
}
3937
```
4038

packages/expect/src/jest-asymmetric-matchers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export abstract class AsymmetricMatcher<
2121

2222
constructor(protected sample: T, protected inverse = false) {}
2323

24-
protected getMatcherContext(expect?: Vi.ExpectStatic): State {
24+
protected getMatcherContext(expect?: Chai.ExpectStatic): State {
2525
return {
2626
...getState(expect || (globalThis as any)[GLOBAL_EXPECT]),
2727
equals,

packages/expect/src/jest-expect.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { assertTypes, getColors } from '@vitest/utils'
33
import type { Constructable } from '@vitest/utils'
44
import type { EnhancedSpy } from '@vitest/spy'
55
import { isMockFunction } from '@vitest/spy'
6-
import type { ChaiPlugin } from './types'
6+
import type { Assertion, ChaiPlugin } from './types'
77
import { arrayBufferEquality, generateToBeMessage, iterableEquality, equals as jestEquals, sparseArrayEquality, subsetEquality, typeEquality } from './jest-utils'
88
import type { AsymmetricMatcher } from './jest-asymmetric-matchers'
99
import { diff, stringify } from './jest-matcher-utils'
@@ -14,8 +14,8 @@ import { recordAsyncExpect } from './utils'
1414
export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
1515
const c = () => getColors()
1616

17-
function def(name: keyof Vi.Assertion | (keyof Vi.Assertion)[], fn: ((this: Chai.AssertionStatic & Vi.Assertion, ...args: any[]) => any)) {
18-
const addMethod = (n: keyof Vi.Assertion) => {
17+
function def(name: keyof Assertion | (keyof Assertion)[], fn: ((this: Chai.AssertionStatic & Assertion, ...args: any[]) => any)) {
18+
const addMethod = (n: keyof Assertion) => {
1919
utils.addMethod(chai.Assertion.prototype, n, fn)
2020
utils.addMethod((globalThis as any)[JEST_MATCHERS_OBJECT].matchers, n, fn)
2121
}

packages/expect/src/jest-extend.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { util } from 'chai'
22
import type {
33
ChaiPlugin,
4+
ExpectStatic,
45
MatcherState,
56
MatchersObject,
67
SyncExpectationResult,
@@ -17,7 +18,7 @@ import {
1718
subsetEquality,
1819
} from './jest-utils'
1920

20-
function getMatcherState(assertion: Chai.AssertionStatic & Chai.Assertion, expect: Vi.ExpectStatic) {
21+
function getMatcherState(assertion: Chai.AssertionStatic & Chai.Assertion, expect: ExpectStatic) {
2122
const obj = assertion._obj
2223
const isNot = util.flag(assertion, 'negate') as boolean
2324
const promise = util.flag(assertion, 'promise') || ''
@@ -52,7 +53,7 @@ class JestExtendError extends Error {
5253
}
5354
}
5455

55-
function JestExtendPlugin(expect: Vi.ExpectStatic, matchers: MatchersObject): ChaiPlugin {
56+
function JestExtendPlugin(expect: ExpectStatic, matchers: MatchersObject): ChaiPlugin {
5657
return (c, utils) => {
5758
Object.entries(matchers).forEach(([expectAssertionName, expectAssertion]) => {
5859
function expectWrapper(this: Chai.AssertionStatic & Chai.Assertion, ...args: any[]) {
@@ -123,7 +124,7 @@ function JestExtendPlugin(expect: Vi.ExpectStatic, matchers: MatchersObject): Ch
123124
}
124125

125126
export const JestExtend: ChaiPlugin = (chai, utils) => {
126-
utils.addMethod(chai.expect, 'extend', (expect: Vi.ExpectStatic, expects: MatchersObject) => {
127+
utils.addMethod(chai.expect, 'extend', (expect: ExpectStatic, expects: MatchersObject) => {
127128
chai.use(JestExtendPlugin(expect, expects))
128129
})
129130
}

packages/expect/src/state.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { MatcherState } from './types'
1+
import type { ExpectStatic, MatcherState } from './types'
22
import { GLOBAL_EXPECT, JEST_MATCHERS_OBJECT, MATCHERS_OBJECT } from './constants'
33

44
if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) {
5-
const globalState = new WeakMap<Vi.ExpectStatic, MatcherState>()
5+
const globalState = new WeakMap<ExpectStatic, MatcherState>()
66
const matchers = Object.create(null)
77
Object.defineProperty(globalThis, MATCHERS_OBJECT, {
88
get: () => globalState,
@@ -16,13 +16,13 @@ if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) {
1616
})
1717
}
1818

19-
export function getState<State extends MatcherState = MatcherState>(expect: Vi.ExpectStatic): State {
19+
export function getState<State extends MatcherState = MatcherState>(expect: ExpectStatic): State {
2020
return (globalThis as any)[MATCHERS_OBJECT].get(expect)
2121
}
2222

2323
export function setState<State extends MatcherState = MatcherState>(
2424
state: Partial<State>,
25-
expect: Vi.ExpectStatic,
25+
expect: ExpectStatic,
2626
): void {
2727
const map = (globalThis as any)[MATCHERS_OBJECT]
2828
const current = map.get(expect) || {}

packages/expect/src/types.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { use as chaiUse } from 'chai'
99
*/
1010

1111
import type { Formatter } from 'picocolors/types'
12+
import type { Constructable } from '@vitest/utils'
1213
import type { diff, getMatcherUtils, stringify } from './jest-matcher-utils'
1314

1415
export type FirstFunctionArgument<T> = T extends (arg: infer A) => unknown ? A : never
@@ -96,3 +97,106 @@ export interface RawMatcherFn<T extends MatcherState = MatcherState> {
9697
}
9798

9899
export type MatchersObject<T extends MatcherState = MatcherState> = Record<string, RawMatcherFn<T>>
100+
101+
export interface ExpectStatic extends Chai.ExpectStatic, AsymmetricMatchersContaining {
102+
<T>(actual: T, message?: string): Assertion<T>
103+
104+
extend(expects: MatchersObject): void
105+
assertions(expected: number): void
106+
hasAssertions(): void
107+
anything(): any
108+
any(constructor: unknown): any
109+
getState(): MatcherState
110+
setState(state: Partial<MatcherState>): void
111+
not: AsymmetricMatchersContaining
112+
}
113+
114+
export interface AsymmetricMatchersContaining {
115+
stringContaining(expected: string): any
116+
objectContaining<T = any>(expected: T): any
117+
arrayContaining<T = unknown>(expected: Array<T>): any
118+
stringMatching(expected: string | RegExp): any
119+
}
120+
121+
export interface JestAssertion<T = any> extends jest.Matchers<void, T> {
122+
// Jest compact
123+
toEqual<E>(expected: E): void
124+
toStrictEqual<E>(expected: E): void
125+
toBe<E>(expected: E): void
126+
toMatch(expected: string | RegExp): void
127+
toMatchObject<E extends {} | any[]>(expected: E): void
128+
toContain<E>(item: E): void
129+
toContainEqual<E>(item: E): void
130+
toBeTruthy(): void
131+
toBeFalsy(): void
132+
toBeGreaterThan(num: number | bigint): void
133+
toBeGreaterThanOrEqual(num: number | bigint): void
134+
toBeLessThan(num: number | bigint): void
135+
toBeLessThanOrEqual(num: number | bigint): void
136+
toBeNaN(): void
137+
toBeUndefined(): void
138+
toBeNull(): void
139+
toBeDefined(): void
140+
toBeInstanceOf<E>(expected: E): void
141+
toBeCalledTimes(times: number): void
142+
toHaveLength(length: number): void
143+
toHaveProperty<E>(property: string | (string | number)[], value?: E): void
144+
toBeCloseTo(number: number, numDigits?: number): void
145+
toHaveBeenCalledTimes(times: number): void
146+
toHaveBeenCalled(): void
147+
toBeCalled(): void
148+
toHaveBeenCalledWith<E extends any[]>(...args: E): void
149+
toBeCalledWith<E extends any[]>(...args: E): void
150+
toHaveBeenNthCalledWith<E extends any[]>(n: number, ...args: E): void
151+
nthCalledWith<E extends any[]>(nthCall: number, ...args: E): void
152+
toHaveBeenLastCalledWith<E extends any[]>(...args: E): void
153+
lastCalledWith<E extends any[]>(...args: E): void
154+
toThrow(expected?: string | Constructable | RegExp | Error): void
155+
toThrowError(expected?: string | Constructable | RegExp | Error): void
156+
toReturn(): void
157+
toHaveReturned(): void
158+
toReturnTimes(times: number): void
159+
toHaveReturnedTimes(times: number): void
160+
toReturnWith<E>(value: E): void
161+
toHaveReturnedWith<E>(value: E): void
162+
toHaveLastReturnedWith<E>(value: E): void
163+
lastReturnedWith<E>(value: E): void
164+
toHaveNthReturnedWith<E>(nthCall: number, value: E): void
165+
nthReturnedWith<E>(nthCall: number, value: E): void
166+
}
167+
168+
type VitestAssertion<A, T> = {
169+
[K in keyof A]: A[K] extends Chai.Assertion
170+
? Assertion<T>
171+
: A[K] extends (...args: any[]) => any
172+
? A[K] // not converting function since they may contain overload
173+
: VitestAssertion<A[K], T>
174+
} & ((type: string, message?: string) => Assertion)
175+
176+
type Promisify<O> = {
177+
[K in keyof O]: O[K] extends (...args: infer A) => infer R
178+
? O extends R
179+
? Promisify<O[K]>
180+
: (...args: A) => Promise<R>
181+
: O[K]
182+
}
183+
184+
export interface Assertion<T = any> extends VitestAssertion<Chai.Assertion, T>, JestAssertion<T> {
185+
toBeTypeOf(expected: 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined'): void
186+
toHaveBeenCalledOnce(): void
187+
toSatisfy<E>(matcher: (value: E) => boolean, message?: string): void
188+
189+
resolves: Promisify<Assertion<T>>
190+
rejects: Promisify<Assertion<T>>
191+
}
192+
193+
declare global {
194+
// support augmenting jest.Matchers by other libraries
195+
namespace jest {
196+
197+
// eslint-disable-next-line unused-imports/no-unused-vars
198+
interface Matchers<R, T = {}> {}
199+
}
200+
}
201+
202+
export {}

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,22 @@ import './setup'
55
import type { Test } from '@vitest/runner'
66
import { getCurrentTest } from '@vitest/runner'
77
import { GLOBAL_EXPECT, getState, setState } from '@vitest/expect'
8+
import type { Assertion, ExpectStatic } from '@vitest/expect'
89
import type { MatcherState } from '../../types/chai'
910
import { getCurrentEnvironment, getFullName } from '../../utils'
1011

1112
export function createExpect(test?: Test) {
12-
const expect = ((value: any, message?: string): Vi.Assertion => {
13+
const expect = ((value: any, message?: string): Assertion => {
1314
const { assertionCalls } = getState(expect)
1415
setState({ assertionCalls: assertionCalls + 1 }, expect)
15-
const assert = chai.expect(value, message) as unknown as Vi.Assertion
16+
const assert = chai.expect(value, message) as unknown as Assertion
1617
const _test = test || getCurrentTest()
1718
if (_test)
1819
// @ts-expect-error internal
19-
return assert.withTest(_test) as Vi.Assertion
20+
return assert.withTest(_test) as Assertion
2021
else
2122
return assert
22-
}) as Vi.ExpectStatic
23+
}) as ExpectStatic
2324
Object.assign(expect, chai.expect)
2425

2526
expect.getState = () => getState<MatcherState>(expect)

packages/vitest/src/runtime/runners/test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { CancelReason, Suite, Test, TestContext, VitestRunner, VitestRunnerImportSource } from '@vitest/runner'
2+
import type { ExpectStatic } from '@vitest/expect'
23
import { GLOBAL_EXPECT, getState, setState } from '@vitest/expect'
34
import { getSnapshotClient } from '../../integrations/snapshot/chai'
45
import { vi } from '../../integrations/vi'
@@ -103,7 +104,7 @@ export class VitestTestRunner implements VitestRunner {
103104
}
104105

105106
extendTestContext(context: TestContext): TestContext {
106-
let _expect: Vi.ExpectStatic | undefined
107+
let _expect: ExpectStatic | undefined
107108
Object.defineProperty(context, 'expect', {
108109
get() {
109110
if (!_expect)

0 commit comments

Comments
 (0)