UNPKG

42.5 kBJavaScriptView Raw
1var utils = require('./utils');
2var isRegExp = utils.isRegExp;
3var leftPad = utils.leftPad;
4var arrayChanges = require('array-changes');
5var leven = require('leven');
6var detectIndent = require('detect-indent');
7var defaultDepth = require('./defaultDepth');
8var AssertionString = require('./AssertionString');
9
10module.exports = function(expect) {
11 expect.addType({
12 name: 'wrapperObject',
13 identify: false,
14 equal: function equal(a, b, equal$1) {
15 return a === b || equal$1(this.unwrap(a), this.unwrap(b));
16 },
17 inspect: function inspect(value, depth, output, inspect$1) {
18 output.append(this.prefix(output.clone(), value));
19 output.append(inspect$1(this.unwrap(value), depth));
20 output.append(this.suffix(output.clone(), value));
21 return output;
22 },
23 diff: function diff(actual, expected, output, diff$1, inspect) {
24 output.inline = true;
25 actual = this.unwrap(actual);
26 expected = this.unwrap(expected);
27 var comparison = diff$1(actual, expected);
28 var prefixOutput = this.prefix(output.clone(), actual);
29 var suffixOutput = this.suffix(output.clone(), actual);
30 if (comparison && comparison.inline) {
31 return output
32 .append(prefixOutput)
33 .append(comparison)
34 .append(suffixOutput);
35 } else {
36 return output
37 .append(prefixOutput)
38 .nl()
39 .indentLines()
40 .i()
41 .block(function() {
42 this.append(inspect(actual))
43 .sp()
44 .annotationBlock(function() {
45 this.shouldEqualError(expected, inspect);
46 if (comparison) {
47 this.nl(2).append(comparison);
48 }
49 });
50 })
51 .nl()
52 .outdentLines()
53 .append(suffixOutput);
54 }
55 }
56 });
57
58 if (typeof Symbol === 'function') {
59 expect.addType({
60 name: 'Symbol',
61 identify: function identify(obj) {
62 return typeof obj === 'symbol';
63 },
64 inspect: function inspect(obj, depth, output, inspect$1) {
65 return output
66 .jsKeyword('Symbol')
67 .text('(')
68 .singleQuotedString(obj.toString().replace(/^Symbol\(|\)$/g, ''))
69 .text(')');
70 }
71 });
72 }
73
74 expect.addType({
75 name: 'object',
76 indent: true,
77 forceMultipleLines: false,
78 identify: function identify(obj) {
79 return obj && typeof obj === 'object';
80 },
81 prefix: function prefix(output, obj) {
82 var constructor = obj.constructor;
83 var constructorName =
84 constructor &&
85 typeof constructor === 'function' &&
86 constructor !== Object &&
87 utils.getFunctionName(constructor);
88 if (constructorName && constructorName !== 'Object') {
89 output.text((constructorName + "("));
90 }
91 return output.text('{');
92 },
93 property: function property(output, key, inspectedValue, isArrayLike) {
94 return output.propertyForObject(key, inspectedValue, isArrayLike);
95 },
96 suffix: function suffix(output, obj) {
97 output.text('}');
98 var constructor = obj.constructor;
99 var constructorName =
100 constructor &&
101 typeof constructor === 'function' &&
102 constructor !== Object &&
103 utils.getFunctionName(constructor);
104 if (constructorName && constructorName !== 'Object') {
105 output.text(')');
106 }
107 return output;
108 },
109 delimiter: function delimiter(output, i, length) {
110 if (i < length - 1) {
111 output.text(',');
112 }
113 return output;
114 },
115 getKeys: Object.getOwnPropertySymbols
116 ? function (obj) {
117 var keys = Object.getOwnPropertyNames(obj);
118 var symbols = Object.getOwnPropertySymbols(obj);
119 if (symbols.length > 0) {
120 return keys.concat(symbols);
121 } else {
122 return keys;
123 }
124 }
125 : Object.getOwnPropertyNames,
126 // If Symbol support is not detected default to undefined which, when
127 // passed to Array.prototype.sort, means "natural" (asciibetical) sort.
128 keyComparator:
129 typeof Symbol === 'function'
130 ? function (a, b) {
131 var aIsSymbol, bIsSymbol;
132 var aString = a;
133 var bString = b;
134 aIsSymbol = typeof a === 'symbol';
135 bIsSymbol = typeof b === 'symbol';
136 if (aIsSymbol) {
137 if (bIsSymbol) {
138 aString = a.toString();
139 bString = b.toString();
140 } else {
141 return 1;
142 }
143 } else if (bIsSymbol) {
144 return -1;
145 }
146
147 if (aString < bString) {
148 return -1;
149 } else if (aString > bString) {
150 return 1;
151 }
152
153 return 0;
154 }
155 : undefined,
156 equal: function equal(a, b, equal$1) {
157 return utils.checkObjectEqualityUsingType(a, b, this, equal$1);
158 },
159 hasKey: function hasKey(obj, key, own) {
160 if (own) {
161 return Object.prototype.hasOwnProperty.call(obj, key);
162 } else {
163 return key in obj;
164 }
165 },
166 inspect: function inspect(obj, depth, output, inspect$1) {
167 var keys = this.getKeys(obj);
168 if (keys.length === 0) {
169 this.prefix(output, obj);
170 this.suffix(output, obj);
171 return output;
172 }
173 var type = this;
174 var inspectedItems = keys.map(function (key, index) {
175 var propertyDescriptor =
176 Object.getOwnPropertyDescriptor &&
177 Object.getOwnPropertyDescriptor(obj, key);
178 var hasGetter = propertyDescriptor && propertyDescriptor.get;
179 var hasSetter = propertyDescriptor && propertyDescriptor.set;
180 var propertyOutput = output.clone();
181 if (hasSetter && !hasGetter) {
182 propertyOutput.text('set').sp();
183 }
184 // Inspect the setter function if there's no getter:
185 var value;
186 if (hasSetter && !hasGetter) {
187 value = hasSetter;
188 } else {
189 value = type.valueForKey(obj, key);
190 }
191
192 var inspectedValue = inspect$1(value);
193 if (value && value._expectIt) {
194 inspectedValue = output.clone().block(inspectedValue);
195 }
196 type.property(propertyOutput, key, inspectedValue);
197
198 propertyOutput.amend(
199 type.delimiter(output.clone(), index, keys.length)
200 );
201
202 if (hasGetter && hasSetter) {
203 propertyOutput.sp().jsComment('/* getter/setter */');
204 } else if (hasGetter) {
205 propertyOutput.sp().jsComment('/* getter */');
206 }
207
208 return propertyOutput;
209 });
210
211 var maxLineLength =
212 output.preferredWidth - (depth === Infinity ? 0 : depth) * 2 - 2;
213 var width = 0;
214 var compact =
215 inspectedItems.length > 5 ||
216 inspectedItems.every(function (inspectedItem) {
217 if (inspectedItem.isMultiline()) {
218 return false;
219 }
220 width += inspectedItem.size().width;
221 return width < maxLineLength;
222 });
223
224 var itemsOutput = output.clone();
225 if (compact) {
226 var currentLineLength = 0;
227 inspectedItems.forEach(function (inspectedItem, index) {
228 var size = inspectedItem.size();
229 currentLineLength += size.width + 1;
230 if (index > 0) {
231 if (size.height === 1 && currentLineLength < maxLineLength) {
232 itemsOutput.sp();
233 } else {
234 itemsOutput.nl();
235 currentLineLength = size.width;
236 }
237
238 if (size.height > 1) {
239 // Make sure that we don't append more to this line
240 currentLineLength = maxLineLength;
241 }
242 }
243 itemsOutput.append(inspectedItem);
244 });
245 } else {
246 inspectedItems.forEach(function (inspectedItem, index) {
247 if (index > 0) {
248 itemsOutput.nl();
249 }
250 itemsOutput.append(inspectedItem);
251 });
252 }
253
254 var prefixOutput = this.prefix(output.clone(), obj);
255 var suffixOutput = this.suffix(output.clone(), obj);
256 output.append(prefixOutput);
257 if (this.forceMultipleLines || itemsOutput.isMultiline()) {
258 if (!prefixOutput.isEmpty()) {
259 output.nl();
260 }
261 if (this.indent) {
262 output.indentLines().i();
263 }
264 output.block(itemsOutput);
265 if (this.indent) {
266 output.outdentLines();
267 }
268 if (!suffixOutput.isEmpty()) {
269 output.nl();
270 }
271 } else {
272 output
273 .sp(prefixOutput.isEmpty() ? 0 : 1)
274 .append(itemsOutput)
275 .sp(suffixOutput.isEmpty() ? 0 : 1);
276 }
277 return output.append(suffixOutput);
278 },
279 diff: function diff(actual, expected, output, diff$1, inspect, equal) {
280 if (actual.constructor !== expected.constructor) {
281 return output
282 .text('Mismatching constructors ')
283 .text(
284 (actual.constructor && utils.getFunctionName(actual.constructor)) ||
285 actual.constructor
286 )
287 .text(' should be ')
288 .text(
289 (expected.constructor &&
290 utils.getFunctionName(expected.constructor)) ||
291 expected.constructor
292 );
293 }
294
295 output.inline = true;
296 var actualKeys = this.getKeys(actual);
297 var expectedKeys = this.getKeys(expected);
298 var keys = this.uniqueKeys(actualKeys, expectedKeys);
299 var prefixOutput = this.prefix(output.clone(), actual);
300 output.append(prefixOutput).nl(prefixOutput.isEmpty() ? 0 : 1);
301
302 if (this.indent) {
303 output.indentLines();
304 }
305 var type = this;
306 keys.forEach(function (key, index) {
307 output
308 .nl(index > 0 ? 1 : 0)
309 .i()
310 .block(function() {
311 var valueActual = type.valueForKey(actual, key);
312 var valueExpected = type.valueForKey(expected, key);
313 var annotation = output.clone();
314 var conflicting = !equal(valueActual, valueExpected);
315 var isInlineDiff = false;
316 var valueOutput;
317 if (conflicting) {
318 if (!type.hasKey(expected, key)) {
319 annotation.error('should be removed');
320 isInlineDiff = true;
321 } else if (!type.hasKey(actual, key)) {
322 this.error('// missing').sp();
323 valueOutput = output.clone().appendInspected(valueExpected);
324 isInlineDiff = true;
325 } else {
326 var keyDiff = diff$1(valueActual, valueExpected);
327 if (!keyDiff || (keyDiff && !keyDiff.inline)) {
328 annotation.shouldEqualError(valueExpected);
329 if (keyDiff) {
330 annotation.nl(2).append(keyDiff);
331 }
332 } else {
333 isInlineDiff = true;
334 valueOutput = keyDiff;
335 }
336 }
337 } else {
338 isInlineDiff = true;
339 }
340
341 if (!valueOutput) {
342 valueOutput = inspect(valueActual, conflicting ? Infinity : null);
343 }
344
345 valueOutput.amend(
346 type.delimiter(output.clone(), index, actualKeys.length)
347 );
348 if (!isInlineDiff) {
349 valueOutput = output.clone().block(valueOutput);
350 }
351 type.property(this, key, valueOutput);
352 if (!annotation.isEmpty()) {
353 this.sp().annotationBlock(annotation);
354 }
355 });
356 });
357
358 if (this.indent) {
359 output.outdentLines();
360 }
361 var suffixOutput = this.suffix(output.clone(), actual);
362 return output.nl(suffixOutput.isEmpty() ? 0 : 1).append(suffixOutput);
363 },
364 similar: function similar(a, b) {
365 var this$1 = this;
366
367 if (a === null || b === null) {
368 return false;
369 }
370
371 var typeA = typeof a;
372 var typeB = typeof b;
373
374 if (typeA !== typeB) {
375 return false;
376 }
377
378 if (typeA === 'string') {
379 return leven(a, b) < a.length / 2;
380 }
381
382 if (typeA !== 'object' || !a) {
383 return false;
384 }
385
386 if (utils.isArray(a) && utils.isArray(b)) {
387 return true;
388 }
389
390 var aKeys = this.getKeys(a);
391 var bKeys = this.getKeys(b);
392 var numberOfSimilarKeys = 0;
393 var requiredSimilarKeys = Math.round(
394 Math.max(aKeys.length, bKeys.length) / 2
395 );
396 return aKeys.concat(bKeys).some(function (key) {
397 if (this$1.hasKey(a, key) && this$1.hasKey(b, key)) {
398 numberOfSimilarKeys += 1;
399 }
400 return numberOfSimilarKeys >= requiredSimilarKeys;
401 });
402 },
403 uniqueKeys: utils.uniqueStringsAndSymbols,
404 valueForKey: function valueForKey(obj, key) {
405 return obj[key];
406 }
407 });
408
409 expect.addType({
410 name: 'type',
411 base: 'object',
412 identify: function identify(value) {
413 return value && value._unexpectedType;
414 },
415 inspect: function inspect(ref, depth, output) {
416 var name = ref.name;
417
418 return output.text('type: ').jsKeyword(name);
419 }
420 });
421
422 expect.addType({
423 name: 'array-like',
424 base: 'object',
425 identify: false,
426 numericalPropertiesOnly: true,
427 getKeys: function getKeys(obj) {
428 var keys = new Array(obj.length);
429 for (var i = 0; i < obj.length; i += 1) {
430 keys[i] = i;
431 }
432 if (!this.numericalPropertiesOnly) {
433 keys.push.apply(keys, this.getKeysNonNumerical(obj));
434 }
435 return keys;
436 },
437 getKeysNonNumerical: Object.getOwnPropertySymbols
438 ? function (obj) {
439 var keys = [];
440 Object.keys(obj).forEach(function (key) {
441 if (!utils.numericalRegExp.test(key)) {
442 keys.push(key);
443 }
444 });
445 var symbols = Object.getOwnPropertySymbols(obj);
446 if (symbols.length > 0) {
447 keys.push.apply(keys, symbols);
448 }
449 return keys;
450 }
451 : function (obj) {
452 var keys = [];
453 Object.keys(obj).forEach(function (key) {
454 if (!utils.numericalRegExp.test(key)) {
455 keys.push(key);
456 }
457 });
458 return keys;
459 },
460 equal: function equal(a, b, equal$1) {
461 var this$1 = this;
462
463 if (a === b) {
464 return true;
465 } else if (a.constructor === b.constructor && a.length === b.length) {
466 var i;
467
468 // compare numerically indexed elements
469 for (i = 0; i < a.length; i += 1) {
470 if (!equal$1(this.valueForKey(a, i), this.valueForKey(b, i))) {
471 return false;
472 }
473 }
474
475 // compare non-numerical keys if enabled for the type
476 if (!this.numericalPropertiesOnly) {
477 var aKeys = this.getKeysNonNumerical(a).filter(function (key) {
478 // include keys whose value is not undefined
479 return typeof this$1.valueForKey(a, key) !== 'undefined';
480 });
481 var bKeys = this.getKeysNonNumerical(b).filter(function (key) {
482 // include keys whose value is not undefined on either LHS or RHS
483 return (
484 typeof this$1.valueForKey(b, key) !== 'undefined' ||
485 typeof this$1.valueForKey(a, key) !== 'undefined'
486 );
487 });
488
489 if (aKeys.length !== bKeys.length) {
490 return false;
491 }
492
493 for (i = 0; i < aKeys.length; i += 1) {
494 if (
495 !equal$1(
496 this.valueForKey(a, aKeys[i]),
497 this.valueForKey(b, bKeys[i])
498 )
499 ) {
500 return false;
501 }
502 }
503 }
504
505 return true;
506 } else {
507 return false;
508 }
509 },
510 prefix: function prefix(output) {
511 return output.text('[');
512 },
513 suffix: function suffix(output) {
514 return output.text(']');
515 },
516 inspect: function inspect(arr, depth, output, inspect$1) {
517 var this$1 = this;
518
519 var prefixOutput = this.prefix(output.clone(), arr);
520 var suffixOutput = this.suffix(output.clone(), arr);
521 var keys = this.getKeys(arr);
522 if (keys.length === 0) {
523 return output.append(prefixOutput).append(suffixOutput);
524 }
525
526 if (depth === 1 && arr.length > 10) {
527 return output
528 .append(prefixOutput)
529 .text('...')
530 .append(suffixOutput);
531 }
532
533 var inspectedItems = keys.map(function (key) {
534 var inspectedValue;
535 if (this$1.hasKey(arr, key)) {
536 inspectedValue = inspect$1(this$1.valueForKey(arr, key));
537 } else if (utils.numericalRegExp.test(key)) {
538 // Sparse array entry
539 inspectedValue = output.clone();
540 } else {
541 // Not present non-numerical property returned by getKeys
542 inspectedValue = inspect$1(undefined);
543 }
544 return this$1.property(output.clone(), key, inspectedValue, true);
545 });
546
547 var currentDepth = defaultDepth - Math.min(defaultDepth, depth);
548 var maxLineLength =
549 output.preferredWidth - 20 - currentDepth * output.indentationWidth - 2;
550 var width = 0;
551 var multipleLines =
552 this.forceMultipleLines ||
553 inspectedItems.some(function (o) {
554 if (o.isMultiline()) {
555 return true;
556 }
557
558 var size = o.size();
559 width += size.width;
560 return width > maxLineLength;
561 });
562 inspectedItems.forEach(function (inspectedItem, index) {
563 inspectedItem.amend(this$1.delimiter(output.clone(), index, keys.length));
564 });
565 if (multipleLines) {
566 output.append(prefixOutput);
567 if (!prefixOutput.isEmpty()) {
568 output.nl();
569 }
570 if (this.indent) {
571 output.indentLines();
572 }
573 inspectedItems.forEach(function (inspectedItem, index) {
574 output
575 .nl(index > 0 ? 1 : 0)
576 .i()
577 .block(inspectedItem);
578 });
579
580 if (this.indent) {
581 output.outdentLines();
582 }
583
584 if (!suffixOutput.isEmpty()) {
585 output.nl();
586 }
587
588 return output.append(suffixOutput);
589 } else {
590 output.append(prefixOutput).sp(prefixOutput.isEmpty() ? 0 : 1);
591 inspectedItems.forEach(function (inspectedItem, index) {
592 output.append(inspectedItem);
593 var lastIndex = index === inspectedItems.length - 1;
594 if (!lastIndex) {
595 output.sp();
596 }
597 });
598 return output.sp(suffixOutput.isEmpty() ? 0 : 1).append(suffixOutput);
599 }
600 },
601 diffLimit: 512,
602 diff: function diff(actual, expected, output, diff$1, inspect, equal) {
603 output.inline = true;
604
605 if (Math.max(actual.length, expected.length) > this.diffLimit) {
606 output.jsComment(("Diff suppressed due to size > " + (this.diffLimit)));
607 return output;
608 }
609
610 if (actual.constructor !== expected.constructor) {
611 return this.baseType.diff(actual, expected, output);
612 }
613
614 var prefixOutput = this.prefix(output.clone(), actual);
615 output.append(prefixOutput).nl(prefixOutput.isEmpty() ? 0 : 1);
616
617 if (this.indent) {
618 output.indentLines();
619 }
620
621 var actualElements = utils.duplicateArrayLikeUsingType(actual, this);
622 var actualKeys = this.getKeys(actual);
623 var expectedElements = utils.duplicateArrayLikeUsingType(expected, this);
624 var expectedKeys = this.getKeys(expected);
625 var nonNumericalKeysAndSymbols =
626 !this.numericalPropertiesOnly &&
627 utils.uniqueNonNumericalStringsAndSymbols(actualKeys, expectedKeys);
628
629 var type = this;
630 var changes = arrayChanges(
631 actualElements,
632 expectedElements,
633 equal,
634 function (a, b) { return type.similar(a, b); },
635 {
636 includeNonNumericalProperties: nonNumericalKeysAndSymbols
637 }
638 );
639 var indexOfLastNonInsert = changes.reduce(
640 function (previousValue, diffItem, index) { return diffItem.type === 'insert' ? previousValue : index; },
641 -1
642 );
643 var packing = utils.packArrows(changes); // NOTE: Will have side effects in changes if the packing results in too many arrow lanes
644 output.arrowsAlongsideChangeOutputs(
645 packing,
646 changes.map(function (diffItem, index) {
647 var delimiterOutput = type.delimiter(
648 output.clone(),
649 index,
650 indexOfLastNonInsert + 1
651 );
652 if (diffItem.type === 'moveTarget') {
653 return output.clone();
654 } else {
655 return output.clone().block(function() {
656 if (diffItem.type === 'moveSource') {
657 var propertyOutput = type.property(
658 output.clone(),
659 diffItem.actualIndex,
660 inspect(diffItem.value),
661 true
662 );
663 this.amend(propertyOutput)
664 .amend(delimiterOutput)
665 .sp()
666 .error('// should be moved');
667 } else if (diffItem.type === 'insert') {
668 this.annotationBlock(function() {
669 this.error('missing ').block(function() {
670 var index =
671 typeof diffItem.actualIndex !== 'undefined'
672 ? diffItem.actualIndex
673 : diffItem.expectedIndex;
674 var propertyOutput = type.property(
675 output.clone(),
676 index,
677 inspect(diffItem.value),
678 true
679 );
680 this.amend(propertyOutput);
681 });
682 });
683 } else if (diffItem.type === 'remove') {
684 this.block(function() {
685 var propertyOutput = type.property(
686 output.clone(),
687 diffItem.actualIndex,
688 inspect(diffItem.value),
689 true
690 );
691 this.amend(propertyOutput)
692 .amend(delimiterOutput)
693 .sp()
694 .error('// should be removed');
695 });
696 } else if (diffItem.type === 'equal') {
697 this.block(function() {
698 var propertyOutput = type.property(
699 output.clone(),
700 diffItem.actualIndex,
701 inspect(diffItem.value),
702 true
703 );
704 this.amend(propertyOutput).amend(delimiterOutput);
705 });
706 } else {
707 this.block(function() {
708 var valueDiff = diff$1(diffItem.value, diffItem.expected);
709 if (valueDiff && valueDiff.inline) {
710 this.append(valueDiff).append(delimiterOutput);
711 } else {
712 var propertyOutput = type.property(
713 output.clone(),
714 diffItem.actualIndex,
715 inspect(diffItem.value),
716 true
717 );
718 this.append(propertyOutput)
719 .append(delimiterOutput)
720 .sp()
721 .annotationBlock(function() {
722 this.shouldEqualError(diffItem.expected, inspect);
723 if (valueDiff) {
724 this.nl(2).append(valueDiff);
725 }
726 });
727 }
728 });
729 }
730 });
731 }
732 })
733 );
734
735 if (this.indent) {
736 output.outdentLines();
737 }
738
739 var suffixOutput = this.suffix(output.clone(), actual);
740 return output.nl(suffixOutput.isEmpty() ? 0 : 1).append(suffixOutput);
741 }
742 });
743
744 expect.addType({
745 name: 'array',
746 base: 'array-like',
747 numericalPropertiesOnly: false,
748 identify: function identify(arr) {
749 return utils.isArray(arr);
750 }
751 });
752
753 expect.addType({
754 name: 'arguments',
755 base: 'array-like',
756 prefix: function prefix(output) {
757 return output.text('arguments(', 'cyan');
758 },
759 suffix: function suffix(output) {
760 return output.text(')', 'cyan');
761 },
762 identify: function identify(obj) {
763 return Object.prototype.toString.call(obj) === '[object Arguments]';
764 }
765 });
766
767 var errorMethodBlacklist = [
768 'message',
769 'name',
770 'description',
771 'line',
772 'number',
773 'column',
774 'sourceId',
775 'sourceURL',
776 'stack',
777 'stackArray',
778 '__stackCleaned__',
779 'isOperational' // added by the promise implementation
780 ].reduce(function (result, prop) {
781 result[prop] = true;
782 return result;
783 }, {});
784
785 if (new Error().hasOwnProperty('arguments')) {
786 // node.js 0.10 adds two extra non-enumerable properties to Error instances:
787 errorMethodBlacklist.arguments = true;
788 errorMethodBlacklist.type = true;
789 }
790
791 expect.addType({
792 base: 'object',
793 name: 'Error',
794 identify: function identify(value) {
795 return utils.isError(value);
796 },
797 getKeys: function getKeys(value) {
798 var keys = this.baseType
799 .getKeys(value)
800 .filter(function (key) { return !errorMethodBlacklist[key]; });
801 keys.unshift('message');
802 return keys;
803 },
804 unwrap: function unwrap(value) {
805 return this.getKeys(value).reduce(function (result, key) {
806 result[key] = value[key];
807 return result;
808 }, {});
809 },
810 equal: function equal(a, b, equal$1) {
811 return (
812 a === b ||
813 (equal$1(a.message, b.message) &&
814 utils.checkObjectEqualityUsingType(a, b, this, equal$1))
815 );
816 },
817 inspect: function inspect(value, depth, output, inspect$1) {
818 output.errorName(value).text('(');
819 var keys = this.getKeys(value);
820 if (keys.length === 1 && keys[0] === 'message') {
821 if (value.message !== '') {
822 output.append(inspect$1(value.message));
823 }
824 } else {
825 output.append(inspect$1(this.unwrap(value), depth));
826 }
827 return output.text(')');
828 },
829 diff: function diff(actual, expected, output, diff$1) {
830 if (actual.constructor !== expected.constructor) {
831 return output
832 .text('Mismatching constructors ')
833 .errorName(actual)
834 .text(' should be ')
835 .errorName(expected);
836 }
837
838 output = diff$1(this.unwrap(actual), this.unwrap(expected));
839 if (output) {
840 output = output
841 .clone()
842 .errorName(actual)
843 .text('(')
844 .append(output)
845 .text(')');
846 output.inline = false;
847 }
848 return output;
849 }
850 });
851
852 var unexpectedErrorMethodBlacklist = [
853 'output',
854 '_isUnexpected',
855 'htmlMessage',
856 '_hasSerializedErrorMessage',
857 'expect',
858 'assertion',
859 'originalError'
860 ].reduce(function (result, prop) {
861 result[prop] = true;
862 return result;
863 }, {});
864 expect.addType({
865 base: 'Error',
866 name: 'UnexpectedError',
867 identify: function identify(value) {
868 return (
869 value &&
870 typeof value === 'object' &&
871 value._isUnexpected &&
872 this.baseType.identify(value)
873 );
874 },
875 getKeys: function getKeys(value) {
876 return this.baseType
877 .getKeys(value)
878 .filter(function (key) { return !unexpectedErrorMethodBlacklist[key]; });
879 },
880 inspect: function inspect(value, depth, output) {
881 output.jsFunctionName(this.name).text('(');
882 var errorMessage = value.getErrorMessage(output);
883 if (errorMessage.isMultiline()) {
884 output
885 .nl()
886 .indentLines()
887 .i()
888 .block(errorMessage)
889 .nl();
890 } else {
891 output.append(errorMessage);
892 }
893 return output.text(')');
894 }
895 });
896
897 expect.addType({
898 name: 'date',
899 identify: function identify(obj) {
900 return Object.prototype.toString.call(obj) === '[object Date]';
901 },
902 equal: function equal(a, b) {
903 return a.getTime() === b.getTime();
904 },
905 inspect: function inspect(date, depth, output, inspect$1) {
906 // TODO: Inspect "new" as an operator and Date as a built-in once we have the styles defined:
907 var dateStr = date.toUTCString().replace(/UTC/, 'GMT');
908 var milliseconds = date.getUTCMilliseconds();
909 if (milliseconds > 0) {
910 var millisecondsStr = String(milliseconds);
911 while (millisecondsStr.length < 3) {
912 millisecondsStr = "0" + millisecondsStr;
913 }
914 dateStr = dateStr.replace(' GMT', ("." + millisecondsStr + " GMT"));
915 }
916
917 return output
918 .jsKeyword('new')
919 .sp()
920 .text('Date(')
921 .append(inspect$1(dateStr).text(')'));
922 }
923 });
924
925 expect.addType({
926 base: 'object',
927 name: 'function',
928 identify: function identify(f) {
929 return typeof f === 'function';
930 },
931 getKeys: Object.keys,
932 equal: function equal(a, b) {
933 return a === b;
934 },
935 inspect: function inspect(f, depth, output, inspect$1) {
936 // Don't break when a function has its own custom #toString:
937 var source = Function.prototype.toString
938 .call(f)
939 .replace(/\r\n?|\n\r?/g, '\n');
940 var name = utils.getFunctionName(f) || '';
941 var preamble;
942 var body;
943 var bodyIndent;
944 var matchSource = source.match(
945 /^\s*((?:async )?\s*(?:\S+\s*=>|\([^)]*\)\s*=>|class|function\s?(?:\*\s*)?\w*\s*\([^)]*\)))([\s\S]*)$/
946 );
947 if (matchSource) {
948 // Normalize so there's always space after "function" and never after "*" for generator functions:
949 preamble = matchSource[1]
950 .replace(/function(\S)/, 'function $1')
951 .replace(/\* /, '*');
952 if (preamble === 'function ()' && name) {
953 // fn.bind() doesn't seem to include the name in the .toString() output:
954 preamble = "function " + name + "()";
955 }
956 body = matchSource[2];
957 var matchBodyAndIndent = body.match(/^(\s*\{)([\s\S]*?)([ ]*)\}\s*$/);
958 var openingBrace;
959 var isWrappedInBraces = true;
960 var closingBrace = '}';
961 var reindentBodyLevel = 0;
962 if (matchBodyAndIndent) {
963 openingBrace = matchBodyAndIndent[1];
964 body = matchBodyAndIndent[2];
965 bodyIndent = matchBodyAndIndent[3] || '';
966 if (bodyIndent.length === 1) {
967 closingBrace = ' }';
968 }
969 } else {
970 // Attempt to match an arrow function with an implicit return body.
971 matchBodyAndIndent = body.match(/^(\s*)([\s\S]*?)([ ]*)\s*$/);
972
973 if (matchBodyAndIndent) {
974 openingBrace = matchBodyAndIndent[1];
975 isWrappedInBraces = false;
976 body = matchBodyAndIndent[2];
977 var matchInitialNewline = openingBrace.match(/^\n( +)/);
978 if (matchInitialNewline) {
979 openingBrace = '\n';
980 if (/\n/.test(body)) {
981 // An arrow function whose body starts with a newline, as prettier likes to output, eg.:
982 // () =>
983 // foo(
984 // 1
985 // );
986 // Shuffle/hack things around so it will be formatted correctly:
987 bodyIndent = matchInitialNewline[1];
988 reindentBodyLevel = 1;
989 } else {
990 body = body.replace(/^\s*/, ' ');
991 }
992 } else {
993 bodyIndent = matchBodyAndIndent[3] || '';
994 }
995 closingBrace = '';
996 }
997 }
998
999 // Remove leading indentation unless the function is a one-liner or it uses multiline string literals
1000 if (/\n/.test(body) && !/\\\n/.test(body)) {
1001 body = body.replace(new RegExp(("^ {" + (bodyIndent.length) + "}"), 'mg'), '');
1002 var indent = detectIndent(body);
1003 body = body.replace(
1004 new RegExp(("^(?:" + (indent.indent) + ")*"), 'mg'),
1005 function (ref) {
1006 var length = ref.length;
1007
1008 return utils.leftPad(
1009 '',
1010 (length / indent.amount + reindentBodyLevel) *
1011 output.indentationWidth,
1012 ' '
1013 );
1014 }
1015 );
1016 }
1017 if (!name || name === 'anonymous') {
1018 name = '';
1019 }
1020 if (/^\s*\[native code\]\s*$/.test(body)) {
1021 body = ' /* native code */ ';
1022 closingBrace = '}';
1023 } else if (/^\s*$/.test(body)) {
1024 body = '';
1025 } else if (
1026 /^\s*[^\r\n]{1,30}\s*$/.test(body) &&
1027 body.indexOf('//') === -1 &&
1028 isWrappedInBraces
1029 ) {
1030 body = " " + (body.trim()) + " ";
1031 closingBrace = '}';
1032 } else {
1033 body = body.replace(
1034 /^((?:.*\n){3}( *).*\n)[\s\S]*?\n[\s\S]*?\n((?:.*\n){3})$/,
1035 '$1$2// ... lines removed ...\n$3'
1036 );
1037 }
1038 if (matchBodyAndIndent) {
1039 body = openingBrace + body + closingBrace;
1040 } else {
1041 // Strip trailing space from arrow function body
1042 body = body.replace(/[ ]*$/, '');
1043 }
1044 } else {
1045 preamble = "function " + name + "( /*...*/ ) ";
1046 body = '{ /*...*/ }';
1047 }
1048 return output.code(preamble + body, 'javascript');
1049 },
1050 diff: function diff(actual, expected, output) {
1051 // Avoid rendering an object diff when both are functions
1052 if (typeof actual !== 'function' || typeof expected !== 'function') {
1053 return this.baseType.diff(actual, expected, output);
1054 }
1055 }
1056 });
1057
1058 expect.addType({
1059 base: 'function',
1060 name: 'expect.it',
1061 identify: function identify(f) {
1062 return typeof f === 'function' && f._expectIt;
1063 },
1064 inspect: function inspect(ref, depth, output, inspect$1) {
1065 var _expectations = ref._expectations;
1066 var _OR = ref._OR;
1067
1068 output.text('expect.it(');
1069 var orBranch = false;
1070 _expectations.forEach(function (expectation, index) {
1071 if (expectation === _OR) {
1072 orBranch = true;
1073 return;
1074 }
1075
1076 if (orBranch) {
1077 output.text(')\n .or(');
1078 } else if (index > 0) {
1079 output.text(')\n .and(');
1080 }
1081
1082 var args = Array.prototype.slice.call(expectation);
1083 args.forEach(function (arg, i) {
1084 if (i > 0) {
1085 output.text(', ');
1086 }
1087 output.append(inspect$1(arg));
1088 });
1089 orBranch = false;
1090 });
1091
1092 return output.amend(')');
1093 }
1094 });
1095
1096 expect.addType({
1097 name: 'Promise',
1098 base: 'object',
1099 identify: function identify(obj) {
1100 return (
1101 obj && this.baseType.identify(obj) && typeof obj.then === 'function'
1102 );
1103 },
1104 inspect: function inspect(promise, depth, output, inspect$1) {
1105 output.jsFunctionName('Promise');
1106 if (promise.isPending && promise.isPending()) {
1107 output.sp().yellow('(pending)');
1108 } else if (promise.isFulfilled && promise.isFulfilled()) {
1109 output.sp().green('(fulfilled)');
1110 if (promise.value) {
1111 var value = promise.value();
1112 if (typeof value !== 'undefined') {
1113 output
1114 .sp()
1115 .text('=>')
1116 .sp()
1117 .append(inspect$1(value));
1118 }
1119 }
1120 } else if (promise.isRejected && promise.isRejected()) {
1121 output.sp().red('(rejected)');
1122 var reason = promise.reason();
1123 if (typeof reason !== 'undefined') {
1124 output
1125 .sp()
1126 .text('=>')
1127 .sp()
1128 .append(inspect$1(promise.reason()));
1129 }
1130 }
1131 return output;
1132 }
1133 });
1134
1135 expect.addType({
1136 name: 'regexp',
1137 base: 'object',
1138 identify: isRegExp,
1139 equal: function equal(a, b) {
1140 return (
1141 a === b ||
1142 (a.source === b.source &&
1143 a.global === b.global &&
1144 a.ignoreCase === b.ignoreCase &&
1145 a.multiline === b.multiline)
1146 );
1147 },
1148 inspect: function inspect(regExp, depth, output) {
1149 return output.jsRegexp(regExp);
1150 },
1151 diff: function diff(actual, expected, output, diff$1, inspect) {
1152 output.inline = false;
1153 return output.stringDiff(String(actual), String(expected), {
1154 type: 'Chars',
1155 markUpSpecialCharacters: true
1156 });
1157 }
1158 });
1159
1160 expect.addType({
1161 name: 'binaryArray',
1162 base: 'array-like',
1163 digitWidth: 2,
1164 hexDumpWidth: 16,
1165 identify: false,
1166 prefix: function prefix(output) {
1167 return output.code(((this.name) + "(["), 'javascript');
1168 },
1169 suffix: function suffix(output) {
1170 return output.code('])', 'javascript');
1171 },
1172 equal: function equal(a, b) {
1173 if (a === b) {
1174 return true;
1175 }
1176
1177 if (a.length !== b.length) {
1178 return false;
1179 }
1180
1181 for (var i = 0; i < a.length; i += 1) {
1182 if (a[i] !== b[i]) {
1183 return false;
1184 }
1185 }
1186
1187 return true;
1188 },
1189 hexDump: function hexDump(obj, maxLength) {
1190 var hexDump = '';
1191 if (typeof maxLength !== 'number' || maxLength === 0) {
1192 maxLength = obj.length;
1193 }
1194 for (var i = 0; i < maxLength; i += this.hexDumpWidth) {
1195 if (hexDump.length > 0) {
1196 hexDump += '\n';
1197 }
1198 var hexChars = '';
1199 var asciiChars = ' │';
1200
1201 for (var j = 0; j < this.hexDumpWidth; j += 1) {
1202 if (i + j < maxLength) {
1203 var octet = obj[i + j];
1204 hexChars += (leftPad(
1205 octet.toString(16).toUpperCase(),
1206 this.digitWidth,
1207 '0'
1208 )) + " ";
1209 asciiChars += String.fromCharCode(octet)
1210 .replace(/\n/g, '␊')
1211 .replace(/\r/g, '␍');
1212 } else if (this.digitWidth === 2) {
1213 hexChars += ' ';
1214 }
1215 }
1216
1217 if (this.digitWidth === 2) {
1218 hexDump += (hexChars + asciiChars) + "│";
1219 } else {
1220 hexDump += hexChars.replace(/\s+$/, '');
1221 }
1222 }
1223 return hexDump;
1224 },
1225 inspect: function inspect(obj, depth, output) {
1226 this.prefix(output, obj);
1227 var codeStr = '';
1228 for (var i = 0; i < Math.min(this.hexDumpWidth, obj.length); i += 1) {
1229 if (i > 0) {
1230 codeStr += ', ';
1231 }
1232 var octet = obj[i];
1233 codeStr += "0x" + (leftPad(
1234 octet.toString(16).toUpperCase(),
1235 this.digitWidth,
1236 '0'
1237 ));
1238 }
1239 if (obj.length > this.hexDumpWidth) {
1240 codeStr += " /* " + (obj.length - this.hexDumpWidth) + " more */ ";
1241 }
1242 output.code(codeStr, 'javascript');
1243 this.suffix(output, obj);
1244 return output;
1245 },
1246 diffLimit: 512,
1247 diff: function diff(actual, expected, output, diff$1, inspect) {
1248 output.inline = false;
1249 if (Math.max(actual.length, expected.length) > this.diffLimit) {
1250 output.jsComment(("Diff suppressed due to size > " + (this.diffLimit)));
1251 } else {
1252 output
1253 .stringDiff(this.hexDump(actual), this.hexDump(expected), {
1254 type: 'Chars',
1255 markUpSpecialCharacters: false
1256 })
1257 // eslint-disable-next-line no-control-regex
1258 .replaceText(/[\x00-\x1f\x7f-\xff␊␍]/g, '.')
1259 .replaceText(/[│ ]/g, function(styles, content) {
1260 this.text(content);
1261 });
1262 }
1263 return output;
1264 }
1265 });
1266
1267 [8, 16, 32].forEach(function(numBits) {
1268 ['Int', 'Uint'].forEach(function (intOrUint) {
1269 var constructorName = (intOrUint + numBits) + "Array";
1270 var Constructor = global[constructorName];
1271 if (typeof Constructor !== 'undefined') {
1272 expect.addType({
1273 name: constructorName,
1274 base: 'binaryArray',
1275 hexDumpWidth: 128 / numBits,
1276 digitWidth: numBits / 4,
1277 identify: function identify(obj) {
1278 return obj instanceof Constructor;
1279 }
1280 });
1281 }
1282 }, this);
1283 }, this);
1284
1285 if (typeof Buffer !== 'undefined') {
1286 expect.addType({
1287 name: 'Buffer',
1288 base: 'binaryArray',
1289 identify: Buffer.isBuffer
1290 });
1291 }
1292
1293 expect.addType({
1294 name: 'string',
1295 identify: function identify(value) {
1296 return typeof value === 'string';
1297 },
1298 inspect: function inspect(value, depth, output) {
1299 return output.singleQuotedString(value);
1300 },
1301 diffLimit: 65536,
1302 diff: function diff(actual, expected, output, diff$1, inspect) {
1303 if (Math.max(actual.length, expected.length) > this.diffLimit) {
1304 output.jsComment(("Diff suppressed due to size > " + (this.diffLimit)));
1305 return output;
1306 }
1307 output.stringDiff(actual, expected, {
1308 type: 'WordsWithSpace',
1309 markUpSpecialCharacters: true
1310 });
1311 output.inline = false;
1312 return output;
1313 }
1314 });
1315
1316 expect.addType({
1317 name: 'number',
1318 identify: function identify(value) {
1319 return typeof value === 'number' && !isNaN(value);
1320 },
1321 inspect: function inspect(value, depth, output) {
1322 if (value === 0 && 1 / value === -Infinity) {
1323 value = '-0';
1324 } else {
1325 value = String(value);
1326 }
1327 return output.jsNumber(String(value));
1328 }
1329 });
1330
1331 expect.addType({
1332 name: 'NaN',
1333 identify: function identify(value) {
1334 return typeof value === 'number' && isNaN(value);
1335 },
1336 inspect: function inspect(value, depth, output) {
1337 return output.jsPrimitive(value);
1338 }
1339 });
1340
1341 expect.addType({
1342 name: 'boolean',
1343 identify: function identify(value) {
1344 return typeof value === 'boolean';
1345 },
1346 inspect: function inspect(value, depth, output) {
1347 return output.jsPrimitive(value);
1348 }
1349 });
1350
1351 expect.addType({
1352 name: 'undefined',
1353 identify: function identify(value) {
1354 return typeof value === 'undefined';
1355 },
1356 inspect: function inspect(value, depth, output) {
1357 return output.jsPrimitive(value);
1358 }
1359 });
1360
1361 expect.addType({
1362 name: 'null',
1363 identify: function identify(value) {
1364 return value === null;
1365 },
1366 inspect: function inspect(value, depth, output) {
1367 return output.jsPrimitive(value);
1368 }
1369 });
1370
1371 expect.addType({
1372 name: 'assertion',
1373 identify: function identify(value) {
1374 return value instanceof AssertionString;
1375 }
1376 });
1377};