1 | 'use strict';
|
2 |
|
3 | var deepEqual = require('deep-equal');
|
4 | var defined = require('defined');
|
5 | var path = require('path');
|
6 | var inherits = require('inherits');
|
7 | var EventEmitter = require('events').EventEmitter;
|
8 | var has = require('has');
|
9 | var isRegExp = require('is-regex');
|
10 | var trim = require('string.prototype.trim');
|
11 | var callBind = require('call-bind');
|
12 | var callBound = require('call-bind/callBound');
|
13 | var forEach = require('for-each');
|
14 | var inspect = require('object-inspect');
|
15 | var is = require('object-is');
|
16 | var objectKeys = require('object-keys');
|
17 | var every = require('array.prototype.every');
|
18 | var mockProperty = require('mock-property');
|
19 |
|
20 | var isEnumerable = callBound('Object.prototype.propertyIsEnumerable');
|
21 | var toLowerCase = callBound('String.prototype.toLowerCase');
|
22 | var isProto = callBound('Object.prototype.isPrototypeOf');
|
23 | var $exec = callBound('RegExp.prototype.exec');
|
24 | var objectToString = callBound('Object.prototype.toString');
|
25 | var $split = callBound('String.prototype.split');
|
26 | var $replace = callBound('String.prototype.replace');
|
27 | var $strSlice = callBound('String.prototype.slice');
|
28 | var $push = callBound('Array.prototype.push');
|
29 | var $shift = callBound('Array.prototype.shift');
|
30 | var $slice = callBound('Array.prototype.slice');
|
31 |
|
32 | var nextTick = typeof setImmediate !== 'undefined'
|
33 | ? setImmediate
|
34 | : process.nextTick;
|
35 | var safeSetTimeout = setTimeout;
|
36 | var safeClearTimeout = clearTimeout;
|
37 |
|
38 |
|
39 | function 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 |
|
61 | function 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 |
|
101 | inherits(Test, EventEmitter);
|
102 |
|
103 | Test.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 |
|
142 | Test.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 |
|
165 | Test.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 |
|
172 | Test.prototype.plan = function plan(n) {
|
173 | this._plan = n;
|
174 | this.emit('plan', n);
|
175 | };
|
176 |
|
177 | Test.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 |
|
189 | Test.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 |
|
201 | Test.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 |
|
209 | function 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 |
|
247 | Test.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 |
|
268 | Test.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 |
|
278 | Test.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 |
|
375 | Test.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 |
|
417 | err = err || _err;
|
418 | });
|
419 | } else {
|
420 | next();
|
421 | }
|
422 | }
|
423 |
|
424 | next();
|
425 | };
|
426 |
|
427 | Test.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 |
|
442 | Test.prototype._pendingAsserts = function _pendingAsserts() {
|
443 | if (this._plan === undefined) {
|
444 | return 1;
|
445 | }
|
446 | return this._plan - (this._progeny.length + this.assertCount);
|
447 | };
|
448 |
|
449 | Test.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 |
|
490 |
|
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 |
|
501 |
|
502 |
|
503 |
|
504 |
|
505 |
|
506 |
|
507 |
|
508 |
|
509 |
|
510 |
|
511 |
|
512 |
|
513 |
|
514 |
|
515 |
|
516 |
|
517 |
|
518 |
|
519 |
|
520 |
|
521 |
|
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 |
|
539 |
|
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 |
|
572 | Test.prototype.fail = function fail(msg, extra) {
|
573 | this._assert(false, {
|
574 | message: msg,
|
575 | operator: 'fail',
|
576 | extra: extra
|
577 | });
|
578 | };
|
579 |
|
580 | Test.prototype.pass = function pass(msg, extra) {
|
581 | this._assert(true, {
|
582 | message: msg,
|
583 | operator: 'pass',
|
584 | extra: extra
|
585 | });
|
586 | };
|
587 |
|
588 | Test.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 |
|
597 | var testAssert = function assert(value, msg, extra) {
|
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 | };
|
606 | Test.prototype.ok
|
607 | = Test.prototype['true']
|
608 | = Test.prototype.assert
|
609 | = testAssert;
|
610 |
|
611 | function 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 | }
|
620 | Test.prototype.notOk
|
621 | = Test.prototype['false']
|
622 | = Test.prototype.notok
|
623 | = notOK;
|
624 |
|
625 | function 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 | }
|
633 | Test.prototype.error
|
634 | = Test.prototype.ifError
|
635 | = Test.prototype.ifErr
|
636 | = Test.prototype.iferror
|
637 | = error;
|
638 |
|
639 | function 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 | }
|
651 | Test.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 |
|
659 | function 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 |
|
672 | Test.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 |
|
683 | function 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 |
|
696 | Test.prototype.looseEqual
|
697 | = Test.prototype.looseEquals
|
698 | = looseEqual;
|
699 |
|
700 | function 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 | }
|
712 | Test.prototype.notLooseEqual
|
713 | = Test.prototype.notLooseEquals
|
714 | = notLooseEqual;
|
715 |
|
716 | function 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 | }
|
728 | Test.prototype.deepEqual
|
729 | = Test.prototype.deepEquals
|
730 | = Test.prototype.isEquivalent
|
731 | = Test.prototype.same
|
732 | = tapeDeepEqual;
|
733 |
|
734 | function 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 | }
|
746 | Test.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 |
|
757 | function 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 |
|
770 | Test.prototype.deepLooseEqual
|
771 | = deepLooseEqual;
|
772 |
|
773 | function 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 | }
|
785 | Test.prototype.notDeepLooseEqual
|
786 | = notDeepLooseEqual;
|
787 |
|
788 | Test.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') {
|
827 | var keys = objectKeys(expected);
|
828 |
|
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 |
|
856 | Test.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 |
|
877 | Test.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 |
|
910 | Test.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 |
|
944 | Test.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 |
|
950 | module.exports = Test;
|
951 |
|
952 |
|