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 i = spliceData[i - 0 + 2] = Object.create(item)
238 i.f = curry(i.f, i.r = _isArray(row) ? row : isObj(row) ? Object.entries(row) : (row = [row]))
239 i.n = format(i.n, row, conf)
240 if (item.f.length > i.r.length + 2 || i.r.length !== spliceData[2].r.length) throw "Invalid data for: " + i.n
241 }, spliceData[2]))
242 splicePos += data.length - 1
243 }
244 tests.splice.apply(tests, spliceData)
245 return describe
246 }
247
248 function nextCase() {
249 var tick
250 , args = tests[splicePos = runPos++]
251 if (!args) printResult()
252 else if (args.t === 1) nextSuite(args)
253 else {
254 testCase.i = ++totalCases
255 if (args.p && args.p !== testSuite) testSuite = args.p
256 testCase.indent = testSuite.indent
257 testCase.n = (args.t < 3 ? "" : "it " + (args.t < 4 ? "" : "should ")) + args.n
258 testCase.errors = []
259 testCase.total = testCase.passed = 0
260 if (args.s || testSuite.s || argv.length && argv.indexOf("" + totalCases) < 0) {
261 skipped++
262 if (!argv.length) line("skip", testCase)
263 return nextCase()
264 }
265 Object.assign(testCase, assert)
266 testCase.end = end
267 testCase.ok = testCase
268 testCase.plan = function(planned) {
269 testCase.planned = planned
270 if (planned <= testCase.total) end()
271 return testCase
272 }
273 testCase.setTimeout = function(ms) {
274 _clearTimeout(tick)
275 tick = _setTimeout(end, ms, "TIMEOUT: " + ms + "ms")
276 return testCase
277 }
278
279 try {
280 testCase.setTimeout(conf.timeout)
281 args = args.f.call(testCase, testCase, (testCase.mock = args.f.length > 1 && new Mock()))
282 if (args && args.then) args.then(curry(end, null), end)
283 } catch (e) {
284 print("" + e)
285 end(e)
286 }
287 }
288 function testCase(value, message, actual, op, expected) {
289 testCase.total++
290 if (testCase.ended) {
291 fail("assertion after end")
292 }
293 if (value) {
294 testCase.passed++
295 } else {
296 fail("Assertion:" + testCase.total + ": " + (message || (
297 op ? op +
298 "\nexpected: " + stringify(expected) +
299 "\nactual: " + stringify(actual)
300 : stringify(value) + " is truthy"
301 )))
302 }
303 return testCase.plan(testCase.planned)
304 }
305 function fail(_err) {
306 var row, start, i = 0
307 , err = type(_err) != "error" ? _Error(_err) : _err
308 , stack = err.stack
309 if (stack) {
310 // iotjs returns stack as Array
311 for (stack = _isArray(stack) ? stack : (stack + "").replace(err, "").split("\n"); (row = stack[++i]); ) {
312 if (row.indexOf(conf.file) < 0) {
313 if (!start) start = i
314 }
315 if (i - start >= conf.stack) break
316 }
317 err = [ err ].concat(stack.slice(start, i)).join("\n")
318 }
319
320 if (push(testCase.errors, err) == 1) {
321 push(failedCases, testCase)
322 }
323 if (describe.result) printResult()
324 return testCase
325 }
326 function end(err) {
327 _clearTimeout(tick)
328 if (err) fail(err)
329 if (testCase.ended) return fail("ended multiple times")
330 testCase.ended = _Date.now()
331
332 if (testCase.planned != void 0 && testCase.planned !== testCase.total) {
333 fail("planned " + testCase.planned + " actual " + testCase.total)
334 }
335 if (testCase.mock) {
336 testCase.n += testCase.mock.txt
337 testCase.mock.restore()
338 }
339
340 totalAsserts += testCase.total
341 passedAsserts += testCase.passed
342
343 line(testCase.errors.length ? "nok" : "ok", testCase)
344 if (runPos % 1000) nextCase()
345 else _setTimeout(nextCase, 1)
346 }
347 }
348 function nextSuite(newSuite) {
349 if (!argv.length) line("suite", newSuite)
350 newSuite.p = inSuite
351 inSuite = testSuite = newSuite
352 if (isFn(testSuite.f)) {
353 testSuite.f.call(describe)
354 } else if (isObj(testSuite.f)) {
355 each(testSuite.f, curry(def, 0))
356 }
357 inSuite = newSuite.p
358 nextCase()
359 }
360 function printResult() {
361 testSuite = null
362 conf.total = totalCases
363 var testCase
364 , nums = []
365 , failed = failedCases.length
366 conf.fail = describe.failed += failed
367 conf.pass = totalCases - conf.fail
368 conf.s = skipped
369 conf.passAsserts = passedAsserts
370 conf.totalAsserts = totalAsserts
371 conf.passGreen = conf.fail ? "" : conf.green + conf.bold
372 conf.failRed = conf.fail ? conf.red : ""
373 conf.timeStr = conf.time ? " in " + (_Date.now() - started) + " ms at " + started.toTimeString().slice(0, 8) : ""
374 if (conf.status) _process.exitCode = conf.fail
375 if (failed) {
376 for (; (testCase = failedCases[--failed]); ) {
377 nums[failed] = testCase.i
378 print("---")
379 line("nok", testCase)
380 print(testCase.errors.join("\n"))
381 }
382 conf.failNums = nums.join(", ")
383 print("...")
384 line("failSum", conf)
385 failedCases.length = 0
386 }
387 describe.result = line("sum", conf)
388 if (skipped) {
389 line("skipSum", conf)
390 }
391 if (describe.onend) describe.onend()
392 }
393
394 function This() {
395 return this
396 }
397 function format(str, map, fallback) {
398 return str.replace(lineRe, function(_, field) {
399 return map[field] != null ? map[field] : fallback[field]
400 })
401 }
402 function line(name, map) {
403 return print(format(conf[name], map, conf))
404 }
405 function opts(argv, defaults) {
406 for (var arg, conf = Object.assign({}, defaults), i = argv.length; i; ) {
407 arg = argv[--i].split(/=|--(no-)?/)
408 if (arg[0] === "") {
409 conf[arg[2]] = arg[4] || !arg[1]
410 argv.splice(i, 1)
411 }
412 }
413 return conf
414 }
415 function print(str) {
416 if (!str) return
417 if (testSuite && testSuite.indent) {
418 str = str.split("\n").join("\n" + testSuite.indent)
419 }
420 describe.output += str + "\n"
421 if (describe.onprint) describe.onprint(str)
422 if (_global.console && console.log) console.log(str + conf.reset)
423 return str
424 }
425
426 // A spy is a wrapper function to verify an invocation
427 // A stub is a spy with replaced behavior
428 function Mock() {
429 this.txt = ""
430 this._r = []
431 }
432 Mock.prototype = describe.mock = {
433 fn: function(origin) {
434 spy.called = 0
435 spy.calls = []
436 spy.errors = 0
437 spy.results = []
438 return spy
439 function spy() {
440 var err, key, result
441 , args = slice(arguments)
442 if (isFn(origin)) {
443 try {
444 result = origin.apply(this, arguments)
445 } catch(e) {
446 spy.errors++
447 err = e
448 }
449 } else if (_isArray(origin)) {
450 result = origin[spy.called % origin.length]
451 } else if (isObj(origin)) {
452 key = JSON.stringify(args).slice(1, -1)
453 result = hasOwn(origin, key) ? origin[key] : origin["*"]
454 } else result = origin
455 spy.called++
456 push(spy.results, result)
457 push(spy.calls, {
458 scope: this,
459 args: args,
460 error: err,
461 result: result
462 })
463 return result
464 }
465 },
466 rand: function(seed_) {
467 var seed = seed_ || conf.seed
468 this.txt += " #seed:" + seed
469 this.swap(Math, "random", xorshift128(seed))
470 },
471 spy: function(obj, name, stub) {
472 this.swap(obj, name, this.fn(stub || obj[name]))
473 },
474 swap: function swap(obj, name, fn) {
475 if (isObj(name)) {
476 each(name, curry(swap, obj, this))
477 return
478 }
479 var existing = obj[name]
480 push(this._r, obj, name, hasOwn(obj, name) && existing)
481 obj[name] = fn
482 if (fn === fn && obj[name] !== fn) throw _Error("Unable to swap " + stringify(name))
483 return existing
484 },
485 restore: function() {
486 for (var arr = this._r, i = arr.length; --i > 0; i -= 2) {
487 if (arr[i]) {
488 arr[i - 2][arr[i - 1]] = arr[i]
489 } else {
490 delete arr[i - 2][arr[i - 1]]
491 }
492 }
493 /*** mockTime ***/
494 this.tick(_Infinity, true)
495 },
496 time: function(newTime, newZone) {
497 var mock = this
498 if (!mock._time) {
499 mock._time = fakeNow = _Date.now()
500 mock.swap(_global, fakeTimers)
501 mock.swap(_process, { nextTick: fakeNextTick, hrtime: fakeHrtime })
502 }
503 if (newTime) {
504 fakeNow = isStr(newTime) ? _Date.parse(newTime) : newTime
505 mock.tick(0)
506 }
507 fakeDate._z = newZone
508 },
509 tick: function(amount, noRepeat) {
510 var t
511 , nextNow = type(amount) === "number" ? fakeNow + amount : timers[0] ? timers[0].at : fakeNow
512
513 for (; (t = timers[0]) && t.at <= nextNow; ) {
514 fakeNow = t.at
515 timers.shift()
516 if (isStr(t.fn)) t.fn = Function(t.fn)
517 if (isFn(t.fn)) t.fn.apply(null, t.args)
518 if (!noRepeat && t.repeat) {
519 t.at += t.ms
520 fakeTimeout(t)
521 }
522 }
523 fakeNow = nextNow
524 /* mockTime end */
525 }
526 }
527
528 function xorshift128(a) {
529 var b = a * 2e3, c = a * 3e4, d = a * 4e5
530 return function() {
531 var t = d ^ (d << 11)
532 d = c; c = b; b = a
533 a ^= t ^ (t >>> 8) ^ (a >>> 19)
534 return (a >>> 0) / 4294967295
535 }
536 }
537
538 function _deepEqual(actual, expected, circ) {
539 if (
540 actual === expected ||
541 // null == undefined
542 expected === null && actual == expected ||
543 // make NaN equal to NaN
544 actual !== actual && expected !== expected
545 ) return true
546
547 var key, aKeys, len
548 , aType = typeof actual
549
550 if (
551 aType !== "object" ||
552 actual == null || // jshint ignore:line
553 aType !== typeof expected ||
554 (aType = type(actual)) != type(expected) ||
555 (actual.constructor && actual.constructor !== expected.constructor) ||
556 (aType == "date" && actual.getTime() !== expected.getTime()) ||
557 (aType == "regexp" && "" + actual !== "" + expected)
558 ) {
559 return false
560 }
561
562 key = circ.indexOf(actual)
563 if (key > -1) return true
564 push(circ, actual)
565
566 if (aType == "array" || aType == "arguments") {
567 len = actual.length
568 if (len !== expected.length) return false
569 for (; len--; ) {
570 if (!_deepEqual(actual[len], expected[len], circ)) return false
571 }
572 } else {
573 aKeys = _keys(actual)
574 len = aKeys.length
575 if (len !== _keys(expected).length) return false
576 for (; len--; ) {
577 key = aKeys[len]
578 if (
579 !hasOwn(expected, key) ||
580 !_deepEqual(actual[key], expected[key], circ)
581 ) return false
582 }
583 }
584 return true
585 }
586
587 function type(obj) {
588 /* jshint -W041 */
589 // Standard clearly states that NaN and Infinity are numbers
590 // but this is not useful for testing.
591 return (
592 obj !== obj ? "nan" :
593 obj === _Infinity || obj === -_Infinity ? "infinity" :
594 obj == null ? "" + obj :
595 toStr.call(obj).slice(8, -1).toLowerCase()
596 )
597 }
598 function num(a, b) {
599 return type(a -= 0) === "number" ? a : b
600 }
601 function isStr(str) {
602 return typeof str === "string"
603 }
604 function isFn(fn) {
605 return typeof fn === "function"
606 }
607 function isObj(obj) {
608 return type(obj) === "object"
609 }
610 function own(a, b) {
611 if (a === b) {
612 own.lastMsg = "Can not be strictEqual"
613 } else if (a) {
614 for (var k in b) if (hasOwn(b, k)) {
615 if (!hasOwn(a, k) || (
616 isObj(b[k]) ? !own(a[k], b[k]) :
617 _isArray(b[k]) ? b[k].some(itemNotOwn, a[k]) :
618 a[k] !== b[k]
619 )) {
620 own.lastMsg = own.lastMsg || k + " " + stringify(a[k]) + " != " + stringify(b[k])
621 return false
622 }
623 }
624 return true
625 }
626 function itemNotOwn(val, idx) {
627 return isObj(val) ? !own(this[idx], val) : this[idx] !== val
628 }
629 }
630 function curry(fn, arg, scope) {
631 return fn.bind.apply(fn, [scope].concat(arg))
632 }
633
634 function each(arr, fn) {
635 if (arr) {
636 if (isStr(arr)) arr = arr.split(",")
637 for (var i in arr) if (hasOwn(arr, i)) fn(i, arr[i])
638 }
639 }
640
641 function stringify(item) {
642 var max = conf.cut > 0 ? conf.cut : _Infinity
643 , str = _stringify(item, max, [])
644 return str.length > max ? str.slice(0, max - 3) + ".." + str.slice(-1) : str
645 }
646
647 function _stringify(item, max, circ) {
648 var i, t, tmp
649 , left = max
650 , str =
651 isStr(item) ? JSON.stringify(item) :
652 isFn(item) ? ("" + item).replace(/^\w+|\s+|{[\s\S]*/g, "") :
653 !item || item === true || typeof item === "number" ? "" + item :
654 (t = type(item)) === "error" || t === "symbol" || t === "regexp" ? item.toString() :
655 item.toJSON ? item.toJSON() :
656 item
657
658 if (!isStr(str)) {
659 if (circ.indexOf(str) > -1) return "Circular"
660 push(circ, str)
661 tmp = []
662 for (i in str) if (hasOwn(str, i)) {
663 i = (t === "object" ? _stringify(i, left) + ":" : "") + _stringify(str[i], left, circ)
664 push(tmp, i)
665 left -= i.length
666 if (left < 0) break
667 }
668 str =
669 t === "array" ? "[" + tmp + "]" :
670 t === "arguments" ? t + "[" + tmp + "]" :
671 (t = item.constructor) === Object ? "{" + tmp + "}" :
672 (t ? t.name || /^\w+\s+([^\s(]+)|/.exec(t)[1] || "<anon>" : "<null>") + "{" + tmp + "}"
673 }
674 return str
675 }
676}(this, setTimeout, clearTimeout, Date, Error, Infinity) // jshint ignore:line
677