11'use strict' ;
22
33const {
4+ ArrayIsArray,
45 ArrayPrototypeJoin,
56 ArrayPrototypePop,
67 Error,
78 ErrorCaptureStackTrace,
8- MathMax,
9+ Map,
10+ Object,
911 ObjectAssign,
1012 ObjectDefineProperty,
1113 ObjectGetPrototypeOf,
14+ ObjectKeys,
1215 String,
13- StringPrototypeEndsWith,
14- StringPrototypeRepeat,
1516 StringPrototypeSlice,
1617 StringPrototypeSplit,
1718} = primordials ;
1819
1920const { inspect } = require ( 'internal/util/inspect' ) ;
20- const {
21- removeColors,
22- } = require ( 'internal/util' ) ;
2321const colors = require ( 'internal/util/colors' ) ;
24- const {
25- validateObject,
26- } = require ( 'internal/validators' ) ;
22+ const { validateObject } = require ( 'internal/validators' ) ;
2723const { isErrorStackTraceLimitWritable } = require ( 'internal/errors' ) ;
28-
24+ const { myersDiff , printMyersDiff } = require ( 'internal/assert/myers_diff' ) ;
2925
3026const kReadableOperator = {
3127 deepStrictEqual : 'Expected values to be strictly deep-equal:' ,
@@ -41,269 +37,77 @@ const kReadableOperator = {
4137 notDeepEqualUnequal : 'Expected values not to be loosely deep-equal:' ,
4238} ;
4339
44- // Comparing short primitives should just show === / !== instead of using the
45- // diff.
46- const kMaxShortLength = 12 ;
47-
4840function copyError ( source ) {
49- const target = ObjectAssign ( { __proto__ : ObjectGetPrototypeOf ( source ) } , source ) ;
50- ObjectDefineProperty ( target , 'message' , { __proto__ : null , value : source . message } ) ;
41+ const target = ObjectAssign (
42+ { __proto__ : ObjectGetPrototypeOf ( source ) } ,
43+ source ,
44+ ) ;
45+ ObjectDefineProperty ( target , 'message' , {
46+ __proto__ : null ,
47+ value : source . message ,
48+ } ) ;
5149 return target ;
5250}
5351
5452function inspectValue ( val ) {
5553 // The util.inspect default values could be changed. This makes sure the
5654 // error messages contain the necessary information nevertheless.
57- return inspect (
58- val ,
59- {
60- compact : false ,
61- customInspect : false ,
62- depth : 1000 ,
63- maxArrayLength : Infinity ,
64- // Assert compares only enumerable properties (with a few exceptions).
65- showHidden : false ,
66- // Assert does not detect proxies currently.
67- showProxy : false ,
68- sorted : true ,
69- // Inspect getters as we also check them when comparing entries.
70- getters : true ,
71- } ,
72- ) ;
55+ return inspect ( val , {
56+ compact : false ,
57+ customInspect : false ,
58+ depth : 1000 ,
59+ maxArrayLength : Infinity ,
60+ // Assert compares only enumerable properties (with a few exceptions).
61+ showHidden : false ,
62+ // Assert does not detect proxies currently.
63+ showProxy : false ,
64+ sorted : true ,
65+ // Inspect getters as we also check them when comparing entries.
66+ getters : true ,
67+ } ) ;
7368}
7469
75- function createErrDiff ( actual , expected , operator ) {
76- let other = '' ;
77- let res = '' ;
78- let end = '' ;
79- let skipped = false ;
80- const actualInspected = inspectValue ( actual ) ;
81- const actualLines = StringPrototypeSplit ( actualInspected , '\n' ) ;
82- const expectedLines = StringPrototypeSplit ( inspectValue ( expected ) , '\n' ) ;
70+ function showSimpleDiff ( test ) {
71+ const isPrimitive = test !== Object ( test ) ;
72+ const isEmptyArray = ArrayIsArray ( test ) && test . length === 0 ;
73+ const isSimpleObject = ! isPrimitive && ! ( test instanceof Map ) && ObjectKeys ( test ) . length === 0 ;
8374
84- let i = 0 ;
85- let indicator = '' ;
75+ return isPrimitive || isEmptyArray || isSimpleObject ;
76+ }
8677
78+ function checkOperator ( actual , expected , operator ) {
8779 // In case both values are objects or functions explicitly mark them as not
8880 // reference equal for the `strictEqual` operator.
89- if ( operator === 'strictEqual' &&
90- ( ( typeof actual === 'object' && actual !== null &&
91- typeof expected === 'object' && expected !== null ) ||
92- ( typeof actual === 'function' && typeof expected === 'function' ) ) ) {
81+ if (
82+ operator === 'strictEqual' &&
83+ ( ( typeof actual === 'object' &&
84+ actual !== null &&
85+ typeof expected === 'object' &&
86+ expected !== null ) ||
87+ ( typeof actual === 'function' && typeof expected === 'function' ) )
88+ ) {
9389 operator = 'strictEqualObject' ;
9490 }
9591
96- // If "actual" and "expected" fit on a single line and they are not strictly
97- // equal, check further special handling.
98- if ( actualLines . length === 1 && expectedLines . length === 1 &&
99- actualLines [ 0 ] !== expectedLines [ 0 ] ) {
100- // Check for the visible length using the `removeColors()` function, if
101- // appropriate.
102- const c = inspect . defaultOptions . colors ;
103- const actualRaw = c ? removeColors ( actualLines [ 0 ] ) : actualLines [ 0 ] ;
104- const expectedRaw = c ? removeColors ( expectedLines [ 0 ] ) : expectedLines [ 0 ] ;
105- const inputLength = actualRaw . length + expectedRaw . length ;
106- // If the character length of "actual" and "expected" together is less than
107- // kMaxShortLength and if neither is an object and at least one of them is
108- // not `zero`, use the strict equal comparison to visualize the output.
109- if ( inputLength <= kMaxShortLength ) {
110- if ( ( typeof actual !== 'object' || actual === null ) &&
111- ( typeof expected !== 'object' || expected === null ) &&
112- ( actual !== 0 || expected !== 0 ) ) { // -0 === +0
113- return `${ kReadableOperator [ operator ] } \n\n` +
114- `${ actualLines [ 0 ] } !== ${ expectedLines [ 0 ] } \n` ;
115- }
116- } else if ( operator !== 'strictEqualObject' ) {
117- // If the stderr is a tty and the input length is lower than the current
118- // columns per line, add a mismatch indicator below the output. If it is
119- // not a tty, use a default value of 80 characters.
120- const maxLength = process . stderr . isTTY ? process . stderr . columns : 80 ;
121- if ( inputLength < maxLength ) {
122- while ( actualRaw [ i ] === expectedRaw [ i ] ) {
123- i ++ ;
124- }
125- // Ignore the first characters.
126- if ( i > 2 ) {
127- // Add position indicator for the first mismatch in case it is a
128- // single line and the input length is less than the column length.
129- indicator = `\n ${ StringPrototypeRepeat ( ' ' , i ) } ^` ;
130- i = 0 ;
131- }
132- }
133- }
134- }
135-
136- // Remove all ending lines that match (this optimizes the output for
137- // readability by reducing the number of total changed lines).
138- let a = actualLines [ actualLines . length - 1 ] ;
139- let b = expectedLines [ expectedLines . length - 1 ] ;
140- while ( a === b ) {
141- if ( i ++ < 3 ) {
142- end = `\n ${ a } ${ end } ` ;
143- } else {
144- other = a ;
145- }
146- ArrayPrototypePop ( actualLines ) ;
147- ArrayPrototypePop ( expectedLines ) ;
148- if ( actualLines . length === 0 || expectedLines . length === 0 )
149- break ;
150- a = actualLines [ actualLines . length - 1 ] ;
151- b = expectedLines [ expectedLines . length - 1 ] ;
152- }
153-
154- const maxLines = MathMax ( actualLines . length , expectedLines . length ) ;
155- // Strict equal with identical objects that are not identical by reference.
156- // E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() })
157- if ( maxLines === 0 ) {
158- // We have to get the result again. The lines were all removed before.
159- const actualLines = StringPrototypeSplit ( actualInspected , '\n' ) ;
160-
161- // Only remove lines in case it makes sense to collapse those.
162- // TODO: Accept env to always show the full error.
163- if ( actualLines . length > 50 ) {
164- actualLines [ 46 ] = `${ colors . blue } ...${ colors . white } ` ;
165- while ( actualLines . length > 47 ) {
166- ArrayPrototypePop ( actualLines ) ;
167- }
168- }
169-
170- return `${ kReadableOperator . notIdentical } \n\n` +
171- `${ ArrayPrototypeJoin ( actualLines , '\n' ) } \n` ;
172- }
173-
174- // There were at least five identical lines at the end. Mark a couple of
175- // skipped.
176- if ( i >= 5 ) {
177- end = `\n${ colors . blue } ...${ colors . white } ${ end } ` ;
178- skipped = true ;
179- }
180- if ( other !== '' ) {
181- end = `\n ${ other } ${ end } ` ;
182- other = '' ;
183- }
92+ return operator ;
93+ }
18494
185- let printedLines = 0 ;
186- let identical = 0 ;
187- const msg = kReadableOperator [ operator ] +
188- `\n${ colors . green } + actual ${ colors . white } ${ colors . red } - expected ${ colors . white } ` ;
189- const skippedMsg = ` ${ colors . blue } ...${ colors . white } Lines skipped` ;
95+ function createErrDiff ( actual , expected , operator ) {
96+ operator = checkOperator ( actual , expected , operator ) ;
97+ const nopSkippedMessage = `\n ${ colors . blue } ... ${ colors . white } Lines skipped which didn't differ` ;
98+ const insertedSkippedMessage = `\n${ colors . green } ... ${ colors . white } Lines skipped which were inserted ` ;
99+ const deletedSkippedMessage = `\n ${ colors . red } ...${ colors . white } Lines skipped which were deleted ` ;
190100
191- let lines = actualLines ;
192- let plusMinus = `${ colors . green } +${ colors . white } ` ;
193- let maxLength = expectedLines . length ;
194- if ( actualLines . length < maxLines ) {
195- lines = expectedLines ;
196- plusMinus = `${ colors . red } -${ colors . white } ` ;
197- maxLength = actualLines . length ;
198- }
101+ const simpleDiff = showSimpleDiff ( actual ) && showSimpleDiff ( expected ) ;
102+ const isStringComparison = typeof actual === 'string' && typeof expected === 'string' ;
103+ const header = simpleDiff ? '' : `${ colors . green } + actual${ colors . white } ${ colors . red } - expected${ colors . white } ` ;
104+ const headerMessage = `${ kReadableOperator [ operator ] } \n${ header } ` ;
199105
200- for ( i = 0 ; i < maxLines ; i ++ ) {
201- if ( maxLength < i + 1 ) {
202- // If more than two former lines are identical, print them. Collapse them
203- // in case more than five lines were identical.
204- if ( identical > 2 ) {
205- if ( identical > 3 ) {
206- if ( identical > 4 ) {
207- if ( identical === 5 ) {
208- res += `\n ${ lines [ i - 3 ] } ` ;
209- printedLines ++ ;
210- } else {
211- res += `\n${ colors . blue } ...${ colors . white } ` ;
212- skipped = true ;
213- }
214- }
215- res += `\n ${ lines [ i - 2 ] } ` ;
216- printedLines ++ ;
217- }
218- res += `\n ${ lines [ i - 1 ] } ` ;
219- printedLines ++ ;
220- }
221- // No identical lines before.
222- identical = 0 ;
223- // Add the expected line to the cache.
224- if ( lines === actualLines ) {
225- res += `\n${ plusMinus } ${ lines [ i ] } ` ;
226- } else {
227- other += `\n${ plusMinus } ${ lines [ i ] } ` ;
228- }
229- printedLines ++ ;
230- // Only extra actual lines exist
231- // Lines diverge
232- } else {
233- const expectedLine = expectedLines [ i ] ;
234- let actualLine = actualLines [ i ] ;
235- // If the lines diverge, specifically check for lines that only diverge by
236- // a trailing comma. In that case it is actually identical and we should
237- // mark it as such.
238- let divergingLines =
239- actualLine !== expectedLine &&
240- ( ! StringPrototypeEndsWith ( actualLine , ',' ) ||
241- StringPrototypeSlice ( actualLine , 0 , - 1 ) !== expectedLine ) ;
242- // If the expected line has a trailing comma but is otherwise identical,
243- // add a comma at the end of the actual line. Otherwise the output could
244- // look weird as in:
245- //
246- // [
247- // 1 // No comma at the end!
248- // + 2
249- // ]
250- //
251- if ( divergingLines &&
252- StringPrototypeEndsWith ( expectedLine , ',' ) &&
253- StringPrototypeSlice ( expectedLine , 0 , - 1 ) === actualLine ) {
254- divergingLines = false ;
255- actualLine += ',' ;
256- }
257- if ( divergingLines ) {
258- // If more than two former lines are identical, print them. Collapse
259- // them in case more than five lines were identical.
260- if ( identical > 2 ) {
261- if ( identical > 3 ) {
262- if ( identical > 4 ) {
263- if ( identical === 5 ) {
264- res += `\n ${ actualLines [ i - 3 ] } ` ;
265- printedLines ++ ;
266- } else {
267- res += `\n${ colors . blue } ...${ colors . white } ` ;
268- skipped = true ;
269- }
270- }
271- res += `\n ${ actualLines [ i - 2 ] } ` ;
272- printedLines ++ ;
273- }
274- res += `\n ${ actualLines [ i - 1 ] } ` ;
275- printedLines ++ ;
276- }
277- // No identical lines before.
278- identical = 0 ;
279- // Add the actual line to the result and cache the expected diverging
280- // line so consecutive diverging lines show up as +++--- and not +-+-+-.
281- res += `\n${ colors . green } +${ colors . white } ${ actualLine } ` ;
282- other += `\n${ colors . red } -${ colors . white } ${ expectedLine } ` ;
283- printedLines += 2 ;
284- // Lines are identical
285- } else {
286- // Add all cached information to the result before adding other things
287- // and reset the cache.
288- res += other ;
289- other = '' ;
290- identical ++ ;
291- // The very first identical line since the last diverging line is be
292- // added to the result.
293- if ( identical <= 2 ) {
294- res += `\n ${ actualLine } ` ;
295- printedLines ++ ;
296- }
297- }
298- }
299- // Inspected object to big (Show ~50 rows max)
300- if ( printedLines > 50 && i < maxLines - 2 ) {
301- return `${ msg } ${ skippedMsg } \n${ res } \n${ colors . blue } ...${ colors . white } ${ other } \n` +
302- `${ colors . blue } ...${ colors . white } ` ;
303- }
304- }
106+ const diff = myersDiff ( actual , expected , true ) ;
107+ const { message, skipped } = printMyersDiff ( diff , simpleDiff , isStringComparison ) ;
108+ const skippedMessahe = skipped ? `${ nopSkippedMessage } ${ insertedSkippedMessage } ${ deletedSkippedMessage } ` : '' ;
305109
306- return `${ msg } ${ skipped ? skippedMsg : '' } \n${ res } ${ other } ${ end } ${ indicator } ` ;
110+ return `${ headerMessage } ${ skippedMessahe } \n${ message } \n ` ;
307111}
308112
309113function addEllipsis ( string ) {
0 commit comments