UNPKG

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