[ospec] cleanup in code and tests, better error messages

This commit is contained in:
Pierre-Yves Gérardy 2018-05-26 12:23:00 +02:00 committed by Pierre-Yves Gérardy
parent e473536866
commit 9c1f8d5f35
2 changed files with 119 additions and 100 deletions

View file

@ -7,17 +7,21 @@ else window.o = m()
var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty
var ospecFileName = getStackName(ensureStackTrace(new Error), /[\/\\](.*?):\d+:\d+/), timeoutStackName var ospecFileName = getStackName(ensureStackTrace(new Error), /[\/\\](.*?):\d+:\d+/), timeoutStackName
var globalTimeout = noTimeoutRightNow var globalTimeout = noTimeoutRightNow
var hooks = {
__before: true,
__beforeEach: true,
__after: true,
__afterEach: true
}
if (name != null) spec[name] = ctx = {} if (name != null) spec[name] = ctx = {}
function o(subject, predicate) { function o(subject, predicate) {
if (predicate === undefined) { if (predicate === undefined) return new Assert(subject)
if (results == null) throw new Error("Assertions should not occur outside test definitions") else {
return new Assert(subject) subject = String(subject)
} if (hasOwn.call(hooks, subject)) throw new Error("'" + subject + "' is a reserved test name")
else if (results == null) { if (subject.slice(0, 2) === "__") console.warn("test names starting with '__' are reserved for internal use\n" + o.cleanStackTrace(ensureStackTrace(new Error)))
ctx[unique(subject)] = new Task(predicate, ensureStackTrace(new Error)) ctx[unique(subject)] = new Task(predicate, ensureStackTrace(new Error))
} else {
throw new Error("Test definition shouldn't be nested. To group tests use `o.spec()`")
} }
} }
o.before = hook("__before") o.before = hook("__before")
@ -36,7 +40,6 @@ else window.o = m()
highlight("/!\\ WARNING /!\\ o.only() mode") + "\n" + o.cleanStackTrace(ensureStackTrace(new Error)) + "\n", highlight("/!\\ WARNING /!\\ o.only() mode") + "\n" + o.cleanStackTrace(ensureStackTrace(new Error)) + "\n",
cStyle("red"), "" cStyle("red"), ""
) )
o(subject, only = predicate) o(subject, only = predicate)
} }
o.spy = function(fn) { o.spy = function(fn) {
@ -93,23 +96,25 @@ else window.o = m()
function test(spec, pre, post, finalize) { function test(spec, pre, post, finalize) {
pre = [].concat(pre, spec["__beforeEach"] || []) pre = [].concat(pre, spec["__beforeEach"] || [])
post = [].concat(spec["__afterEach"] || [], post) post = [].concat(spec["__afterEach"] || [], post)
series([].concat(spec["__before"] || [], Object.keys(spec).map(function(key) { series([].concat(spec["__before"] || [], Object.keys(spec).reduce(function(tasks, key) {
return new Task(function(done, timeout) { if (!hasOwn.call(hooks, key)) {
timeout(Infinity) tasks.push(new Task(function(done, timeout) {
if (key.slice(0, 2) === "__") return done() timeout(Infinity)
if (only !== null && spec[key].fn !== only && spec[key] instanceof Task) return done() if (only !== null && spec[key].fn !== only && spec[key] instanceof Task) return done()
subjects.push(key) subjects.push(key)
var pop = new Task(function pop() { var pop = new Task(function pop() {
subjects.pop() subjects.pop()
done() done()
}, null) }, null)
if (spec[key] instanceof Task) series([].concat(pre, spec[key], post, pop)) if (spec[key] instanceof Task) series([].concat(pre, spec[key], post, pop))
else test(spec[key], pre, post, pop) else test(spec[key], pre, post, pop)
}, null) }, null))
}), spec["__after"] || [], finalize)) }
return tasks
}, []), spec["__after"] || [], finalize))
} }
function series(tasks) { function series(tasks) {
@ -162,7 +167,8 @@ else window.o = m()
fn(done, setDelay) fn(done, setDelay)
} }
catch (e) { catch (e) {
finalizeAsync(e) if (task.err != null) finalizeAsync(e)
else throw e
} }
if (timeout === 0) { if (timeout === 0) {
startTimer() startTimer()
@ -239,8 +245,16 @@ else window.o = m()
return false return false
} }
function Assert(value) {this.value = value} function isRunning() {return results != null}
function Task(fn, err) {this.fn = fn, this.err = err} function Assert(value) {
if (!isRunning()) throw new Error("Assertions should not occur outside test definitions")
this.value = value
}
function Task(fn, err) {
if (err != null && isRunning()) throw new Error("Test definitions and hooks shouldn't be nested. To group tests use `o.spec()`")
this.fn = fn
this.err = err
}
function define(name, verb, compare) { function define(name, verb, compare) {
Assert.prototype[name] = function assert(value) { Assert.prototype[name] = function assert(value) {
if (compare(this.value, value)) record(null) if (compare(this.value, value)) record(null)

View file

@ -3,91 +3,96 @@
var callAsync = require("../../test-utils/callAsync") var callAsync = require("../../test-utils/callAsync")
var o = require("../ospec") var o = require("../ospec")
new function(o) { o("o.only", function(done) {
o = o.new() var oo = o.new()
o.spec("ospec", function() { oo.spec("ospec", function() {
o("skipped", function() { oo("skipped", function() {
o(true).equals(false) oo(true).equals(false)
}) })
o.only(".only()", function() { oo.only(".only()", function() {
o(2).equals(2) oo(2).equals(2)
}, true) }, true)
}) })
o.run() oo.run(function(results){
}(o) o(results.length).equals(1)
o(results[0].pass).equals(true)
new function(o) { done()
var clone = o.new()
clone.spec("clone", function() {
clone("fail", function() {
clone(true).equals(false)
})
clone("pass", function() {
clone(true).equals(true)
})
}) })
})
// Predicate test passing on clone results // Predicate test passing on clone results
o.spec("reporting", function() { o.spec("reporting", function() {
o("reports per instance", function(done, timeout) { var oo
timeout(100) // Waiting on clone o.beforeEach(function(){
oo = o.new()
clone.run(function(results) { oo.spec("clone", function() {
o(typeof results).equals("object") oo("fail", function() {
o("length" in results).equals(true) oo(true).equals(false)
o(results.length).equals(2)("Two results") })
o("error" in results[0] && "pass" in results[0]).equals(true)("error and pass keys present in failing result") oo("pass", function() {
o(!("error" in results[1]) && "pass" in results[1]).equals(true)("only pass key present in passing result") oo(true).equals(true)
o(results[0].pass).equals(false)("Test meant to fail has failed")
o(results[1].pass).equals(true)("Test meant to pass has passed")
done()
}) })
}) })
o("o.report() returns the number of failures", function () { })
var log = console.log, error = console.error o("reports per instance", function(done, timeout) {
console.log = o.spy() timeout(100) // Waiting on clone
console.error = o.spy()
function makeError(msg) {try{throw msg ? new Error(msg) : new Error} catch(e){return e}} oo.run(function(results) {
try { o(typeof results).equals("object")
var errCount = o.report([{pass: true}, {pass: true}]) o("length" in results).equals(true)
o(results.length).equals(2)("Two results")
o(errCount).equals(0) o("error" in results[0] && "pass" in results[0]).equals(true)("error and pass keys present in failing result")
o(console.log.callCount).equals(1) o(!("error" in results[1]) && "pass" in results[1]).equals(true)("only pass key present in passing result")
o(console.error.callCount).equals(0) o(results[0].pass).equals(false)("Test meant to fail has failed")
o(results[1].pass).equals(true)("Test meant to pass has passed")
errCount = o.report([ done()
{pass: false, error: makeError("hey"), message: "hey"}
])
o(errCount).equals(1)
o(console.log.callCount).equals(2)
o(console.error.callCount).equals(1)
errCount = o.report([
{pass: false, error: makeError("hey"), message: "hey"},
{pass: true},
{pass: false, error: makeError("ho"), message: "ho"}
])
o(errCount).equals(2)
o(console.log.callCount).equals(3)
o(console.error.callCount).equals(3)
} catch (e) {
o(1).equals(0)("Error while testing the reporter")
}
console.log = log
console.error = error
}) })
}) })
}(o) o("o.report() returns the number of failures", function () {
var log = console.log, error = console.error
console.log = o.spy()
console.error = o.spy()
function makeError(msg) {try{throw msg ? new Error(msg) : new Error} catch(e){return e}}
try {
var errCount = o.report([{pass: true}, {pass: true}])
o(errCount).equals(0)
o(console.log.callCount).equals(1)
o(console.error.callCount).equals(0)
errCount = o.report([
{pass: false, error: makeError("hey"), message: "hey"}
])
o(errCount).equals(1)
o(console.log.callCount).equals(2)
o(console.error.callCount).equals(1)
errCount = o.report([
{pass: false, error: makeError("hey"), message: "hey"},
{pass: true},
{pass: false, error: makeError("ho"), message: "ho"}
])
o(errCount).equals(2)
o(console.log.callCount).equals(3)
o(console.error.callCount).equals(3)
} catch (e) {
o(1).equals(0)("Error while testing the reporter")
}
console.log = log
console.error = error
})
})
o.spec("ospec", function() { o.spec("ospec", function() {
o.spec("sync", function() { o.spec("sync", function() {
@ -197,7 +202,7 @@ o.spec("ospec", function() {
done() done()
}) })
}) })
o.beforeEach(function(done) { o.beforeEach(function(done) {
o(b).equals(0) o(b).equals(0)
callAsync(function() { callAsync(function() {
@ -211,15 +216,15 @@ o.spec("ospec", function() {
done() done()
}) })
}) })
o("hooks work as intended the first time", function(done) { o("hooks work as intended the first time", function(done) {
callAsync(function() { callAsync(function() {
var spy = o.spy() var spy = o.spy()
spy(a) spy(a)
o(a).equals(1) o(a).equals(1)
o(b).equals(1) o(b).equals(1)
done() done()
}) })
}) })
@ -227,10 +232,10 @@ o.spec("ospec", function() {
callAsync(function() { callAsync(function() {
var spy = o.spy() var spy = o.spy()
spy(a) spy(a)
o(a).equals(1) o(a).equals(1)
o(b).equals(1) o(b).equals(1)
done() done()
}) })
}) })