UNPKG

12.9 kBJavaScriptView Raw
1var deepEqual = require('deep-equal');
2var defined = require('defined');
3var path = require('path');
4var inherits = require('inherits');
5var EventEmitter = require('events').EventEmitter;
6var has = require('has');
7var trim = require('string.prototype.trim');
8var bind = require('function-bind');
9var isEnumerable = bind.call(Function.call, Object.prototype.propertyIsEnumerable);
10
11module.exports = Test;
12
13var nextTick = typeof setImmediate !== 'undefined'
14 ? setImmediate
15 : process.nextTick
16;
17var safeSetTimeout = setTimeout;
18
19inherits(Test, EventEmitter);
20
21var getTestArgs = function (name_, opts_, cb_) {
22 var name = '(anonymous)';
23 var opts = {};
24 var cb;
25
26 for (var i = 0; i < arguments.length; i++) {
27 var arg = arguments[i];
28 var t = typeof arg;
29 if (t === 'string') {
30 name = arg;
31 }
32 else if (t === 'object') {
33 opts = arg || opts;
34 }
35 else if (t === 'function') {
36 cb = arg;
37 }
38 }
39 return { name: name, opts: opts, cb: cb };
40};
41
42function Test (name_, opts_, cb_) {
43 if (! (this instanceof Test)) {
44 return new Test(name_, opts_, cb_);
45 }
46
47 var args = getTestArgs(name_, opts_, cb_);
48
49 this.readable = true;
50 this.name = args.name || '(anonymous)';
51 this.assertCount = 0;
52 this.pendingCount = 0;
53 this._skip = args.opts.skip || false;
54 this._timeout = args.opts.timeout;
55 this._objectPrintDepth = args.opts.objectPrintDepth || 5;
56 this._plan = undefined;
57 this._cb = args.cb;
58 this._progeny = [];
59 this._ok = true;
60
61 for (var prop in this) {
62 this[prop] = (function bind(self, val) {
63 if (typeof val === 'function') {
64 return function bound() {
65 return val.apply(self, arguments);
66 };
67 }
68 else return val;
69 })(this, this[prop]);
70 }
71}
72
73Test.prototype.run = function () {
74 if (this._skip) {
75 this.comment('SKIP ' + this.name);
76 }
77 if (!this._cb || this._skip) {
78 return this._end();
79 }
80 if (this._timeout != null) {
81 this.timeoutAfter(this._timeout);
82 }
83 this.emit('prerun');
84 this._cb(this);
85 this.emit('run');
86};
87
88Test.prototype.test = function (name, opts, cb) {
89 var self = this;
90 var t = new Test(name, opts, cb);
91 this._progeny.push(t);
92 this.pendingCount++;
93 this.emit('test', t);
94 t.on('prerun', function () {
95 self.assertCount++;
96 })
97
98 if (!self._pendingAsserts()) {
99 nextTick(function () {
100 self._end();
101 });
102 }
103
104 nextTick(function() {
105 if (!self._plan && self.pendingCount == self._progeny.length) {
106 self._end();
107 }
108 });
109};
110
111Test.prototype.comment = function (msg) {
112 var that = this;
113 trim(msg).split('\n').forEach(function (aMsg) {
114 that.emit('result', trim(aMsg).replace(/^#\s*/, ''));
115 });
116};
117
118Test.prototype.plan = function (n) {
119 this._plan = n;
120 this.emit('plan', n);
121};
122
123Test.prototype.timeoutAfter = function(ms) {
124 if (!ms) throw new Error('timeoutAfter requires a timespan');
125 var self = this;
126 var timeout = safeSetTimeout(function() {
127 self.fail('test timed out after ' + ms + 'ms');
128 self.end();
129 }, ms);
130 this.once('end', function() {
131 clearTimeout(timeout);
132 });
133}
134
135Test.prototype.end = function (err) {
136 var self = this;
137 if (arguments.length >= 1 && !!err) {
138 this.ifError(err);
139 }
140
141 if (this.calledEnd) {
142 this.fail('.end() called twice');
143 }
144 this.calledEnd = true;
145 this._end();
146};
147
148Test.prototype._end = function (err) {
149 var self = this;
150 if (this._progeny.length) {
151 var t = this._progeny.shift();
152 t.on('end', function () { self._end() });
153 t.run();
154 return;
155 }
156
157 if (!this.ended) this.emit('end');
158 var pendingAsserts = this._pendingAsserts();
159 if (!this._planError && this._plan !== undefined && pendingAsserts) {
160 this._planError = true;
161 this.fail('plan != count', {
162 expected : this._plan,
163 actual : this.assertCount
164 });
165 }
166 this.ended = true;
167};
168
169Test.prototype._exit = function () {
170 if (this._plan !== undefined &&
171 !this._planError && this.assertCount !== this._plan) {
172 this._planError = true;
173 this.fail('plan != count', {
174 expected : this._plan,
175 actual : this.assertCount,
176 exiting : true
177 });
178 }
179 else if (!this.ended) {
180 this.fail('test exited without ending', {
181 exiting: true
182 });
183 }
184};
185
186Test.prototype._pendingAsserts = function () {
187 if (this._plan === undefined) {
188 return 1;
189 }
190 else {
191 return this._plan - (this._progeny.length + this.assertCount);
192 }
193};
194
195Test.prototype._assert = function assert (ok, opts) {
196 var self = this;
197 var extra = opts.extra || {};
198
199 var res = {
200 id : self.assertCount ++,
201 ok : Boolean(ok),
202 skip : defined(extra.skip, opts.skip),
203 name : defined(extra.message, opts.message, '(unnamed assert)'),
204 operator : defined(extra.operator, opts.operator),
205 objectPrintDepth : self._objectPrintDepth
206 };
207 if (has(opts, 'actual') || has(extra, 'actual')) {
208 res.actual = defined(extra.actual, opts.actual);
209 }
210 if (has(opts, 'expected') || has(extra, 'expected')) {
211 res.expected = defined(extra.expected, opts.expected);
212 }
213 this._ok = Boolean(this._ok && ok);
214
215 if (!ok) {
216 res.error = defined(extra.error, opts.error, new Error(res.name));
217 }
218
219 if (!ok) {
220 var e = new Error('exception');
221 var err = (e.stack || '').split('\n');
222 var dir = path.dirname(__dirname) + path.sep;
223
224 for (var i = 0; i < err.length; i++) {
225 var m = /^[^\s]*\s*\bat\s+(.+)/.exec(err[i]);
226 if (!m) {
227 continue;
228 }
229
230 var s = m[1].split(/\s+/);
231 var filem = /((?:\/|[A-Z]:\\)[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[1]);
232 if (!filem) {
233 filem = /((?:\/|[A-Z]:\\)[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[2]);
234
235 if (!filem) {
236 filem = /((?:\/|[A-Z]:\\)[^:\s]+:(\d+)(?::(\d+))?)/.exec(s[3]);
237
238 if (!filem) {
239 continue;
240 }
241 }
242 }
243
244 if (filem[1].slice(0, dir.length) === dir) {
245 continue;
246 }
247
248 res.functionName = s[0];
249 res.file = filem[1];
250 res.line = Number(filem[2]);
251 if (filem[3]) res.column = filem[3];
252
253 res.at = m[1];
254 break;
255 }
256 }
257
258 self.emit('result', res);
259
260 var pendingAsserts = self._pendingAsserts();
261 if (!pendingAsserts) {
262 if (extra.exiting) {
263 self._end();
264 } else {
265 nextTick(function () {
266 self._end();
267 });
268 }
269 }
270
271 if (!self._planError && pendingAsserts < 0) {
272 self._planError = true;
273 self.fail('plan != count', {
274 expected : self._plan,
275 actual : self._plan - pendingAsserts
276 });
277 }
278};
279
280Test.prototype.fail = function (msg, extra) {
281 this._assert(false, {
282 message : msg,
283 operator : 'fail',
284 extra : extra
285 });
286};
287
288Test.prototype.pass = function (msg, extra) {
289 this._assert(true, {
290 message : msg,
291 operator : 'pass',
292 extra : extra
293 });
294};
295
296Test.prototype.skip = function (msg, extra) {
297 this._assert(true, {
298 message : msg,
299 operator : 'skip',
300 skip : true,
301 extra : extra
302 });
303};
304
305Test.prototype.ok
306= Test.prototype['true']
307= Test.prototype.assert
308= function (value, msg, extra) {
309 this._assert(value, {
310 message : defined(msg, 'should be truthy'),
311 operator : 'ok',
312 expected : true,
313 actual : value,
314 extra : extra
315 });
316};
317
318Test.prototype.notOk
319= Test.prototype['false']
320= Test.prototype.notok
321= function (value, msg, extra) {
322 this._assert(!value, {
323 message : defined(msg, 'should be falsy'),
324 operator : 'notOk',
325 expected : false,
326 actual : value,
327 extra : extra
328 });
329};
330
331Test.prototype.error
332= Test.prototype.ifError
333= Test.prototype.ifErr
334= Test.prototype.iferror
335= function (err, msg, extra) {
336 this._assert(!err, {
337 message : defined(msg, String(err)),
338 operator : 'error',
339 actual : err,
340 extra : extra
341 });
342};
343
344Test.prototype.equal
345= Test.prototype.equals
346= Test.prototype.isEqual
347= Test.prototype.is
348= Test.prototype.strictEqual
349= Test.prototype.strictEquals
350= function (a, b, msg, extra) {
351 this._assert(a === b, {
352 message : defined(msg, 'should be equal'),
353 operator : 'equal',
354 actual : a,
355 expected : b,
356 extra : extra
357 });
358};
359
360Test.prototype.notEqual
361= Test.prototype.notEquals
362= Test.prototype.notStrictEqual
363= Test.prototype.notStrictEquals
364= Test.prototype.isNotEqual
365= Test.prototype.isNot
366= Test.prototype.not
367= Test.prototype.doesNotEqual
368= Test.prototype.isInequal
369= function (a, b, msg, extra) {
370 this._assert(a !== b, {
371 message : defined(msg, 'should not be equal'),
372 operator : 'notEqual',
373 actual : a,
374 notExpected : b,
375 extra : extra
376 });
377};
378
379Test.prototype.deepEqual
380= Test.prototype.deepEquals
381= Test.prototype.isEquivalent
382= Test.prototype.same
383= function (a, b, msg, extra) {
384 this._assert(deepEqual(a, b, { strict: true }), {
385 message : defined(msg, 'should be equivalent'),
386 operator : 'deepEqual',
387 actual : a,
388 expected : b,
389 extra : extra
390 });
391};
392
393Test.prototype.deepLooseEqual
394= Test.prototype.looseEqual
395= Test.prototype.looseEquals
396= function (a, b, msg, extra) {
397 this._assert(deepEqual(a, b), {
398 message : defined(msg, 'should be equivalent'),
399 operator : 'deepLooseEqual',
400 actual : a,
401 expected : b,
402 extra : extra
403 });
404};
405
406Test.prototype.notDeepEqual
407= Test.prototype.notEquivalent
408= Test.prototype.notDeeply
409= Test.prototype.notSame
410= Test.prototype.isNotDeepEqual
411= Test.prototype.isNotDeeply
412= Test.prototype.isNotEquivalent
413= Test.prototype.isInequivalent
414= function (a, b, msg, extra) {
415 this._assert(!deepEqual(a, b, { strict: true }), {
416 message : defined(msg, 'should not be equivalent'),
417 operator : 'notDeepEqual',
418 actual : a,
419 notExpected : b,
420 extra : extra
421 });
422};
423
424Test.prototype.notDeepLooseEqual
425= Test.prototype.notLooseEqual
426= Test.prototype.notLooseEquals
427= function (a, b, msg, extra) {
428 this._assert(!deepEqual(a, b), {
429 message : defined(msg, 'should be equivalent'),
430 operator : 'notDeepLooseEqual',
431 actual : a,
432 expected : b,
433 extra : extra
434 });
435};
436
437Test.prototype['throws'] = function (fn, expected, msg, extra) {
438 if (typeof expected === 'string') {
439 msg = expected;
440 expected = undefined;
441 }
442
443 var caught = undefined;
444
445 try {
446 fn();
447 } catch (err) {
448 caught = { error : err };
449 if ((err != null) && (!isEnumerable(err, 'message') || !has(err, 'message'))) {
450 var message = err.message;
451 delete err.message;
452 err.message = message;
453 }
454 }
455
456 var passed = caught;
457
458 if (expected instanceof RegExp) {
459 passed = expected.test(caught && caught.error);
460 expected = String(expected);
461 }
462
463 if (typeof expected === 'function' && caught) {
464 passed = caught.error instanceof expected;
465 caught.error = caught.error.constructor;
466 }
467
468 this._assert(typeof fn === 'function' && passed, {
469 message : defined(msg, 'should throw'),
470 operator : 'throws',
471 actual : caught && caught.error,
472 expected : expected,
473 error: !passed && caught && caught.error,
474 extra : extra
475 });
476};
477
478Test.prototype.doesNotThrow = function (fn, expected, msg, extra) {
479 if (typeof expected === 'string') {
480 msg = expected;
481 expected = undefined;
482 }
483 var caught = undefined;
484 try {
485 fn();
486 }
487 catch (err) {
488 caught = { error : err };
489 }
490 this._assert(!caught, {
491 message : defined(msg, 'should not throw'),
492 operator : 'throws',
493 actual : caught && caught.error,
494 expected : expected,
495 error : caught && caught.error,
496 extra : extra
497 });
498};
499
500Test.skip = function (name_, _opts, _cb) {
501 var args = getTestArgs.apply(null, arguments);
502 args.opts.skip = true;
503 return Test(args.name, args.opts, args.cb);
504};
505
506// vim: set softtabstop=4 shiftwidth=4:
507