Skip to content

Commit 4252328

Browse files
ShreeBoharaclaudehi-ogawacodex
authored
fix(expect): fix toMatchObject with asymmetric matchers (#11100)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Hiroshi Ogawa <hi.ogawa.zz@gmail.com> Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com>
1 parent 5b95efb commit 4252328

2 files changed

Lines changed: 48 additions & 4 deletions

File tree

‎packages/expect/src/jest-utils.ts‎

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,15 @@ function asymmetricMatch(a: any, b: any, customTesters: Array<Tester>) {
7777
return undefined
7878
}
7979

80+
// Asymmetric matchers define their own subset semantics.
81+
const testers = customTesters.filter(tester => !isSubsetEqualityTester(tester))
82+
8083
if (asymmetricA) {
81-
return a.asymmetricMatch(b, customTesters)
84+
return a.asymmetricMatch(b, testers)
8285
}
8386

8487
if (asymmetricB) {
85-
return b.asymmetricMatch(a, customTesters)
88+
return b.asymmetricMatch(a, testers)
8689
}
8790
}
8891

@@ -604,6 +607,20 @@ function isObjectWithKeys(a: any) {
604607
)
605608
}
606609

610+
// Recursive subset testers are closures, so track them by identity.
611+
const subsetEqualityTesters = new WeakSet<Tester>()
612+
613+
function registerSubsetEqualityTester<T extends Tester>(
614+
tester: T,
615+
): T {
616+
subsetEqualityTesters.add(tester)
617+
return tester
618+
}
619+
620+
function isSubsetEqualityTester(tester: Tester): boolean {
621+
return tester === subsetEquality || subsetEqualityTesters.has(tester)
622+
}
623+
607624
export function subsetEquality(
608625
object: unknown,
609626
subset: unknown,
@@ -617,7 +634,7 @@ export function subsetEquality(
617634
// there are circular references in the subset passed to it.
618635
const subsetEqualityWithContext
619636
= (seenReferences: WeakMap<object, boolean> = new WeakMap()) =>
620-
(object: any, subset: any): boolean | undefined => {
637+
registerSubsetEqualityTester((object: any, subset: any): boolean | undefined => {
621638
if (!isObjectWithKeys(subset)) {
622639
return undefined
623640
}
@@ -645,7 +662,7 @@ export function subsetEquality(
645662
seenReferences.delete(subset[key])
646663
return result
647664
})
648-
}
665+
})
649666

650667
return subsetEqualityWithContext()(object, subset)
651668
}

‎test/unit/test/jest-expect.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,33 @@ describe('jest-expect', () => {
226226
)
227227
})
228228

229+
// https://github.com/vitest-dev/vitest/issues/11071
230+
it('asymmetric matchers and toMatchObject equality', () => {
231+
expect([{ id: 1 }]).not.toMatchObject(
232+
expect.arrayContaining([{ id: 1, required: 'x' }]),
233+
)
234+
expect({ nested: [{ id: 1 }] }).not.toMatchObject({
235+
nested: expect.arrayContaining([{ id: 1, required: 'x' }]),
236+
})
237+
expect({ nested: [{ id: 1, extra: true }] }).not.toMatchObject({
238+
nested: expect.arrayContaining([{ id: 1 }]),
239+
})
240+
expect({ nested: [{ id: 1 }] }).not.toMatchObject({
241+
nested: expect.arrayContaining([{ id: 1 }]),
242+
extra: true,
243+
})
244+
245+
expect({ nested: [{ id: 1, required: 'x' }] }).toMatchObject({
246+
nested: expect.arrayContaining([{ id: 1, required: 'x' }]),
247+
})
248+
expect({ nested: [{ id: 1, required: 'x' }, { extra: true }] }).toMatchObject({
249+
nested: expect.arrayContaining([{ id: 1, required: 'x' }]),
250+
})
251+
expect({ nested: [{ id: 1 }], extra: true }).toMatchObject({
252+
nested: expect.arrayContaining([{ id: 1 }]),
253+
})
254+
})
255+
229256
it('asymmetric matchers negate', () => {
230257
expect('bar').toEqual(expect.not.stringContaining('zoo'))
231258
expect('bar').toEqual(expect.not.stringMatching(/zoo/))

0 commit comments

Comments
 (0)