UNPKG

82.5 kBJavaScriptView Raw
1const utils = require('./utils');
2const arrayChanges = require('array-changes');
3const arrayChangesAsync = require('array-changes-async');
4const throwIfNonUnexpectedError = require('./throwIfNonUnexpectedError');
5const objectIs = utils.objectIs;
6const extend = utils.extend;
7
8module.exports = expect => {
9 expect.addAssertion('<any> [not] to be (ok|truthy)', (expect, subject) => {
10 const not = !!expect.flags.not;
11 const condition = !!subject;
12 if (condition === not) {
13 expect.fail();
14 }
15 });
16
17 expect.addAssertion(
18 '<any> [not] to be (ok|truthy) <string>',
19 (expect, subject, message) => {
20 const not = !!expect.flags.not;
21 const condition = !!subject;
22 if (condition === not) {
23 expect.fail({
24 errorMode: 'bubble',
25 message
26 });
27 }
28 }
29 );
30
31 expect.addAssertion('<any> [not] to be <any>', (expect, subject, value) => {
32 if (objectIs(subject, value) === expect.flags.not) {
33 expect.fail({
34 label: 'should be'
35 });
36 }
37 });
38
39 expect.addAssertion(
40 '<string> [not] to be <string>',
41 (expect, subject, value) => {
42 expect(subject, '[not] to equal', value);
43 }
44 );
45
46 expect.addAssertion('<boolean> [not] to be true', (expect, subject) => {
47 expect(subject, '[not] to be', true);
48 });
49
50 expect.addAssertion('<boolean> [not] to be false', (expect, subject) => {
51 expect(subject, '[not] to be', false);
52 });
53
54 expect.addAssertion('<any> [not] to be falsy', (expect, subject) => {
55 expect(subject, '[!not] to be truthy');
56 });
57
58 expect.addAssertion(
59 '<any> [not] to be falsy <string>',
60 (expect, subject, message) => {
61 const not = !!expect.flags.not;
62 const condition = !!subject;
63 if (condition !== not) {
64 expect.fail({
65 errorMode: 'bubble',
66 message
67 });
68 }
69 }
70 );
71
72 expect.addAssertion('<any> [not] to be null', (expect, subject) => {
73 expect(subject, '[not] to be', null);
74 });
75
76 expect.addAssertion('<any> [not] to be undefined', (expect, subject) => {
77 expect(typeof subject === 'undefined', '[not] to be truthy');
78 });
79
80 expect.addAssertion('<any> [not] to be defined', (expect, subject) => {
81 expect(subject, '[!not] to be undefined');
82 });
83
84 expect.addAssertion('<number|NaN> [not] to be NaN', (expect, subject) => {
85 expect(isNaN(subject), '[not] to be truthy');
86 });
87
88 expect.addAssertion(
89 '<number> [not] to be close to <number> <number?>',
90 (expect, subject, value, epsilon) => {
91 expect.errorMode = 'bubble';
92 if (typeof epsilon !== 'number') {
93 epsilon = 1e-9;
94 }
95
96 expect.withError(
97 () => {
98 expect(
99 Math.abs(subject - value),
100 '[not] to be less than or equal to',
101 epsilon
102 );
103 },
104 e => {
105 expect.fail(output => {
106 output
107 .error('expected ')
108 .appendInspected(subject)
109 .sp()
110 .error(expect.testDescription)
111 .sp()
112 .appendInspected(value)
113 .sp()
114 .text('(epsilon: ')
115 .jsNumber(epsilon.toExponential())
116 .text(')');
117 });
118 }
119 );
120 }
121 );
122
123 expect.addAssertion(
124 '<any> [not] to be (a|an) <type>',
125 (expect, subject, type) => {
126 expect.argsOutput[0] = output => {
127 output.text(type.name);
128 };
129 expect(type.identify(subject), '[not] to be true');
130 }
131 );
132
133 expect.addAssertion(
134 '<any> [not] to be (a|an) <string>',
135 (expect, subject, typeName) => {
136 typeName = /^reg(?:exp?|ular expression)$/.test(typeName)
137 ? 'regexp'
138 : typeName;
139 expect.argsOutput[0] = output => {
140 output.jsString(typeName);
141 };
142 if (!expect.getType(typeName)) {
143 expect.errorMode = 'nested';
144 expect.fail(output => {
145 output
146 .error('Unknown type:')
147 .sp()
148 .jsString(typeName);
149 });
150 }
151 expect(expect.subjectType.is(typeName), '[not] to be truthy');
152 }
153 );
154
155 expect.addAssertion(
156 '<any> [not] to be (a|an) <function>',
157 (expect, subject, Constructor) => {
158 const className = utils.getFunctionName(Constructor);
159 if (className) {
160 expect.argsOutput[0] = output => {
161 output.text(className);
162 };
163 }
164 expect(subject instanceof Constructor, '[not] to be truthy');
165 }
166 );
167
168 expect.addAssertion(
169 '<any> [not] to be one of <array>',
170 (expect, subject, superset) => {
171 let found = false;
172
173 for (let i = 0; i < superset.length; i += 1) {
174 found = found || objectIs(subject, superset[i]);
175 }
176
177 if (found === expect.flags.not) {
178 expect.fail();
179 }
180 }
181 );
182
183 // Alias for common '[not] to be (a|an)' assertions
184 expect.addAssertion(
185 '<any> [not] to be an (object|array)',
186 (expect, subject) => {
187 expect(subject, '[not] to be an', expect.alternations[0]);
188 }
189 );
190
191 expect.addAssertion(
192 '<any> [not] to be a (boolean|number|string|function|regexp|regex|regular expression|date)',
193 (expect, subject) => {
194 expect(subject, '[not] to be a', expect.alternations[0]);
195 }
196 );
197
198 expect.addAssertion(
199 '<string> to be (the empty|an empty|a non-empty) string',
200 (expect, subject) => {
201 expect(
202 subject,
203 expect.alternations[0] === 'a non-empty'
204 ? 'not to be empty'
205 : 'to be empty'
206 );
207 }
208 );
209
210 expect.addAssertion(
211 '<array-like> to be (the empty|an empty|a non-empty) array',
212 (expect, subject) => {
213 expect(
214 subject,
215 expect.alternations[0] === 'a non-empty'
216 ? 'not to be empty'
217 : 'to be empty'
218 );
219 }
220 );
221
222 expect.addAssertion('<string> to match <regexp>', (expect, subject, regexp) =>
223 expect.withError(
224 () => {
225 const captures = subject.match(regexp);
226 expect(captures, 'to be truthy');
227 return captures;
228 },
229 e => {
230 e.label = 'should match';
231 expect.fail(e);
232 }
233 )
234 );
235
236 expect.addAssertion(
237 '<string> not to match <regexp>',
238 (expect, subject, regexp) =>
239 expect.withError(
240 () => {
241 expect(regexp.test(subject), 'to be false');
242 },
243 e => {
244 expect.fail({
245 label: 'should not match',
246 diff(output) {
247 output.inline = false;
248 let lastIndex = 0;
249 function flushUntilIndex(i) {
250 if (i > lastIndex) {
251 output.text(subject.substring(lastIndex, i));
252 lastIndex = i;
253 }
254 }
255 subject.replace(new RegExp(regexp.source, 'g'), ($0, index) => {
256 flushUntilIndex(index);
257 lastIndex += $0.length;
258 output.removedHighlight($0);
259 });
260 flushUntilIndex(subject.length);
261 return output;
262 }
263 });
264 }
265 )
266 );
267
268 expect.addAssertion(
269 '<object> [not] to have own property <string|Symbol>',
270 (expect, subject, key) => {
271 expect(
272 Object.prototype.hasOwnProperty.call(subject, key),
273 '[not] to be truthy'
274 );
275 return subject[key];
276 }
277 );
278
279 expect.addAssertion(
280 '<object> to have (enumerable|unenumerable|configurable|unconfigurable|writable|unwritable|readonly) property <string|Symbol>',
281 (expect, subject, key) => {
282 let attribute = expect.alternations[0];
283 let negated = false;
284 if (attribute.indexOf('un') === 0) {
285 attribute = attribute.substr(2);
286 negated = true;
287 } else if (attribute === 'readonly') {
288 attribute = 'writable';
289 negated = true;
290 }
291 const descriptor = Object.getOwnPropertyDescriptor(subject, key);
292 expect(descriptor, 'to be defined');
293 expect(descriptor[attribute] !== negated, 'to be true');
294 return subject[key];
295 }
296 );
297
298 expect.addAssertion(
299 '<object> [not] to have property <string|Symbol>',
300 (expect, subject, key) => {
301 const subjectType = expect.findTypeOf(subject);
302 const subjectKey = subjectType.is('function')
303 ? subject[key]
304 : subjectType.valueForKey(subject, key);
305 expect(subjectKey, '[!not] to be undefined');
306 return subjectKey;
307 }
308 );
309
310 expect.addAssertion(
311 '<object> to have [own] property <string|Symbol> <any>',
312 (expect, subject, key, expectedPropertyValue) =>
313 expect(subject, 'to have [own] property', key).then(
314 actualPropertyValue => {
315 expect.argsOutput = function() {
316 this.appendInspected(key)
317 .sp()
318 .error('with a value of')
319 .sp()
320 .appendInspected(expectedPropertyValue);
321 };
322 expect(actualPropertyValue, 'to equal', expectedPropertyValue);
323 return actualPropertyValue;
324 }
325 )
326 );
327
328 expect.addAssertion(
329 '<object> [not] to [only] have [own] properties <array>',
330 (expect, subject, propertyNames) => {
331 const unsupportedPropertyNames = propertyNames.filter(propertyName => {
332 const type = typeof propertyName;
333 return type !== 'string' && type !== 'number' && type !== 'symbol';
334 });
335 if (unsupportedPropertyNames.length > 0) {
336 expect.errorMode = 'nested';
337 expect.fail(function() {
338 this.error(
339 'All expected properties must be passed as strings, symbols, or numbers, but these are not:'
340 ).indentLines();
341 unsupportedPropertyNames.forEach(function(propertyName) {
342 this.nl()
343 .i()
344 .appendInspected(propertyName);
345 }, this);
346 this.outdentLines();
347 });
348 }
349
350 if (expect.flags.only) {
351 if (expect.flags.not) {
352 expect.errorMode = 'bubble';
353 expect.fail(
354 'The "not" flag cannot be used together with "to only have properties".'
355 );
356 }
357 if (expect.flags.own) {
358 expect.errorMode = 'bubble';
359 expect.fail(
360 'The "own" flag cannot be used together with "to only have properties".'
361 );
362 }
363 const subjectType = expect.subjectType;
364 const subjectKeys = subjectType.getKeys(subject).filter(
365 key =>
366 // include only those keys whose value is not undefined
367 typeof subjectType.valueForKey(subject, key) !== 'undefined'
368 );
369
370 expect.withError(
371 () => {
372 expect(subjectKeys.length === propertyNames.length, 'to be true');
373 // now catch differing property names
374 const keyInValue = {};
375 propertyNames.forEach(key => {
376 keyInValue[key] = true;
377 });
378 subjectKeys.forEach(key =>
379 expect(
380 Object.prototype.hasOwnProperty.call(keyInValue, key),
381 'to be true'
382 )
383 );
384 },
385 () => {
386 expect.fail({
387 diff: (output, diff, inspect, equal) => {
388 output.inline = true;
389
390 const keyInValue = {};
391 propertyNames.forEach(key => {
392 keyInValue[key] = true;
393 });
394
395 subjectType.prefix(output, subject);
396 output.nl().indentLines();
397
398 subjectKeys.forEach((key, index) => {
399 const propertyOutput = subjectType.property(
400 output.clone(),
401 key,
402 inspect(subjectType.valueForKey(subject, key))
403 );
404 const delimiterOutput = subjectType.delimiter(
405 output.clone(),
406 index,
407 subjectKeys.length
408 );
409
410 output
411 .i()
412 .block(function() {
413 this.append(propertyOutput).amend(delimiterOutput);
414 if (
415 !Object.prototype.hasOwnProperty.call(keyInValue, key)
416 ) {
417 this.sp().annotationBlock(function() {
418 this.error('should be removed');
419 });
420 } else {
421 delete keyInValue[key];
422 }
423 })
424 .nl();
425 });
426
427 // list any remaining value properties as missing
428 Object.keys(keyInValue).forEach(valueKey => {
429 output
430 .i()
431 .annotationBlock(function() {
432 this.error('missing')
433 .sp()
434 .append(inspect(valueKey));
435 })
436 .nl();
437 });
438
439 output.outdentLines();
440 subjectType.suffix(output, subject);
441
442 return output;
443 }
444 });
445 }
446 );
447 } else {
448 propertyNames.forEach(propertyName => {
449 expect(
450 subject,
451 '[not] to have [own] property',
452 typeof propertyName === 'number'
453 ? String(propertyName)
454 : propertyName
455 );
456 });
457 }
458 }
459 );
460
461 expect.addAssertion(
462 '<object> to have [own] properties <object>',
463 (expect, subject, properties) => {
464 expect.withError(
465 () => {
466 Object.keys(properties).forEach(property => {
467 const value = properties[property];
468 if (typeof value === 'undefined') {
469 expect(subject, 'not to have [own] property', property);
470 } else {
471 expect(subject, 'to have [own] property', property, value);
472 }
473 });
474 },
475 e => {
476 expect.fail({
477 diff(output, diff) {
478 output.inline = false;
479 const expected = extend({}, properties);
480 const actual = {};
481 const propertyNames = expect.findTypeOf(subject).getKeys(subject);
482 // Might put duplicates into propertyNames, but that does not matter:
483 for (const propertyName in subject) {
484 if (
485 !Object.prototype.hasOwnProperty.call(subject, propertyName)
486 ) {
487 propertyNames.push(propertyName);
488 }
489 }
490 propertyNames.forEach(propertyName => {
491 if (
492 (!expect.flags.own ||
493 Object.prototype.hasOwnProperty.call(
494 subject,
495 propertyName
496 )) &&
497 !(propertyName in properties)
498 ) {
499 expected[propertyName] = subject[propertyName];
500 }
501 if (
502 (!expect.flags.own ||
503 Object.prototype.hasOwnProperty.call(
504 subject,
505 propertyName
506 )) &&
507 !(propertyName in actual)
508 ) {
509 actual[propertyName] = subject[propertyName];
510 }
511 });
512 return utils.wrapConstructorNameAroundOutput(
513 diff(actual, expected),
514 subject
515 );
516 }
517 });
518 }
519 );
520 }
521 );
522
523 expect.addAssertion(
524 '<string|array-like> [not] to have length <number>',
525 (expect, subject, length) => {
526 if (!expect.flags.not) {
527 expect.errorMode = 'nested';
528 }
529 expect(subject.length, '[not] to be', length);
530 }
531 );
532
533 expect.addAssertion(
534 '<string|array-like> [not] to be empty',
535 (expect, subject) => {
536 expect(subject, '[not] to have length', 0);
537 }
538 );
539
540 expect.addAssertion(
541 '<string|array-like|object> to be non-empty',
542 (expect, subject) => {
543 expect(subject, 'not to be empty');
544 }
545 );
546
547 expect.addAssertion(
548 '<object> to [not] [only] have keys <array>',
549 (expect, subject, keys) => {
550 const keysInSubject = {};
551 const subjectType = expect.findTypeOf(subject);
552 const subjectKeys = subjectType.getKeys(subject);
553 subjectKeys.forEach(key => {
554 keysInSubject[key] = true;
555 });
556
557 if (expect.flags.not && keys.length === 0) {
558 return;
559 }
560
561 const hasKeys = keys.every(key => keysInSubject[key]);
562
563 if (expect.flags.only) {
564 expect(hasKeys, 'to be truthy');
565 expect.withError(
566 () => {
567 expect(subjectKeys.length === keys.length, '[not] to be truthy');
568 },
569 () => {
570 expect.fail({
571 diff:
572 !expect.flags.not &&
573 ((output, diff, inspect, equal) => {
574 output.inline = true;
575 const keyInValue = {};
576 keys.forEach(key => {
577 keyInValue[key] = true;
578 });
579 const subjectIsArrayLike = subjectType.is('array-like');
580
581 subjectType.prefix(output, subject);
582 output.nl().indentLines();
583
584 subjectKeys.forEach((key, index) => {
585 const propertyOutput = subjectType.property(
586 output.clone(),
587 key,
588 inspect(subjectType.valueForKey(subject, key)),
589 subjectIsArrayLike
590 );
591 const delimiterOutput = subjectType.delimiter(
592 output.clone(),
593 index,
594 subjectKeys.length
595 );
596
597 output
598 .i()
599 .block(function() {
600 this.append(propertyOutput).amend(delimiterOutput);
601 if (!keyInValue[key]) {
602 this.sp().annotationBlock(function() {
603 this.error('should be removed');
604 });
605 }
606 })
607 .nl();
608 });
609
610 output.outdentLines();
611 subjectType.suffix(output, subject);
612
613 return output;
614 })
615 });
616 }
617 );
618 } else {
619 expect(hasKeys, '[not] to be truthy');
620 }
621 }
622 );
623
624 expect.addAssertion('<object> [not] to be empty', (expect, subject) => {
625 if (
626 expect.flags.not &&
627 !expect.findTypeOf(subject).getKeys(subject).length
628 ) {
629 return expect.fail();
630 }
631 expect(subject, 'to [not] only have keys', []);
632 });
633
634 expect.addAssertion(
635 '<object> not to have keys <array>',
636 (expect, subject, keys) => {
637 expect(subject, 'to not have keys', keys);
638 }
639 );
640
641 expect.addAssertion(
642 '<object> not to have key <string>',
643 (expect, subject, value) => {
644 expect(subject, 'to not have keys', [value]);
645 }
646 );
647
648 expect.addAssertion('<object> not to have keys <string+>', function(
649 expect,
650 subject,
651 value
652 ) {
653 expect(
654 subject,
655 'to not have keys',
656 Array.prototype.slice.call(arguments, 2)
657 );
658 });
659
660 expect.addAssertion(
661 '<object> to [not] [only] have key <string>',
662 (expect, subject, value) => {
663 expect(subject, 'to [not] [only] have keys', [value]);
664 }
665 );
666
667 expect.addAssertion('<object> to [not] [only] have keys <string+>', function(
668 expect,
669 subject
670 ) {
671 expect(
672 subject,
673 'to [not] [only] have keys',
674 Array.prototype.slice.call(arguments, 2)
675 );
676 });
677
678 expect.addAssertion('<string> [not] to contain <string+>', function(
679 expect,
680 subject
681 ) {
682 const args = Array.prototype.slice.call(arguments, 2);
683 args.forEach(arg => {
684 if (arg === '') {
685 throw new Error(
686 `The '${expect.testDescription}' assertion does not support the empty string`
687 );
688 }
689 });
690 expect.withError(
691 () => {
692 args.forEach(arg => {
693 expect(subject.indexOf(arg) !== -1, '[not] to be truthy');
694 });
695 },
696 e => {
697 expect.fail({
698 diff(output) {
699 output.inline = false;
700 let lastIndex = 0;
701 function flushUntilIndex(i) {
702 if (i > lastIndex) {
703 output.text(subject.substring(lastIndex, i));
704 lastIndex = i;
705 }
706 }
707 if (expect.flags.not) {
708 subject.replace(
709 new RegExp(
710 args.map(arg => utils.escapeRegExpMetaChars(arg)).join('|'),
711 'g'
712 ),
713 ($0, index) => {
714 flushUntilIndex(index);
715 lastIndex += $0.length;
716 output.removedHighlight($0);
717 }
718 );
719 flushUntilIndex(subject.length);
720 } else {
721 const ranges = [];
722 args.forEach(arg => {
723 let needle = arg;
724 let partial = false;
725 while (needle.length > 1) {
726 let found = false;
727 lastIndex = -1;
728 let index;
729 do {
730 index = subject.indexOf(needle, lastIndex + 1);
731 if (index !== -1) {
732 found = true;
733 ranges.push({
734 startIndex: index,
735 endIndex: index + needle.length,
736 partial
737 });
738 }
739 lastIndex = index;
740 } while (lastIndex !== -1);
741 if (found) {
742 break;
743 }
744 needle = arg.substr(0, needle.length - 1);
745 partial = true;
746 }
747 });
748 lastIndex = 0;
749 ranges
750 .sort((a, b) => a.startIndex - b.startIndex)
751 .forEach(({ startIndex, endIndex, partial }) => {
752 flushUntilIndex(startIndex);
753 const firstUncoveredIndex = Math.max(startIndex, lastIndex);
754 if (endIndex > firstUncoveredIndex) {
755 if (partial) {
756 output.partialMatch(
757 subject.substring(firstUncoveredIndex, endIndex)
758 );
759 } else {
760 output.match(
761 subject.substring(firstUncoveredIndex, endIndex)
762 );
763 }
764 lastIndex = endIndex;
765 }
766 });
767 flushUntilIndex(subject.length);
768 }
769 return output;
770 }
771 });
772 }
773 );
774 });
775
776 expect.addAssertion('<array-like> [not] to contain <any+>', function(
777 expect,
778 subject
779 ) {
780 const args = Array.prototype.slice.call(arguments, 2);
781 expect.withError(
782 () => {
783 args.forEach(arg => {
784 expect(
785 subject &&
786 Array.prototype.some.call(subject, item =>
787 expect.equal(item, arg)
788 ),
789 '[not] to be truthy'
790 );
791 });
792 },
793 e => {
794 expect.fail({
795 diff:
796 expect.flags.not &&
797 ((output, diff, inspect, equal) =>
798 diff(
799 subject,
800 Array.prototype.filter.call(
801 subject,
802 item => !args.some(arg => equal(item, arg))
803 )
804 ))
805 });
806 }
807 );
808 });
809
810 expect.addAssertion(
811 [
812 '<string> [not] to begin with <string>',
813 '<string> [not] to start with <string>'
814 ],
815 (expect, subject, value) => {
816 if (value === '') {
817 throw new Error(
818 `The '${expect.testDescription}' assertion does not support a prefix of the empty string`
819 );
820 }
821 var isTruncated = false;
822 var outputSubject = utils.truncateSubjectStringForBegin(subject, value);
823 if (outputSubject === null) {
824 outputSubject = subject;
825 } else {
826 isTruncated = true;
827 }
828 expect.subjectOutput = output => {
829 output = output.jsString(
830 "'" + outputSubject.replace(/\n/g, '\\n') + "'"
831 );
832 if (isTruncated) {
833 output.jsComment('...');
834 }
835 };
836 expect.withError(
837 () => {
838 expect(subject.substr(0, value.length), '[not] to equal', value);
839 },
840 () => {
841 expect.fail({
842 diff(output) {
843 output.inline = false;
844 if (expect.flags.not) {
845 output
846 .removedHighlight(value)
847 .text(subject.substr(value.length));
848 } else {
849 let i = 0;
850 while (subject[i] === value[i]) {
851 i += 1;
852 }
853 if (i === 0) {
854 // No common prefix, omit diff
855 return null;
856 } else {
857 output
858 .partialMatch(subject.substr(0, i))
859 .text(outputSubject.substr(i))
860 .jsComment(isTruncated ? '...' : '');
861 }
862 }
863 return output;
864 }
865 });
866 }
867 );
868 }
869 );
870
871 expect.addAssertion(
872 '<string> [not] to end with <string>',
873 (expect, subject, value) => {
874 if (value === '') {
875 throw new Error(
876 `The '${expect.testDescription}' assertion does not support a suffix of the empty string`
877 );
878 }
879 var isTruncated = false;
880 var outputSubject = utils.truncateSubjectStringForEnd(subject, value);
881 if (outputSubject === null) {
882 outputSubject = subject;
883 } else {
884 isTruncated = true;
885 }
886 expect.subjectOutput = output => {
887 if (isTruncated) {
888 output = output.jsComment('...');
889 }
890 output.jsString("'" + outputSubject.replace(/\n/g, '\\n') + "'");
891 };
892 expect.withError(
893 () => {
894 expect(subject.substr(-value.length), '[not] to equal', value);
895 },
896 () => {
897 expect.fail({
898 diff(output) {
899 output.inline = false;
900 if (expect.flags.not) {
901 output
902 .text(subject.substr(0, subject.length - value.length))
903 .removedHighlight(value);
904 } else {
905 let i = 0;
906 while (
907 outputSubject[outputSubject.length - 1 - i] ===
908 value[value.length - 1 - i]
909 ) {
910 i += 1;
911 }
912 if (i === 0) {
913 // No common suffix, omit diff
914 return null;
915 }
916 output
917 .jsComment(isTruncated ? '...' : '')
918 .text(outputSubject.substr(0, outputSubject.length - i))
919 .partialMatch(
920 outputSubject.substr(
921 outputSubject.length - i,
922 outputSubject.length
923 )
924 );
925 }
926 return output;
927 }
928 });
929 }
930 );
931 }
932 );
933
934 expect.addAssertion('<number> [not] to be finite', (expect, subject) => {
935 expect(isFinite(subject), '[not] to be truthy');
936 });
937
938 expect.addAssertion('<number> [not] to be infinite', (expect, subject) => {
939 expect(!isNaN(subject) && !isFinite(subject), '[not] to be truthy');
940 });
941
942 expect.addAssertion(
943 [
944 '<number> [not] to be within <number> <number>',
945 '<BigInt> [not] to be within <BigInt> <BigInt>',
946 '<string> [not] to be within <string> <string>'
947 ],
948 (expect, subject, start, finish) => {
949 expect.argsOutput = output => {
950 output
951 .appendInspected(start)
952 .text('..')
953 .appendInspected(finish);
954 };
955 expect(subject >= start && subject <= finish, '[not] to be truthy');
956 }
957 );
958
959 expect.addAssertion(
960 [
961 '<number> [not] to be (less than|below) <number>',
962 '<BigInt> [not] to be (less than|below) <BigInt>',
963 '<string> [not] to be (less than|below) <string>'
964 ],
965 (expect, subject, value) => {
966 expect(subject < value, '[not] to be truthy');
967 }
968 );
969
970 expect.addAssertion(
971 '<string> [not] to be (less than|below) <string>',
972 (expect, subject, value) => {
973 expect(subject < value, '[not] to be truthy');
974 }
975 );
976
977 expect.addAssertion(
978 [
979 '<number> [not] to be less than or equal to <number>',
980 '<BigInt> [not] to be less than or equal to <BigInt>',
981 '<string> [not] to be less than or equal to <string>'
982 ],
983 (expect, subject, value) => {
984 expect(subject <= value, '[not] to be truthy');
985 }
986 );
987
988 expect.addAssertion(
989 [
990 '<number> [not] to be (greater than|above) <number>',
991 '<BigInt> [not] to be (greater than|above) <BigInt>',
992 '<string> [not] to be (greater than|above) <string>'
993 ],
994 (expect, subject, value) => {
995 expect(subject > value, '[not] to be truthy');
996 }
997 );
998
999 expect.addAssertion(
1000 [
1001 '<number> [not] to be greater than or equal to <number>',
1002 '<BigInt> [not] to be greater than or equal to <BigInt>',
1003 '<string> [not] to be greater than or equal to <string>'
1004 ],
1005 (expect, subject, value) => {
1006 expect(subject >= value, '[not] to be truthy');
1007 }
1008 );
1009
1010 expect.addAssertion('<number> [not] to be positive', (expect, subject) => {
1011 expect(subject, '[not] to be greater than', 0);
1012 });
1013
1014 expect.addAssertion('<BigInt> [not] to be positive', (expect, subject) => {
1015 expect(subject > 0, '[not] to be true');
1016 });
1017
1018 expect.addAssertion('<number> [not] to be negative', (expect, subject) => {
1019 expect(subject, '[not] to be less than', 0);
1020 });
1021
1022 expect.addAssertion('<BigInt> [not] to be negative', (expect, subject) => {
1023 expect(subject < 0, '[not] to be true');
1024 });
1025
1026 expect.addAssertion('<any> to equal <any>', (expect, subject, value) => {
1027 expect.withError(
1028 () => {
1029 expect(expect.equal(value, subject), 'to be truthy');
1030 },
1031 e => {
1032 expect.fail({
1033 label: 'should equal',
1034 diff(output, diff) {
1035 return diff(subject, value);
1036 }
1037 });
1038 }
1039 );
1040 });
1041
1042 expect.addAssertion('<any> not to equal <any>', (expect, subject, value) => {
1043 expect(expect.equal(value, subject), 'to be falsy');
1044 });
1045
1046 expect.addAssertion('<function> to error', (expect, subject) =>
1047 expect
1048 .promise(() => subject())
1049 .then(
1050 () => {
1051 expect.fail();
1052 },
1053 error => error
1054 )
1055 );
1056
1057 expect.addAssertion(
1058 '<function> to error [with] <any>',
1059 (expect, subject, arg) =>
1060 expect(subject, 'to error').then(error => {
1061 expect.errorMode = 'nested';
1062 return expect.withError(
1063 () => {
1064 return expect(error, 'to satisfy', arg);
1065 },
1066 e => {
1067 e.originalError = error;
1068 throw e;
1069 }
1070 );
1071 })
1072 );
1073
1074 expect.addAssertion('<function> not to error', (expect, subject) => {
1075 let threw = false;
1076 return expect
1077 .promise(() => {
1078 try {
1079 return subject();
1080 } catch (e) {
1081 threw = true;
1082 throw e;
1083 }
1084 })
1085 .caught(error => {
1086 expect.errorMode = 'nested';
1087 expect.fail({
1088 output(output) {
1089 output
1090 .error(threw ? 'threw' : 'returned promise rejected with')
1091 .error(': ')
1092 .appendErrorMessage(error);
1093 },
1094 originalError: error
1095 });
1096 });
1097 });
1098
1099 expect.addAssertion('<function> not to throw', (expect, subject) => {
1100 let threw = false;
1101 let error;
1102
1103 try {
1104 subject();
1105 } catch (e) {
1106 error = e;
1107 threw = true;
1108 }
1109
1110 if (threw) {
1111 expect.errorMode = 'nested';
1112 expect.fail({
1113 output(output) {
1114 output.error('threw: ').appendErrorMessage(error);
1115 },
1116 originalError: error
1117 });
1118 }
1119 });
1120
1121 expect.addAssertion(
1122 '<function> to (throw|throw error|throw exception)',
1123 (expect, subject) => {
1124 try {
1125 subject();
1126 } catch (e) {
1127 return e;
1128 }
1129 expect.errorMode = 'nested';
1130 expect.fail('did not throw');
1131 }
1132 );
1133
1134 expect.addAssertion('<object> to satisfy <function>', expect =>
1135 expect.fail()
1136 );
1137
1138 expect.addAssertion(
1139 '<function> to throw (a|an) <function>',
1140 (expect, subject, value) => {
1141 const constructorName = utils.getFunctionName(value);
1142 if (constructorName) {
1143 expect.argsOutput[0] = output => {
1144 output.jsFunctionName(constructorName);
1145 };
1146 }
1147 expect.errorMode = 'nested';
1148 return expect(subject, 'to throw').tap(error => {
1149 expect(error, 'to be a', value);
1150 });
1151 }
1152 );
1153
1154 expect.addAssertion(
1155 '<function> to (throw|throw error|throw exception) <any>',
1156 (expect, subject, arg) => {
1157 expect.errorMode = 'nested';
1158 return expect(subject, 'to throw').then(error => {
1159 // in the presence of a matcher an error must have been thrown.
1160
1161 expect.errorMode = 'nested';
1162 return expect.withError(
1163 () => {
1164 return expect(error, 'to satisfy', arg);
1165 },
1166 err => {
1167 err.originalError = error;
1168 throw err;
1169 }
1170 );
1171 });
1172 }
1173 );
1174
1175 expect.addAssertion(
1176 '<function> to have arity <number>',
1177 (expect, { length }, value) => {
1178 expect(length, 'to equal', value);
1179 }
1180 );
1181
1182 expect.addAssertion(
1183 [
1184 '<object> to have values [exhaustively] satisfying <any>',
1185 '<object> to have values [exhaustively] satisfying <assertion>',
1186 '<object> to be (a map|a hash|an object) whose values [exhaustively] satisfy <any>',
1187 '<object> to be (a map|a hash|an object) whose values [exhaustively] satisfy <assertion>'
1188 ],
1189 (expect, subject, nextArg) => {
1190 expect.errorMode = 'nested';
1191 expect(subject, 'not to be empty');
1192 expect.errorMode = 'bubble';
1193
1194 const keys = expect.subjectType.getKeys(subject);
1195 const expected = {};
1196 keys.forEach(key => {
1197 if (typeof nextArg === 'string') {
1198 expected[key] = expect.it(s => expect.shift(s));
1199 } else {
1200 expected[key] = nextArg;
1201 }
1202 });
1203 return expect.withError(
1204 () => expect(subject, 'to [exhaustively] satisfy', expected),
1205 err => {
1206 expect.fail({
1207 message(output) {
1208 output.append(
1209 expect.standardErrorMessage(output.clone(), {
1210 compact: err && err._isUnexpected && err.hasDiff()
1211 })
1212 );
1213 },
1214 diff(output) {
1215 const diff = err.getDiff({ output });
1216 diff.inline = true;
1217 return diff;
1218 }
1219 });
1220 }
1221 );
1222 }
1223 );
1224
1225 expect.addAssertion(
1226 [
1227 '<array-like> to have items [exhaustively] satisfying <any>',
1228 '<array-like> to have items [exhaustively] satisfying <assertion>',
1229 '<array-like> to be an array whose items [exhaustively] satisfy <any>',
1230 '<array-like> to be an array whose items [exhaustively] satisfy <assertion>'
1231 ],
1232 (expect, subject, ...rest) => {
1233 // ...
1234 expect.errorMode = 'nested';
1235 expect(subject, 'not to be empty');
1236 expect.errorMode = 'bubble';
1237
1238 return expect.withError(
1239 () =>
1240 expect(subject, 'to have values [exhaustively] satisfying', ...rest),
1241 err => {
1242 expect.fail({
1243 message(output) {
1244 output.append(
1245 expect.standardErrorMessage(output.clone(), {
1246 compact: err && err._isUnexpected && err.hasDiff()
1247 })
1248 );
1249 },
1250 diff(output) {
1251 const diff = err.getDiff({ output });
1252 diff.inline = true;
1253 return diff;
1254 }
1255 });
1256 }
1257 );
1258 }
1259 );
1260
1261 expect.addAssertion(
1262 [
1263 '<object> to have keys satisfying <any>',
1264 '<object> to have keys satisfying <assertion>',
1265 '<object> to be (a map|a hash|an object) whose (keys|properties) satisfy <any>',
1266 '<object> to be (a map|a hash|an object) whose (keys|properties) satisfy <assertion>'
1267 ],
1268 (expect, subject, ...rest) => {
1269 expect.errorMode = 'nested';
1270 expect(subject, 'not to be empty');
1271 expect.errorMode = 'default';
1272
1273 const keys = expect.subjectType.getKeys(subject);
1274 return expect(keys, 'to have items satisfying', ...rest);
1275 }
1276 );
1277
1278 expect.addAssertion(
1279 [
1280 '<object> [not] to have a value [exhaustively] satisfying <any>',
1281 '<object> [not] to have a value [exhaustively] satisfying <assertion>'
1282 ],
1283 (expect, subject, nextArg) => {
1284 expect.errorMode = 'nested';
1285 expect(subject, 'not to be empty');
1286 expect.errorMode = 'bubble';
1287
1288 const subjectType = expect.findTypeOf(subject);
1289 const keys = subjectType.getKeys(subject);
1290 const not = !!expect.flags.not;
1291
1292 const keyResults = new Array(keys.length);
1293
1294 expect.withError(
1295 () =>
1296 expect.promise[not ? 'all' : 'any'](
1297 keys.map(key => {
1298 let expected;
1299 if (typeof nextArg === 'string') {
1300 expected = expect.it(s => expect.shift(s));
1301 } else {
1302 expected = nextArg;
1303 }
1304
1305 keyResults[key] = expect.promise(() =>
1306 expect(
1307 subjectType.valueForKey(subject, key),
1308 '[not] to [exhaustively] satisfy',
1309 expected
1310 )
1311 );
1312 return keyResults[key];
1313 })
1314 ),
1315 err => {
1316 expect.fail({
1317 message(output) {
1318 output.append(
1319 expect.standardErrorMessage(output.clone(), {
1320 compact: err && err._isUnexpected && err.hasDiff()
1321 })
1322 );
1323 },
1324 diff:
1325 expect.flags.not &&
1326 ((output, diff, inspect, equal) => {
1327 const expectedObject = subjectType.is('array-like') ? [] : {};
1328 keys.forEach(key => {
1329 if (keyResults[key].isFulfilled()) {
1330 expectedObject[key] = subjectType.valueForKey(subject, key);
1331 }
1332 });
1333
1334 return diff(subject, expectedObject);
1335 })
1336 });
1337 }
1338 );
1339 }
1340 );
1341
1342 expect.addAssertion(
1343 [
1344 '<array-like> [not] to have an item [exhaustively] satisfying <any>',
1345 '<array-like> [not] to have an item [exhaustively] satisfying <assertion>'
1346 ],
1347 (expect, subject, ...rest) => {
1348 expect.errorMode = 'nested';
1349 expect(subject, 'not to be empty');
1350 expect.errorMode = 'default';
1351
1352 return expect(
1353 subject,
1354 '[not] to have a value [exhaustively] satisfying',
1355 ...rest
1356 );
1357 }
1358 );
1359
1360 expect.addAssertion('<object> to be canonical', (expect, subject) => {
1361 const stack = [];
1362
1363 (function traverse(obj) {
1364 let i;
1365 for (i = 0; i < stack.length; i += 1) {
1366 if (stack[i] === obj) {
1367 return;
1368 }
1369 }
1370 if (obj && typeof obj === 'object') {
1371 const keys = Object.keys(obj);
1372 for (i = 0; i < keys.length - 1; i += 1) {
1373 expect(keys[i], 'to be less than', keys[i + 1]);
1374 }
1375 stack.push(obj);
1376 keys.forEach(key => {
1377 traverse(obj[key]);
1378 });
1379 stack.pop();
1380 }
1381 })(subject);
1382 });
1383
1384 expect.addAssertion(
1385 '<Error> to have message <any>',
1386 (expect, subject, value) => {
1387 expect.errorMode = 'nested';
1388 return expect(
1389 subject.isUnexpected
1390 ? subject.getErrorMessage('text').toString()
1391 : subject.message,
1392 'to satisfy',
1393 value
1394 );
1395 }
1396 );
1397
1398 expect.addAssertion(
1399 '<Error> to [exhaustively] satisfy <Error>',
1400 (expect, subject, value) => {
1401 expect(subject.constructor, 'to be', value.constructor);
1402
1403 const unwrappedValue = expect.argTypes[0].unwrap(value);
1404 return expect.withError(
1405 () => expect(subject, 'to [exhaustively] satisfy', unwrappedValue),
1406 e => {
1407 expect.fail({
1408 diff(output, diff) {
1409 output.inline = false;
1410 const unwrappedSubject = expect.subjectType.unwrap(subject);
1411 return utils.wrapConstructorNameAroundOutput(
1412 diff(unwrappedSubject, unwrappedValue),
1413 subject
1414 );
1415 }
1416 });
1417 }
1418 );
1419 }
1420 );
1421
1422 expect.addAssertion(
1423 '<Error> to [exhaustively] satisfy <object>',
1424 (expect, subject, value) => {
1425 const valueType = expect.argTypes[0];
1426 const subjectKeys = expect.subjectType.getKeys(subject);
1427 const valueKeys = valueType.getKeys(value);
1428 const convertedSubject = {};
1429 subjectKeys.concat(valueKeys).forEach(key => {
1430 convertedSubject[key] = subject[key];
1431 });
1432 return expect(convertedSubject, 'to [exhaustively] satisfy', value);
1433 }
1434 );
1435
1436 expect.addAssertion(
1437 '<Error> to [exhaustively] satisfy <regexp|string|any>',
1438 (expect, { message }, value) =>
1439 expect(message, 'to [exhaustively] satisfy', value)
1440 );
1441
1442 expect.addAssertion(
1443 '<UnexpectedError> to [exhaustively] satisfy <regexp|string>',
1444 (expect, error, value) => {
1445 expect.errorMode = 'bubble';
1446 return expect(error, 'to have message', value);
1447 }
1448 );
1449
1450 expect.addAssertion(
1451 '<binaryArray> to [exhaustively] satisfy <expect.it>',
1452 (expect, subject, value) =>
1453 expect.withError(
1454 () => value(subject, expect.context),
1455 e => {
1456 expect.fail({
1457 diff(output, diff, inspect, equal) {
1458 output.inline = false;
1459 return output.appendErrorMessage(e);
1460 }
1461 });
1462 }
1463 )
1464 );
1465
1466 expect.addAssertion(
1467 '<any|Error> to [exhaustively] satisfy <expect.it>',
1468 (expect, subject, value) => expect.promise(() => value(subject))
1469 );
1470
1471 if (typeof Buffer !== 'undefined') {
1472 expect.addAssertion(
1473 '<Buffer> [when] decoded as <string> <assertion?>',
1474 (expect, subject, value) => expect.shift(subject.toString(value))
1475 );
1476 }
1477
1478 expect.addAssertion(
1479 '<any> not to [exhaustively] satisfy [assertion] <any>',
1480 (expect, subject, value) =>
1481 expect.promise((resolve, reject) =>
1482 expect
1483 .promise(() =>
1484 expect(subject, 'to [exhaustively] satisfy [assertion]', value)
1485 )
1486 .then(() => {
1487 try {
1488 expect.fail();
1489 } catch (e) {
1490 reject(e);
1491 }
1492 })
1493 .caught(e => {
1494 if (!e || !e._isUnexpected) {
1495 reject(e);
1496 } else {
1497 resolve();
1498 }
1499 })
1500 )
1501 );
1502
1503 expect.addAssertion(
1504 '<any> to [exhaustively] satisfy assertion <any>',
1505 (expect, subject, value) => {
1506 expect.errorMode = 'bubble'; // to satisfy assertion 'to be a number' => to be a number
1507 return expect(subject, 'to [exhaustively] satisfy', value);
1508 }
1509 );
1510
1511 expect.addAssertion(
1512 '<any> to [exhaustively] satisfy assertion <assertion>',
1513 (expect, subject) => {
1514 expect.errorMode = 'bubble'; // to satisfy assertion 'to be a number' => to be a number
1515 return expect.shift();
1516 }
1517 );
1518
1519 expect.addAssertion(
1520 '<any|object> to [exhaustively] satisfy [assertion] <expect.it>',
1521 (expect, subject, value) =>
1522 expect.withError(
1523 () => value(subject, expect.context),
1524 e => {
1525 expect.fail({
1526 diff(output) {
1527 output.inline = false;
1528 return output.appendErrorMessage(e);
1529 }
1530 });
1531 }
1532 )
1533 );
1534
1535 expect.addAssertion(
1536 '<regexp> to [exhaustively] satisfy <regexp>',
1537 (expect, subject, value) => {
1538 expect(subject, 'to equal', value);
1539 }
1540 );
1541
1542 expect.addAssertion(
1543 '<string> to [exhaustively] satisfy <regexp>',
1544 (expect, subject, value) => {
1545 expect.errorMode = 'bubble';
1546 return expect(subject, 'to match', value);
1547 }
1548 );
1549
1550 expect.addAssertion(
1551 '<function> to [exhaustively] satisfy <function>',
1552 (expect, subject, value) => {
1553 expect.errorMode = 'bubble';
1554 expect(subject, 'to be', value);
1555 }
1556 );
1557
1558 expect.addAssertion(
1559 '<binaryArray> to [exhaustively] satisfy <binaryArray>',
1560 (expect, subject, value) => {
1561 expect.errorMode = 'bubble';
1562 expect(subject, 'to equal', value);
1563 }
1564 );
1565
1566 expect.addAssertion(
1567 '<any> to [exhaustively] satisfy <any>',
1568 (expect, subject, value) => {
1569 expect.errorMode = 'bubble';
1570 expect(subject, 'to equal', value);
1571 }
1572 );
1573
1574 expect.addAssertion(
1575 '<array-like> to [exhaustively] satisfy <array-like>',
1576 (expect, subject, value) => {
1577 expect.errorMode = 'bubble';
1578 const subjectType = expect.subjectType;
1579 const subjectKeys = subjectType.getKeys(subject);
1580 const valueType = expect.argTypes[0];
1581 const valueKeys = valueType.getKeys(value).filter(
1582 key =>
1583 utils.numericalRegExp.test(key) ||
1584 typeof key === 'symbol' ||
1585 // include keys whose value is not undefined on either LHS or RHS
1586 typeof valueType.valueForKey(value, key) !== 'undefined' ||
1587 typeof subjectType.valueForKey(subject, key) !== 'undefined'
1588 );
1589 const keyPromises = {};
1590 valueKeys.forEach(function(keyInValue) {
1591 keyPromises[keyInValue] = expect.promise(function() {
1592 const subjectKey = subjectType.valueForKey(subject, keyInValue);
1593 const valueKey = valueType.valueForKey(value, keyInValue);
1594 const valueKeyType = expect.findTypeOf(valueKey);
1595
1596 if (valueKeyType.is('expect.it')) {
1597 expect.context.thisObject = subject;
1598 return valueKey(subjectKey, expect.context);
1599 } else {
1600 return expect(subjectKey, 'to [exhaustively] satisfy', valueKey);
1601 }
1602 });
1603 });
1604 return expect.promise
1605 .all([
1606 expect.promise(() => {
1607 // create subject key presence object
1608 const remainingKeysInSubject = {};
1609 subjectKeys.forEach(key => {
1610 remainingKeysInSubject[key] = 1; // present in subject
1611 });
1612 // discard or mark missing each previously seen value key
1613 valueKeys.forEach(key => {
1614 if (
1615 !remainingKeysInSubject[key] &&
1616 (utils.numericalRegExp.test(key) || expect.flags.exhaustively)
1617 ) {
1618 remainingKeysInSubject[key] = 2; // present in value
1619 } else {
1620 delete remainingKeysInSubject[key];
1621 }
1622 });
1623 // check whether there are any outstanding keys we cannot account for
1624 const outstandingKeys = Object.keys(remainingKeysInSubject).filter(
1625 key =>
1626 utils.numericalRegExp.test(key) ||
1627 typeof key === 'symbol' ||
1628 (typeof subjectType.valueForKey(subject, key) !== 'undefined' &&
1629 // key was only in the value
1630 remainingKeysInSubject[key] === 2)
1631 );
1632 // key checking succeeds with no outstanding keys
1633 expect(outstandingKeys.length === 0, 'to be truthy');
1634 }),
1635 expect.promise.all(keyPromises)
1636 ])
1637 .caught(() => {
1638 let i = 0;
1639 return expect.promise.settle(keyPromises).then(() => {
1640 const toSatisfyMatrix = new Array(subject.length);
1641 for (i = 0; i < subject.length; i += 1) {
1642 toSatisfyMatrix[i] = new Array(value.length);
1643 if (i < value.length) {
1644 toSatisfyMatrix[i][i] =
1645 keyPromises[i].isFulfilled() || keyPromises[i].reason();
1646 }
1647 }
1648 if (subject.length > 10 || value.length > 10) {
1649 const indexByIndexChanges = [];
1650 for (i = 0; i < subject.length; i += 1) {
1651 const promise = keyPromises[i];
1652 if (i < value.length) {
1653 indexByIndexChanges.push({
1654 type: promise.isFulfilled() ? 'equal' : 'similar',
1655 value: subject[i],
1656 expected: value[i],
1657 actualIndex: i,
1658 expectedIndex: i,
1659 last: i === Math.max(subject.length, value.length) - 1
1660 });
1661 } else {
1662 indexByIndexChanges.push({
1663 type: 'remove',
1664 value: subject[i],
1665 actualIndex: i,
1666 last: i === subject.length - 1
1667 });
1668 }
1669 }
1670 for (i = subject.length; i < value.length; i += 1) {
1671 indexByIndexChanges.push({
1672 type: 'insert',
1673 value: value[i],
1674 expectedIndex: i
1675 });
1676 }
1677 return failWithChanges(indexByIndexChanges);
1678 }
1679
1680 let isAsync = false;
1681 const subjectElements = utils.duplicateArrayLikeUsingType(
1682 subject,
1683 subjectType
1684 );
1685 const valueElements = utils.duplicateArrayLikeUsingType(
1686 value,
1687 valueType
1688 );
1689 const nonNumericalKeysAndSymbols =
1690 !subjectType.numericalPropertiesOnly &&
1691 utils.uniqueNonNumericalStringsAndSymbols(subjectKeys, valueKeys);
1692
1693 const changes = arrayChanges(
1694 subjectElements,
1695 valueElements,
1696 function equal(a, b, aIndex, bIndex) {
1697 toSatisfyMatrix[aIndex] = toSatisfyMatrix[aIndex] || [];
1698 const existingResult = toSatisfyMatrix[aIndex][bIndex];
1699 if (typeof existingResult !== 'undefined') {
1700 return existingResult === true;
1701 }
1702 let result;
1703 try {
1704 result = expect(a, 'to [exhaustively] satisfy', b);
1705 } catch (err) {
1706 throwIfNonUnexpectedError(err);
1707 toSatisfyMatrix[aIndex][bIndex] = err;
1708 return false;
1709 }
1710 result.then(
1711 () => {},
1712 () => {}
1713 );
1714 if (result.isPending()) {
1715 isAsync = true;
1716 return false;
1717 }
1718 toSatisfyMatrix[aIndex][bIndex] = true;
1719 return true;
1720 },
1721 (a, b) => subjectType.similar(a, b),
1722 {
1723 includeNonNumericalProperties: nonNumericalKeysAndSymbols
1724 }
1725 );
1726 if (isAsync) {
1727 return expect
1728 .promise((resolve, reject) => {
1729 arrayChangesAsync(
1730 subject,
1731 value,
1732 function equal(a, b, aIndex, bIndex, fn) {
1733 toSatisfyMatrix[aIndex] = toSatisfyMatrix[aIndex] || [];
1734 const existingResult = toSatisfyMatrix[aIndex][bIndex];
1735 if (typeof existingResult !== 'undefined') {
1736 return fn(existingResult === true);
1737 }
1738 expect
1739 .promise(() =>
1740 expect(a, 'to [exhaustively] satisfy', b)
1741 )
1742 .then(
1743 () => {
1744 toSatisfyMatrix[aIndex][bIndex] = true;
1745 fn(true);
1746 },
1747 err => {
1748 toSatisfyMatrix[aIndex][bIndex] = err;
1749 fn(false);
1750 }
1751 );
1752 },
1753 (a, b, aIndex, bIndex, fn) => {
1754 fn(subjectType.similar(a, b));
1755 },
1756 nonNumericalKeysAndSymbols,
1757 resolve
1758 );
1759 })
1760 .then(failWithChanges);
1761 } else {
1762 return failWithChanges(changes);
1763 }
1764
1765 function failWithChanges(changes) {
1766 expect.errorMode = 'default';
1767 expect.fail({
1768 diff(output, diff, inspect, equal) {
1769 output.inline = true;
1770 const indexOfLastNonInsert = changes.reduce(
1771 (previousValue, { type }, index) =>
1772 type === 'insert' ? previousValue : index,
1773 -1
1774 );
1775 const prefixOutput = subjectType.prefix(
1776 output.clone(),
1777 subject
1778 );
1779 output
1780 .append(prefixOutput)
1781 .nl(prefixOutput.isEmpty() ? 0 : 1);
1782
1783 if (subjectType.indent) {
1784 output.indentLines();
1785 }
1786 const packing = utils.packArrows(changes); // NOTE: Will have side effects in changes if the packing results in too many arrow lanes
1787 output.arrowsAlongsideChangeOutputs(
1788 packing,
1789 changes.map((diffItem, index) => {
1790 const delimiterOutput = subjectType.delimiter(
1791 output.clone(),
1792 index,
1793 indexOfLastNonInsert + 1
1794 );
1795 const type = diffItem.type;
1796 if (type === 'moveTarget') {
1797 return output.clone();
1798 } else {
1799 return output.clone().block(function() {
1800 if (type === 'moveSource') {
1801 const propertyOutput = subjectType.property(
1802 output.clone(),
1803 diffItem.actualIndex,
1804 inspect(diffItem.value),
1805 true
1806 );
1807
1808 this.append(propertyOutput)
1809 .amend(delimiterOutput)
1810 .sp()
1811 .error('// should be moved');
1812 } else if (type === 'insert') {
1813 this.annotationBlock(function() {
1814 if (
1815 expect
1816 .findTypeOf(diffItem.value)
1817 .is('expect.it')
1818 ) {
1819 this.error('missing: ').block(function() {
1820 this.omitSubject = undefined;
1821 const promise =
1822 keyPromises[diffItem.expectedIndex];
1823 if (promise.isRejected()) {
1824 this.appendErrorMessage(promise.reason());
1825 } else {
1826 this.appendInspected(diffItem.value);
1827 }
1828 });
1829 } else {
1830 const index =
1831 typeof diffItem.actualIndex !== 'undefined'
1832 ? diffItem.actualIndex
1833 : diffItem.expectedIndex;
1834 const propertyOutput = subjectType.property(
1835 output.clone(),
1836 index,
1837 inspect(diffItem.value),
1838 true
1839 );
1840 this.error('missing ').append(propertyOutput);
1841 }
1842 });
1843 } else {
1844 const propertyOutput = subjectType.property(
1845 output.clone(),
1846 diffItem.actualIndex,
1847 inspect(diffItem.value),
1848 true
1849 );
1850
1851 this.block(function() {
1852 if (type === 'remove') {
1853 this.append(propertyOutput)
1854 .amend(delimiterOutput)
1855 .sp()
1856 .error('// should be removed');
1857 } else if (type === 'equal') {
1858 this.append(propertyOutput).amend(
1859 delimiterOutput
1860 );
1861 } else {
1862 const toSatisfyResult =
1863 toSatisfyMatrix[diffItem.actualIndex][
1864 diffItem.expectedIndex
1865 ];
1866 const valueDiff =
1867 toSatisfyResult &&
1868 toSatisfyResult !== true &&
1869 toSatisfyResult.getDiff({
1870 output: output.clone()
1871 });
1872 if (valueDiff && valueDiff.inline) {
1873 this.append(valueDiff).amend(delimiterOutput);
1874 } else {
1875 this.append(propertyOutput)
1876 .amend(delimiterOutput)
1877 .sp()
1878 .annotationBlock(function() {
1879 this.omitSubject = diffItem.value;
1880 const label = toSatisfyResult.getLabel();
1881 if (label) {
1882 this.error(label)
1883 .sp()
1884 .block(inspect(diffItem.expected));
1885 if (valueDiff) {
1886 this.nl(2).append(valueDiff);
1887 }
1888 } else {
1889 this.appendErrorMessage(
1890 toSatisfyResult
1891 );
1892 }
1893 });
1894 }
1895 }
1896 });
1897 }
1898 });
1899 }
1900 })
1901 );
1902
1903 if (subjectType.indent) {
1904 output.outdentLines();
1905 }
1906 const suffixOutput = subjectType.suffix(
1907 output.clone(),
1908 subject
1909 );
1910 output
1911 .nl(suffixOutput.isEmpty() ? 0 : 1)
1912 .append(suffixOutput);
1913
1914 return output;
1915 }
1916 });
1917 }
1918 });
1919 });
1920 }
1921 );
1922
1923 expect.addAssertion(
1924 '<object> to [exhaustively] satisfy <object>',
1925 (expect, subject, value) => {
1926 const valueType = expect.argTypes[0];
1927 const subjectType = expect.subjectType;
1928 const subjectIsArrayLike = subjectType.is('array-like');
1929 if (subject === value) {
1930 return;
1931 }
1932 if (valueType.is('array-like') && !subjectIsArrayLike) {
1933 expect.fail();
1934 }
1935
1936 const subjectKeys = subjectType.getKeys(subject);
1937 const valueKeys = valueType.getKeys(value);
1938 // calculate the unique keys early given enumerability no
1939 // longer affects what is included in the list of keys
1940 const uniqueKeys = subjectType.uniqueKeys(subjectKeys, valueKeys);
1941
1942 const promiseByKey = {};
1943 let forceExhaustivelyComparison = false;
1944 uniqueKeys.forEach((key, index) => {
1945 const subjectHasKey = subjectType.hasKey(subject, key);
1946 const valueKey = valueType.hasKey(value, key, true)
1947 ? valueType.valueForKey(value, key)
1948 : undefined;
1949 const valueKeyType = expect.findTypeOf(valueKey);
1950 if (expect.flags.exhaustively) {
1951 if (valueKeyType.is('expect.it') && !subjectHasKey) {
1952 // ensure value only expect.it key is marked missing
1953 forceExhaustivelyComparison = true;
1954 }
1955 } else if (subjectHasKey && typeof valueKey === 'undefined') {
1956 // ignore subject only keys unless we are being exhaustive
1957 return;
1958 }
1959 const subjectKey = subjectHasKey
1960 ? subjectType.valueForKey(subject, key)
1961 : undefined;
1962
1963 promiseByKey[key] = expect.promise(() => {
1964 if (valueKeyType.is('expect.it')) {
1965 expect.context.thisObject = subject;
1966 return valueKey(subjectKey, expect.context);
1967 } else {
1968 return expect(subjectKey, 'to [exhaustively] satisfy', valueKey);
1969 }
1970 });
1971 });
1972
1973 return expect.promise
1974 .all([
1975 expect.promise(() => {
1976 if (forceExhaustivelyComparison) {
1977 throw new Error('exhaustive comparison failure');
1978 }
1979 }),
1980 expect.promise.all(promiseByKey)
1981 ])
1982 .caught(() =>
1983 expect.promise.settle(promiseByKey).then(() => {
1984 expect.fail({
1985 diff(output, diff, inspect, equal) {
1986 output.inline = true;
1987 const subjectIsArrayLike = subjectType.is('array-like');
1988 // Skip missing keys expected to be missing so they don't get rendered in the diff
1989 const keys = uniqueKeys.filter(key => {
1990 return (
1991 subjectType.hasKey(subject, key) ||
1992 typeof valueType.valueForKey(value, key) !== 'undefined'
1993 );
1994 });
1995 const prefixOutput = subjectType.prefix(
1996 output.clone(),
1997 subject
1998 );
1999 output.append(prefixOutput).nl(prefixOutput.isEmpty() ? 0 : 1);
2000
2001 if (subjectType.indent) {
2002 output.indentLines();
2003 }
2004 keys.forEach((key, index) => {
2005 const subjectKey = subjectType.valueForKey(subject, key);
2006 const valueKey = valueType.valueForKey(value, key);
2007 output
2008 .nl(index > 0 ? 1 : 0)
2009 .i()
2010 .block(function() {
2011 let valueOutput;
2012 const annotation = output.clone();
2013 let conflicting;
2014
2015 if (
2016 Object.prototype.hasOwnProperty.call(
2017 promiseByKey,
2018 key
2019 ) &&
2020 promiseByKey[key].isRejected()
2021 ) {
2022 conflicting = promiseByKey[key].reason();
2023 }
2024
2025 const missingArrayIndex =
2026 subjectType.is('array-like') &&
2027 !subjectType.hasKey(subject, key);
2028
2029 let isInlineDiff = true;
2030
2031 output.omitSubject = subjectKey;
2032 if (!valueType.hasKey(value, key)) {
2033 if (expect.flags.exhaustively) {
2034 annotation.error('should be removed');
2035 } else {
2036 conflicting = null;
2037 }
2038 } else if (!subjectType.hasKey(subject, key)) {
2039 if (expect.findTypeOf(valueKey).is('expect.it')) {
2040 if (promiseByKey[key].isRejected()) {
2041 output.error('// missing:').sp();
2042 valueOutput = output
2043 .clone()
2044 .appendErrorMessage(promiseByKey[key].reason());
2045 } else {
2046 output.error('// missing').sp();
2047 valueOutput = output
2048 .clone()
2049 .error('should satisfy')
2050 .sp()
2051 .block(inspect(value[key]));
2052 }
2053 } else {
2054 output.error('// missing').sp();
2055 valueOutput = inspect(valueKey);
2056 }
2057 } else if (conflicting || missingArrayIndex) {
2058 const keyDiff =
2059 conflicting && conflicting.getDiff({ output });
2060 isInlineDiff = !keyDiff || keyDiff.inline;
2061 if (missingArrayIndex) {
2062 output.error('// missing').sp();
2063 }
2064 if (keyDiff && keyDiff.inline) {
2065 valueOutput = keyDiff;
2066 } else if (
2067 expect.findTypeOf(valueKey).is('expect.it')
2068 ) {
2069 isInlineDiff = false;
2070 annotation.appendErrorMessage(conflicting);
2071 } else if (!keyDiff || (keyDiff && !keyDiff.inline)) {
2072 annotation
2073 .error(
2074 (conflicting && conflicting.getLabel()) ||
2075 'should satisfy'
2076 )
2077 .sp()
2078 .block(inspect(valueKey));
2079
2080 if (keyDiff) {
2081 annotation.nl(2).append(keyDiff);
2082 }
2083 } else {
2084 valueOutput = keyDiff;
2085 }
2086 }
2087
2088 if (!valueOutput) {
2089 if (
2090 missingArrayIndex ||
2091 !subjectType.hasKey(subject, key)
2092 ) {
2093 valueOutput = output.clone();
2094 } else {
2095 valueOutput = inspect(subjectKey);
2096 }
2097 }
2098
2099 const omitDelimiter =
2100 missingArrayIndex || index >= subjectKeys.length - 1;
2101
2102 if (!omitDelimiter) {
2103 const delimiterOutput = subjectType.delimiter(
2104 output.clone(),
2105 index,
2106 keys.length
2107 );
2108 valueOutput.amend(delimiterOutput);
2109 }
2110
2111 const annotationOnNextLine =
2112 !isInlineDiff &&
2113 output.preferredWidth <
2114 this.size().width +
2115 valueOutput.size().width +
2116 annotation.size().width;
2117
2118 if (!annotation.isEmpty()) {
2119 if (!valueOutput.isEmpty()) {
2120 if (annotationOnNextLine) {
2121 valueOutput.nl();
2122 } else {
2123 valueOutput.sp();
2124 }
2125 }
2126
2127 valueOutput.annotationBlock(function() {
2128 this.append(annotation);
2129 });
2130 }
2131
2132 if (!isInlineDiff) {
2133 valueOutput = output.clone().block(valueOutput);
2134 }
2135
2136 const propertyOutput = subjectType.property(
2137 output.clone(),
2138 key,
2139 valueOutput,
2140 subjectIsArrayLike
2141 );
2142
2143 this.append(propertyOutput);
2144 });
2145 });
2146
2147 if (subjectType.indent) {
2148 output.outdentLines();
2149 }
2150 const suffixOutput = subjectType.suffix(
2151 output.clone(),
2152 subject
2153 );
2154 return output
2155 .nl(suffixOutput.isEmpty() ? 0 : 1)
2156 .append(suffixOutput);
2157 }
2158 });
2159 })
2160 );
2161 }
2162 );
2163
2164 function wrapDiffWithTypePrefixAndSuffix(e, type, subject) {
2165 const createDiff = e.getDiffMethod();
2166 if (createDiff) {
2167 return function(output, ...rest) {
2168 type.prefix(output, subject);
2169 const result = createDiff.call(this, output, ...rest);
2170 type.suffix(output, subject);
2171 return result;
2172 };
2173 }
2174 }
2175
2176 expect.addAssertion(
2177 '<wrapperObject> to [exhaustively] satisfy <wrapperObject>',
2178 (expect, subject, value) => {
2179 const type = expect.findCommonType(subject, value);
2180 expect(type.is('wrapperObject'), 'to be truthy');
2181 return expect.withError(
2182 () =>
2183 expect(
2184 type.unwrap(subject),
2185 'to [exhaustively] satisfy',
2186 type.unwrap(value)
2187 ),
2188 e => {
2189 expect.fail({
2190 label: e.getLabel(),
2191 diff: wrapDiffWithTypePrefixAndSuffix(e, type, subject)
2192 });
2193 }
2194 );
2195 }
2196 );
2197
2198 expect.addAssertion(
2199 '<wrapperObject> to [exhaustively] satisfy <any>',
2200 (expect, subject, value) => {
2201 const subjectType = expect.subjectType;
2202
2203 return expect.withError(
2204 () =>
2205 expect(
2206 subjectType.unwrap(subject),
2207 'to [exhaustively] satisfy',
2208 value
2209 ),
2210 e => {
2211 expect.fail({
2212 label: e.getLabel(),
2213 diff: wrapDiffWithTypePrefixAndSuffix(e, subjectType, subject)
2214 });
2215 }
2216 );
2217 }
2218 );
2219
2220 expect.addAssertion(
2221 '<function> [when] called with <array-like> <assertion?>',
2222 (expect, subject, args) => {
2223 // ...
2224 expect.errorMode = 'nested';
2225 expect.argsOutput[0] = output => {
2226 output.appendItems(args, ', ');
2227 };
2228
2229 const thisObject = expect.context.thisObject || null;
2230
2231 return expect.shift(subject.apply(thisObject, args));
2232 }
2233 );
2234
2235 expect.addAssertion(
2236 '<function> [when] called <assertion?>',
2237 (expect, subject) => {
2238 expect.errorMode = 'nested';
2239
2240 const thisObject = expect.context.thisObject || null;
2241
2242 return expect.shift(subject.call(thisObject));
2243 }
2244 );
2245
2246 function instantiate(Constructor, args) {
2247 function ProxyConstructor() {
2248 return Constructor.apply(this, args);
2249 }
2250 ProxyConstructor.prototype = Constructor.prototype;
2251 return new ProxyConstructor();
2252 }
2253
2254 expect.addAssertion(
2255 [
2256 '<array-like> [when] passed as parameters to [async] <function> <assertion?>',
2257 '<array-like> [when] passed as parameters to [constructor] <function> <assertion?>'
2258 ],
2259 (expect, subject, fn) => {
2260 // ...
2261 expect.errorMode = 'nested';
2262 let args = subject;
2263 if (expect.flags.async) {
2264 return expect.promise(run => {
2265 args = [
2266 ...args,
2267 run((err, result) => {
2268 expect(err, 'to be falsy');
2269 return expect.shift(result);
2270 })
2271 ];
2272 fn(...args);
2273 });
2274 } else {
2275 return expect.shift(
2276 expect.flags.constructor ? instantiate(fn, args) : fn(...args)
2277 );
2278 }
2279 }
2280 );
2281
2282 expect.addAssertion(
2283 [
2284 '<any> [when] passed as parameter to [async] <function> <assertion?>',
2285 '<any> [when] passed as parameter to [constructor] <function> <assertion?>'
2286 ],
2287 (expect, subject, fn) => {
2288 // ...
2289 expect.errorMode = 'nested';
2290 let args = [subject];
2291 if (expect.flags.async) {
2292 return expect.promise(run => {
2293 args = [
2294 ...args,
2295 run((err, result) => {
2296 expect(err, 'to be falsy');
2297 return expect.shift(result);
2298 })
2299 ];
2300 fn(...args);
2301 });
2302 } else {
2303 return expect.shift(
2304 expect.flags.constructor ? instantiate(fn, args) : fn(...args)
2305 );
2306 }
2307 }
2308 );
2309
2310 expect.addAssertion(
2311 [
2312 '<array-like> [when] sorted [numerically] <assertion?>',
2313 '<array-like> [when] sorted by <function> <assertion?>'
2314 ],
2315 (expect, subject, compareFunction) => {
2316 if (expect.flags.numerically) {
2317 compareFunction = (a, b) => a - b;
2318 }
2319 return expect.shift(
2320 Array.prototype.slice
2321 .call(subject)
2322 .sort(
2323 typeof compareFunction === 'function' ? compareFunction : undefined
2324 )
2325 );
2326 }
2327 );
2328
2329 expect.addAssertion('<Promise> to be rejected', (expect, subject) => {
2330 expect.errorMode = 'nested';
2331 return expect
2332 .promise(() => subject)
2333 .then(
2334 obj => {
2335 expect.fail(output => {
2336 output
2337 .appendInspected(subject)
2338 .sp()
2339 .text('unexpectedly fulfilled');
2340 if (typeof obj !== 'undefined') {
2341 output
2342 .sp()
2343 .text('with')
2344 .sp()
2345 .appendInspected(obj);
2346 }
2347 });
2348 },
2349 err => err
2350 );
2351 });
2352
2353 expect.addAssertion('<function> to be rejected', (expect, subject) => {
2354 expect.errorMode = 'nested';
2355 return expect(
2356 expect.promise(() => subject()),
2357 'to be rejected'
2358 );
2359 });
2360
2361 expect.addAssertion(
2362 [
2363 '<Promise> to be rejected with <any>',
2364 '<Promise> to be rejected with error [exhaustively] satisfying <any>'
2365 ],
2366 (expect, subject, value) => {
2367 expect.errorMode = 'nested';
2368 return expect(subject, 'to be rejected').tap(err =>
2369 expect.withError(
2370 () => expect(err, 'to [exhaustively] satisfy', value),
2371 e => {
2372 e.originalError = err;
2373 throw e;
2374 }
2375 )
2376 );
2377 }
2378 );
2379
2380 expect.addAssertion(
2381 [
2382 '<function> to be rejected with <any>',
2383 '<function> to be rejected with error [exhaustively] satisfying <any>'
2384 ],
2385 (expect, subject, value) => {
2386 expect.errorMode = 'nested';
2387 return expect(
2388 expect.promise(() => subject()),
2389 'to be rejected with error [exhaustively] satisfying',
2390 value
2391 );
2392 }
2393 );
2394
2395 expect.addAssertion('<Promise> to be fulfilled', (expect, subject) => {
2396 expect.errorMode = 'nested';
2397 return expect
2398 .promise(() => subject)
2399 .caught(err => {
2400 expect.fail({
2401 output(output) {
2402 output
2403 .appendInspected(subject)
2404 .sp()
2405 .text('unexpectedly rejected');
2406 if (typeof err !== 'undefined') {
2407 output
2408 .sp()
2409 .text('with')
2410 .sp()
2411 .appendInspected(err);
2412 }
2413 },
2414 originalError: err
2415 });
2416 });
2417 });
2418
2419 expect.addAssertion('<function> to be fulfilled', (expect, subject) => {
2420 expect.errorMode = 'nested';
2421 return expect(
2422 expect.promise(() => subject()),
2423 'to be fulfilled'
2424 );
2425 });
2426
2427 expect.addAssertion(
2428 [
2429 '<Promise> to be fulfilled with <any>',
2430 '<Promise> to be fulfilled with value [exhaustively] satisfying <any>'
2431 ],
2432 (expect, subject, value) => {
2433 expect.errorMode = 'nested';
2434 return expect(subject, 'to be fulfilled').tap(fulfillmentValue =>
2435 expect(fulfillmentValue, 'to [exhaustively] satisfy', value)
2436 );
2437 }
2438 );
2439
2440 expect.addAssertion(
2441 [
2442 '<function> to be fulfilled with <any>',
2443 '<function> to be fulfilled with value [exhaustively] satisfying <any>'
2444 ],
2445 (expect, subject, value) => {
2446 expect.errorMode = 'nested';
2447 return expect(
2448 expect.promise(() => subject()),
2449 'to be fulfilled with value [exhaustively] satisfying',
2450 value
2451 );
2452 }
2453 );
2454
2455 expect.addAssertion(
2456 '<Promise> when rejected <assertion>',
2457 (expect, subject, nextAssertion) => {
2458 expect.errorMode = 'nested';
2459 return expect
2460 .promise(() => subject)
2461 .then(
2462 fulfillmentValue => {
2463 if (typeof nextAssertion === 'string') {
2464 expect.argsOutput = output => {
2465 output.error(nextAssertion);
2466 const rest = expect.args.slice(1);
2467 if (rest.length > 0) {
2468 output.sp().appendItems(rest, ', ');
2469 }
2470 };
2471 }
2472 expect.fail(output => {
2473 output
2474 .appendInspected(subject)
2475 .sp()
2476 .text('unexpectedly fulfilled');
2477 if (typeof fulfillmentValue !== 'undefined') {
2478 output
2479 .sp()
2480 .text('with')
2481 .sp()
2482 .appendInspected(fulfillmentValue);
2483 }
2484 });
2485 },
2486 err => {
2487 if (
2488 err.isOperational &&
2489 !Object.prototype.propertyIsEnumerable.call(err, 'isOperational')
2490 ) {
2491 delete err.isOperational;
2492 }
2493
2494 expect.withError(
2495 () => expect.shift(err),
2496 e => {
2497 e.originalError = err;
2498 throw e;
2499 }
2500 );
2501 }
2502 );
2503 }
2504 );
2505
2506 expect.addAssertion(
2507 '<function> when rejected <assertion>',
2508 (expect, subject, ...rest) => {
2509 expect.errorMode = 'nested';
2510 return expect(
2511 expect.promise(() => subject()),
2512 'when rejected',
2513 ...rest
2514 );
2515 }
2516 );
2517
2518 expect.addAssertion(
2519 '<Promise> when fulfilled <assertion>',
2520 (expect, subject, nextAssertion) => {
2521 expect.errorMode = 'nested';
2522 return expect
2523 .promise(() => subject)
2524 .then(
2525 fulfillmentValue => expect.shift(fulfillmentValue),
2526 err => {
2527 // typeof nextAssertion === 'string' because expect.it is handled by the above (and shift only supports those two):
2528 expect.argsOutput = output => {
2529 output.error(nextAssertion);
2530 const rest = expect.args.slice(1);
2531 if (rest.length > 0) {
2532 output.sp().appendItems(rest, ', ');
2533 }
2534 };
2535 expect.fail({
2536 output(output) {
2537 output
2538 .appendInspected(subject)
2539 .sp()
2540 .text('unexpectedly rejected');
2541 if (typeof err !== 'undefined') {
2542 output
2543 .sp()
2544 .text('with')
2545 .sp()
2546 .appendInspected(err);
2547 }
2548 },
2549 originalError: err
2550 });
2551 }
2552 );
2553 }
2554 );
2555
2556 expect.addAssertion(
2557 '<function> when fulfilled <assertion>',
2558 (expect, subject, ...rest) => {
2559 expect.errorMode = 'nested';
2560 return expect(
2561 expect.promise(() => subject()),
2562 'when fulfilled',
2563 ...rest
2564 );
2565 }
2566 );
2567
2568 expect.addAssertion('<function> to call the callback', (expect, subject) => {
2569 expect.errorMode = 'nested';
2570 return expect.promise(run => {
2571 let async = false;
2572 let calledTwice = false;
2573 let callbackArgs;
2574 function cb(...args) {
2575 if (callbackArgs) {
2576 calledTwice = true;
2577 } else {
2578 callbackArgs = Array.prototype.slice.call(args);
2579 }
2580 if (async) {
2581 setTimeout(assert, 0);
2582 }
2583 }
2584
2585 var assert = run(() => {
2586 if (calledTwice) {
2587 expect.fail(function() {
2588 this.error('The callback was called twice');
2589 });
2590 }
2591 return callbackArgs;
2592 });
2593
2594 subject(cb);
2595 async = true;
2596 if (callbackArgs) {
2597 return assert();
2598 }
2599 });
2600 });
2601
2602 expect.addAssertion(
2603 '<function> to call the callback without error',
2604 (expect, subject) =>
2605 expect(subject, 'to call the callback').then(callbackArgs => {
2606 const err = callbackArgs[0];
2607 if (err) {
2608 expect.errorMode = 'nested';
2609 expect.fail({
2610 message(output) {
2611 output.error('called the callback with: ');
2612 if (err.getErrorMessage) {
2613 output.appendErrorMessage(err);
2614 } else {
2615 output.appendInspected(err);
2616 }
2617 }
2618 });
2619 } else {
2620 return callbackArgs.slice(1);
2621 }
2622 })
2623 );
2624
2625 expect.addAssertion(
2626 '<function> to call the callback with error',
2627 (expect, subject) =>
2628 expect(subject, 'to call the callback').spread(err => {
2629 expect(err, 'to be truthy');
2630 return err;
2631 })
2632 );
2633
2634 expect.addAssertion(
2635 '<function> to call the callback with error <any>',
2636 (expect, subject, value) =>
2637 expect(subject, 'to call the callback with error').tap(err => {
2638 expect.errorMode = 'nested';
2639 expect(err, 'to satisfy', value);
2640 })
2641 );
2642};