litejs
Version:
Single-page application framework
510 lines (462 loc) • 12 kB
JavaScript
!function(exports) {
var doneTick, lastSuite, lastCase, started, ended
, _global = exports.window || global
, Fn = exports.Fn || require("../lib/fn").Fn
, assert = exports.assert || require("./assert")
, nativeTimeout = setTimeout
, nativeClearTimeout = clearTimeout
, nativeDate = Date
, updateSnaps = exports.testUpdateSnaps = {}
, hasOwn = updateSnaps.hasOwnProperty
/*** mockTime */
, fakeNow
, timers = []
, timerId = 0
, fakeTimers = {
setTimeout: fakeTimeout.bind(null, false),
setInterval: fakeTimeout.bind(null, true),
clearTimeout: fakeClear,
clearInterval: fakeClear,
Date: fakeDate
}
/* mock time end */
, color = (process.stdout || updateSnaps).isTTY && process.argv.indexOf("--no-color") == -1
, only = []
, totalCases = 0
, failedCases = 0
, skipCases = 0
, lastAssert = 0
, skipAssert = 0
, passedAsserts = 0
, bold = color ? "\x1b[1m" : ""
, italic = color ? "\x1b[3m" : ""
, strike = color ? "\x1b[9m" : ""
, underline = color ? "\x1b[4m" : ""
, red = color ? "\x1b[31m" : ""
, green = color ? "\x1b[32m" : ""
, yellow = color ? "\x1b[33m" : ""
, reset = color ? "\x1b[0m" : ""
for (var arg, argi = 2; arg = process.argv[argi++]; ) {
if (arg === "-u") {
updateSnaps[process.argv[argi++]] = true
} else {
only.push(arg)
}
}
exports.defineAssert = defineAssert
exports.describe = describe
exports.test = function(name, next, opts) {
return (lastSuite || describe()).test(name, next, opts)
}
exports.it = function(name, next, opts) {
return exports.test("it " + name, next, opts)
}
function TestSuite(name) {
lastSuite = this
checkEnd(lastAssert)
if (!started) {
started = nativeDate.now()
if (!only.length) {
print("TAP version 13")
}
}
if (lastCase && !lastCase.ended) {
lastCase.end()
}
if (!only.length) {
print("# " + (name || "{unnamed test suite}"))
}
}
TestSuite.prototype = {
wait: Fn.hold,
describe: describe,
it: function(name, next, opts) {
return this.test("it " + name, next, opts)
},
test: function(name, next, opts) {
if (lastCase && !lastCase.ended) {
lastCase.end()
}
if (typeof name === "function") {
next = name
name = ""
}
if (typeof next !== "function") {
opts = next
next = null
}
var testSuite = this
, testCase = lastCase = new TestCase(name, opts)
checkEnd()
;["describe", "it", "test"].forEach(function(name) {
testCase[name] = function() {
return testSuite[name].apply(testSuite, arguments)
}
})
if (next && !testCase.opts.skip) {
nativeClearTimeout(doneTick)
testCase.setTimeout()
testCase.resume = testSuite.wait()
next(
testCase,
(testCase.mock = next.length > 1 && new Mock)
)
return testSuite
}
return testCase
},
_it: This,
_test: This
}
function TestCase(name, opts) {
var testCase = this
, opts = testCase.opts = opts || {}
, id = ++totalCases
testCase.name = id + " - " + (name || "{unnamed test case}")
testCase.failed = []
testCase.passedAsserts = 0
testCase.totalAsserts = 0
if (only.length && only.indexOf("" + id) === -1) {
opts.skip = "command line"
}
return testCase
}
TestCase.prototype = {
plan: function(num) {
this.planned = num
return this
},
setTimeout: function(ms) {
var testCase = this
nativeClearTimeout(testCase.timeout)
testCase.timeout = nativeTimeout(function() {
throw Error("Timeout on running '" + testCase.name + "'")
}, ms || 5000)
return testCase
},
end: function() {
var testCase = this
, name = testCase.name
, n = "\n "
if (testCase.ended) {
failedCases++
throw Error("'" + name + "' ended multiple times")
}
testCase.ended = nativeDate.now()
name += " [" + testCase.passedAsserts + "/" + testCase.totalAsserts + "]"
if (testCase.opts.skip) {
skipCases++
if (only.length === 0) {
print("ok " + name + " # skip - " + testCase.opts.skip)
}
return
}
if (testCase.planned != void 0 && testCase.planned !== testCase.totalAsserts) {
testCase.failed.push("Planned " + testCase.planned + " actual " + testCase.totalAsserts)
}
if (testCase.failed.length) {
failedCases++
print("not ok " + name + n + "---\n" + testCase.failed.join("\n").replace(/^/gm, " ") + n + "...")
} else {
print("ok " + name)
}
if (testCase.timeout) {
nativeClearTimeout(testCase.timeout)
testCase.timeout = null
if (testCase.mock) {
testCase.mock.restore()
}
testCase.resume()
checkEnd()
}
}
}
Object.keys(assert).forEach(defineAssert)
chainable(TestSuite, TestCase)
// Terminology
// - A spy is a wrapper function to verify an invocation
// - A stub is a spy with replaced behavior.
if (_global.setImmediate) {
fakeTimers.setImmediate = fakeNextTick
fakeTimers.clearImmediate = fakeClear
}
function fakeDate(year, month, date, hr, min, sec, ms) {
return (
arguments.length > 1 ?
new nativeDate(year|0, month|0, date||1, hr|0, min|0, sec|0, ms|0) :
new nativeDate(year || Math.floor(fakeNow))
)
}
fakeDate.now = function() {
return Math.floor(fakeNow)
}
fakeDate.parse = nativeDate.parse
// [seconds, nanoseconds]
function fakeHrtime(time) {
var diff = Array.isArray(time) ? fakeNow - (time[0] * 1e3 + time[1] / 1e6) : fakeNow
return [Math.floor(diff / 1000), Math.round((diff % 1e3) * 1e3) * 1e3]
}
function fakeTimeout(repeat, fn, ms) {
if (typeof repeat !== "object") {
repeat = {
id: ++timerId,
repeat: repeat,
fn: fn,
args: timers.slice.call(arguments, 3),
at: fakeNow + ms,
ms: ms
}
}
for (var i = timers.length; i--; ) {
if (timers[i].at <= repeat.at) break
}
timers.splice(i + 1, 0, repeat)
return {
id: repeat.id,
unref: This
}
}
function fakeNextTick(fn) {
fakeTimeout({
id: ++timerId,
fn: fn,
args: timers.slice.call(arguments, 1),
at: fakeNow - 1
})
}
function fakeClear(id) {
if (id != null) for (var i = timers.length; i--; ) {
if (timers[i].id === id || timers[i].id === id.id) {
timers.splice(i, 1)
break
}
}
}
function Mock() {
var mock = this
mock.replaced = []
}
Mock.prototype = {
fn: function(origin) {
spy.called = 0
spy.calls = []
return spy
function spy() {
var key, result
, args = timers.slice.call(arguments)
if (typeof origin === "function") {
result = origin.apply(this, arguments)
} else if (Array.isArray(origin)) {
result = origin[spy.called % origin.length]
} else if (origin && origin.constructor === Object) {
key = JSON.stringify(args).slice(1, -1)
result = hasOwn.call(origin, key) ? origin[key] : origin["*"]
}
spy.called++
spy.calls.push({
scope: this,
args: args,
result: result
})
return result
}
},
map: function(obj, stubs, justStubs) {
var key
, mock = this
, obj2 = justStubs ? stubs : obj
for (key in obj2) {
mock.spy(obj, key, stubs && stubs[key])
}
if (obj.prototype) {
mock.map(obj.prototype, stubs)
}
},
replace: function(obj, name, fn) {
var mock = this
, existing = obj[name]
if (typeof existing === "function") {
mock.replaced.push(obj, name, hasOwn.call(obj, name) && existing)
obj[name] = fn
}
return existing
},
spy: function(obj, name, stub) {
var mock = this
mock.replace(obj, name, mock.fn(stub || obj[name]))
},
time: function(newTime) {
var key
, mock = this
if (!mock.timeFreeze) {
mock.timeFreeze = fakeNow = nativeDate.now()
for (key in fakeTimers) {
mock.replace(_global, key, fakeTimers[key])
}
if (process.nextTick) {
mock.replace(process, "nextTick", fakeNextTick)
mock.replace(process, "hrtime", fakeHrtime)
}
}
if (newTime) {
fakeNow = typeof newTime === "string" ? nativeDate.parse(newTime) : newTime
mock.tick(0)
}
},
tick: function(amount, noRepeat) {
if (typeof amount === "number") {
fakeNow += amount
} else if (timers[0]) {
fakeNow = timers[0].at
}
for (var t; t = timers[0]; ) {
if (t.at <= fakeNow) {
timers.shift()
if (typeof t.fn === "string") t.fn = Function(t.fn)
if (typeof t.fn === "function") t.fn.apply(null, t.args)
if (!noRepeat && t.repeat) {
t.at += t.ms
fakeTimeout(t)
}
} else {
break
}
}
},
restore: function() {
var arr = this.replaced
, i = arr.length
for (; --i > 0; i -= 2) {
if (arr[i]) {
arr[i - 2][arr[i - 1]] = arr[i]
} else {
delete arr[i - 2][arr[i - 1]]
}
}
if (timers.length) {
this.tick(Infinity, true)
}
}
}
function print(str) {
console.log(str + reset)
}
function describe(name) {
return lastSuite && lastSuite !== this ? lastSuite.describe(name) : new TestSuite(name)
}
function checkEnd() {
var curSuite = lastSuite
, curCase = lastCase
, curAssert = lastAssert
nativeClearTimeout(doneTick)
doneTick = setTimeout(function() {
if (curAssert === lastAssert && curCase === lastCase && lastCase && !lastCase.timeout && curSuite == lastSuite) {
if (!lastCase.ended) {
lastCase.end()
}
end()
}
}, 1)
}
function end() {
if (ended) {
throw Error("ended in multiple times")
}
nativeClearTimeout(doneTick)
ended = nativeDate.now()
if (!only.length) {
print("1.." + totalCases)
if (skipAssert) {
print("# " + yellow + bold + "skip " + skipCases + "/" + skipAssert)
}
print("#" + (failedCases ? "" : green + bold) + " pass " + (totalCases - failedCases)
+ "/" + totalCases
+ " [" + passedAsserts + "/" + lastAssert + "]"
+ " in " + (ended - started) + " ms")
failedCases && print("#" + red + bold + " fail " + failedCases
+ " [" + (lastAssert - passedAsserts) + "]")
}
if (process.exit) {
process.exit(failedCases ? 1 : 0)
}
/*
* FAILED tests 1, 3, 6
* Failed 3/6 tests, 50.00% okay
* PASS 1 test executed in 0.023s, 1 passed, 0 failed, 0 dubious, 0 skipped.
*/
}
function defineAssert(key, fn, _skip) {
if (!assert[key]) {
assert[key] = fn
}
TestSuite.prototype["_" + key] = TestCase.prototype["_" + key] = skip
TestSuite.prototype[key] = _skip === true ? skip : function() {
var testCase = this.test("", null)
return testCase[key].apply(testCase, arguments)
},
TestCase.prototype[key] = _skip === true ? skip : assertWrapper
function assertWrapper(a, b, c) {
var testCase = this
if (testCase.opts.skip) {
return skip.call(testCase)
}
lastAssert++
if (!testCase.timeout) checkEnd()
testCase.totalAsserts++
try {
assert[key].call(assert, a, b, c, assertWrapper)
passedAsserts++
testCase.passedAsserts++
} catch(e) {
testCase.failed.push(testCase.opts.noStack ? e.message : e.stack)
}
if (testCase.planned != null && testCase.planned <= testCase.totalAsserts) {
testCase.end()
}
return testCase
}
return this
}
function chainable() {
var a
, arr = []
, j, i = 0
for (; a = arguments[i++]; ) {
arr.push.apply(arr, Object.keys(a.prototype))
}
for (i = 0; a = arguments[i++]; ) {
for (j = arr.length; j--; ) {
if (!a.prototype[arr[j]]) {
a.prototype[arr[j]] = This
}
}
}
}
function skip() {
skipAssert++
return this
}
function This() {
return this
}
describe.diff = diff
describe.colorDiff = colorDiff
function colorDiff(a, b) {
var res = diff(a, b)
console.log(
a.slice(0, res[0]) +
bold + red + strike + a.slice(res[0], res[0] + res[1]) +
green + b.slice(res[0], res[0]+res[2]) +
reset + a.slice(res[0] + res[1])
)
}
function diff(a, b, re) {
var c = 0, d = a.length, e = b.length
for (; a.charAt(c) && a.charAt(c) == b.charAt(c); c++);
for (; d > c && e > c && a.charAt(d - 1) == b.charAt(e - 1); d--) e--;
return [c, d - c, e - c]
}
}(this)
/*
* http://sourceforge.net/projects/portableapps/files/
*/