Skip to content

Commit

Permalink
feat: remove unrelated noise from diff for toMatchObject()
Browse files Browse the repository at this point in the history
  • Loading branch information
geersch committed Mar 15, 2024
1 parent a8da192 commit 77ffa06
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 18 deletions.
4 changes: 2 additions & 2 deletions packages/expect/src/jest-expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { MockInstance } from '@vitest/spy'
import { isMockFunction } from '@vitest/spy'
import type { Test } from '@vitest/runner'
import type { Assertion, ChaiPlugin } from './types'
import { arrayBufferEquality, generateToBeMessage, iterableEquality, equals as jestEquals, sparseArrayEquality, subsetEquality, typeEquality } from './jest-utils'
import { arrayBufferEquality, generateToBeMessage, getObjectSubset, iterableEquality, equals as jestEquals, sparseArrayEquality, subsetEquality, typeEquality } from './jest-utils'
import type { AsymmetricMatcher } from './jest-asymmetric-matchers'
import { diff, getCustomEqualityTesters, stringify } from './jest-matcher-utils'
import { JEST_MATCHERS_OBJECT } from './constants'
Expand Down Expand Up @@ -166,7 +166,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
'expected #{this} to match object #{exp}',
'expected #{this} to not match object #{exp}',
expected,
actual,
getObjectSubset(actual, expected),
)
})
def('toMatch', function (expected: string | RegExp) {
Expand Down
57 changes: 56 additions & 1 deletion packages/expect/src/jest-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ export function iterableEquality(a: any, b: any, customTesters: Array<Tester> =
/**
* Checks if `hasOwnProperty(object, key)` up the prototype chain, stopping at `Object.prototype`.
*/
function hasPropertyInObject(object: object, key: string): boolean {
function hasPropertyInObject(object: object, key: string | symbol): boolean {
const shouldTerminate
= !object || typeof object !== 'object' || object === Object.prototype

Expand Down Expand Up @@ -540,3 +540,58 @@ export function generateToBeMessage(deepEqualityName: string, expected = '#{this
export function pluralize(word: string, count: number): string {
return `${count} ${word}${count === 1 ? '' : 's'}`
}

export function getObjectKeys(object: object): Array<string | symbol> {
return [
...Object.keys(object),
...Object.getOwnPropertySymbols(object).filter(
s => Object.getOwnPropertyDescriptor(object, s)?.enumerable,
),
]
}

export function getObjectSubset(object: any, subset: any, customTesters: Array<Tester> = [], seenReferences: WeakMap<object, boolean> = new WeakMap()): any {
if (Array.isArray(object)) {
if (Array.isArray(subset) && subset.length === object.length) {
// The map method returns correct subclass of subset.
return subset.map((sub: any, i: number) =>
getObjectSubset(object[i], sub, customTesters),
)
}
}
else if (object instanceof Date) {
return object
}
else if (isObject(object) && isObject(subset)) {
if (
equals(object, subset, [
...customTesters,
iterableEquality,
subsetEquality,
])
) {
// Avoid unnecessary copy which might return Object instead of subclass.
return subset
}

const trimmed: any = {}
seenReferences.set(object, trimmed)

for (const key of getObjectKeys(object).filter(key =>
hasPropertyInObject(subset, key),
)) {
trimmed[key] = seenReferences.has(object[key])
? seenReferences.get(object[key])
: getObjectSubset(
object[key],
subset[key],
customTesters,
seenReferences,
)
}

if (getObjectKeys(trimmed).length > 0)
return trimmed
}
return object
}
102 changes: 87 additions & 15 deletions test/core/test/jest-expect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -901,24 +901,96 @@ it('correctly prints diff with asymmetric matchers', () => {
}
})

it('toHaveProperty error diff', () => {
setupColors(getDefaultColors())
// make it easy for dev who trims trailing whitespace on IDE
function trim(s: string): string {
return s.replaceAll(/ *$/gm, '')
}

// make it easy for dev who trims trailing whitespace on IDE
function trim(s: string): string {
return s.replaceAll(/ *$/gm, '')
function getError(f: () => unknown) {
try {
f()
return expect.unreachable()
}

function getError(f: () => unknown) {
try {
f()
return expect.unreachable()
}
catch (error) {
const processed = processError(error)
return [processed.message, trim(processed.diff)]
}
catch (error) {
const processed = processError(error)
return [processed.message, trim(processed.diff)]
}
}

it('toMatchObject error diff', () => {
setupColors(getDefaultColors())

// single property on root
expect(getError(() => expect({ a: 1, b: 2, c: { d: 4 } }).toMatchObject({ b: 3 }))).toMatchInlineSnapshot(`
[
"expected { a: 1, b: 2, c: { d: 4 } } to match object { b: 3 }",
"- Expected
+ Received
Object {
- "b": 3,
+ "b": 2,
}",
]
`)

// nested property
expect(getError(() => expect({ a: 1, b: 2, c: { d: 4 } }).toMatchObject({ c: { d: 5 } }))).toMatchInlineSnapshot(`
[
"expected { a: 1, b: 2, c: { d: 4 } } to match object { c: { d: 5 } }",
"- Expected
+ Received
Object {
"c": Object {
- "d": 5,
+ "d": 4,
},
}",
]
`)

// multiple nested properties
expect(getError(() => expect({ a: 1, b: 2, c: { d: 4 }, foo: { value: 'bar' }, bar: { value: 'foo' } }).toMatchObject({ c: { d: 5 }, foo: { value: 'biz' } }))).toMatchInlineSnapshot(`
[
"expected { a: 1, b: 2, c: { d: 4 }, …(2) } to match object { c: { d: 5 }, foo: { value: 'biz' } }",
"- Expected
+ Received
Object {
"c": Object {
- "d": 5,
+ "d": 4,
},
"foo": Object {
- "value": "biz",
+ "value": "bar",
},
}",
]
`)

// property on root, nothing stripped
expect(getError(() => expect({ a: 1, b: 2, c: { d: 4 } }).toMatchObject({ a: 1, b: 3, c: { d: 4 } }))).toMatchInlineSnapshot(`
[
"expected { a: 1, b: 2, c: { d: 4 } } to match object { a: 1, b: 3, c: { d: 4 } }",
"- Expected
+ Received
Object {
"a": 1,
- "b": 3,
+ "b": 2,
"c": Object {
"d": 4,
},
}",
]
`)
})

it('toHaveProperty error diff', () => {
setupColors(getDefaultColors())

// non match value
expect(getError(() => expect({ name: 'foo' }).toHaveProperty('name', 'bar'))).toMatchInlineSnapshot(`
Expand Down

0 comments on commit 77ffa06

Please sign in to comment.