UNPKG

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