UNPKG

18.4 kBJavaScriptView Raw
1/*!
2 * assert.js - assertions for javascript
3 * Copyright (c) 2018, Christopher Jeffrey (MIT License).
4 * https://github.com/chjj/bsert
5 */
6
7'use strict';
8
9/**
10 * AssertionError
11 */
12
13class AssertionError extends Error {
14 constructor(options) {
15 if (typeof options === 'string')
16 options = { message: options };
17
18 if (options === null || typeof options !== 'object')
19 options = {};
20
21 let message = null;
22 let operator = 'fail';
23 let generatedMessage = Boolean(options.generatedMessage);
24
25 if (options.message != null)
26 message = toString(options.message);
27
28 if (typeof options.operator === 'string')
29 operator = options.operator;
30
31 if (message == null) {
32 if (operator === 'fail') {
33 message = 'Assertion failed.';
34 } else {
35 const a = stringify(options.actual);
36 const b = stringify(options.expected);
37
38 message = `${a} ${operator} ${b}`;
39 }
40
41 generatedMessage = true;
42 }
43
44 super(message);
45
46 let start = this.constructor;
47
48 if (typeof options.stackStartFunction === 'function')
49 start = options.stackStartFunction;
50 else if (typeof options.stackStartFn === 'function')
51 start = options.stackStartFn;
52
53 this.type = 'AssertionError';
54 this.name = 'AssertionError [ERR_ASSERTION]';
55 this.code = 'ERR_ASSERTION';
56 this.generatedMessage = generatedMessage;
57 this.actual = options.actual;
58 this.expected = options.expected;
59 this.operator = operator;
60
61 if (Error.captureStackTrace)
62 Error.captureStackTrace(this, start);
63 }
64}
65
66/*
67 * Assert
68 */
69
70function assert(value, message) {
71 if (!value) {
72 let generatedMessage = false;
73
74 if (arguments.length === 0) {
75 message = 'No value argument passed to `assert()`.';
76 generatedMessage = true;
77 } else if (message == null) {
78 message = 'Assertion failed.';
79 generatedMessage = true;
80 } else if (isError(message)) {
81 throw message;
82 }
83
84 throw new AssertionError({
85 message,
86 actual: value,
87 expected: true,
88 operator: '==',
89 generatedMessage,
90 stackStartFn: assert
91 });
92 }
93}
94
95function equal(actual, expected, message) {
96 if (!Object.is(actual, expected)) {
97 if (isError(message))
98 throw message;
99
100 throw new AssertionError({
101 message,
102 actual,
103 expected,
104 operator: 'strictEqual',
105 stackStartFn: equal
106 });
107 }
108}
109
110function notEqual(actual, expected, message) {
111 if (Object.is(actual, expected)) {
112 if (isError(message))
113 throw message;
114
115 throw new AssertionError({
116 message,
117 actual,
118 expected,
119 operator: 'notStrictEqual',
120 stackStartFn: notEqual
121 });
122 }
123}
124
125function fail(message) {
126 let generatedMessage = false;
127
128 if (isError(message))
129 throw message;
130
131 if (message == null) {
132 message = 'Assertion failed.';
133 generatedMessage = true;
134 }
135
136 throw new AssertionError({
137 message,
138 actual: false,
139 expected: true,
140 operator: 'fail',
141 generatedMessage,
142 stackStartFn: fail
143 });
144}
145
146function throws(func, expected, message) {
147 if (typeof expected === 'string') {
148 message = expected;
149 expected = undefined;
150 }
151
152 let thrown = false;
153 let err = null;
154
155 enforce(typeof func === 'function', 'func', 'function');
156
157 try {
158 func();
159 } catch (e) {
160 thrown = true;
161 err = e;
162 }
163
164 if (!thrown) {
165 let generatedMessage = false;
166
167 if (message == null) {
168 message = 'Missing expected exception.';
169 generatedMessage = true;
170 }
171
172 throw new AssertionError({
173 message,
174 actual: undefined,
175 expected,
176 operator: 'throws',
177 generatedMessage,
178 stackStartFn: throws
179 });
180 }
181
182 if (!testError(err, expected, message, throws))
183 throw err;
184}
185
186function doesNotThrow(func, expected, message) {
187 if (typeof expected === 'string') {
188 message = expected;
189 expected = undefined;
190 }
191
192 let thrown = false;
193 let err = null;
194
195 enforce(typeof func === 'function', 'func', 'function');
196
197 try {
198 func();
199 } catch (e) {
200 thrown = true;
201 err = e;
202 }
203
204 if (!thrown)
205 return;
206
207 if (testError(err, expected, message, doesNotThrow)) {
208 let generatedMessage = false;
209
210 if (message == null) {
211 message = 'Got unwanted exception.';
212 generatedMessage = true;
213 }
214
215 throw new AssertionError({
216 message,
217 actual: err,
218 expected,
219 operator: 'doesNotThrow',
220 generatedMessage,
221 stackStartFn: doesNotThrow
222 });
223 }
224
225 throw err;
226}
227
228async function rejects(func, expected, message) {
229 if (typeof expected === 'string') {
230 message = expected;
231 expected = undefined;
232 }
233
234 let thrown = false;
235 let err = null;
236
237 if (typeof func !== 'function')
238 enforce(isPromise(func), 'func', 'promise');
239
240 try {
241 if (isPromise(func))
242 await func;
243 else
244 await func();
245 } catch (e) {
246 thrown = true;
247 err = e;
248 }
249
250 if (!thrown) {
251 let generatedMessage = false;
252
253 if (message == null) {
254 message = 'Missing expected rejection.';
255 generatedMessage = true;
256 }
257
258 throw new AssertionError({
259 message,
260 actual: undefined,
261 expected,
262 operator: 'rejects',
263 generatedMessage,
264 stackStartFn: rejects
265 });
266 }
267
268 if (!testError(err, expected, message, rejects))
269 throw err;
270}
271
272async function doesNotReject(func, expected, message) {
273 if (typeof expected === 'string') {
274 message = expected;
275 expected = undefined;
276 }
277
278 let thrown = false;
279 let err = null;
280
281 if (typeof func !== 'function')
282 enforce(isPromise(func), 'func', 'promise');
283
284 try {
285 if (isPromise(func))
286 await func;
287 else
288 await func();
289 } catch (e) {
290 thrown = true;
291 err = e;
292 }
293
294 if (!thrown)
295 return;
296
297 if (testError(err, expected, message, doesNotReject)) {
298 let generatedMessage = false;
299
300 if (message == null) {
301 message = 'Got unwanted rejection.';
302 generatedMessage = true;
303 }
304
305 throw new AssertionError({
306 message,
307 actual: undefined,
308 expected,
309 operator: 'doesNotReject',
310 generatedMessage,
311 stackStartFn: doesNotReject
312 });
313 }
314
315 throw err;
316}
317
318function ifError(err) {
319 if (err != null) {
320 let message = 'ifError got unwanted exception: ';
321
322 if (typeof err === 'object' && typeof err.message === 'string') {
323 if (err.message.length === 0 && err.constructor)
324 message += err.constructor.name;
325 else
326 message += err.message;
327 } else {
328 message += stringify(err);
329 }
330
331 throw new AssertionError({
332 message,
333 actual: err,
334 expected: null,
335 operator: 'ifError',
336 generatedMessage: true,
337 stackStartFn: ifError
338 });
339 }
340}
341
342function deepEqual(actual, expected, message) {
343 if (!isDeepEqual(actual, expected, false)) {
344 if (isError(message))
345 throw message;
346
347 throw new AssertionError({
348 message,
349 actual,
350 expected,
351 operator: 'deepStrictEqual',
352 stackStartFn: deepEqual
353 });
354 }
355}
356
357function notDeepEqual(actual, expected, message) {
358 if (isDeepEqual(actual, expected, true)) {
359 if (isError(message))
360 throw message;
361
362 throw new AssertionError({
363 message,
364 actual,
365 expected,
366 operator: 'notDeepStrictEqual',
367 stackStartFn: notDeepEqual
368 });
369 }
370}
371
372function bufferEqual(actual, expected, enc, message) {
373 if (!isEncoding(enc)) {
374 message = enc;
375 enc = null;
376 }
377
378 if (enc == null)
379 enc = 'hex';
380
381 expected = bufferize(actual, expected, enc);
382
383 enforce(isBuffer(actual), 'actual', 'buffer');
384 enforce(isBuffer(expected), 'expected', 'buffer');
385
386 if (actual !== expected && !actual.equals(expected)) {
387 if (isError(message))
388 throw message;
389
390 throw new AssertionError({
391 message,
392 actual: actual.toString(enc),
393 expected: expected.toString(enc),
394 operator: 'bufferEqual',
395 stackStartFn: bufferEqual
396 });
397 }
398}
399
400function notBufferEqual(actual, expected, enc, message) {
401 if (!isEncoding(enc)) {
402 message = enc;
403 enc = null;
404 }
405
406 if (enc == null)
407 enc = 'hex';
408
409 expected = bufferize(actual, expected, enc);
410
411 enforce(isBuffer(actual), 'actual', 'buffer');
412 enforce(isBuffer(expected), 'expected', 'buffer');
413
414 if (actual === expected || actual.equals(expected)) {
415 if (isError(message))
416 throw message;
417
418 throw new AssertionError({
419 message,
420 actual: actual.toString(enc),
421 expected: expected.toString(enc),
422 operator: 'notBufferEqual',
423 stackStartFn: notBufferEqual
424 });
425 }
426}
427
428function enforce(value, name, type) {
429 if (!value) {
430 let msg;
431
432 if (name == null) {
433 msg = 'Invalid type for parameter.';
434 } else {
435 if (type == null)
436 msg = `Invalid type for "${name}".`;
437 else
438 msg = `"${name}" must be a(n) ${type}.`;
439 }
440
441 const err = new TypeError(msg);
442
443 if (Error.captureStackTrace)
444 Error.captureStackTrace(err, enforce);
445
446 throw err;
447 }
448}
449
450function range(value, name) {
451 if (!value) {
452 const msg = name != null
453 ? `"${name}" is out of range.`
454 : 'Parameter is out of range.';
455
456 const err = new RangeError(msg);
457
458 if (Error.captureStackTrace)
459 Error.captureStackTrace(err, range);
460
461 throw err;
462 }
463}
464
465/*
466 * Stringification
467 */
468
469function stringify(value) {
470 switch (typeof value) {
471 case 'undefined':
472 return 'undefined';
473 case 'object':
474 if (value === null)
475 return 'null';
476 return `[${objectName(value)}]`;
477 case 'boolean':
478 return `${value}`;
479 case 'number':
480 return `${value}`;
481 case 'string':
482 if (value.length > 80)
483 value = `${value.substring(0, 77)}...`;
484 return JSON.stringify(value);
485 case 'symbol':
486 return tryString(value);
487 case 'function':
488 return `[${funcName(value)}]`;
489 case 'bigint':
490 return `${value}n`;
491 default:
492 return `[${typeof value}]`;
493 }
494}
495
496function toString(value) {
497 if (typeof value === 'string')
498 return value;
499
500 if (isError(value))
501 return tryString(value);
502
503 return stringify(value);
504}
505
506function tryString(value) {
507 try {
508 return String(value);
509 } catch (e) {
510 return 'Object';
511 }
512}
513
514/*
515 * Error Testing
516 */
517
518function testError(err, expected, message, func) {
519 if (expected == null)
520 return true;
521
522 if (isRegExp(expected))
523 return expected.test(err);
524
525 if (typeof expected !== 'function') {
526 if (func === doesNotThrow || func === doesNotReject)
527 throw new TypeError('"expected" must not be an object.');
528
529 if (typeof expected !== 'object')
530 throw new TypeError('"expected" must be an object.');
531
532 let generatedMessage = false;
533
534 if (message == null) {
535 const name = func === rejects ? 'rejection' : 'exception';
536 message = `Missing expected ${name}.`;
537 generatedMessage = true;
538 }
539
540 if (err == null || typeof err !== 'object') {
541 throw new AssertionError({
542 actual: err,
543 expected,
544 message,
545 operator: func.name,
546 generatedMessage,
547 stackStartFn: func
548 });
549 }
550
551 const keys = Object.keys(expected);
552
553 if (isError(expected))
554 keys.push('name', 'message');
555
556 if (keys.length === 0)
557 throw new TypeError('"expected" may not be an empty object.');
558
559 for (const key of keys) {
560 const expect = expected[key];
561 const value = err[key];
562
563 if (typeof value === 'string'
564 && isRegExp(expect)
565 && expect.test(value)) {
566 continue;
567 }
568
569 if ((key in err) && isDeepEqual(value, expect, false))
570 continue;
571
572 throw new AssertionError({
573 actual: err,
574 expected: expected,
575 message,
576 operator: func.name,
577 generatedMessage,
578 stackStartFn: func
579 });
580 }
581
582 return true;
583 }
584
585 if (expected.prototype !== undefined && (err instanceof expected))
586 return true;
587
588 if (Error.isPrototypeOf(expected))
589 return false;
590
591 return expected.call({}, err) === true;
592}
593
594/*
595 * Comparisons
596 */
597
598function isDeepEqual(x, y, fail) {
599 try {
600 return compare(x, y, null);
601 } catch (e) {
602 return fail;
603 }
604}
605
606function compare(a, b, cache) {
607 // Primitives.
608 if (Object.is(a, b))
609 return true;
610
611 if (!isObject(a) || !isObject(b))
612 return false;
613
614 // Semi-primitives.
615 if (objectString(a) !== objectString(b))
616 return false;
617
618 if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b))
619 return false;
620
621 if (isBuffer(a) && isBuffer(b))
622 return a.equals(b);
623
624 if (isDate(a))
625 return Object.is(a.getTime(), b.getTime());
626
627 if (isRegExp(a)) {
628 return a.source === b.source
629 && a.global === b.global
630 && a.multiline === b.multiline
631 && a.lastIndex === b.lastIndex
632 && a.ignoreCase === b.ignoreCase;
633 }
634
635 if (isError(a)) {
636 if (a.message !== b.message)
637 return false;
638 }
639
640 if (isArrayBuffer(a)) {
641 a = new Uint8Array(a);
642 b = new Uint8Array(b);
643 }
644
645 if (isView(a) && !isBuffer(a)) {
646 if (isBuffer(b))
647 return false;
648
649 const x = new Uint8Array(a.buffer);
650 const y = new Uint8Array(b.buffer);
651
652 if (x.length !== y.length)
653 return false;
654
655 for (let i = 0; i < x.length; i++) {
656 if (x[i] !== y[i])
657 return false;
658 }
659
660 return true;
661 }
662
663 if (isSet(a)) {
664 if (a.size !== b.size)
665 return false;
666
667 const keys = new Set([...a, ...b]);
668
669 return keys.size === a.size;
670 }
671
672 // Recursive.
673 if (!cache) {
674 cache = {
675 a: new Map(),
676 b: new Map(),
677 p: 0
678 };
679 } else {
680 const aa = cache.a.get(a);
681
682 if (aa != null) {
683 const bb = cache.b.get(b);
684 if (bb != null)
685 return aa === bb;
686 }
687
688 cache.p += 1;
689 }
690
691 cache.a.set(a, cache.p);
692 cache.b.set(b, cache.p);
693
694 const ret = recurse(a, b, cache);
695
696 cache.a.delete(a);
697 cache.b.delete(b);
698
699 return ret;
700}
701
702function recurse(a, b, cache) {
703 if (isMap(a)) {
704 if (a.size !== b.size)
705 return false;
706
707 const keys = new Set([...a.keys(), ...b.keys()]);
708
709 if (keys.size !== a.size)
710 return false;
711
712 for (const key of keys) {
713 if (!compare(a.get(key), b.get(key), cache))
714 return false;
715 }
716
717 return true;
718 }
719
720 if (isArray(a)) {
721 if (a.length !== b.length)
722 return false;
723
724 for (let i = 0; i < a.length; i++) {
725 if (!compare(a[i], b[i], cache))
726 return false;
727 }
728
729 return true;
730 }
731
732 const ak = ownKeys(a);
733 const bk = ownKeys(b);
734
735 if (ak.length !== bk.length)
736 return false;
737
738 const keys = new Set([...ak, ...bk]);
739
740 if (keys.size !== ak.length)
741 return false;
742
743 for (const key of keys) {
744 if (!compare(a[key], b[key], cache))
745 return false;
746 }
747
748 return true;
749}
750
751function ownKeys(obj) {
752 const keys = Object.keys(obj);
753
754 if (!Object.getOwnPropertySymbols)
755 return keys;
756
757 if (!Object.getOwnPropertyDescriptor)
758 return keys;
759
760 const symbols = Object.getOwnPropertySymbols(obj);
761
762 for (const symbol of symbols) {
763 const desc = Object.getOwnPropertyDescriptor(obj, symbol);
764
765 if (desc && desc.enumerable)
766 keys.push(symbol);
767 }
768
769 return keys;
770}
771
772/*
773 * Helpers
774 */
775
776function objectString(obj) {
777 if (obj === undefined)
778 return '[object Undefined]';
779
780 if (obj === null)
781 return '[object Null]';
782
783 try {
784 return Object.prototype.toString.call(obj);
785 } catch (e) {
786 return '[object Object]';
787 }
788}
789
790function objectType(obj) {
791 return objectString(obj).slice(8, -1);
792}
793
794function objectName(obj) {
795 const type = objectType(obj);
796
797 if (obj == null)
798 return type;
799
800 if (type !== 'Object' && type !== 'Error')
801 return type;
802
803 let ctor, name;
804
805 try {
806 ctor = obj.constructor;
807 } catch (e) {
808 ;
809 }
810
811 if (ctor == null)
812 return type;
813
814 try {
815 name = ctor.name;
816 } catch (e) {
817 return type;
818 }
819
820 if (typeof name !== 'string' || name.length === 0)
821 return type;
822
823 return name;
824}
825
826function funcName(func) {
827 let name;
828
829 try {
830 name = func.name;
831 } catch (e) {
832 ;
833 }
834
835 if (typeof name !== 'string' || name.length === 0)
836 return 'Function';
837
838 return `Function: ${name}`;
839}
840
841function isArray(obj) {
842 return Array.isArray(obj);
843}
844
845function isArrayBuffer(obj) {
846 return obj instanceof ArrayBuffer;
847}
848
849function isBuffer(obj) {
850 return isObject(obj)
851 && typeof obj.writeUInt32LE === 'function'
852 && typeof obj.equals === 'function';
853}
854
855function isDate(obj) {
856 return obj instanceof Date;
857}
858
859function isError(obj) {
860 return obj instanceof Error;
861}
862
863function isMap(obj) {
864 return obj instanceof Map;
865}
866
867function isObject(obj) {
868 return obj && typeof obj === 'object';
869}
870
871function isPromise(obj) {
872 return obj instanceof Promise;
873}
874
875function isRegExp(obj) {
876 return obj instanceof RegExp;
877}
878
879function isSet(obj) {
880 return obj instanceof Set;
881}
882
883function isView(obj) {
884 return ArrayBuffer.isView(obj);
885}
886
887function isEncoding(enc) {
888 if (typeof enc !== 'string')
889 return false;
890
891 switch (enc) {
892 case 'ascii':
893 case 'binary':
894 case 'base64':
895 case 'hex':
896 case 'latin1':
897 case 'ucs2':
898 case 'utf8':
899 case 'utf16le':
900 return true;
901 }
902
903 return false;
904}
905
906function bufferize(actual, expected, enc) {
907 if (typeof expected === 'string') {
908 if (!isBuffer(actual))
909 return null;
910
911 const {constructor} = actual;
912
913 if (!constructor || typeof constructor.from !== 'function')
914 return null;
915
916 if (!isEncoding(enc))
917 return null;
918
919 if (enc === 'hex' && (expected.length & 1))
920 return null;
921
922 const raw = constructor.from(expected, enc);
923
924 if (enc === 'hex' && raw.length !== (expected.length >>> 1))
925 return null;
926
927 return raw;
928 }
929
930 return expected;
931}
932
933/*
934 * API
935 */
936
937assert.AssertionError = AssertionError;
938assert.assert = assert;
939assert.strict = assert;
940assert.ok = assert;
941assert.equal = equal;
942assert.notEqual = notEqual;
943assert.strictEqual = equal;
944assert.notStrictEqual = notEqual;
945assert.fail = fail;
946assert.throws = throws;
947assert.doesNotThrow = doesNotThrow;
948assert.rejects = rejects;
949assert.doesNotReject = doesNotReject;
950assert.ifError = ifError;
951assert.deepEqual = deepEqual;
952assert.notDeepEqual = notDeepEqual;
953assert.deepStrictEqual = deepEqual;
954assert.notDeepStrictEqual = notDeepEqual;
955assert.bufferEqual = bufferEqual;
956assert.notBufferEqual = notBufferEqual;
957assert.enforce = enforce;
958assert.range = range;
959
960/*
961 * Expose
962 */
963
964module.exports = assert;