1 |
|
2 |
|
3 | !function(exports) {
|
4 | var doneTick, lastSuite, lastCase, started, ended
|
5 | , _global = exports.window || global
|
6 | , Fn = exports.Fn || require("../lib/fn").Fn
|
7 | , assert = exports.assert || require("./assert")
|
8 | , nativeTimeout = setTimeout
|
9 | , nativeClearTimeout = clearTimeout
|
10 | , nativeDate = Date
|
11 | , updateSnaps = exports.testUpdateSnaps = {}
|
12 | , hasOwn = updateSnaps.hasOwnProperty
|
13 |
|
14 | , fakeNow
|
15 | , timers = []
|
16 | , timerId = 0
|
17 | , fakeTimers = {
|
18 | setTimeout: fakeTimeout.bind(null, false),
|
19 | setInterval: fakeTimeout.bind(null, true),
|
20 | clearTimeout: fakeClear,
|
21 | clearInterval: fakeClear,
|
22 | Date: fakeDate
|
23 | }
|
24 |
|
25 | , color = (process.stdout || updateSnaps).isTTY && process.argv.indexOf("--no-color") == -1
|
26 | , only = []
|
27 | , totalCases = 0
|
28 | , failedCases = 0
|
29 | , skipCases = 0
|
30 | , lastAssert = 0
|
31 | , skipAssert = 0
|
32 | , passedAsserts = 0
|
33 | , bold = color ? "\x1b[1m" : ""
|
34 | , italic = color ? "\x1b[3m" : ""
|
35 | , strike = color ? "\x1b[9m" : ""
|
36 | , underline = color ? "\x1b[4m" : ""
|
37 | , red = color ? "\x1b[31m" : ""
|
38 | , green = color ? "\x1b[32m" : ""
|
39 | , yellow = color ? "\x1b[33m" : ""
|
40 | , reset = color ? "\x1b[0m" : ""
|
41 |
|
42 | for (var arg, argi = 2; arg = process.argv[argi++]; ) {
|
43 | if (arg === "-u") {
|
44 | updateSnaps[process.argv[argi++]] = true
|
45 | } else {
|
46 | only.push(arg)
|
47 | }
|
48 | }
|
49 |
|
50 | exports.defineAssert = defineAssert
|
51 | exports.describe = describe
|
52 | exports.test = function(name, next, opts) {
|
53 | return (lastSuite || describe()).test(name, next, opts)
|
54 | }
|
55 | exports.it = function(name, next, opts) {
|
56 | return exports.test("it " + name, next, opts)
|
57 | }
|
58 |
|
59 |
|
60 | function TestSuite(name) {
|
61 | lastSuite = this
|
62 | checkEnd(lastAssert)
|
63 | if (!started) {
|
64 | started = nativeDate.now()
|
65 | if (!only.length) {
|
66 | print("TAP version 13")
|
67 | }
|
68 | }
|
69 | if (lastCase && !lastCase.ended) {
|
70 | lastCase.end()
|
71 | }
|
72 | if (!only.length) {
|
73 | print("# " + (name || "{unnamed test suite}"))
|
74 | }
|
75 | }
|
76 |
|
77 | TestSuite.prototype = {
|
78 | wait: Fn.hold,
|
79 | describe: describe,
|
80 | it: function(name, next, opts) {
|
81 | return this.test("it " + name, next, opts)
|
82 | },
|
83 | test: function(name, next, opts) {
|
84 | if (lastCase && !lastCase.ended) {
|
85 | lastCase.end()
|
86 | }
|
87 | if (typeof name === "function") {
|
88 | next = name
|
89 | name = ""
|
90 | }
|
91 | if (typeof next !== "function") {
|
92 | opts = next
|
93 | next = null
|
94 | }
|
95 | var testSuite = this
|
96 | , testCase = lastCase = new TestCase(name, opts)
|
97 | checkEnd()
|
98 |
|
99 | ;["describe", "it", "test"].forEach(function(name) {
|
100 | testCase[name] = function() {
|
101 | return testSuite[name].apply(testSuite, arguments)
|
102 | }
|
103 | })
|
104 |
|
105 | if (next && !testCase.opts.skip) {
|
106 | nativeClearTimeout(doneTick)
|
107 | testCase.setTimeout()
|
108 | testCase.resume = testSuite.wait()
|
109 | next(
|
110 | testCase,
|
111 | (testCase.mock = next.length > 1 && new Mock)
|
112 | )
|
113 | return testSuite
|
114 | }
|
115 |
|
116 | return testCase
|
117 | },
|
118 | _it: This,
|
119 | _test: This
|
120 | }
|
121 |
|
122 | function TestCase(name, opts) {
|
123 | var testCase = this
|
124 | , opts = testCase.opts = opts || {}
|
125 | , id = ++totalCases
|
126 | testCase.name = id + " - " + (name || "{unnamed test case}")
|
127 | testCase.failed = []
|
128 | testCase.passedAsserts = 0
|
129 | testCase.totalAsserts = 0
|
130 |
|
131 | if (only.length && only.indexOf("" + id) === -1) {
|
132 | opts.skip = "command line"
|
133 | }
|
134 |
|
135 | return testCase
|
136 | }
|
137 |
|
138 | TestCase.prototype = {
|
139 | plan: function(num) {
|
140 | this.planned = num
|
141 | return this
|
142 | },
|
143 | setTimeout: function(ms) {
|
144 | var testCase = this
|
145 | nativeClearTimeout(testCase.timeout)
|
146 | testCase.timeout = nativeTimeout(function() {
|
147 | throw Error("Timeout on running '" + testCase.name + "'")
|
148 | }, ms || 5000)
|
149 | return testCase
|
150 | },
|
151 | end: function() {
|
152 | var testCase = this
|
153 | , name = testCase.name
|
154 | , n = "\n "
|
155 |
|
156 | if (testCase.ended) {
|
157 | failedCases++
|
158 | throw Error("'" + name + "' ended multiple times")
|
159 | }
|
160 |
|
161 | testCase.ended = nativeDate.now()
|
162 | name += " [" + testCase.passedAsserts + "/" + testCase.totalAsserts + "]"
|
163 |
|
164 | if (testCase.opts.skip) {
|
165 | skipCases++
|
166 | if (only.length === 0) {
|
167 | print("ok " + name + " # skip - " + testCase.opts.skip)
|
168 | }
|
169 | return
|
170 | }
|
171 |
|
172 | if (testCase.planned != void 0 && testCase.planned !== testCase.totalAsserts) {
|
173 | testCase.failed.push("Planned " + testCase.planned + " actual " + testCase.totalAsserts)
|
174 | }
|
175 |
|
176 | if (testCase.failed.length) {
|
177 | failedCases++
|
178 | print("not ok " + name + n + "---\n" + testCase.failed.join("\n").replace(/^/gm, " ") + n + "...")
|
179 | } else {
|
180 | print("ok " + name)
|
181 | }
|
182 | if (testCase.timeout) {
|
183 | nativeClearTimeout(testCase.timeout)
|
184 | testCase.timeout = null
|
185 | if (testCase.mock) {
|
186 | testCase.mock.restore()
|
187 | }
|
188 | testCase.resume()
|
189 | checkEnd()
|
190 | }
|
191 | }
|
192 | }
|
193 |
|
194 | Object.keys(assert).forEach(defineAssert)
|
195 |
|
196 | chainable(TestSuite, TestCase)
|
197 |
|
198 |
|
199 |
|
200 |
|
201 | if (_global.setImmediate) {
|
202 | fakeTimers.setImmediate = fakeNextTick
|
203 | fakeTimers.clearImmediate = fakeClear
|
204 | }
|
205 | function fakeDate(year, month, date, hr, min, sec, ms) {
|
206 | return (
|
207 | arguments.length > 1 ?
|
208 | new nativeDate(year|0, month|0, date||1, hr|0, min|0, sec|0, ms|0) :
|
209 | new nativeDate(year || Math.floor(fakeNow))
|
210 | )
|
211 | }
|
212 | fakeDate.now = function() {
|
213 | return Math.floor(fakeNow)
|
214 | }
|
215 | fakeDate.parse = nativeDate.parse
|
216 |
|
217 | function fakeHrtime(time) {
|
218 | var diff = Array.isArray(time) ? fakeNow - (time[0] * 1e3 + time[1] / 1e6) : fakeNow
|
219 | return [Math.floor(diff / 1000), Math.round((diff % 1e3) * 1e3) * 1e3]
|
220 | }
|
221 |
|
222 | function fakeTimeout(repeat, fn, ms) {
|
223 | if (typeof repeat !== "object") {
|
224 | repeat = {
|
225 | id: ++timerId,
|
226 | repeat: repeat,
|
227 | fn: fn,
|
228 | args: timers.slice.call(arguments, 3),
|
229 | at: fakeNow + ms,
|
230 | ms: ms
|
231 | }
|
232 | }
|
233 | for (var i = timers.length; i--; ) {
|
234 | if (timers[i].at <= repeat.at) break
|
235 | }
|
236 | timers.splice(i + 1, 0, repeat)
|
237 | return {
|
238 | id: repeat.id,
|
239 | unref: This
|
240 | }
|
241 | }
|
242 |
|
243 | function fakeNextTick(fn) {
|
244 | fakeTimeout({
|
245 | id: ++timerId,
|
246 | fn: fn,
|
247 | args: timers.slice.call(arguments, 1),
|
248 | at: fakeNow - 1
|
249 | })
|
250 | }
|
251 |
|
252 | function fakeClear(id) {
|
253 | if (id != null) for (var i = timers.length; i--; ) {
|
254 | if (timers[i].id === id || timers[i].id === id.id) {
|
255 | timers.splice(i, 1)
|
256 | break
|
257 | }
|
258 | }
|
259 | }
|
260 |
|
261 | function Mock() {
|
262 | var mock = this
|
263 | mock.replaced = []
|
264 | }
|
265 | Mock.prototype = {
|
266 | fn: function(origin) {
|
267 | spy.called = 0
|
268 | spy.calls = []
|
269 | return spy
|
270 | function spy() {
|
271 | var key, result
|
272 | , args = timers.slice.call(arguments)
|
273 | if (typeof origin === "function") {
|
274 | result = origin.apply(this, arguments)
|
275 | } else if (Array.isArray(origin)) {
|
276 | result = origin[spy.called % origin.length]
|
277 | } else if (origin && origin.constructor === Object) {
|
278 | key = JSON.stringify(args).slice(1, -1)
|
279 | result = hasOwn.call(origin, key) ? origin[key] : origin["*"]
|
280 | }
|
281 | spy.called++
|
282 | spy.calls.push({
|
283 | scope: this,
|
284 | args: args,
|
285 | result: result
|
286 | })
|
287 | return result
|
288 | }
|
289 | },
|
290 | map: function(obj, stubs, justStubs) {
|
291 | var key
|
292 | , mock = this
|
293 | , obj2 = justStubs ? stubs : obj
|
294 | for (key in obj2) {
|
295 | mock.spy(obj, key, stubs && stubs[key])
|
296 | }
|
297 | if (obj.prototype) {
|
298 | mock.map(obj.prototype, stubs)
|
299 | }
|
300 | },
|
301 | replace: function(obj, name, fn) {
|
302 | var mock = this
|
303 | , existing = obj[name]
|
304 | if (typeof existing === "function") {
|
305 | mock.replaced.push(obj, name, hasOwn.call(obj, name) && existing)
|
306 | obj[name] = fn
|
307 | }
|
308 | return existing
|
309 | },
|
310 | spy: function(obj, name, stub) {
|
311 | var mock = this
|
312 | mock.replace(obj, name, mock.fn(stub || obj[name]))
|
313 | },
|
314 | time: function(newTime) {
|
315 | var key
|
316 | , mock = this
|
317 | if (!mock.timeFreeze) {
|
318 | mock.timeFreeze = fakeNow = nativeDate.now()
|
319 | for (key in fakeTimers) {
|
320 | mock.replace(_global, key, fakeTimers[key])
|
321 | }
|
322 | if (process.nextTick) {
|
323 | mock.replace(process, "nextTick", fakeNextTick)
|
324 | mock.replace(process, "hrtime", fakeHrtime)
|
325 | }
|
326 | }
|
327 | if (newTime) {
|
328 | fakeNow = typeof newTime === "string" ? nativeDate.parse(newTime) : newTime
|
329 | mock.tick(0)
|
330 | }
|
331 | },
|
332 | tick: function(amount, noRepeat) {
|
333 | if (typeof amount === "number") {
|
334 | fakeNow += amount
|
335 | } else if (timers[0]) {
|
336 | fakeNow = timers[0].at
|
337 | }
|
338 |
|
339 | for (var t; t = timers[0]; ) {
|
340 | if (t.at <= fakeNow) {
|
341 | timers.shift()
|
342 | if (typeof t.fn === "string") t.fn = Function(t.fn)
|
343 | if (typeof t.fn === "function") t.fn.apply(null, t.args)
|
344 | if (!noRepeat && t.repeat) {
|
345 | t.at += t.ms
|
346 | fakeTimeout(t)
|
347 | }
|
348 | } else {
|
349 | break
|
350 | }
|
351 | }
|
352 | },
|
353 | restore: function() {
|
354 | var arr = this.replaced
|
355 | , i = arr.length
|
356 | for (; --i > 0; i -= 2) {
|
357 | if (arr[i]) {
|
358 | arr[i - 2][arr[i - 1]] = arr[i]
|
359 | } else {
|
360 | delete arr[i - 2][arr[i - 1]]
|
361 | }
|
362 | }
|
363 | if (timers.length) {
|
364 | this.tick(Infinity, true)
|
365 | }
|
366 | }
|
367 | }
|
368 |
|
369 | function print(str) {
|
370 | console.log(str + reset)
|
371 | }
|
372 |
|
373 | function describe(name) {
|
374 | return lastSuite && lastSuite !== this ? lastSuite.describe(name) : new TestSuite(name)
|
375 | }
|
376 |
|
377 | function checkEnd() {
|
378 | var curSuite = lastSuite
|
379 | , curCase = lastCase
|
380 | , curAssert = lastAssert
|
381 |
|
382 | nativeClearTimeout(doneTick)
|
383 | doneTick = setTimeout(function() {
|
384 | if (curAssert === lastAssert && curCase === lastCase && lastCase && !lastCase.timeout && curSuite == lastSuite) {
|
385 | if (!lastCase.ended) {
|
386 | lastCase.end()
|
387 | }
|
388 | end()
|
389 | }
|
390 | }, 1)
|
391 | }
|
392 |
|
393 | function end() {
|
394 | if (ended) {
|
395 | throw Error("ended in multiple times")
|
396 | }
|
397 | nativeClearTimeout(doneTick)
|
398 | ended = nativeDate.now()
|
399 |
|
400 | if (!only.length) {
|
401 | print("1.." + totalCases)
|
402 |
|
403 | if (skipAssert) {
|
404 | print("# " + yellow + bold + "skip " + skipCases + "/" + skipAssert)
|
405 | }
|
406 |
|
407 | print("#" + (failedCases ? "" : green + bold) + " pass " + (totalCases - failedCases)
|
408 | + "/" + totalCases
|
409 | + " [" + passedAsserts + "/" + lastAssert + "]"
|
410 | + " in " + (ended - started) + " ms")
|
411 |
|
412 | failedCases && print("#" + red + bold + " fail " + failedCases
|
413 | + " [" + (lastAssert - passedAsserts) + "]")
|
414 | }
|
415 |
|
416 | if (process.exit) {
|
417 | process.exit(failedCases ? 1 : 0)
|
418 | }
|
419 | |
420 |
|
421 |
|
422 |
|
423 |
|
424 | }
|
425 |
|
426 | function defineAssert(key, fn, _skip) {
|
427 | if (!assert[key]) {
|
428 | assert[key] = fn
|
429 | }
|
430 | TestSuite.prototype["_" + key] = TestCase.prototype["_" + key] = skip
|
431 | TestSuite.prototype[key] = _skip === true ? skip : function() {
|
432 | var testCase = this.test("", null)
|
433 | return testCase[key].apply(testCase, arguments)
|
434 | },
|
435 | TestCase.prototype[key] = _skip === true ? skip : assertWrapper
|
436 | function assertWrapper(a, b, c) {
|
437 | var testCase = this
|
438 | if (testCase.opts.skip) {
|
439 | return skip.call(testCase)
|
440 | }
|
441 | lastAssert++
|
442 | if (!testCase.timeout) checkEnd()
|
443 | testCase.totalAsserts++
|
444 | try {
|
445 | assert[key].call(assert, a, b, c, assertWrapper)
|
446 | passedAsserts++
|
447 | testCase.passedAsserts++
|
448 | } catch(e) {
|
449 | testCase.failed.push(testCase.opts.noStack ? e.message : e.stack)
|
450 | }
|
451 | if (testCase.planned != null && testCase.planned <= testCase.totalAsserts) {
|
452 | testCase.end()
|
453 | }
|
454 | return testCase
|
455 | }
|
456 | return this
|
457 | }
|
458 |
|
459 | function chainable() {
|
460 | var a
|
461 | , arr = []
|
462 | , j, i = 0
|
463 | for (; a = arguments[i++]; ) {
|
464 | arr.push.apply(arr, Object.keys(a.prototype))
|
465 | }
|
466 | for (i = 0; a = arguments[i++]; ) {
|
467 | for (j = arr.length; j--; ) {
|
468 | if (!a.prototype[arr[j]]) {
|
469 | a.prototype[arr[j]] = This
|
470 | }
|
471 | }
|
472 | }
|
473 | }
|
474 |
|
475 | function skip() {
|
476 | skipAssert++
|
477 | return this
|
478 | }
|
479 |
|
480 | function This() {
|
481 | return this
|
482 | }
|
483 |
|
484 | describe.diff = diff
|
485 | describe.colorDiff = colorDiff
|
486 |
|
487 | function colorDiff(a, b) {
|
488 | var res = diff(a, b)
|
489 | console.log(
|
490 | a.slice(0, res[0]) +
|
491 | bold + red + strike + a.slice(res[0], res[0] + res[1]) +
|
492 | green + b.slice(res[0], res[0]+res[2]) +
|
493 | reset + a.slice(res[0] + res[1])
|
494 | )
|
495 | }
|
496 |
|
497 | function diff(a, b, re) {
|
498 | var c = 0, d = a.length, e = b.length
|
499 | for (; a.charAt(c) && a.charAt(c) == b.charAt(c); c++);
|
500 | for (; d > c && e > c && a.charAt(d - 1) == b.charAt(e - 1); d--) e--;
|
501 | return [c, d - c, e - c]
|
502 | }
|
503 | }(this)
|
504 |
|
505 |
|
506 |
|
507 |
|
508 |
|
509 |
|