UNPKG

18.2 kBJavaScriptView Raw
1
2
3!function(exports, _setTimeout, _clearTimeout, _Date, _Error, _Infinity) {
4 var started, testSuite, timerType, inSuite
5 , tests = []
6 , describe = exports.describe = curry(def, 1)
7 , _global = describe.global = exports.window || global
8 , _process = _global.process || /* c8 ignore next */ { exit: This }
9 , _isArray = Array.isArray
10 , _keys = Object.keys
11 , call = def.bind.bind(def.call)
12 , slice = call(tests.slice)
13 , push = call(tests.push)
14 , lineRe = /{(\w+)}/g
15 , totalCases = 0
16 , failedCases = []
17 , totalAsserts = 0
18 , passedAsserts = 0
19 , skipped = 0
20 , runPos = 0
21 , splicePos = 0
22 , assert = describe.assert = {
23 notOk: function(value, message) {
24 return this(!value, message, value, "!=", "falsy")
25 },
26 equal: function(actual, expected, message) {
27 return this(
28 arguments.length > 1 && _deepEqual(actual, expected, []),
29 message, actual, "equal", expected
30 )
31 },
32 notEqual: function(actual, expected, message) {
33 return this(
34 arguments.length > 1 && !_deepEqual(actual, expected, []),
35 message, actual, "notEqual", expected
36 )
37 },
38 skip: This,
39 strictEqual: function(actual, expected, message) {
40 return this(
41 arguments.length > 1 && actual === expected,
42 message, actual, "===", expected
43 )
44 },
45 notStrictEqual: function(actual, expected, message) {
46 return this(
47 arguments.length > 1 && actual !== expected,
48 message, actual, "!==", expected
49 )
50 },
51 own: function(actual, expected, message) {
52 own.lastMsg = ""
53 return this(own(actual, expected), message || own.lastMsg)
54 },
55 notOwn: function(actual, expected, message) {
56 own.lastMsg = ""
57 return this(!own(actual, expected), message || own.lastMsg)
58 },
59 throws: function(fn, message) {
60 var actual = false
61 try {
62 fn()
63 } catch(e) {
64 actual = true
65 }
66 return this(actual, message || "throws")
67 },
68 type: function(thing, expected) {
69 var actual = type(thing)
70 return this(actual === expected, 0, actual, "type", expected)
71 },
72 anyOf: function(a, b) {
73 return this(
74 _isArray(b) && b.indexOf(a) > -1,
75 "should be one from " + stringify(b) + ", got " + a
76 )
77 }
78 }
79 , conf = describe.conf = {
80 // process.platform === 'win32' -> √×.
81 file: (_Error().stack + " /cli/test.js:").match(/\S+?:(?=[:\d)]*$)/m)[0],
82 global: "describe,it",
83 head: "",
84 indent: " ",
85 suite: "{indent}{n}", //➜✺✽❖❣❢•※⁕∅
86 ok: "{indent} {green}✔{reset} {i}. {n} [{passed}/{total}]",
87 nok: "{indent} {red}✘{reset} {i}. {n} [{passed}/{total}]",
88 skip: "{indent} {yellow}∅{reset} {i}. {n}",
89 sum: "1..{total}\n#{passGreen} pass {pass}/{total} [{passAsserts}/{totalAsserts}]{timeStr}",
90 failSum: "#{red}{bold} FAIL tests {failNums}",
91 skipSum: "#{yellow}{bold} skip {s}",
92 bold: "\x1b[1m",
93 red: "\x1b[31m",
94 green: "\x1b[32m",
95 yellow: "\x1b[33m",
96 reset: "\x1b[0m",
97 color: (_process.stdout || /* c8 ignore next */ _process).isTTY,
98 cut: 1500,
99 delay: 1,
100 seed: (Math.random() * 1e5)|0,
101 stack: 9,
102 status: 1,
103 time: 1,
104 timeout: 999,
105 total: 0
106 }
107 , toStr = conf.toString
108 , hasOwn = call(conf.hasOwnProperty)
109 , argv = _process.argv && _process.argv.slice(2) || /* c8 ignore next */ []
110 , arg, argi = argv.length
111 /*** mockTime ***/
112 , fakeNow
113 , timers = []
114 , timerId = 0
115 , fakeTimers = {
116 setTimeout: curry(fakeTimeout, false),
117 setInterval: curry(fakeTimeout, true),
118 clearTimeout: fakeClear,
119 clearInterval: fakeClear,
120 setImmediate: fakeNextTick,
121 clearImmediate: fakeClear,
122 Date: fakeDate
123 }
124 function fakeDate(year, month, date, hr, min, sec, ms) {
125 return (
126 arguments.length > 1 ?
127 new _Date(num(year), num(month), num(date, 1), num(hr), num(min), num(sec), num(ms)) :
128 new _Date(num(year, fakeNow))
129 )
130 }
131 fakeDate.now = function() {
132 return fakeNow
133 }
134 fakeDate.parse = _Date.parse
135 fakeDate.UTC = _Date.UTC
136 function fakeHrtime(time) {
137 var diff = _isArray(time) ? fakeNow - (time[0] * 1e3 + time[1] / 1e6) : fakeNow
138 return [Math.floor(diff / 1000), Math.round((diff % 1e3) * 1e3) * 1e3] // [seconds, nanoseconds]
139 }
140 function fakeTimeout(repeat, fn, ms) {
141 if (Date === _Date) {
142 return _setTimeout.apply(this, slice(arguments, 1))
143 }
144 if (!isObj(repeat)) {
145 repeat = {
146 id: ++timerId,
147 repeat: repeat,
148 fn: fn,
149 args: slice(arguments, 3),
150 at: fakeNow + ms,
151 ms: ms
152 }
153 }
154 for (var i = timers.length; i-- && !(timers[i].at <= repeat.at);); // jshint ignore:line
155 timers.splice(i + 1, 0, repeat)
156 return timerType == "number" ? /* c8 ignore next */ repeat.id : {
157 id: repeat.id,
158 unref: This
159 }
160 }
161 function fakeNextTick(fn) {
162 fakeTimeout({
163 id: ++timerId,
164 fn: fn,
165 args: slice(arguments, 1),
166 at: fakeNow - 1
167 })
168 }
169 function fakeClear(id) {
170 if (id) for (var i = timers.length; i--; ) {
171 if (timers[i].id === id || timers[i].id === id.id) {
172 timers.splice(i, 1)
173 break
174 }
175 }
176 }
177 /* mockTime end */
178
179 describe.describe = describe
180 describe.test = curry(def, 2)
181 describe.it = curry(def, 3)
182 describe.should = curry(def, 4)
183 describe.failed = 0
184 describe.output = ""
185
186 describe.format = format
187 describe.print = print
188 describe.stringify = stringify
189
190 for (; argi; ) {
191 arg = argv[--argi].split(/=|--(no-)?/)
192 if (arg[0] === "") {
193 conf[arg[2]] = arg[4] || !arg[1]
194 argv.splice(argi, 1)
195 }
196 }
197
198 each(conf.global, function(_i, value) {
199 _global[value] = describe[value]
200 })
201
202 function def(t, name, data, fn) {
203 if (t < 1) t = isFn(data) + 1
204 if (!started) {
205 started = new _Date()
206
207 if (!conf.color) {
208 conf.bold = conf.red = conf.green = conf.yellow = conf.reset = ""
209 }
210
211 if (conf.tap) {
212 conf.head = "TAP version 13"
213 conf.suite = "# {n}"
214 conf.ok = conf.skip = "ok {i} - {n} [{passed}/{total}]"
215 conf.nok = "not " + conf.ok
216 conf.indent = ""
217 } else if (conf.brief) {
218 conf.suite = conf.ok = conf.indent = ""
219 conf.skip = "{yellow}skip {i} - {n}"
220 conf.sum = conf.sum.slice(11)
221 }
222
223 if (t !== 1) def(1, "Tests")
224 line("head")
225 timerType = type(_setTimeout(nextCase, conf.delay|0))
226 }
227 if (!isStr(name)) {
228 fn = data
229 data = name
230 name = "Unnamed Test" + (t > 1 ? "Case" : "Suite")
231 }
232 if (!isFn(fn)) {
233 fn = data
234 }
235 var spliceData = [++splicePos, 0, {
236 p: inSuite,
237 indent: inSuite ? inSuite.indent + (t > 1 ? "" : conf.indent) : "",
238 s: t > 1 && !isFn(fn) ? "pending" : name.charAt(0) === "_" ? "by name" : 0,
239 t: t,
240 n: name,
241 f: fn
242 }]
243 if (data !== fn) {
244 each(data, curry(function(item, i, row) {
245 i = spliceData[i - 0 + 2] = Object.create(item)
246 i.f = curry(i.f, i.r = _isArray(row) ? row : (row = [row]))
247 i.n = format(i.n, row, conf)
248 if (i.r.length !== spliceData[2].r.length) throw "Different data size: " + i.n
249 }, spliceData[2]))
250 splicePos += data.length - 1
251 }
252 tests.splice.apply(tests, spliceData)
253 return describe
254 }
255
256 function nextCase() {
257 var tick
258 , args = tests[splicePos = runPos++]
259 if (!args) printResult()
260 else if (args.t === 1) nextSuite(args)
261 else {
262 testCase.i = ++totalCases
263 if (args.p && args.p !== testSuite) testSuite = args.p
264 testCase.indent = testSuite.indent
265 testCase.n = (args.t < 3 ? "" : "it " + (args.t < 4 ? "" : "should ")) + args.n
266 testCase.errors = []
267 testCase.total = testCase.passed = 0
268 if (args.s || testSuite.s || argv.length && argv.indexOf("" + totalCases) < 0) {
269 skipped++
270 if (!argv.length) line("skip", testCase)
271 return nextCase()
272 }
273 Object.assign(testCase, assert)
274 testCase.end = end
275 testCase.ok = testCase
276 testCase.plan = function(planned) {
277 testCase.planned = planned
278 if (planned <= testCase.total) end()
279 return testCase
280 }
281 testCase.setTimeout = function(ms) {
282 _clearTimeout(tick)
283 tick = _setTimeout(end, ms, "TIMEOUT: " + ms + "ms")
284 return testCase
285 }
286
287 try {
288 testCase.setTimeout(conf.timeout)
289 args = args.f.call(testCase, testCase, (testCase.mock = args.f.length > 1 && new Mock()))
290 if (args && args.then) args.then(curry(end, null), end)
291 } catch (e) {
292 print("" + e)
293 end(e)
294 }
295 }
296 function testCase(value, message, actual, op, expected) {
297 testCase.total++
298 if (testCase.ended) {
299 fail("assertion after end")
300 }
301 if (value) {
302 testCase.passed++
303 } else {
304 fail("Assertion:" + testCase.total + ": " + (message || (
305 op ? op +
306 "\nexpected: " + stringify(expected) +
307 "\nactual: " + stringify(actual)
308 : stringify(value) + " is truthy"
309 )))
310 }
311 return testCase.plan(testCase.planned)
312 }
313 function fail(_err) {
314 var row, start, i = 0
315 , err = type(_err) != "error" ? _Error(_err) : _err
316 , stack = err.stack
317 if (stack) {
318 // iotjs returns stack as Array
319 for (stack = _isArray(stack) ? stack : (stack + "").replace(err, "").split("\n"); (row = stack[++i]); ) {
320 if (row.indexOf(conf.file) < 0) {
321 if (!start) start = i
322 }
323 if (i - start >= conf.stack) break
324 }
325 err = [ err ].concat(stack.slice(start, i)).join("\n")
326 }
327
328 if (push(testCase.errors, err) == 1) {
329 push(failedCases, testCase)
330 }
331 if (describe.result) printResult()
332 return testCase
333 }
334 function end(err) {
335 _clearTimeout(tick)
336 if (err) fail(err)
337 if (testCase.ended) return fail("ended multiple times")
338 testCase.ended = _Date.now()
339
340 if (testCase.planned != void 0 && testCase.planned !== testCase.total) {
341 fail("planned " + testCase.planned + " actual " + testCase.total)
342 }
343 if (testCase.mock) {
344 testCase.n += testCase.mock.txt
345 testCase.mock.restore()
346 }
347
348 totalAsserts += testCase.total
349 passedAsserts += testCase.passed
350
351 line(testCase.errors.length ? "nok" : "ok", testCase)
352 if (runPos % 1000) nextCase()
353 else _setTimeout(nextCase, 1)
354 }
355 }
356 function nextSuite(newSuite) {
357 if (!argv.length) line("suite", newSuite)
358 newSuite.p = inSuite
359 inSuite = testSuite = newSuite
360 if (isFn(testSuite.f)) {
361 testSuite.f.call(describe)
362 } else if (isObj(testSuite.f)) {
363 each(testSuite.f, curry(def, 0))
364 }
365 inSuite = newSuite.p
366 nextCase()
367 }
368 function printResult() {
369 testSuite = null
370 conf.total = totalCases
371 var testCase
372 , nums = []
373 , failed = failedCases.length
374 conf.fail = describe.failed += failed
375 conf.pass = totalCases - conf.fail
376 conf.s = skipped
377 conf.passAsserts = passedAsserts
378 conf.totalAsserts = totalAsserts
379 conf.passGreen = conf.fail ? "" : conf.green + conf.bold
380 conf.failRed = conf.fail ? conf.red : ""
381 conf.timeStr = conf.time ? " in " + (_Date.now() - started) + " ms at " + started.toTimeString().slice(0, 8) : ""
382 if (conf.status) _process.exitCode = conf.fail
383 if (failed) {
384 for (; (testCase = failedCases[--failed]); ) {
385 nums[failed] = testCase.i
386 print("---")
387 line("nok", testCase)
388 print(testCase.errors.join("\n"))
389 }
390 conf.failNums = nums.join(", ")
391 print("...")
392 line("failSum", conf)
393 failedCases.length = 0
394 }
395 describe.result = line("sum", conf)
396 if (skipped) {
397 line("skipSum", conf)
398 }
399 if (describe.onend) describe.onend()
400 }
401
402 function This() {
403 return this
404 }
405 function format(str, map, fallback) {
406 return str.replace(lineRe, function(_, field) {
407 return map[field] != null ? map[field] : fallback[field]
408 })
409 }
410 function line(name, map) {
411 return print(format(conf[name], map, conf))
412 }
413 function print(str) {
414 if (!str) return
415 if (testSuite && testSuite.indent) {
416 str = str.split("\n").join("\n" + testSuite.indent)
417 }
418 describe.output += str + "\n"
419 if (describe.onprint) describe.onprint(str)
420 if (_global.console && console.log) console.log(str + conf.reset)
421 return str
422 }
423
424 // A spy is a wrapper function to verify an invocation
425 // A stub is a spy with replaced behavior
426 function Mock() {
427 this.txt = ""
428 this._r = []
429 }
430 Mock.prototype = describe.mock = {
431 fn: function(origin) {
432 spy.called = 0
433 spy.calls = []
434 spy.errors = 0
435 spy.results = []
436 return spy
437 function spy() {
438 var err, key, result
439 , args = slice(arguments)
440 if (isFn(origin)) {
441 try {
442 result = origin.apply(this, arguments)
443 } catch(e) {
444 spy.errors++
445 err = e
446 }
447 } else if (_isArray(origin)) {
448 result = origin[spy.called % origin.length]
449 } else if (isObj(origin)) {
450 key = JSON.stringify(args).slice(1, -1)
451 result = hasOwn(origin, key) ? origin[key] : origin["*"]
452 } else result = origin
453 spy.called++
454 push(spy.results, result)
455 push(spy.calls, {
456 scope: this,
457 args: args,
458 error: err,
459 result: result
460 })
461 return result
462 }
463 },
464 rand: function(seed_) {
465 var seed = seed_ || conf.seed
466 this.txt += " #seed:" + seed
467 this.swap(Math, "random", xorshift128(seed))
468 },
469 spy: function(obj, name, stub) {
470 this.swap(obj, name, this.fn(stub || obj[name]))
471 },
472 swap: function swap(obj, name, fn) {
473 if (isObj(name)) {
474 each(name, curry(swap, obj, this))
475 return
476 }
477 var existing = obj[name]
478 push(this._r, obj, name, hasOwn(obj, name) && existing)
479 obj[name] = fn
480 if (fn === fn && obj[name] !== fn) throw _Error("Unable to swap " + stringify(name))
481 return existing
482 },
483 restore: function() {
484 for (var arr = this._r, i = arr.length; --i > 0; i -= 2) {
485 if (arr[i]) {
486 arr[i - 2][arr[i - 1]] = arr[i]
487 } else {
488 delete arr[i - 2][arr[i - 1]]
489 }
490 }
491 /*** mockTime ***/
492 this.tick(_Infinity, true)
493 },
494 time: function(newTime, newZone) {
495 var mock = this
496 if (!mock._time) {
497 mock._time = fakeNow = _Date.now()
498 mock.swap(_global, fakeTimers)
499 mock.swap(_process, { nextTick: fakeNextTick, hrtime: fakeHrtime })
500 }
501 if (newTime) {
502 fakeNow = isStr(newTime) ? _Date.parse(newTime) : newTime
503 mock.tick(0)
504 }
505 fakeDate._z = newZone
506 },
507 tick: function(amount, noRepeat) {
508 var t
509 , nextNow = type(amount) === "number" ? fakeNow + amount : timers[0] ? timers[0].at : fakeNow
510
511 for (; (t = timers[0]) && t.at <= nextNow; ) {
512 fakeNow = t.at
513 timers.shift()
514 if (isStr(t.fn)) t.fn = Function(t.fn)
515 if (isFn(t.fn)) t.fn.apply(null, t.args)
516 if (!noRepeat && t.repeat) {
517 t.at += t.ms
518 fakeTimeout(t)
519 }
520 }
521 fakeNow = nextNow
522 /* mockTime end */
523 }
524 }
525
526 function xorshift128(a) {
527 var b = a * 2e3, c = a * 3e4, d = a * 4e5
528 return function() {
529 var t = d ^ (d << 11)
530 d = c; c = b; b = a
531 a ^= t ^ (t >>> 8) ^ (a >>> 19)
532 return (a >>> 0) / 4294967295
533 }
534 }
535
536 function _deepEqual(actual, expected, circ) {
537 if (
538 actual === expected ||
539 // null == undefined
540 expected === null && actual == expected ||
541 // make NaN equal to NaN
542 actual !== actual && expected !== expected
543 ) return true
544
545 var key, aKeys, len
546 , aType = typeof actual
547
548 if (
549 aType !== "object" ||
550 actual == null || // jshint ignore:line
551 aType !== typeof expected ||
552 (aType = type(actual)) != type(expected) ||
553 (actual.constructor && actual.constructor !== expected.constructor) ||
554 (aType == "date" && actual.getTime() !== expected.getTime()) ||
555 (aType == "regexp" && "" + actual !== "" + expected)
556 ) {
557 return false
558 }
559
560 key = circ.indexOf(actual)
561 if (key > -1) return true
562 push(circ, actual)
563
564 if (aType == "array" || aType == "arguments") {
565 len = actual.length
566 if (len !== expected.length) return false
567 for (; len--; ) {
568 if (!_deepEqual(actual[len], expected[len], circ)) return false
569 }
570 } else {
571 aKeys = _keys(actual)
572 len = aKeys.length
573 if (len !== _keys(expected).length) return false
574 for (; len--; ) {
575 key = aKeys[len]
576 if (
577 !hasOwn(expected, key) ||
578 !_deepEqual(actual[key], expected[key], circ)
579 ) return false
580 }
581 }
582 return true
583 }
584
585 function type(obj) {
586 /* jshint -W041 */
587 // Standard clearly states that NaN and Infinity are numbers
588 // but this is not useful for testing.
589 return (
590 obj !== obj ? "nan" :
591 obj === _Infinity || obj === -_Infinity ? "infinity" :
592 obj == null ? "" + obj :
593 toStr.call(obj).slice(8, -1).toLowerCase()
594 )
595 }
596 function num(a, b) {
597 return type(a -= 0) === "number" ? a : b
598 }
599 function isStr(str) {
600 return typeof str === "string"
601 }
602 function isFn(fn) {
603 return typeof fn === "function"
604 }
605 function isObj(obj) {
606 return type(obj) === "object"
607 }
608 function own(a, b) {
609 if (a === b) {
610 own.lastMsg = "Can not be strictEqual"
611 } else if (a) {
612 for (var k in b) if (hasOwn(b, k)) {
613 if (!hasOwn(a, k) || (
614 isObj(b[k]) ? !own(a[k], b[k]) :
615 _isArray(b[k]) ? b[k].some(itemNotOwn, a[k]) :
616 a[k] !== b[k]
617 )) {
618 own.lastMsg = own.lastMsg || k + " " + stringify(a[k]) + " != " + stringify(b[k])
619 return false
620 }
621 }
622 return true
623 }
624 function itemNotOwn(val, idx) {
625 return isObj(val) ? !own(this[idx], val) : this[idx] !== val
626 }
627 }
628 function curry(fn, arg, scope) {
629 return fn.bind.apply(fn, [scope].concat(arg))
630 }
631
632 function each(arr, fn) {
633 if (arr) {
634 if (isStr(arr)) arr = arr.split(",")
635 for (var i in arr) if (hasOwn(arr, i)) fn(i, arr[i])
636 }
637 }
638
639 function stringify(item) {
640 var max = conf.cut > 0 ? conf.cut : _Infinity
641 , str = _stringify(item, max, [])
642 return str.length > max ? str.slice(0, max - 3) + ".." + str.slice(-1) : str
643 }
644
645 function _stringify(item, max, circ) {
646 var i, t, tmp
647 , left = max
648 , str =
649 isStr(item) ? JSON.stringify(item) :
650 isFn(item) ? ("" + item).replace(/^\w+|\s+|{[\s\S]*/g, "") :
651 !item || item === true || typeof item === "number" ? "" + item :
652 (t = type(item)) === "error" || t === "symbol" || t === "regexp" ? item.toString() :
653 item.toJSON ? item.toJSON() :
654 item
655
656 if (!isStr(str)) {
657 if (circ.indexOf(str) > -1) return "Circular"
658 push(circ, str)
659 tmp = []
660 for (i in str) if (hasOwn(str, i)) {
661 i = (t === "object" ? _stringify(i, left) + ":" : "") + _stringify(str[i], left, circ)
662 push(tmp, i)
663 left -= i.length
664 if (left < 0) break
665 }
666 str =
667 t === "array" ? "[" + tmp + "]" :
668 t === "arguments" ? t + "[" + tmp + "]" :
669 (t = item.constructor) === Object ? "{" + tmp + "}" :
670 (t ? t.name || /^\w+\s+([^\s(]+)|/.exec(t)[1] || "<anon>" : "<null>") + "{" + tmp + "}"
671 }
672 return str
673 }
674}(this, setTimeout, clearTimeout, Date, Error, Infinity) // jshint ignore:line
675