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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy