Comparing version 2.1.0 to 3.0.0
@@ -5,5 +5,28 @@ # Change Log for ospec | ||
## Upcoming... | ||
_2018-xx-yy_ | ||
<!-- Add new lines here. Version number will be decided later --> | ||
## 3.0.0 | ||
_2018-06-20_ | ||
### Breaking | ||
- Better input checking to prevent misuses of the library. Misues of the library will now throw errors, rather than report failures. This may uncover bugs in your test suites. Since it is potentially a disruptive update this change triggers a semver major bump. ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
- Change the reserved character for hooks and test suite meta-information from `"__"` to `"\x01"`. Tests whose name start with `"\0x01"` will be rejected ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
### Features | ||
>>>>>>> [ospec/bin] Back to ES5 (and complimentary cleanup), fix #2160 | ||
- Give async timeout a stack trace that points to the problematic test ([#2154](https://github.com/MithrilJS/mithril.js/pull/2154) [@gilbert](github.com/gilbert), [#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
- deprecate the `timeout` parameter in async tests in favour of `o.timeout()` for setting the timeout delay. The `timeout` parameter still works for v3, and will be removed in v4 ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
- add `o.defaultTimeout()` for setting the the timeout delay for the current spec and its children ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
- adds the possibility to select more than one test with o.only ([#2171](https://github.com/MithrilJS/mithril.js/pull/2171)) | ||
### Bug fixes | ||
- Detect duplicate calls to `done()` properly [#2162](https://github.com/MithrilJS/mithril.js/issues/2162) ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
- Don't try to report internal errors as assertion failures, throw them instead ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
- Don't ignore, silently, tests whose name start with the test suite meta-information sequence (was `"__"` up to this version) ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
- Fix the `done()` call detection logic [#2158](https://github.com/MithrilJS/mithril.js/issues/2158) and assorted fixes (accept non-English names, tolerate comments) ([#2167](https://github.com/MithrilJS/mithril.js/pull/2167)) | ||
- Catch exceptions thrown in synchronous tests and report them as assertion failures ([#2171](https://github.com/MithrilJS/mithril.js/pull/2171)) | ||
- Fix a stack overflow when using `o.only()` with a large test suite ([#2171](https://github.com/MithrilJS/mithril.js/pull/2171)) | ||
## 2.1.0 | ||
_2018-05-25_ | ||
### Features | ||
- Pinpoint the `o.only()` call site ([#2157](https://github.com/MithrilJS/mithril.js/pull/2157)) | ||
@@ -10,0 +33,0 @@ - Improved wording, spacing and color-coding of report messages and errors ([#2147](https://github.com/MithrilJS/mithril.js/pull/2147), [@maranomynet](https://github.com/maranomynet)) |
248
ospec.js
@@ -1,2 +0,1 @@ | ||
/* eslint-disable global-require, no-bitwise, no-process-exit */ | ||
"use strict" | ||
@@ -7,24 +6,29 @@ ;(function(m) { | ||
})(function init(name) { | ||
var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty | ||
var spec = {}, subjects = [], results, only = [], ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty | ||
var ospecFileName = getStackName(ensureStackTrace(new Error), /[\/\\](.*?):\d+:\d+/), timeoutStackName | ||
var globalTimeout = noTimeoutRightNow | ||
var currentTestError = null | ||
if (name != null) spec[name] = ctx = {} | ||
try {throw new Error} catch (e) { | ||
var ospecFileName = e.stack && (/[\/\\](.*?):\d+:\d+/).test(e.stack) ? e.stack.match(/[\/\\](.*?):\d+:\d+/)[1] : null | ||
} | ||
function o(subject, predicate) { | ||
if (predicate === undefined) { | ||
if (results == null) throw new Error("Assertions should not occur outside test definitions") | ||
if (!isRunning()) throw new Error("Assertions should not occur outside test definitions") | ||
return new Assert(subject) | ||
} | ||
else if (results == null) { | ||
ctx[unique(subject)] = predicate | ||
} else { | ||
throw new Error("Test definition shouldn't be nested. To group tests use `o.spec()`") | ||
if (isRunning()) throw new Error("Test definitions and hooks shouldn't be nested. To group tests use `o.spec()`") | ||
subject = String(subject) | ||
if (subject.charCodeAt(0) === 1) throw new Error("test names starting with '\\x01' are reserved for internal use") | ||
ctx[unique(subject)] = new Task(predicate, ensureStackTrace(new Error)) | ||
} | ||
} | ||
o.before = hook("__before") | ||
o.after = hook("__after") | ||
o.beforeEach = hook("__beforeEach") | ||
o.afterEach = hook("__afterEach") | ||
o.before = hook("\x01before") | ||
o.after = hook("\x01after") | ||
o.beforeEach = hook("\x01beforeEach") | ||
o.afterEach = hook("\x01afterEach") | ||
o.specTimeout = function (t) { | ||
if (isRunning()) throw new Error("o.specTimeout() can only be called before o.run()") | ||
if (hasOwn.call(ctx, "\x01specTimeout")) throw new Error("A default timeout has already been defined in this context") | ||
if (typeof t !== "number") throw new Error("o.specTimeout() expects a number as argument") | ||
ctx["\x01specTimeout"] = t | ||
} | ||
o.new = init | ||
@@ -38,9 +42,8 @@ o.spec = function(subject, predicate) { | ||
o.only = function(subject, predicate, silent) { | ||
if (!silent) { | ||
console.log(highlight("/!\\ WARNING /!\\ o.only() mode")) | ||
try {throw new Error} catch (e) { | ||
console.log(this.cleanStackTrace(e) + "\n") | ||
} | ||
} | ||
o(subject, only = predicate) | ||
if (!silent) console.log( | ||
highlight("/!\\ WARNING /!\\ o.only() mode") + "\n" + o.cleanStackTrace(ensureStackTrace(new Error)) + "\n", | ||
cStyle("red"), "" | ||
) | ||
only.push(predicate) | ||
o(subject, predicate) | ||
} | ||
@@ -77,42 +80,42 @@ o.spy = function(fn) { | ||
// skip ospec-related entries on the stack | ||
while (stack[i].indexOf(ospecFileName) !== -1) i++ | ||
// now we're in user code | ||
while (stack[i] != null && stack[i].indexOf(ospecFileName) !== -1) i++ | ||
// now we're in user code (or past the stack end) | ||
return stack[i] | ||
} | ||
o.timeout = function(n) { | ||
globalTimeout(n) | ||
} | ||
o.run = function(reporter) { | ||
results = [] | ||
start = new Date | ||
test(spec, [], [], function() { | ||
test(spec, [], [], new Task(function() { | ||
setTimeout(function () { | ||
timeoutStackName = getStackName({stack: o.cleanStackTrace(ensureStackTrace(new Error))}, /([\w \.]+?:\d+:\d+)/) | ||
if (typeof reporter === "function") reporter(results) | ||
else { | ||
var errCount = o.report(results) | ||
if (hasProcess && errCount !== 0) process.exit(1) | ||
if (hasProcess && errCount !== 0) process.exit(1) // eslint-disable-line no-process-exit | ||
} | ||
}) | ||
}) | ||
}, null), 200 /*default timeout delay*/) | ||
function test(spec, pre, post, finalize) { | ||
pre = [].concat(pre, spec["__beforeEach"] || []) | ||
post = [].concat(spec["__afterEach"] || [], post) | ||
series([].concat(spec["__before"] || [], Object.keys(spec).map(function(key) { | ||
return function(done, timeout) { | ||
timeout(Infinity) | ||
if (key.slice(0, 2) === "__") return done() | ||
if (only !== null && spec[key] !== only && typeof only === typeof spec[key]) return done() | ||
subjects.push(key) | ||
var type = typeof spec[key] | ||
if (type === "object") test(spec[key], pre, post, pop) | ||
if (type === "function") series([].concat(pre, spec[key], post, pop)) | ||
function pop() { | ||
subjects.pop() | ||
done() | ||
} | ||
function test(spec, pre, post, finalize, defaultDelay) { | ||
if (hasOwn.call(spec, "\x01specTimeout")) defaultDelay = spec["\x01specTimeout"] | ||
pre = [].concat(pre, spec["\x01beforeEach"] || []) | ||
post = [].concat(spec["\x01afterEach"] || [], post) | ||
series([].concat(spec["\x01before"] || [], Object.keys(spec).reduce(function(tasks, key) { | ||
if (key.charCodeAt(0) !== 1 && (only.length === 0 || only.indexOf(spec[key].fn) !== -1 || !(spec[key] instanceof Task))) { | ||
tasks.push(new Task(function(done) { | ||
o.timeout(Infinity) | ||
subjects.push(key) | ||
var pop = new Task(function pop() {subjects.pop(), done()}, null) | ||
if (spec[key] instanceof Task) series([].concat(pre, spec[key], post, pop), defaultDelay) | ||
else test(spec[key], pre, post, pop, defaultDelay) | ||
}, null)) | ||
} | ||
}), spec["__after"] || [], finalize)) | ||
return tasks | ||
}, []), spec["\x01after"] || [], finalize), defaultDelay) | ||
} | ||
function series(fns) { | ||
function series(tasks, defaultDelay) { | ||
var cursor = 0 | ||
@@ -122,42 +125,57 @@ next() | ||
function next() { | ||
if (cursor === fns.length) return | ||
if (cursor === tasks.length) return | ||
var fn = fns[cursor++] | ||
var timeout = 0, delay = 200, s = new Date | ||
var task = tasks[cursor++] | ||
var fn = task.fn | ||
currentTestError = task.err | ||
var timeout = 0, delay = defaultDelay, s = new Date | ||
var current = cursor | ||
var arg | ||
globalTimeout = setDelay | ||
var isDone = false | ||
// public API, may only be called once from use code (or after returned Promise resolution) | ||
function done(err) { | ||
if (err) { | ||
if (err instanceof Error) record(err.message, err) | ||
else record(String(err)) | ||
subjects.pop() | ||
next() | ||
if (!isDone) isDone = true | ||
else throw new Error("`" + arg + "()` should only be called once") | ||
if (timeout === undefined) console.warn("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms\n" + o.cleanStackTrace(task.err)) | ||
finalizeAsync(err) | ||
} | ||
// for internal use only | ||
function finalizeAsync(err) { | ||
if (err == null) { | ||
if (task.err != null) succeed(new Assert) | ||
} else { | ||
if (err instanceof Error) fail(new Assert, err.message, err) | ||
else fail(new Assert, String(err), null) | ||
} | ||
if (timeout !== undefined) { | ||
timeout = clearTimeout(timeout) | ||
if (delay !== Infinity) record(null) | ||
if (!isDone) next() | ||
else throw new Error("`" + arg + "()` should only be called once") | ||
isDone = true | ||
} | ||
else console.log("# elapsed: " + Math.round(new Date - s) + "ms, expected under " + delay + "ms") | ||
if (timeout !== undefined) timeout = clearTimeout(timeout) | ||
if (current === cursor) next() | ||
} | ||
function startTimer() { | ||
timeout = setTimeout(function() { | ||
timeout = undefined | ||
record("async test timed out") | ||
next() | ||
finalizeAsync("async test timed out after " + delay + "ms") | ||
}, Math.min(delay, 2147483647)) | ||
} | ||
function setDelay (t) { | ||
if (typeof t !== "number") throw new Error("timeout() and o.timeout() expect a number as argument") | ||
delay = t | ||
} | ||
if (fn.length > 0) { | ||
var body = fn.toString() | ||
var arg = (body.match(/\(([\w$]+)/) || body.match(/([\w$]+)\s*=>/) || []).pop() | ||
if (body.indexOf(arg) === body.lastIndexOf(arg)) throw new Error("`" + arg + "()` should be called at least once") | ||
arg = (body.match(/^(.+?)(?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*=>/) || body.match(/\((?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*(.+?)(?:\s|\/\*[\s\S]*?\*\/|\/\/.*?\n)*[,\)]/) || []).pop() | ||
if (body.indexOf(arg) === body.lastIndexOf(arg)) { | ||
var e = new Error | ||
e.stack = "`" + arg + "()` should be called at least once\n" + o.cleanStackTrace(task.err) | ||
throw e | ||
} | ||
try { | ||
fn(done, function(t) {delay = t}) | ||
fn(done, setDelay) | ||
} | ||
catch (e) { | ||
done(e) | ||
if (task.err != null) finalizeAsync(e) | ||
// The errors of internal tasks (which don't have an Err) are ospec bugs and must be rethrown. | ||
else throw e | ||
} | ||
@@ -167,12 +185,18 @@ if (timeout === 0) { | ||
} | ||
} | ||
else { | ||
var p = fn() | ||
if (p && p.then) { | ||
startTimer() | ||
p.then(function() { done() }, done) | ||
} else { | ||
nextTickish(next) | ||
} else { | ||
try{ | ||
var p = fn() | ||
if (p && p.then) { | ||
startTimer() | ||
p.then(function() { done() }, done) | ||
} else { | ||
nextTickish(next) | ||
} | ||
} catch (e) { | ||
if (task.err != null) finalizeAsync(e) | ||
// The errors of internal tasks (which don't have an Err) are ospec bugs and must be rethrown. | ||
else throw e | ||
} | ||
} | ||
globalTimeout = noTimeoutRightNow | ||
} | ||
@@ -191,3 +215,3 @@ } | ||
if (ctx[name]) throw new Error("This hook should be defined outside of a loop or inside a nested test group:\n" + predicate) | ||
ctx[name] = predicate | ||
ctx[name] = new Task(predicate, ensureStackTrace(new Error)) | ||
} | ||
@@ -209,3 +233,3 @@ } | ||
if (a === b) return true | ||
if (a === null ^ b === null || a === undefined ^ b === undefined) return false | ||
if (a === null ^ b === null || a === undefined ^ b === undefined) return false // eslint-disable-line no-bitwise | ||
if (typeof a === "object" && typeof b === "object") { | ||
@@ -242,29 +266,33 @@ var aIsArgs = isArguments(a), bIsArgs = isArguments(b) | ||
function Assert(value) {this.value = value} | ||
function isRunning() {return results != null} | ||
function Assert(value) { | ||
this.value = value | ||
this.i = results.length | ||
results.push({pass: null, context: "", message: "Incomplete assertion in the test definition starting at...", error: currentTestError, testError: currentTestError}) | ||
} | ||
function Task(fn, err) { | ||
this.fn = fn | ||
this.err = err | ||
} | ||
function define(name, verb, compare) { | ||
Assert.prototype[name] = function assert(value) { | ||
if (compare(this.value, value)) record(null) | ||
else record(serialize(this.value) + "\n " + verb + "\n" + serialize(value)) | ||
if (compare(this.value, value)) succeed(this) | ||
else fail(this, serialize(this.value) + "\n " + verb + "\n" + serialize(value)) | ||
var self = this | ||
return function(message) { | ||
var result = results[results.length - 1] | ||
result.message = message + "\n\n" + result.message | ||
if (!self.pass) self.message = message + "\n\n" + self.message | ||
} | ||
} | ||
} | ||
function record(message, error) { | ||
var result = {pass: message === null} | ||
if (result.pass === false) { | ||
if (error == null) { | ||
error = new Error | ||
if (error.stack === undefined) new function() {try {throw error} catch (e) {error = e}} | ||
} | ||
result.context = subjects.join(" > ") | ||
result.message = message | ||
result.error = error | ||
} | ||
results.push(result) | ||
function succeed(assertion) { | ||
results[assertion.i].pass = true | ||
} | ||
function fail(assertion, message, error) { | ||
results[assertion.i].pass = false | ||
results[assertion.i].context = subjects.join(" > ") | ||
results[assertion.i].message = message | ||
results[assertion.i].error = error != null ? error : ensureStackTrace(new Error) | ||
} | ||
function serialize(value) { | ||
if (hasProcess) return require("util").inspect(value) | ||
if (hasProcess) return require("util").inspect(value) // eslint-disable-line global-require | ||
if (value === null || (typeof value === "object" && !(value instanceof Array)) || typeof value === "number") return String(value) | ||
@@ -274,2 +302,5 @@ else if (typeof value === "function") return value.name || "<anonymous function>" | ||
} | ||
function noTimeoutRightNow() { | ||
throw new Error("o.timeout must be called snchronously from within a test definition or a hook") | ||
} | ||
var colorCodes = { | ||
@@ -287,2 +318,10 @@ red: "31m", | ||
} | ||
function ensureStackTrace(error) { | ||
// mandatory to get a stack in IE 10 and 11 (and maybe other envs?) | ||
if (error.stack === undefined) try { throw error } catch(e) {return e} | ||
else return error | ||
} | ||
function getStackName(e, exp) { | ||
return e.stack && exp.test(e.stack) ? e.stack.match(exp)[1] : null | ||
} | ||
@@ -292,4 +331,11 @@ o.report = function (results) { | ||
for (var i = 0, r; r = results[i]; i++) { | ||
if (r.pass == null) { | ||
r.testError.stack = r.message + "\n" + o.cleanStackTrace(r.testError) | ||
r.testError.message = r.message | ||
throw r.testError | ||
} | ||
if (!r.pass) { | ||
var stackTrace = o.cleanStackTrace(r.error) | ||
var couldHaveABetterStackTrace = !stackTrace || timeoutStackName != null && stackTrace.indexOf(timeoutStackName) !== -1 | ||
if (couldHaveABetterStackTrace) stackTrace = r.testError != null ? o.cleanStackTrace(r.testError) : r.error.stack || "" | ||
console.error( | ||
@@ -296,0 +342,0 @@ (hasProcess ? "\n" : "") + |
{ | ||
"name": "ospec", | ||
"version": "2.1.0", | ||
"version": "3.0.0", | ||
"description": "Noiseless testing framework", | ||
@@ -5,0 +5,0 @@ "main": "ospec.js", |
@@ -167,18 +167,43 @@ ospec [![NPM Version](https://img.shields.io/npm/v/ospec.svg)](https://www.npmjs.com/package/ospec) [![NPM License](https://img.shields.io/npm/l/ospec.svg)](https://www.npmjs.com/package/ospec) | ||
By default, asynchronous tests time out after 20ms. This can be changed on a per-test basis using the `timeout` argument: | ||
#### Timeout delays | ||
By default, asynchronous tests time out after 200ms. You can change that default for the current test suite and | ||
its children by using the `o.specTimeout(delay)` function. | ||
```javascript | ||
o.spec("a spec that must timeout quickly", function(done, timeout) { | ||
// wait 20ms before bailing out of the tests of this suite and | ||
// its descendants | ||
o.specTimeout(20) | ||
o("some test", function(done) { | ||
setTimeout(done, 10) // this will pass | ||
}) | ||
o.spec("a child suite where the delay also applies", function () { | ||
o("some test", function(done) { | ||
setTimeout(done, 30) // this will time out. | ||
}) | ||
}) | ||
}) | ||
o.spec("a spec that uses the default delay", function() { | ||
// ... | ||
}) | ||
``` | ||
This can also be changed on a per-test basis using the `o.timeout(delay)` function from within a test: | ||
```javascript | ||
o("setTimeout calls callback", function(done, timeout) { | ||
timeout(50) //wait 50ms before bailing out of the test | ||
o.timeout(500) //wait 500ms before bailing out of the test | ||
setTimeout(done, 30) | ||
setTimeout(done, 300) | ||
}) | ||
``` | ||
Note that the `timeout` function call must be the first statement in its test. This currently does not work for promise tests. You can combine both methods to do this: | ||
Note that the `o.timeout` function call must be the first statement in its test. It also works with Promise-returning tests: | ||
```javascript | ||
o("promise test", function(done, timeout) { | ||
timeout(1000) | ||
someOtherAsyncFunctionThatTakes900ms().then(done) | ||
o("promise test", function() { | ||
o.timeout(1000) | ||
return someOtherAsyncFunctionThatTakes900ms() | ||
}) | ||
@@ -188,6 +213,5 @@ ``` | ||
```javascript | ||
o("promise test", async function(done, timeout) { | ||
timeout(1000) | ||
o("promise test", async function() { | ||
o.timeout(1000) | ||
await someOtherAsyncFunctionThatTakes900ms() | ||
done() | ||
}) | ||
@@ -252,8 +276,9 @@ ``` | ||
### Running only one test | ||
### Running only some tests | ||
A test can be temporarily made to run exclusively by calling `o.only()` instead of `o`. This is useful when troubleshooting regressions, to zero-in on a failing test, and to avoid saturating console log w/ irrelevant debug information. | ||
One or more tests can be temporarily made to run exclusively by calling `o.only()` instead of `o`. This is useful when troubleshooting regressions, to zero-in on a failing test, and to avoid saturating console log w/ irrelevant debug information. | ||
```javascript | ||
o.spec("math", function() { | ||
// will not run | ||
o("addition", function() { | ||
@@ -263,6 +288,16 @@ o(1 + 1).equals(2) | ||
//only this test will be run, regardless of how many groups there are | ||
// this test will be run, regardless of how many groups there are | ||
o.only("subtraction", function() { | ||
o(1 - 1).notEquals(2) | ||
}) | ||
// will not run | ||
o("multiplication", function() { | ||
o(2 * 2).equals(4) | ||
}) | ||
// this test will be run, regardless of how many groups there are | ||
o.only("division", function() { | ||
o(6 / 2).notEquals(2) | ||
}) | ||
}) | ||
@@ -542,5 +577,7 @@ ``` | ||
### Boolean result.pass | ||
### Boolean|Null result.pass | ||
True if the test passed. **No other keys will exist on the result if this value is true.** | ||
- `true` if the assertion passed. | ||
- `false` if the assertion failed. | ||
- `null` if the assertion was incomplete (`o("partial assertion) // and that's it`). | ||
@@ -551,6 +588,12 @@ --- | ||
The `Error` object explaining the reason behind a failure. | ||
The `Error` object explaining the reason behind a failure. If the assertion failed, the stack will point to the actuall error. If the assertion did pass or was incomplete, this field is identical to `result.testError`. | ||
--- | ||
### Error result.testError | ||
An `Error` object whose stack points to the test definition that wraps the assertion. Useful as a fallback because in some async cases the main may not point to test code. | ||
--- | ||
### String result.message | ||
@@ -580,3 +623,3 @@ | ||
A `>`-separated string showing the structure of the test specification. | ||
In case of failure, a `>`-separated string showing the structure of the test specification. | ||
In the below example, `result.context` would be `testing > rocks`. | ||
@@ -583,0 +626,0 @@ |
@@ -6,91 +6,138 @@ "use strict" | ||
new function(o) { | ||
o = o.new() | ||
o.spec("ospec", function() { | ||
o("skipped", function() { | ||
o(true).equals(false) | ||
// this throws an async error that can't be caught in browsers | ||
if (typeof process !== "undefined") { | ||
o("incomplete assertion", function(done) { | ||
var stackMatcher = /([\w\.\\\/\-]+):(\d+):/ | ||
// /!\ this test relies on the `new Error` expression being six lines | ||
// above the `oo("test", function(){...})` call. | ||
var matches = (new Error).stack.match(stackMatcher) | ||
if (matches != null) { | ||
var name = matches[1] | ||
var num = Number(matches[2]) | ||
} | ||
var oo = o.new() | ||
oo("test", function() { | ||
oo("incomplete") | ||
}) | ||
o.only(".only()", function() { | ||
o(2).equals(2) | ||
}, true) | ||
oo.run(function(results) { | ||
o(results.length).equals(1) | ||
o(results[0].message).equals("Incomplete assertion in the test definition starting at...") | ||
o(results[0].pass).equals(null) | ||
var stack = o.cleanStackTrace(results[0].testError) | ||
var matches2 = stack && stack.match(stackMatcher) | ||
if (matches != null && matches2 != null) { | ||
o(matches[1]).equals(name) | ||
o(Number(matches2[2])).equals(num + 6) | ||
} | ||
done() | ||
}) | ||
}) | ||
} | ||
o.run() | ||
}(o) | ||
o("o.only", function(done) { | ||
var oo = o.new() | ||
new function(o) { | ||
var clone = o.new() | ||
clone.spec("clone", function() { | ||
clone("fail", function() { | ||
clone(true).equals(false) | ||
oo.spec("won't run", function() { | ||
oo("nope, skipped", function() { | ||
o(true).equals(false) | ||
}) | ||
}) | ||
clone("pass", function() { | ||
clone(true).equals(true) | ||
oo.spec("ospec", function() { | ||
oo("skipped as well", function() { | ||
oo(true).equals(false) | ||
}) | ||
oo.only(".only()", function() { | ||
oo(2).equals(2) | ||
}, true) | ||
oo.only("another .only()", function(done) { | ||
done("that fails") | ||
}, true) | ||
}) | ||
// Predicate test passing on clone results | ||
o.spec("reporting", function() { | ||
o("reports per instance", function(done, timeout) { | ||
timeout(100) // Waiting on clone | ||
oo.run(function(results){ | ||
o(results.length).equals(2) | ||
o(results[0].pass).equals(true) | ||
o(results[1].pass).equals(false) | ||
clone.run(function(results) { | ||
o(typeof results).equals("object") | ||
o("length" in results).equals(true) | ||
o(results.length).equals(2)("Two results") | ||
done() | ||
}) | ||
}) | ||
o("error" in results[0] && "pass" in results[0]).equals(true)("error and pass keys present in failing result") | ||
o(!("error" in results[1]) && "pass" in results[1]).equals(true)("only pass key present in passing result") | ||
o(results[0].pass).equals(false)("Test meant to fail has failed") | ||
o(results[1].pass).equals(true)("Test meant to pass has passed") | ||
// Predicate test passing on clone results | ||
o.spec("reporting", function() { | ||
var oo | ||
o.beforeEach(function(){ | ||
oo = o.new() | ||
done() | ||
oo.spec("clone", function() { | ||
oo("fail", function() { | ||
oo(true).equals(false) | ||
}) | ||
oo("pass", function() { | ||
oo(true).equals(true) | ||
}) | ||
}) | ||
o("o.report() returns the number of failures", function () { | ||
var log = console.log, error = console.error | ||
console.log = o.spy() | ||
console.error = o.spy() | ||
}) | ||
o("reports per instance", function(done, timeout) { | ||
timeout(100) // Waiting on clone | ||
function makeError(msg) {try{throw msg ? new Error(msg) : new Error} catch(e){return e}} | ||
try { | ||
var errCount = o.report([{pass: true}, {pass: true}]) | ||
oo.run(function(results) { | ||
o(typeof results).equals("object") | ||
o("length" in results).equals(true) | ||
o(results.length).equals(2)("Two results") | ||
o(errCount).equals(0) | ||
o(console.log.callCount).equals(1) | ||
o(console.error.callCount).equals(0) | ||
o("error" in results[0] && "pass" in results[0]).equals(true)("error and pass keys present in failing result") | ||
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([ | ||
{pass: false, error: makeError("hey"), message: "hey"} | ||
]) | ||
done() | ||
}) | ||
}) | ||
o("o.report() returns the number of failures", function () { | ||
var log = console.log, error = console.error | ||
console.log = o.spy() | ||
console.error = o.spy() | ||
o(errCount).equals(1) | ||
o(console.log.callCount).equals(2) | ||
o(console.error.callCount).equals(1) | ||
function makeError(msg) {try{throw msg ? new Error(msg) : new Error} catch(e){return e}} | ||
try { | ||
var errCount = o.report([{pass: true}, {pass: true}]) | ||
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") | ||
} | ||
o(errCount).equals(0) | ||
o(console.log.callCount).equals(1) | ||
o(console.error.callCount).equals(0) | ||
console.log = log | ||
console.error = error | ||
}) | ||
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) | ||
}) | ||
o.spec("ospec", function() { | ||
o.spec("sync", function() { | ||
var a = 0, b = 0, illegalAssertionThrows = false | ||
var reservedTestNameTrows = false | ||
@@ -104,2 +151,3 @@ o.before(function() {a = 1}) | ||
try {o("illegal assertion")} catch (e) {illegalAssertionThrows = true} | ||
try {o("\x01reserved test name", function(){})} catch (e) {reservedTestNameTrows = true} | ||
@@ -112,2 +160,3 @@ o("assertions", function() { | ||
o(nestedTestDeclarationThrows).equals(true) | ||
o(reservedTestNameTrows).equals(true) | ||
@@ -186,42 +235,361 @@ var spy = o.spy() | ||
var a = 0, b = 0 | ||
o.after(function() { | ||
o(a).equals(0) | ||
o(b).equals(0) | ||
}) | ||
o.spec("", function(){ | ||
o.before(function(done) { | ||
callAsync(function() { | ||
a = 1 | ||
done() | ||
}) | ||
}) | ||
o.after(function(done) { | ||
callAsync(function() { | ||
a = 0 | ||
done() | ||
}) | ||
}) | ||
o.before(function(done) { | ||
callAsync(function() { | ||
a = 1 | ||
o.beforeEach(function(done) { | ||
o(b).equals(0) | ||
callAsync(function() { | ||
b = 1 | ||
done() | ||
}) | ||
}) | ||
o.afterEach(function(done) { | ||
callAsync(function() { | ||
b = 0 | ||
done() | ||
}) | ||
}) | ||
o("hooks work as intended the first time", function(done) { | ||
callAsync(function() { | ||
var spy = o.spy() | ||
spy(a) | ||
o(a).equals(1) | ||
o(b).equals(1) | ||
done() | ||
}) | ||
}) | ||
o("hooks work as intended the second time", function(done) { | ||
callAsync(function() { | ||
var spy = o.spy() | ||
spy(a) | ||
o(a).equals(1) | ||
o(b).equals(1) | ||
done() | ||
}) | ||
}) | ||
}) | ||
}) | ||
o.spec("throwing in test context is recoreded as a failure", function() { | ||
var oo | ||
o.beforeEach(function(){oo = o.new()}) | ||
o.afterEach(function() { | ||
oo.run(function(results) { | ||
o(results.length).equals(1) | ||
o(results[0].pass).equals(false) | ||
}) | ||
}) | ||
o("sync test", function() { | ||
oo("throw in sync test", function() {throw new Error}) | ||
}) | ||
o("async test", function() { | ||
oo("throw in async test", function(done) { | ||
throw new Error | ||
done() // eslint-disable-line no-unreachable | ||
}) | ||
}) | ||
}) | ||
o.spec("timeout", function () { | ||
o("when using done()", function(done) { | ||
var oo = o.new() | ||
var err | ||
// the success of this test is dependent on having the | ||
// oo() call three linew below this one | ||
try {throw new Error} catch(e) {err = e} | ||
if (err.stack) { | ||
var line = Number(err.stack.match(/:(\d+):/)[1]) | ||
oo("", function(oodone, timeout) { | ||
// oodone() keep this line for now | ||
timeout(1) | ||
}) | ||
oo.run((function(results) { | ||
o(results.length).equals(1) | ||
o(results[0].pass).equals(false) | ||
// todo test cleaned up results[0].error stack trace for the presence | ||
// of the timeout stack entry | ||
o(results[0].testError instanceof Error).equals(true) | ||
o(o.cleanStackTrace(results[0].testError).indexOf("test-ospec.js:" + (line + 3) + ":")).notEquals(-1) | ||
done() | ||
})) | ||
} else { | ||
done() | ||
} | ||
}) | ||
o("when using a thenable", function(done) { | ||
var oo = o.new() | ||
var err | ||
// the success of this test is dependent on having the | ||
// oo() call three linew below this one | ||
try {throw new Error} catch(e) {err = e} | ||
if (err.stack) { | ||
var line = Number(err.stack.match(/:(\d+):/)[1]) | ||
oo("", function() { | ||
oo.timeout(1) | ||
return {then: function(){}} | ||
}) | ||
oo.run((function(results) { | ||
o(results.length).equals(1) | ||
o(results[0].pass).equals(false) | ||
o(results[0].testError instanceof Error).equals(true) | ||
o(o.cleanStackTrace(results[0].testError).indexOf("test-ospec.js:" + (line + 3) + ":")).notEquals(-1) | ||
done() | ||
})) | ||
} else { | ||
done() | ||
} | ||
}) | ||
}) | ||
o.spec("o.timeout", function() { | ||
o("throws when called out of test definitions", function(done) { | ||
var oo = o.new() | ||
var count = 0 | ||
try { oo.timeout(1) } catch (e) { count++ } | ||
oo.spec("a spec", function() { | ||
try { oo.timeout(1) } catch (e) { count++ } | ||
}) | ||
oo("", function() { | ||
oo.timeout(30) | ||
return {then: function(f) {setTimeout(f)}} | ||
}) | ||
oo.run(function(){ | ||
o(count).equals(2) | ||
done() | ||
}) | ||
}) | ||
o.after(function(done) { | ||
callAsync(function() { | ||
a = 0 | ||
o("works", function(done) { | ||
var oo = o.new() | ||
var t = new Date | ||
oo("", function() { | ||
oo.timeout(10) | ||
return {then: function() {}} | ||
}) | ||
oo.run(function(){ | ||
o(new Date - t >= 10).equals(true) | ||
o(200 > new Date - t).equals(true) | ||
done() | ||
}) | ||
}) | ||
}) | ||
o.spec("o.specTimeout", function() { | ||
o("throws when called inside of test definitions", function(done) { | ||
var err | ||
var oo = o.new() | ||
oo("", function() { | ||
try { oo.specTimeout(5) } catch (e) {err = e} | ||
return {then: function(f) {setTimeout(f)}} | ||
}) | ||
oo.run(function(){ | ||
o(err instanceof Error).equals(true) | ||
o.beforeEach(function(done) { | ||
callAsync(function() { | ||
b = 1 | ||
done() | ||
}) | ||
}) | ||
o.afterEach(function(done) { | ||
callAsync(function() { | ||
b = 0 | ||
o("works", function(done) { | ||
var oo = o.new() | ||
var t | ||
oo.specTimeout(10) | ||
oo.beforeEach(function () { | ||
t = new Date | ||
}) | ||
oo.afterEach(function () { | ||
var diff = new Date - t | ||
o(diff >= 10).equals(true) | ||
o(diff < 200).equals(true) | ||
}) | ||
oo("", function() { | ||
oo(true).equals(true) | ||
return {then: function() {}} | ||
}) | ||
oo.run(function(results) { | ||
o(results.length).equals(2) | ||
o(results[0].pass).equals(true) | ||
o(results[1].pass).equals(false) | ||
done() | ||
}) | ||
}) | ||
o("The parent and sibling suites are not affected by the specTimeout", function(done) { | ||
var oo = o.new() | ||
var t | ||
o("async hooks", function(done) { | ||
callAsync(function() { | ||
var spy = o.spy() | ||
spy(a) | ||
oo.specTimeout(50) | ||
oo.beforeEach(function () { | ||
t = new Date | ||
}) | ||
oo.afterEach(function () { | ||
var diff = new Date - t | ||
o(diff >= 50).equals(true) | ||
o(diff < 80).equals(true) | ||
}) | ||
o(a).equals(b) | ||
o(a).equals(1)("a and b should be initialized") | ||
oo.spec("nested 1", function () { | ||
oo.specTimeout(80) | ||
}) | ||
oo("", function() { | ||
oo(true).equals(true) | ||
return {then: function() {}} | ||
}) | ||
oo.spec("nested 2", function () { | ||
oo.specTimeout(80) | ||
}) | ||
oo.spec("nested 3", function () { | ||
oo("", function() { | ||
oo(true).equals(true) | ||
return {then: function() {}} | ||
}) | ||
}) | ||
oo.run(function(results) { | ||
o(results.length).equals(4) | ||
o(results[0].pass).equals(true) | ||
o(results[1].pass).equals(false) | ||
o(results[2].pass).equals(true) | ||
o(results[3].pass).equals(false) | ||
done() | ||
}) | ||
}) | ||
o("nested suites inherit the specTimeout", function(done) { | ||
var oo = o.new() | ||
oo.specTimeout(50) | ||
oo.spec("nested", function () { | ||
oo.spec("deeply", function() { | ||
var t | ||
oo.beforeEach(function () { | ||
t = new Date | ||
}) | ||
oo.afterEach(function () { | ||
var diff = new Date - t | ||
o(diff >= 50).equals(true) | ||
o(diff < 80).equals(true) | ||
}) | ||
oo("", function() { | ||
oo(true).equals(true) | ||
return {then: function() {}} | ||
}) | ||
}) | ||
}) | ||
oo.run(function(results) { | ||
o(results.length).equals(2) | ||
o(results[0].pass).equals(true) | ||
o(results[1].pass).equals(false) | ||
done() | ||
}) | ||
}) | ||
}) | ||
o.spec("calling done() twice throws", function () { | ||
o("two successes", function(done) { | ||
var oo = o.new() | ||
var err = null | ||
oo("foo", function(oodone) { | ||
try { | ||
oodone() | ||
oodone() | ||
} catch (e) { | ||
err = e | ||
} | ||
o(err instanceof Error).equals(true) | ||
o(err.message).equals("`oodone()` should only be called once") | ||
}) | ||
oo.run(function(results) { | ||
o(results.length).equals(1) | ||
o(results[0].pass).equals(true) | ||
done() | ||
}) | ||
}) | ||
o("a success followed by an error", function(done) { | ||
var oo = o.new() | ||
var err = null | ||
oo("foo", function(oodone) { | ||
try { | ||
oodone() | ||
oodone("error") | ||
} catch (e) { | ||
err = e | ||
} | ||
o(err instanceof Error).equals(true) | ||
o(err.message).equals("`oodone()` should only be called once") | ||
}) | ||
oo.run(function(results) { | ||
o(results.length).equals(1) | ||
o(results[0].pass).equals(true) | ||
done() | ||
}) | ||
}) | ||
o("two errors", function(done) { | ||
var oo = o.new() | ||
var err = null | ||
oo("foo", function(oodone) { | ||
try { | ||
oodone("bar") | ||
oodone("baz") | ||
} catch (e) { | ||
err = e | ||
} | ||
o(err instanceof Error).equals(true) | ||
o(err.message).equals("`oodone()` should only be called once") | ||
}) | ||
oo.run(function(results) { | ||
o(results.length).equals(1) | ||
o(results[0].pass).equals(false) | ||
o(results[0].message).equals("bar") | ||
done() | ||
}) | ||
}) | ||
o("an error followed by a success", function(done) { | ||
var oo = o.new() | ||
var err = null | ||
oo("foo", function(oodone) { | ||
try { | ||
oodone("bar") | ||
oodone() | ||
} catch (e) { | ||
err = e | ||
} | ||
o(err instanceof Error).equals(true) | ||
o(err.message).equals("`oodone()` should only be called once") | ||
}) | ||
oo.run(function(results) { | ||
o(results.length).equals(1) | ||
o(results[0].pass).equals(false) | ||
o(results[0].message).equals("bar") | ||
done() | ||
}) | ||
}) | ||
}) | ||
o.spec("stack trace cleaner", function() { | ||
@@ -234,3 +602,3 @@ o("handles line breaks", function() { | ||
o(trace).notEquals("break") | ||
o(trace.includes("test-ospec.js")).equals(true) | ||
o(trace.indexOf("test-ospec.js") !== -1).equals(true) | ||
} | ||
@@ -287,1 +655,77 @@ }) | ||
}) | ||
o.spec("the done parser", function() { | ||
o("accepts non-English names", function() { | ||
var oo = o.new() | ||
var threw = false | ||
oo("test", function(完了) { | ||
oo(true).equals(true) | ||
完了() | ||
}) | ||
try {oo.run(function(){})} catch(e) {threw = true} | ||
o(threw).equals(false) | ||
}) | ||
o("tolerates comments", function() { | ||
var oo = o.new() | ||
var threw = false | ||
oo("test", function(/*hey | ||
*/ /**/ //ho | ||
done /*hey | ||
*/ /**/ //huuu | ||
, timeout | ||
) { | ||
timeout(5) | ||
oo(true).equals(true) | ||
done() | ||
}) | ||
try {oo.run(function(){})} catch(e) {threw = true} | ||
o(threw).equals(false) | ||
}) | ||
/*eslint-disable no-eval*/ | ||
try {eval("(()=>{})()"); o.spec("with ES6 arrow functions", function() { | ||
function getCommentContent(f) { | ||
f = f.toString() | ||
return f.slice(f.indexOf("/*") + 2, f.lastIndexOf("*/")) | ||
} | ||
o("has no false positives 1", function(){ | ||
var oo = o.new() | ||
var threw = false | ||
eval(getCommentContent(function(){/* | ||
oo( | ||
'Async test parser mistakenly identified 1st token after a parens to be `done` reference', | ||
done => { | ||
oo(threw).equals(false) | ||
done() | ||
} | ||
) | ||
*/})) | ||
try {oo.run(function(){})} catch(e) {threw = true} | ||
o(threw).equals(false) | ||
}) | ||
o("has no false negatives", function(){ | ||
var oo = o.new() | ||
var threw = false | ||
eval(getCommentContent(function(){/* | ||
oo( | ||
"Multiple references to the wrong thing doesn't fool the checker", | ||
done => { | ||
oo(threw).equals(false) | ||
oo(threw).equals(false) | ||
} | ||
) | ||
*/})) | ||
try {oo.run(function(){})} catch(e) {threw = true} | ||
o(threw).equals(true) | ||
}) | ||
o("isn't fooled by comments", function(){ | ||
var oo = o.new() | ||
var threw = false | ||
oo( | ||
"comments won't throw the parser off", | ||
eval("done /*hey*/ /**/ => {oo(threw).equals(false);done()}") | ||
) | ||
try {oo.run(function(){})} catch(e) {threw = true} | ||
o(threw).equals(false) | ||
}) | ||
})} catch (e) {/*ES5 env, or no eval, ignore*/} | ||
/*eslint-enable no-eval*/ | ||
}) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
56004
997
649
4