Skip to content

Commit b2ec724

Browse files
Copilotsheremet-vahi-ogawa
authored
fix: improve asymmetric matcher diff readability by unwrapping container matchers (#9330)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sheremet-va <16173870+sheremet-va@users.noreply.github.com> Co-authored-by: hi-ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Hiroshi Ogawa <hi.ogawa.zz@gmail.com>
1 parent 7b10ab4 commit b2ec724

File tree

4 files changed

+168
-11
lines changed

4 files changed

+168
-11
lines changed

packages/utils/src/diff/index.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,12 +337,36 @@ export function replaceAsymmetricMatcher(
337337
const actualValue = actual[key]
338338
if (isAsymmetricMatcher(expectedValue)) {
339339
if (expectedValue.asymmetricMatch(actualValue)) {
340-
actual[key] = expectedValue
340+
// When matcher matches, replace expected with actual value
341+
// so they appear the same in the diff
342+
expected[key] = actualValue
343+
}
344+
else if ('sample' in expectedValue && expectedValue.sample !== undefined && isReplaceable(actualValue, expectedValue.sample)) {
345+
// For container matchers (ArrayContaining, ObjectContaining), unwrap and recursively process
346+
// Matcher doesn't match: unwrap but keep structure to show mismatch
347+
const replaced = replaceAsymmetricMatcher(
348+
actualValue,
349+
expectedValue.sample,
350+
actualReplaced,
351+
expectedReplaced,
352+
)
353+
actual[key] = replaced.replacedActual
354+
expected[key] = replaced.replacedExpected
341355
}
342356
}
343357
else if (isAsymmetricMatcher(actualValue)) {
344358
if (actualValue.asymmetricMatch(expectedValue)) {
345-
expected[key] = actualValue
359+
actual[key] = expectedValue
360+
}
361+
else if ('sample' in actualValue && actualValue.sample !== undefined && isReplaceable(actualValue.sample, expectedValue)) {
362+
const replaced = replaceAsymmetricMatcher(
363+
actualValue.sample,
364+
expectedValue,
365+
actualReplaced,
366+
expectedReplaced,
367+
)
368+
actual[key] = replaced.replacedActual
369+
expected[key] = replaced.replacedExpected
346370
}
347371
}
348372
else if (isReplaceable(actualValue, expectedValue)) {

test/core/test/diff.test.ts

Lines changed: 140 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ test('asymmetric matcher in object', () => {
142142
{
143143
- "x": 1,
144144
+ "x": 0,
145-
"y": Anything,
145+
"y": "foo",
146146
}"
147147
`)
148148
})
@@ -159,7 +159,7 @@ test('asymmetric matcher in object with truncated diff', () => {
159159
+ Received
160160
161161
{
162-
"w": Anything,
162+
"w": "foo",
163163
- "x": 1,
164164
+ "x": 0,
165165
... Diff result is truncated"
@@ -174,7 +174,7 @@ test('asymmetric matcher in array', () => {
174174
[
175175
- 1,
176176
+ 0,
177-
Anything,
177+
"foo",
178178
]"
179179
`)
180180
})
@@ -211,12 +211,12 @@ test('asymmetric matcher in nested', () => {
211211
{
212212
- "x": 1,
213213
+ "x": 0,
214-
"y": Anything,
214+
"y": "foo",
215215
},
216216
[
217217
- 1,
218218
+ 0,
219-
Anything,
219+
"bar",
220220
],
221221
]"
222222
`)
@@ -237,8 +237,8 @@ test('asymmetric matcher in nested with truncated diff', () => {
237237
{
238238
- "x": 1,
239239
+ "x": 0,
240-
"y": Anything,
241-
"z": Anything,
240+
"y": "foo",
241+
"z": "bar",
242242
... Diff result is truncated"
243243
`)
244244
})
@@ -353,3 +353,136 @@ function getErrorDiff(actual: unknown, expected: unknown, options?: DiffOptions)
353353
}
354354
return expect.unreachable()
355355
}
356+
357+
test('asymmetric matcher with objectContaining - simple case', () => {
358+
const actual = {
359+
user: {
360+
name: 'John',
361+
age: 25,
362+
email: 'john@example.com',
363+
},
364+
}
365+
366+
const expected = {
367+
user: expect.objectContaining({
368+
name: expect.stringContaining('Jane'),
369+
age: expect.any(Number),
370+
email: expect.stringContaining('example.com'),
371+
}),
372+
}
373+
374+
expect(stripVTControlCharacters(getErrorDiff(actual, expected))).toMatchInlineSnapshot(`
375+
"- Expected
376+
+ Received
377+
378+
{
379+
"user": {
380+
"age": 25,
381+
"email": "john@example.com",
382+
- "name": StringContaining "Jane",
383+
+ "name": "John",
384+
},
385+
}"
386+
`)
387+
})
388+
389+
test('asymmetric matcher with nested objectContaining and arrayContaining', () => {
390+
const actual = {
391+
model: 'veo-3.1-generate-preview',
392+
instances: [
393+
{
394+
prompt: 'walk',
395+
referenceImages: [
396+
{
397+
image: {
398+
gcsUri: 'gs://example/person1.jpg',
399+
mimeType: 'image/png',
400+
},
401+
referenceType: 'asset',
402+
},
403+
{
404+
image: {
405+
gcsUri: 'gs://example/person.jpg',
406+
mimeType: 'image/png',
407+
},
408+
referenceType: 'asset',
409+
},
410+
],
411+
},
412+
],
413+
parameters: {
414+
durationSeconds: '8',
415+
aspectRatio: '16:9',
416+
generateAudio: true,
417+
},
418+
}
419+
420+
const expected = {
421+
model: expect.stringMatching(/^veo-3\.1-(fast-)?generate-preview$/),
422+
instances: expect.arrayContaining([
423+
expect.objectContaining({
424+
prompt: expect.stringMatching(/^(?=.*walking)(?=.*together)(?=.*park).*/i),
425+
referenceImages: expect.arrayContaining([
426+
expect.objectContaining({
427+
image: expect.objectContaining({
428+
gcsUri: expect.stringContaining('person1.jpg'),
429+
mimeType: 'image/jpeg',
430+
}),
431+
referenceType: expect.stringMatching(/^(asset|style)$/),
432+
}),
433+
expect.objectContaining({
434+
image: expect.objectContaining({
435+
gcsUri: expect.stringContaining('person2.png'),
436+
mimeType: 'image/png',
437+
}),
438+
referenceType: expect.stringMatching(/^(asset|style)$/),
439+
}),
440+
]),
441+
}),
442+
]),
443+
parameters: expect.objectContaining({
444+
durationSeconds: expect.any(Number),
445+
aspectRatio: '16:9',
446+
generateAudio: expect.any(Boolean),
447+
}),
448+
}
449+
450+
expect(stripVTControlCharacters(getErrorDiff(actual, expected))).toMatchInlineSnapshot(`
451+
"- Expected
452+
+ Received
453+
454+
{
455+
"instances": [
456+
{
457+
- "prompt": StringMatching /^(?=.*walking)(?=.*together)(?=.*park).*/i,
458+
+ "prompt": "walk",
459+
"referenceImages": [
460+
{
461+
"image": {
462+
"gcsUri": "gs://example/person1.jpg",
463+
- "mimeType": "image/jpeg",
464+
+ "mimeType": "image/png",
465+
},
466+
"referenceType": "asset",
467+
},
468+
{
469+
"image": {
470+
- "gcsUri": StringContaining "person2.png",
471+
+ "gcsUri": "gs://example/person.jpg",
472+
"mimeType": "image/png",
473+
},
474+
"referenceType": "asset",
475+
},
476+
],
477+
},
478+
],
479+
"model": "veo-3.1-generate-preview",
480+
"parameters": {
481+
"aspectRatio": "16:9",
482+
- "durationSeconds": Any<Number>,
483+
+ "durationSeconds": "8",
484+
"generateAudio": true,
485+
},
486+
}"
487+
`)
488+
})

test/core/test/expect.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ describe('Standard Schema', () => {
630630
- ],
631631
- },
632632
+ "age": "thirty",
633-
"name": SchemaMatching,
633+
"name": "John",
634634
},
635635
}"
636636
`)

test/core/test/jest-expect.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1318,7 +1318,7 @@ it('correctly prints diff with asymmetric matchers', () => {
13181318
+ Received
13191319
13201320
{
1321-
"a": Any<Number>,
1321+
"a": 1,
13221322
- "b": Any<Function>,
13231323
+ "b": "string",
13241324
}"

0 commit comments

Comments
 (0)