UNPKG

25.2 kBJavaScriptView Raw
1'use strict';
2
3var deepEqual = require('deep-equal');
4var defined = require('defined');
5var path = require('path');
6var inherits = require('inherits');
7var EventEmitter = require('events').EventEmitter;
8var has = require('has');
9var isRegExp = require('is-regex');
10var trim = require('string.prototype.trim');
11var callBind = require('call-bind');
12var callBound = require('call-bind/callBound');
13var forEach = require('for-each');
14var inspect = require('object-inspect');
15var is = require('object-is');
16var objectKeys = require('object-keys');
17var every = require('array.prototype.every');
18var mockProperty = require('mock-property');
19
20var isEnumerable = callBound('Object.prototype.propertyIsEnumerable');
21var toLowerCase = callBound('String.prototype.toLowerCase');
22var isProto = callBound('Object.prototype.isPrototypeOf');
23var $exec = callBound('RegExp.prototype.exec');
24var objectToString = callBound('Object.prototype.toString');
25var $split = callBound('String.prototype.split');
26var $replace = callBound('String.prototype.replace');
27var $strSlice = callBound('String.prototype.slice');
28var $push = callBound('Array.prototype.push');
29var $shift = callBound('Array.prototype.shift');
30var $slice = callBound('Array.prototype.slice');
31
32var nextTick = typeof setImmediate !== 'undefined'
33 ? setImmediate
34 : process.nextTick;
35var safeSetTimeout = setTimeout;
36var safeClearTimeout = clearTimeout;
37
38// eslint-disable-next-line no-unused-vars
39function getTestArgs(name_, opts_, cb_) {
40 var name = '(anonymous)';
41 var opts = {};
42 var cb;
43
44 for (var i = 0; i < arguments.length; i++) {
45 var arg = arguments[i];
46 if (typeof arg === 'string') {
47 name = arg;
48 } else if (typeof arg === 'object') {
49 opts = arg || opts;
50 } else if (typeof arg === 'function') {
51 cb = arg;
52 }
53 }
54 return {
55 name: name,
56 opts: opts,
57 cb: cb
58 };
59}
60
61function Test(name_, opts_, cb_) {
62 if (!(this instanceof Test)) {
63 return new Test(name_, opts_, cb_);
64 }
65
66 var args = getTestArgs(name_, opts_, cb_);
67
68 this.readable = true;
69 this.name = args.name || '(anonymous)';
70 this.assertCount = 0;
71 this.pendingCount = 0;
72 this._skip = args.opts.skip || false;
73 this._todo = args.opts.todo || false;
74 this._timeout = args.opts.timeout;
75 this._plan = undefined;
76 this._cb = args.cb;
77 this._progeny = [];
78 this._teardown = [];
79 this._ok = true;
80 var depthEnvVar = process.env.NODE_TAPE_OBJECT_PRINT_DEPTH;
81 if (args.opts.objectPrintDepth) {
82 this._objectPrintDepth = args.opts.objectPrintDepth;
83 } else if (depthEnvVar) {
84 if (toLowerCase(depthEnvVar) === 'infinity') {
85 this._objectPrintDepth = Infinity;
86 } else {
87 this._objectPrintDepth = depthEnvVar;
88 }
89 } else {
90 this._objectPrintDepth = 5;
91 }
92
93 var self = this;
94 for (var prop in self) {
95 if (typeof self[prop] === 'function') {
96 self[prop] = callBind(self[prop], self);
97 }
98 }
99}
100
101inherits(Test, EventEmitter);
102
103Test.prototype.run = function run() {
104 this.emit('prerun');
105 if (!this._cb || this._skip) {
106 this._end();
107 return;
108 }
109 if (this._timeout != null) {
110 this.timeoutAfter(this._timeout);
111 }
112
113 var callbackReturn = this._cb(this);
114
115 if (
116 typeof Promise === 'function'
117 && callbackReturn
118 && typeof callbackReturn.then === 'function'
119 ) {
120 var self = this;
121 Promise.resolve(callbackReturn).then(
122 function onResolve() {
123 if (!self.calledEnd) {
124 self.end();
125 }
126 },
127 function onError(err) {
128 if (err instanceof Error || objectToString(err) === '[object Error]') {
129 self.ifError(err);
130 } else {
131 self.fail(err);
132 }
133 self.end();
134 }
135 );
136 return;
137 }
138
139 this.emit('run');
140};
141
142Test.prototype.test = function test(name, opts, cb) {
143 var self = this;
144 var t = new Test(name, opts, cb);
145 $push(this._progeny, t);
146 this.pendingCount++;
147 this.emit('test', t);
148 t.on('prerun', function () {
149 self.assertCount++;
150 });
151
152 if (!self._pendingAsserts()) {
153 nextTick(function () {
154 self._end();
155 });
156 }
157
158 nextTick(function () {
159 if (!self._plan && self.pendingCount == self._progeny.length) {
160 self._end();
161 }
162 });
163};
164
165Test.prototype.comment = function comment(msg) {
166 var that = this;
167 forEach($split(trim(msg), '\n'), function (aMsg) {
168 that.emit('result', $replace(trim(aMsg), /^#\s*/, ''));
169 });
170};
171
172Test.prototype.plan = function plan(n) {
173 this._plan = n;
174 this.emit('plan', n);
175};
176
177Test.prototype.timeoutAfter = function timeoutAfter(ms) {
178 if (!ms) { throw new Error('timeoutAfter requires a timespan'); }
179 var self = this;
180 var timeout = safeSetTimeout(function () {
181 self.fail(self.name + ' timed out after ' + ms + 'ms');
182 self.end();
183 }, ms);
184 this.once('end', function () {
185 safeClearTimeout(timeout);
186 });
187};
188
189Test.prototype.end = function end(err) {
190 if (arguments.length >= 1 && !!err) {
191 this.ifError(err);
192 }
193
194 if (this.calledEnd) {
195 this.fail('.end() already called');
196 }
197 this.calledEnd = true;
198 this._end();
199};
200
201Test.prototype.teardown = function teardown(fn) {
202 if (typeof fn !== 'function') {
203 this.fail('teardown: ' + inspect(fn) + ' is not a function');
204 } else {
205 this._teardown.push(fn);
206 }
207};
208
209function wrapFunction(original) {
210 if (typeof original !== 'undefined' && typeof original !== 'function') {
211 throw new TypeError('`original` must be a function or `undefined`');
212 }
213
214 var bound = original && callBind.apply(original);
215
216 var calls = [];
217
218 var wrapObject = {
219 __proto__: null,
220 wrapped: function wrapped() {
221 var args = $slice(arguments);
222 var completed = false;
223 try {
224 var returned = original ? bound(this, arguments) : void undefined;
225 $push(calls, { args: args, receiver: this, returned: returned });
226 completed = true;
227 return returned;
228 } finally {
229 if (!completed) {
230 $push(calls, { args: args, receiver: this, threw: true });
231 }
232 }
233 },
234 calls: calls,
235 results: function results() {
236 try {
237 return calls;
238 } finally {
239 calls = [];
240 wrapObject.calls = calls;
241 }
242 }
243 };
244 return wrapObject;
245}
246
247Test.prototype.capture = function capture(obj, method) {
248 if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
249 throw new TypeError('`obj` must be an object');
250 }
251 if (typeof method !== 'string' && typeof method !== 'symbol') {
252 throw new TypeError('`method` must be a string or a symbol');
253 }
254 var implementation = arguments.length > 2 ? arguments[2] : void undefined;
255 if (typeof implementation !== 'undefined' && typeof implementation !== 'function') {
256 throw new TypeError('`implementation`, if provided, must be a function');
257 }
258
259 var wrapper = wrapFunction(implementation);
260 var restore = mockProperty(obj, method, { value: wrapper.wrapped });
261 this.teardown(restore);
262
263 wrapper.results.restore = restore;
264
265 return wrapper.results;
266};
267
268Test.prototype.captureFn = function captureFn(original) {
269 if (typeof original !== 'function') {
270 throw new TypeError('`original` must be a function');
271 }
272
273 var wrapObject = wrapFunction(original);
274 wrapObject.wrapped.calls = wrapObject.calls;
275 return wrapObject.wrapped;
276};
277
278Test.prototype.intercept = function intercept(obj, property) {
279 if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
280 throw new TypeError('`obj` must be an object');
281 }
282 if (typeof property !== 'string' && typeof property !== 'symbol') {
283 throw new TypeError('`property` must be a string or a symbol');
284 }
285 var desc = arguments.length > 2 ? arguments[2] : { __proto__: null };
286 if (typeof desc !== 'undefined' && (!desc || typeof desc !== 'object')) {
287 throw new TypeError('`desc`, if provided, must be an object');
288 }
289 if ('configurable' in desc && !desc.configurable) {
290 throw new TypeError('`desc.configurable`, if provided, must be `true`, so that the interception can be restored later');
291 }
292 var isData = 'writable' in desc || 'value' in desc;
293 var isAccessor = 'get' in desc || 'set' in desc;
294 if (isData && isAccessor) {
295 throw new TypeError('`value` and `writable` can not be mixed with `get` and `set`');
296 }
297 var strictMode = arguments.length > 3 ? arguments[3] : true;
298 if (typeof strictMode !== 'boolean') {
299 throw new TypeError('`strictMode`, if provided, must be a boolean');
300 }
301
302 var calls = [];
303 var getter = desc.get && callBind.apply(desc.get);
304 var setter = desc.set && callBind.apply(desc.set);
305 var value = !isAccessor ? desc.value : void undefined;
306 var writable = !!desc.writable;
307
308 function getInterceptor() {
309 var args = $slice(arguments);
310 if (isAccessor) {
311 if (getter) {
312 var completed = false;
313 try {
314 var returned = getter(this, arguments);
315 completed = true;
316 $push(calls, { type: 'get', success: true, value: returned, args: args, receiver: this });
317 return returned;
318 } finally {
319 if (!completed) {
320 $push(calls, { type: 'get', success: false, threw: true, args: args, receiver: this });
321 }
322 }
323 }
324 }
325 $push(calls, { type: 'get', success: true, value: value, args: args, receiver: this });
326 return value;
327 }
328
329 function setInterceptor(v) {
330 var args = $slice(arguments);
331 if (isAccessor && setter) {
332 var completed = false;
333 try {
334 var returned = setter(this, arguments);
335 completed = true;
336 $push(calls, { type: 'set', success: true, value: v, args: args, receiver: this });
337 return returned;
338 } finally {
339 if (!completed) {
340 $push(calls, { type: 'set', success: false, threw: true, args: args, receiver: this });
341 }
342 }
343 }
344 var canSet = isAccessor || writable;
345 if (canSet) {
346 value = v;
347 }
348 $push(calls, { type: 'set', success: !!canSet, value: value, args: args, receiver: this });
349
350 if (!canSet && strictMode) {
351 throw new TypeError('Cannot assign to read only property \'' + property + '\' of object \'' + inspect(obj) + '\'');
352 }
353 return value;
354 }
355
356 var restore = mockProperty(obj, property, {
357 nonEnumerable: !!desc.enumerable,
358 get: getInterceptor,
359 set: setInterceptor
360 });
361 this.teardown(restore);
362
363 function results() {
364 try {
365 return calls;
366 } finally {
367 calls = [];
368 }
369 }
370 results.restore = restore;
371
372 return results;
373};
374
375Test.prototype._end = function _end(err) {
376 var self = this;
377
378 if (!this._cb && !this._todo && !this._skip) {
379 this.fail('# TODO ' + this.name);
380 }
381
382 if (this._progeny.length) {
383 var t = $shift(this._progeny);
384 t.on('end', function () { self._end(); });
385 t.run();
386 return;
387 }
388
389 function completeEnd() {
390 if (!self.ended) { self.emit('end'); }
391 var pendingAsserts = self._pendingAsserts();
392 if (!self._planError && self._plan !== undefined && pendingAsserts) {
393 self._planError = true;
394 self.fail('plan != count', {
395 expected: self._plan,
396 actual: self.assertCount
397 });
398 }
399 self.ended = true;
400 }
401
402 function next() {
403 if (self._teardown.length === 0) {
404 completeEnd();
405 return;
406 }
407 var fn = self._teardown.shift();
408 var res;
409 try {
410 res = fn();
411 } catch (e) {
412 self.fail(e);
413 }
414 if (res && typeof res.then === 'function') {
415 res.then(next, function (_err) {
416 // TODO: wth?
417 err = err || _err;
418 });
419 } else {
420 next();
421 }
422 }
423
424 next();
425};
426
427Test.prototype._exit = function _exit() {
428 if (this._plan !== undefined && !this._planError && this.assertCount !== this._plan) {
429 this._planError = true;
430 this.fail('plan != count', {
431 expected: this._plan,
432 actual: this.assertCount,
433 exiting: true
434 });
435 } else if (!this.ended) {
436 this.fail('test exited without ending: ' + this.name, {
437 exiting: true
438 });
439 }
440};
441
442Test.prototype._pendingAsserts = function _pendingAsserts() {
443 if (this._plan === undefined) {
444 return 1;
445 }
446 return this._plan - (this._progeny.length + this.assertCount);
447};
448
449Test.prototype._assert = function assert(ok, opts) {
450 var self = this;
451 var extra = opts.extra || {};
452
453 ok = !!ok || !!extra.skip;
454
455 var name = defined(extra.message, opts.message, '(unnamed assert)');
456 if (this.calledEnd && opts.operator !== 'fail') {
457 this.fail('.end() already called: ' + name);
458 return;
459 }
460
461 var res = {
462 id: self.assertCount++,
463 ok: ok,
464 skip: defined(extra.skip, opts.skip),
465 todo: defined(extra.todo, opts.todo, self._todo),
466 name: name,
467 operator: defined(extra.operator, opts.operator),
468 objectPrintDepth: self._objectPrintDepth
469 };
470 if (has(opts, 'actual') || has(extra, 'actual')) {
471 res.actual = defined(extra.actual, opts.actual);
472 }
473 if (has(opts, 'expected') || has(extra, 'expected')) {
474 res.expected = defined(extra.expected, opts.expected);
475 }
476 this._ok = !!(this._ok && ok);
477
478 if (!ok && !res.todo) {
479 res.error = defined(extra.error, opts.error, new Error(res.name));
480 }
481
482 if (!ok) {
483 var e = new Error('exception');
484 var err = $split(e.stack || '', '\n');
485 var dir = __dirname + path.sep;
486
487 for (var i = 0; i < err.length; i++) {
488 /*
489 Stack trace lines may resemble one of the following. We need
490 to correctly extract a function name (if any) and path / line
491 number for each line.
492
493 at myFunction (/path/to/file.js:123:45)
494 at myFunction (/path/to/file.other-ext:123:45)
495 at myFunction (/path to/file.js:123:45)
496 at myFunction (C:\path\to\file.js:123:45)
497 at myFunction (/path/to/file.js:123)
498 at Test.<anonymous> (/path/to/file.js:123:45)
499 at Test.bound [as run] (/path/to/file.js:123:45)
500 at /path/to/file.js:123:45
501
502 Regex has three parts. First is non-capturing group for 'at '
503 (plus anything preceding it).
504
505 /^(?:[^\s]*\s*\bat\s+)/
506
507 Second captures function call description (optional). This is
508 not necessarily a valid JS function name, but just what the
509 stack trace is using to represent a function call. It may look
510 like `<anonymous>` or 'Test.bound [as run]'.
511
512 For our purposes, we assume that, if there is a function
513 name, it's everything leading up to the first open
514 parentheses (trimmed) before our pathname.
515
516 /(?:(.*)\s+\()?/
517
518 Last part captures file path plus line no (and optional
519 column no).
520
521 /((?:\/|[a-zA-Z]:\\)[^:\)]+:(\d+)(?::(\d+))?)\)?/
522 */
523 var re = /^(?:[^\s]*\s*\bat\s+)(?:(.*)\s+\()?((?:\/|[a-zA-Z]:\\)[^:)]+:(\d+)(?::(\d+))?)\)?$/;
524 var lineWithTokens = $replace($replace(err[i], process.cwd(), '/$CWD'), __dirname, '/$TEST');
525 var m = re.exec(lineWithTokens);
526
527 if (!m) {
528 continue;
529 }
530
531 var callDescription = m[1] || '<anonymous>';
532 var filePath = $replace($replace(m[2], '/$CWD', process.cwd()), '/$TEST', __dirname);
533
534 if ($strSlice(filePath, 0, dir.length) === dir) {
535 continue;
536 }
537
538 // Function call description may not (just) be a function name.
539 // Try to extract function name by looking at first "word" only.
540 res.functionName = $split(callDescription, /\s+/)[0];
541 res.file = filePath;
542 res.line = Number(m[3]);
543 if (m[4]) { res.column = Number(m[4]); }
544
545 res.at = callDescription + ' (' + filePath + ')';
546 break;
547 }
548 }
549
550 self.emit('result', res);
551
552 var pendingAsserts = self._pendingAsserts();
553 if (!pendingAsserts) {
554 if (extra.exiting) {
555 self._end();
556 } else {
557 nextTick(function () {
558 self._end();
559 });
560 }
561 }
562
563 if (!self._planError && pendingAsserts < 0) {
564 self._planError = true;
565 self.fail('plan != count', {
566 expected: self._plan,
567 actual: self._plan - pendingAsserts
568 });
569 }
570};
571
572Test.prototype.fail = function fail(msg, extra) {
573 this._assert(false, {
574 message: msg,
575 operator: 'fail',
576 extra: extra
577 });
578};
579
580Test.prototype.pass = function pass(msg, extra) {
581 this._assert(true, {
582 message: msg,
583 operator: 'pass',
584 extra: extra
585 });
586};
587
588Test.prototype.skip = function skip(msg, extra) {
589 this._assert(true, {
590 message: msg,
591 operator: 'skip',
592 skip: true,
593 extra: extra
594 });
595};
596
597var testAssert = function assert(value, msg, extra) { // eslint-disable-line func-style
598 this._assert(value, {
599 message: defined(msg, 'should be truthy'),
600 operator: 'ok',
601 expected: true,
602 actual: value,
603 extra: extra
604 });
605};
606Test.prototype.ok
607= Test.prototype['true']
608= Test.prototype.assert
609= testAssert;
610
611function notOK(value, msg, extra) {
612 this._assert(!value, {
613 message: defined(msg, 'should be falsy'),
614 operator: 'notOk',
615 expected: false,
616 actual: value,
617 extra: extra
618 });
619}
620Test.prototype.notOk
621= Test.prototype['false']
622= Test.prototype.notok
623= notOK;
624
625function error(err, msg, extra) {
626 this._assert(!err, {
627 message: defined(msg, String(err)),
628 operator: 'error',
629 error: err,
630 extra: extra
631 });
632}
633Test.prototype.error
634= Test.prototype.ifError
635= Test.prototype.ifErr
636= Test.prototype.iferror
637= error;
638
639function strictEqual(a, b, msg, extra) {
640 if (arguments.length < 2) {
641 throw new TypeError('two arguments must be provided to compare');
642 }
643 this._assert(is(a, b), {
644 message: defined(msg, 'should be strictly equal'),
645 operator: 'equal',
646 actual: a,
647 expected: b,
648 extra: extra
649 });
650}
651Test.prototype.equal
652= Test.prototype.equals
653= Test.prototype.isEqual
654= Test.prototype.strictEqual
655= Test.prototype.strictEquals
656= Test.prototype.is
657= strictEqual;
658
659function notStrictEqual(a, b, msg, extra) {
660 if (arguments.length < 2) {
661 throw new TypeError('two arguments must be provided to compare');
662 }
663 this._assert(!is(a, b), {
664 message: defined(msg, 'should not be strictly equal'),
665 operator: 'notEqual',
666 actual: a,
667 expected: b,
668 extra: extra
669 });
670}
671
672Test.prototype.notEqual
673= Test.prototype.notEquals
674= Test.prototype.isNotEqual
675= Test.prototype.doesNotEqual
676= Test.prototype.isInequal
677= Test.prototype.notStrictEqual
678= Test.prototype.notStrictEquals
679= Test.prototype.isNot
680= Test.prototype.not
681= notStrictEqual;
682
683function looseEqual(a, b, msg, extra) {
684 if (arguments.length < 2) {
685 throw new TypeError('two arguments must be provided to compare');
686 }
687 this._assert(a == b, {
688 message: defined(msg, 'should be loosely equal'),
689 operator: 'looseEqual',
690 actual: a,
691 expected: b,
692 extra: extra
693 });
694}
695
696Test.prototype.looseEqual
697= Test.prototype.looseEquals
698= looseEqual;
699
700function notLooseEqual(a, b, msg, extra) {
701 if (arguments.length < 2) {
702 throw new TypeError('two arguments must be provided to compare');
703 }
704 this._assert(a != b, {
705 message: defined(msg, 'should not be loosely equal'),
706 operator: 'notLooseEqual',
707 actual: a,
708 expected: b,
709 extra: extra
710 });
711}
712Test.prototype.notLooseEqual
713= Test.prototype.notLooseEquals
714= notLooseEqual;
715
716function tapeDeepEqual(a, b, msg, extra) {
717 if (arguments.length < 2) {
718 throw new TypeError('two arguments must be provided to compare');
719 }
720 this._assert(deepEqual(a, b, { strict: true }), {
721 message: defined(msg, 'should be deeply equivalent'),
722 operator: 'deepEqual',
723 actual: a,
724 expected: b,
725 extra: extra
726 });
727}
728Test.prototype.deepEqual
729= Test.prototype.deepEquals
730= Test.prototype.isEquivalent
731= Test.prototype.same
732= tapeDeepEqual;
733
734function notDeepEqual(a, b, msg, extra) {
735 if (arguments.length < 2) {
736 throw new TypeError('two arguments must be provided to compare');
737 }
738 this._assert(!deepEqual(a, b, { strict: true }), {
739 message: defined(msg, 'should not be deeply equivalent'),
740 operator: 'notDeepEqual',
741 actual: a,
742 expected: b,
743 extra: extra
744 });
745}
746Test.prototype.notDeepEqual
747= Test.prototype.notDeepEquals
748= Test.prototype.notEquivalent
749= Test.prototype.notDeeply
750= Test.prototype.notSame
751= Test.prototype.isNotDeepEqual
752= Test.prototype.isNotDeeply
753= Test.prototype.isNotEquivalent
754= Test.prototype.isInequivalent
755= notDeepEqual;
756
757function deepLooseEqual(a, b, msg, extra) {
758 if (arguments.length < 2) {
759 throw new TypeError('two arguments must be provided to compare');
760 }
761 this._assert(deepEqual(a, b), {
762 message: defined(msg, 'should be loosely deeply equivalent'),
763 operator: 'deepLooseEqual',
764 actual: a,
765 expected: b,
766 extra: extra
767 });
768}
769
770Test.prototype.deepLooseEqual
771= deepLooseEqual;
772
773function notDeepLooseEqual(a, b, msg, extra) {
774 if (arguments.length < 2) {
775 throw new TypeError('two arguments must be provided to compare');
776 }
777 this._assert(!deepEqual(a, b), {
778 message: defined(msg, 'should not be loosely deeply equivalent'),
779 operator: 'notDeepLooseEqual',
780 actual: a,
781 expected: b,
782 extra: extra
783 });
784}
785Test.prototype.notDeepLooseEqual
786= notDeepLooseEqual;
787
788Test.prototype['throws'] = function (fn, expected, msg, extra) {
789 if (typeof expected === 'string') {
790 msg = expected;
791 expected = undefined;
792 }
793
794 var caught;
795
796 try {
797 fn();
798 } catch (err) {
799 caught = { error: err };
800 if (Object(err) === err && 'message' in err && (!isEnumerable(err, 'message') || !has(err, 'message'))) {
801 try {
802 var message = err.message;
803 delete err.message;
804 err.message = message;
805 } catch (e) { /**/ }
806 }
807 }
808
809 var passed = caught;
810
811 if (caught) {
812 if (typeof expected === 'string' && caught.error && caught.error.message === expected) {
813 throw new TypeError('The "error/message" argument is ambiguous. The error message ' + inspect(expected) + ' is identical to the message.');
814 }
815 if (typeof expected === 'function') {
816 if (typeof expected.prototype !== 'undefined' && caught.error instanceof expected) {
817 passed = true;
818 } else if (isProto(Error, expected)) {
819 passed = false;
820 } else {
821 passed = expected.call({}, caught.error) === true;
822 }
823 } else if (isRegExp(expected)) {
824 passed = $exec(expected, caught.error) !== null;
825 expected = inspect(expected);
826 } else if (expected && typeof expected === 'object') { // Handle validation objects.
827 var keys = objectKeys(expected);
828 // Special handle errors to make sure the name and the message are compared as well.
829 if (expected instanceof Error) {
830 $push(keys, 'name', 'message');
831 } else if (keys.length === 0) {
832 throw new TypeError('`throws` validation object must not be empty');
833 }
834 passed = every(keys, function (key) {
835 if (typeof caught.error[key] === 'string' && isRegExp(expected[key]) && $exec(expected[key], caught.error[key]) !== null) {
836 return true;
837 }
838 if (key in caught.error && deepEqual(caught.error[key], expected[key], { strict: true })) {
839 return true;
840 }
841 return false;
842 });
843 }
844 }
845
846 this._assert(!!passed, {
847 message: defined(msg, 'should throw'),
848 operator: 'throws',
849 actual: caught && caught.error,
850 expected: expected,
851 error: !passed && caught && caught.error,
852 extra: extra
853 });
854};
855
856Test.prototype.doesNotThrow = function doesNotThrow(fn, expected, msg, extra) {
857 if (typeof expected === 'string') {
858 msg = expected;
859 expected = undefined;
860 }
861 var caught;
862 try {
863 fn();
864 } catch (err) {
865 caught = { error: err };
866 }
867 this._assert(!caught, {
868 message: defined(msg, 'should not throw'),
869 operator: 'throws',
870 actual: caught && caught.error,
871 expected: expected,
872 error: caught && caught.error,
873 extra: extra
874 });
875};
876
877Test.prototype.match = function match(string, regexp, msg, extra) {
878 if (!isRegExp(regexp)) {
879 this._assert(false, {
880 message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
881 operator: 'match',
882 actual: objectToString(regexp),
883 expected: '[object RegExp]',
884 extra: extra
885 });
886 } else if (typeof string !== 'string') {
887 this._assert(false, {
888 message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
889 operator: 'match',
890 actual: string === null ? null : typeof string,
891 expected: 'string',
892 extra: extra
893 });
894 } else {
895 var matches = $exec(regexp, string) !== null;
896 var message = defined(
897 msg,
898 'The input ' + (matches ? 'matched' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
899 );
900 this._assert(matches, {
901 message: message,
902 operator: 'match',
903 actual: string,
904 expected: regexp,
905 extra: extra
906 });
907 }
908};
909
910Test.prototype.doesNotMatch = function doesNotMatch(string, regexp, msg, extra) {
911 if (!isRegExp(regexp)) {
912 this._assert(false, {
913 message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
914 operator: 'doesNotMatch',
915 actual: objectToString(regexp),
916 expected: '[object RegExp]',
917 extra: extra
918 });
919 } else if (typeof string !== 'string') {
920 this._assert(false, {
921 message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
922 operator: 'doesNotMatch',
923 actual: string === null ? null : typeof string,
924 expected: 'string',
925 extra: extra
926 });
927 } else {
928 var matches = $exec(regexp, string) !== null;
929 var message = defined(
930 msg,
931 'The input ' + (matches ? 'was expected to not match' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
932 );
933 this._assert(!matches, {
934 message: message,
935 operator: 'doesNotMatch',
936 actual: string,
937 expected: regexp,
938 extra: extra
939 });
940 }
941};
942
943// eslint-disable-next-line no-unused-vars
944Test.skip = function skip(name_, _opts, _cb) {
945 var args = getTestArgs.apply(null, arguments);
946 args.opts.skip = true;
947 return new Test(args.name, args.opts, args.cb);
948};
949
950module.exports = Test;
951
952// vim: set softtabstop=4 shiftwidth=4: