@@ -12,6 +12,9 @@ import {
12
12
format as prettyFormat ,
13
13
plugins as prettyFormatPlugins ,
14
14
} from '@vitest/pretty-format'
15
+ import c from 'tinyrainbow'
16
+ import { stringify } from '../display'
17
+ import { deepClone , getOwnProperties , getType as getSimpleType } from '../helpers'
15
18
import { getType } from './getType'
16
19
import { DIFF_DELETE , DIFF_EQUAL , DIFF_INSERT , Diff } from './cleanupSemantic'
17
20
import { NO_DIFF_MESSAGE , SIMILAR_MESSAGE } from './constants'
@@ -211,3 +214,160 @@ function getObjectsDifference(
211
214
)
212
215
}
213
216
}
217
+
218
+ const MAX_DIFF_STRING_LENGTH = 20_000
219
+
220
+ function isAsymmetricMatcher ( data : any ) {
221
+ const type = getSimpleType ( data )
222
+ return type === 'Object' && typeof data . asymmetricMatch === 'function'
223
+ }
224
+
225
+ function isReplaceable ( obj1 : any , obj2 : any ) {
226
+ const obj1Type = getSimpleType ( obj1 )
227
+ const obj2Type = getSimpleType ( obj2 )
228
+ return (
229
+ obj1Type === obj2Type && ( obj1Type === 'Object' || obj1Type === 'Array' )
230
+ )
231
+ }
232
+
233
+ export function printDiffOrStringify (
234
+ expected : unknown ,
235
+ received : unknown ,
236
+ options ?: DiffOptions ,
237
+ ) : string | null {
238
+ const { aAnnotation, bAnnotation } = normalizeDiffOptions ( options )
239
+
240
+ if (
241
+ typeof expected === 'string'
242
+ && typeof received === 'string'
243
+ && expected . length > 0
244
+ && received . length > 0
245
+ && expected . length <= MAX_DIFF_STRING_LENGTH
246
+ && received . length <= MAX_DIFF_STRING_LENGTH
247
+ && expected !== received
248
+ ) {
249
+ if ( expected . includes ( '\n' ) || received . includes ( '\n' ) ) {
250
+ return diffStringsUnified ( received , expected , options )
251
+ }
252
+
253
+ const [ diffs ] = diffStringsRaw ( received , expected , true )
254
+ const hasCommonDiff = diffs . some ( diff => diff [ 0 ] === DIFF_EQUAL )
255
+
256
+ const printLabel = getLabelPrinter ( aAnnotation , bAnnotation )
257
+ const expectedLine
258
+ = printLabel ( aAnnotation )
259
+ + printExpected (
260
+ getCommonAndChangedSubstrings ( diffs , DIFF_DELETE , hasCommonDiff ) ,
261
+ )
262
+ const receivedLine
263
+ = printLabel ( bAnnotation )
264
+ + printReceived (
265
+ getCommonAndChangedSubstrings ( diffs , DIFF_INSERT , hasCommonDiff ) ,
266
+ )
267
+
268
+ return `${ expectedLine } \n${ receivedLine } `
269
+ }
270
+
271
+ // if (isLineDiffable(expected, received)) {
272
+ const clonedExpected = deepClone ( expected , { forceWritable : true } )
273
+ const clonedReceived = deepClone ( received , { forceWritable : true } )
274
+ const { replacedExpected, replacedActual } = replaceAsymmetricMatcher ( clonedExpected , clonedReceived )
275
+ const difference = diff ( replacedExpected , replacedActual , options )
276
+
277
+ return difference
278
+ // }
279
+
280
+ // const printLabel = getLabelPrinter(aAnnotation, bAnnotation)
281
+ // const expectedLine = printLabel(aAnnotation) + printExpected(expected)
282
+ // const receivedLine
283
+ // = printLabel(bAnnotation)
284
+ // + (stringify(expected) === stringify(received)
285
+ // ? 'serializes to the same string'
286
+ // : printReceived(received))
287
+
288
+ // return `${expectedLine}\n${receivedLine}`
289
+ }
290
+
291
+ export function replaceAsymmetricMatcher (
292
+ actual : any ,
293
+ expected : any ,
294
+ actualReplaced : WeakSet < WeakKey > = new WeakSet ( ) ,
295
+ expectedReplaced : WeakSet < WeakKey > = new WeakSet ( ) ,
296
+ ) : {
297
+ replacedActual : any
298
+ replacedExpected : any
299
+ } {
300
+ if ( ! isReplaceable ( actual , expected ) ) {
301
+ return { replacedActual : actual , replacedExpected : expected }
302
+ }
303
+ if ( actualReplaced . has ( actual ) || expectedReplaced . has ( expected ) ) {
304
+ return { replacedActual : actual , replacedExpected : expected }
305
+ }
306
+ actualReplaced . add ( actual )
307
+ expectedReplaced . add ( expected )
308
+ getOwnProperties ( expected ) . forEach ( ( key ) => {
309
+ const expectedValue = expected [ key ]
310
+ const actualValue = actual [ key ]
311
+ if ( isAsymmetricMatcher ( expectedValue ) ) {
312
+ if ( expectedValue . asymmetricMatch ( actualValue ) ) {
313
+ actual [ key ] = expectedValue
314
+ }
315
+ }
316
+ else if ( isAsymmetricMatcher ( actualValue ) ) {
317
+ if ( actualValue . asymmetricMatch ( expectedValue ) ) {
318
+ expected [ key ] = actualValue
319
+ }
320
+ }
321
+ else if ( isReplaceable ( actualValue , expectedValue ) ) {
322
+ const replaced = replaceAsymmetricMatcher (
323
+ actualValue ,
324
+ expectedValue ,
325
+ actualReplaced ,
326
+ expectedReplaced ,
327
+ )
328
+ actual [ key ] = replaced . replacedActual
329
+ expected [ key ] = replaced . replacedExpected
330
+ }
331
+ } )
332
+ return {
333
+ replacedActual : actual ,
334
+ replacedExpected : expected ,
335
+ }
336
+ }
337
+
338
+ type PrintLabel = ( string : string ) => string
339
+ export function getLabelPrinter ( ...strings : Array < string > ) : PrintLabel {
340
+ const maxLength = strings . reduce (
341
+ ( max , string ) => ( string . length > max ? string . length : max ) ,
342
+ 0 ,
343
+ )
344
+ return ( string : string ) : string =>
345
+ `${ string } : ${ ' ' . repeat ( maxLength - string . length ) } `
346
+ }
347
+
348
+ const SPACE_SYMBOL = '\u{00B7}' // middle dot
349
+ function replaceTrailingSpaces ( text : string ) : string {
350
+ return text . replace ( / \s + $ / gm, spaces => SPACE_SYMBOL . repeat ( spaces . length ) )
351
+ }
352
+
353
+ function printReceived ( object : unknown ) : string {
354
+ return c . red ( replaceTrailingSpaces ( stringify ( object ) ) )
355
+ }
356
+ function printExpected ( value : unknown ) : string {
357
+ return c . green ( replaceTrailingSpaces ( stringify ( value ) ) )
358
+ }
359
+
360
+ function getCommonAndChangedSubstrings ( diffs : Array < Diff > , op : number , hasCommonDiff : boolean ) : string {
361
+ return diffs . reduce (
362
+ ( reduced : string , diff : Diff ) : string =>
363
+ reduced
364
+ + ( diff [ 0 ] === DIFF_EQUAL
365
+ ? diff [ 1 ]
366
+ : diff [ 0 ] === op
367
+ ? hasCommonDiff
368
+ ? c . inverse ( diff [ 1 ] )
369
+ : diff [ 1 ]
370
+ : '' ) ,
371
+ '' ,
372
+ )
373
+ }
0 commit comments