UNPKG

12 kBJavaScriptView Raw
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 /*** mockTime */
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 /* mock time end */
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 // Terminology
199 // - A spy is a wrapper function to verify an invocation
200 // - A stub is a spy with replaced behavior.
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 // [seconds, nanoseconds]
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 * FAILED tests 1, 3, 6
421 * Failed 3/6 tests, 50.00% okay
422 * PASS 1 test executed in 0.023s, 1 passed, 0 failed, 0 dubious, 0 skipped.
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* http://sourceforge.net/projects/portableapps/files/
508*/
509