Skip to content

Commit 9fe3873

Browse files
authored
fix: copy custom asymmetric matchers to local expect (#4405)
1 parent 970038b commit 9fe3873

File tree

5 files changed

+60
-4
lines changed

5 files changed

+60
-4
lines changed

packages/expect/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export const MATCHERS_OBJECT = Symbol.for('matchers-object')
22
export const JEST_MATCHERS_OBJECT = Symbol.for('$$jest-matchers-object')
33
export const GLOBAL_EXPECT = Symbol.for('expect-global')
4+
export const ASYMMETRIC_MATCHERS_OBJECT = Symbol.for('asymmetric-matchers-object')

packages/expect/src/jest-extend.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
MatchersObject,
77
SyncExpectationResult,
88
} from './types'
9-
import { JEST_MATCHERS_OBJECT } from './constants'
9+
import { ASYMMETRIC_MATCHERS_OBJECT, JEST_MATCHERS_OBJECT } from './constants'
1010
import { AsymmetricMatcher } from './jest-asymmetric-matchers'
1111
import { getState } from './state'
1212

@@ -108,10 +108,12 @@ function JestExtendPlugin(expect: ExpectStatic, matchers: MatchersObject): ChaiP
108108
}
109109
}
110110

111+
const customMatcher = (...sample: [unknown, ...unknown[]]) => new CustomMatcher(false, ...sample)
112+
111113
Object.defineProperty(expect, expectAssertionName, {
112114
configurable: true,
113115
enumerable: true,
114-
value: (...sample: [unknown, ...unknown[]]) => new CustomMatcher(false, ...sample),
116+
value: customMatcher,
115117
writable: true,
116118
})
117119

@@ -121,6 +123,15 @@ function JestExtendPlugin(expect: ExpectStatic, matchers: MatchersObject): ChaiP
121123
value: (...sample: [unknown, ...unknown[]]) => new CustomMatcher(true, ...sample),
122124
writable: true,
123125
})
126+
127+
// keep track of asymmetric matchers on global so that it can be copied over to local context's `expect`.
128+
// note that the negated variant is automatically shared since it's assigned on the single `expect.not` object.
129+
Object.defineProperty(((globalThis as any)[ASYMMETRIC_MATCHERS_OBJECT]), expectAssertionName, {
130+
configurable: true,
131+
enumerable: true,
132+
value: customMatcher,
133+
writable: true,
134+
})
124135
})
125136
}
126137
}

packages/expect/src/state.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { ExpectStatic, MatcherState } from './types'
2-
import { GLOBAL_EXPECT, JEST_MATCHERS_OBJECT, MATCHERS_OBJECT } from './constants'
2+
import { ASYMMETRIC_MATCHERS_OBJECT, GLOBAL_EXPECT, JEST_MATCHERS_OBJECT, MATCHERS_OBJECT } from './constants'
33

44
if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) {
55
const globalState = new WeakMap<ExpectStatic, MatcherState>()
66
const matchers = Object.create(null)
7+
const assymetricMatchers = Object.create(null)
78
Object.defineProperty(globalThis, MATCHERS_OBJECT, {
89
get: () => globalState,
910
})
@@ -14,6 +15,9 @@ if (!Object.prototype.hasOwnProperty.call(globalThis, MATCHERS_OBJECT)) {
1415
matchers,
1516
}),
1617
})
18+
Object.defineProperty(globalThis, ASYMMETRIC_MATCHERS_OBJECT, {
19+
get: () => assymetricMatchers,
20+
})
1721
}
1822

1923
export function getState<State extends MatcherState = MatcherState>(expect: ExpectStatic): State {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as chai from 'chai'
44
import './setup'
55
import type { TaskPopulated, Test } from '@vitest/runner'
66
import { getCurrentTest } from '@vitest/runner'
7-
import { GLOBAL_EXPECT, getState, setState } from '@vitest/expect'
7+
import { ASYMMETRIC_MATCHERS_OBJECT, GLOBAL_EXPECT, getState, setState } from '@vitest/expect'
88
import type { Assertion, ExpectStatic } from '@vitest/expect'
99
import type { MatcherState } from '../../types/chai'
1010
import { getFullName } from '../../utils/tasks'
@@ -23,6 +23,7 @@ export function createExpect(test?: TaskPopulated) {
2323
return assert
2424
}) as ExpectStatic
2525
Object.assign(expect, chai.expect)
26+
Object.assign(expect, (globalThis as any)[ASYMMETRIC_MATCHERS_OBJECT])
2627

2728
expect.getState = () => getState<MatcherState>(expect)
2829
expect.setState = state => setState(state as Partial<MatcherState>, expect)

test/core/test/local-context.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,42 @@ describe('context expect', () => {
3636
expect(localExpect.getState().snapshotState).toBeDefined()
3737
})
3838
})
39+
40+
describe('custom matcher are inherited by local context', () => {
41+
expect.extend({
42+
toEqual_testCustom(received, expected) {
43+
return {
44+
pass: received === expected,
45+
message: () => `test`,
46+
}
47+
},
48+
})
49+
50+
it('basic', ({ expect: localExpect }) => {
51+
// as assertion
52+
expect(expect('test')).toHaveProperty('toEqual_testCustom')
53+
expect(expect.soft('test')).toHaveProperty('toEqual_testCustom')
54+
expect(localExpect('test')).toHaveProperty('toEqual_testCustom')
55+
expect(localExpect.soft('test')).toHaveProperty('toEqual_testCustom')
56+
57+
// as asymmetric matcher
58+
expect(expect).toHaveProperty('toEqual_testCustom')
59+
expect(expect.not).toHaveProperty('toEqual_testCustom')
60+
expect(localExpect).toHaveProperty('toEqual_testCustom')
61+
expect(localExpect.not).toHaveProperty('toEqual_testCustom');
62+
63+
(expect(0) as any).toEqual_testCustom(0);
64+
(expect(0) as any).not.toEqual_testCustom(1);
65+
(localExpect(0) as any).toEqual_testCustom(0);
66+
(localExpect(0) as any).not.toEqual_testCustom(1)
67+
68+
expect(0).toEqual((expect as any).toEqual_testCustom(0))
69+
localExpect(0).toEqual((localExpect as any).toEqual_testCustom(0))
70+
expect(0).toEqual((expect.not as any).toEqual_testCustom(1))
71+
localExpect(0).toEqual((localExpect.not as any).toEqual_testCustom(1))
72+
73+
// asymmetric matcher function is identical
74+
expect((expect as any).toEqual_testCustom).toBe((localExpect as any).toEqual_testCustom)
75+
expect((expect.not as any).toEqual_testCustom).toBe((localExpect.not as any).toEqual_testCustom)
76+
})
77+
})

0 commit comments

Comments
 (0)