1
1
'use strict' ;
2
2
3
3
const {
4
+ ArrayIsArray,
4
5
ArrayPrototypeJoin,
5
6
ArrayPrototypePop,
6
7
Error,
7
8
ErrorCaptureStackTrace,
8
- MathMax,
9
+ Map,
10
+ Object,
9
11
ObjectAssign,
10
12
ObjectDefineProperty,
11
13
ObjectGetPrototypeOf,
14
+ ObjectKeys,
12
15
String,
13
- StringPrototypeEndsWith,
14
- StringPrototypeRepeat,
15
16
StringPrototypeSlice,
16
17
StringPrototypeSplit,
17
18
} = primordials ;
18
19
19
20
const { inspect } = require ( 'internal/util/inspect' ) ;
20
- const {
21
- removeColors,
22
- } = require ( 'internal/util' ) ;
23
21
const colors = require ( 'internal/util/colors' ) ;
24
- const {
25
- validateObject,
26
- } = require ( 'internal/validators' ) ;
22
+ const { validateObject } = require ( 'internal/validators' ) ;
27
23
const { isErrorStackTraceLimitWritable } = require ( 'internal/errors' ) ;
28
-
24
+ const { myersDiff , printMyersDiff } = require ( 'internal/assert/myers_diff' ) ;
29
25
30
26
const kReadableOperator = {
31
27
deepStrictEqual : 'Expected values to be strictly deep-equal:' ,
@@ -41,269 +37,77 @@ const kReadableOperator = {
41
37
notDeepEqualUnequal : 'Expected values not to be loosely deep-equal:' ,
42
38
} ;
43
39
44
- // Comparing short primitives should just show === / !== instead of using the
45
- // diff.
46
- const kMaxShortLength = 12 ;
47
-
48
40
function 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
+ } ) ;
51
49
return target ;
52
50
}
53
51
54
52
function inspectValue ( val ) {
55
53
// The util.inspect default values could be changed. This makes sure the
56
54
// 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
+ } ) ;
73
68
}
74
69
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 ;
83
74
84
- let i = 0 ;
85
- let indicator = '' ;
75
+ return isPrimitive || isEmptyArray || isSimpleObject ;
76
+ }
86
77
78
+ function checkOperator ( actual , expected , operator ) {
87
79
// In case both values are objects or functions explicitly mark them as not
88
80
// 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
+ ) {
93
89
operator = 'strictEqualObject' ;
94
90
}
95
91
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
+ }
184
94
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 } Skipped unchanged lines` ;
98
+ const insertedSkippedMessage = `\n${ colors . green } ... ${ colors . white } Skipped added lines ` ;
99
+ const deletedSkippedMessage = `\n ${ colors . red } ...${ colors . white } Skipped removed lines ` ;
190
100
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 } ` ;
199
105
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 ) ;
107
+ const { message, skipped } = printMyersDiff ( diff , simpleDiff , isStringComparison ) ;
108
+ const skippedMessahe = skipped ? `${ nopSkippedMessage } ${ insertedSkippedMessage } ${ deletedSkippedMessage } ` : '' ;
305
109
306
- return `${ msg } ${ skipped ? skippedMsg : '' } \n${ res } ${ other } ${ end } ${ indicator } ` ;
110
+ return `${ headerMessage } ${ skippedMessahe } \n${ message } \n ` ;
307
111
}
308
112
309
113
function addEllipsis ( string ) {
0 commit comments