Socket
Socket
Sign inDemoInstall

ospec

Package Overview
Dependencies
Maintainers
4
Versions
29
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ospec - npm Package Compare versions

Comparing version 4.0.1 to 4.1.0

changelog.md

815

ospec.js
"use strict"
/*
Ospec is made of four parts:
1. a test definition API That creates a spec/tests tree
2. a test runner that walks said spec tree
3. an assertion API that populates a results array
4. a reporter which presents the results
The tepmoral sequence at run time is 1 then (2 and 3), then 4
The various sections (and sub-sections thereof) share information through stack-managed globals
which are enumerated in the "Setup" section below.
there are three kind of data structures, that reflect the above segregation:
1. Specs, that group other specs and tasks
2. Tasks, that represent hooks and tests, and internal logic
3. Assertions which end up in the results array.
At run-time, the specs are converted to lists of task (one per entry in the spec)
In each of these tasks:
- sub-specs receive the same treament as their parent, when their turn comes.
- tests are also turned into lists of tasks [...beforeEach, test, ...afterEach]
*/
;(function(m) {

@@ -6,70 +32,165 @@ if (typeof module !== "undefined") module["exports"] = m()

})(function init(name) {
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
// # Setup
// const
var hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty
var hasSuiteName = arguments.length !== 0
var only = []
var ospecFileName = getStackName(ensureStackTrace(new Error), /[\/\\](.*?):\d+:\d+/)
var rootSpec = new Spec()
var subjects = []
// stack-managed globals
var globalBail
var globalContext = rootSpec
var globalDepth = 1
var globalFile
var globalTestOrHook = null
var globalTimeout = noTimeoutRightNow
var currentTestError = null
if (name != null) spec[name] = ctx = {}
var globalTimedOutAndPendingResolution = 0
try {throw new Error} catch (e) {
var ospecFileName = e.stack && (/[\/\\](.*?):\d+:\d+/).test(e.stack) ? e.stack.match(/[\/\\](.*?):\d+:\d+/)[1] : null
// Shared state, set only once, but initialization is delayed
var results, stats, timeoutStackName
// # General utils
function isRunning() {return results != null}
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
}
function noTimeoutRightNow() {
throw new Error("`o.timeout()` must be called synchronously from within a test definition or a hook")
}
function timeoutParamDeprecationNotice(n) {
console.error(new Error("`timeout()` as a test argument has been deprecated, use `o.timeout()`"))
o.timeout(n)
}
// TODO: handle async functions?
function validateDone(fn, error) {
if (error == null || fn.length === 0) return
var body = fn.toString()
// Don't change the RegExp by hand, it is generated by
// `scripts/build-done-parser.js`.
// If needed, update the script and paste its output here.
var arg = (body.match(/^(?:(?:function(?:\s|\/\*[^]*?\*\/|\/\/[^\n]*\n)*(?:\b[^\s(\/]+(?:\s|\/\*[^]*?\*\/|\/\/[^\n]*\n)*)?)?\((?:\s|\/\*[^]*?\*\/|\/\/[^\n]*\n)*)?([^\s{[),=\/]+)/) || []).pop()
if (arg) {
if(body.indexOf(arg) === body.lastIndexOf(arg)) {
var doneError = new Error
doneError.stack = "'" + arg + "()' should be called at least once\n" + o.cleanStackTrace(error)
throw doneError
}
} else {
console.warn("we couldn't determine the `done` callback name, please file a bug report at https://github.com/mithriljs/ospec/issues")
arg = "done"
}
return "`" + arg + "()` should only be called once"
}
// # Spec definition
function Spec() {
this.before = []
this.beforeEach = []
this.after = []
this.afterEach = []
this.specTimeout = null
this.customAssert = null
this.children = Object.create(null)
}
// Used for both user-defined tests and internal book keeping
// Internal tasks don't have an `err`. `hookName` is only defined
// for hooks
function Task(fn, err, hookName) {
// This test needs to be here rather than in `o("name", test(){})`
// in order to also cover nested hooks.
if (isRunning() && err != null) throw new Error("Test definitions and hooks shouldn't be nested. To group tests, use 'o.spec()'")
this.context = null
this.file = globalFile
// give tests an extra level of depth (simplifies bail out logic)
this.depth = globalDepth + (hookName == null ? 1 : 0)
this.doneTwiceError = validateDone(fn, err) || "A thenable should only be resolved once"
this.error = err
this.fn = fn
this.hookName = hookName
}
function hook(name) {
return function(predicate) {
if (globalContext[name].length > 0) throw new Error("Attempt to register o." + name + "() more than once. A spec can only have one hook of each kind")
globalContext[name][0] = new Task(predicate, ensureStackTrace(new Error), name)
}
}
function unique(subject) {
if (hasOwn.call(globalContext.children, subject)) {
console.warn("A test or a spec named '" + subject + "' was already defined in this spec")
console.warn(o.cleanStackTrace(ensureStackTrace(new Error)).split("\n")[0])
while (hasOwn.call(globalContext.children, subject)) subject += "*"
}
return subject
}
// # API
function o(subject, predicate) {
if (predicate === undefined) {
if (!isRunning()) throw new Error("Assertions should not occur outside test definitions")
return new Assert(subject)
return new Assertion(subject)
} else {
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))
globalContext.children[unique(subject)] = new Task(predicate, ensureStackTrace(new Error), null)
}
}
o.before = hook("\x01before")
o.after = hook("\x01after")
o.beforeEach = hook("\x01beforeEach")
o.afterEach = hook("\x01afterEach")
o.before = hook("before")
o.after = hook("after")
o.beforeEach = hook("beforeEach")
o.afterEach = hook("afterEach")
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 (globalContext.specTimeout != null) 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
globalContext.specTimeout = t
}
o.new = init
o.spec = function(subject, predicate) {
var parent = ctx
ctx = ctx[unique(subject)] = {}
predicate()
ctx = parent
if (isRunning()) throw new Error("`o.spec()` can't only be called at test definition time, not run time")
// stack managed globals
var parent = globalContext
var name = unique(subject)
globalContext = globalContext.children[name] = new Spec()
globalDepth++
try {
predicate()
} catch(e) {
console.error(e)
globalContext.children[name].children = {"> > BAILED OUT < < <": new Task(function(){
throw e
}, ensureStackTrace(new Error), null)}
}
globalDepth--
globalContext = parent
}
o.only = function(subject, predicate, silent) {
if (!silent) console.log(
highlight("/!\\ WARNING /!\\ o.only() mode") + "\n" + o.cleanStackTrace(ensureStackTrace(new Error)) + "\n",
cStyle("red"), ""
)
var onlyCalledAt = []
o.only = function(subject, predicate) {
onlyCalledAt.push(o.cleanStackTrace(ensureStackTrace(new Error)).split("\n")[0])
only.push(predicate)
o(subject, predicate)
}
o.spy = function(fn) {
var spy = function() {
spy.this = this
spy.args = [].slice.call(arguments)
spy.calls.push({this: this, args: spy.args})
spy.callCount++
if (fn) return fn.apply(this, arguments)
}
if (fn)
Object.defineProperties(spy, {
length: {value: fn.length},
name: {value: fn.name}
})
spy.args = []
spy.calls = []
spy.callCount = 0
return spy
}
o.cleanStackTrace = function(error) {
// For IE 10+ in quirks mode, and IE 9- in any mode, errors don't have a stack
if (error.stack == null) return ""
var i = 0, header = error.message ? error.name + ": " + error.message : error.name, stack
var header = error.message ? error.name + ": " + error.message : error.name, stack
// some environments add the name and message to the stack trace

@@ -84,41 +205,137 @@ if (error.stack.indexOf(header) === 0) {

// skip ospec-related entries on the stack
while (stack[i] != null && stack[i].indexOf(ospecFileName) !== -1) i++
// now we're in user code (or past the stack end)
return stack[i]
return stack.filter(function(line) { return line.indexOf(ospecFileName) === -1 }).join("\n")
}
o.timeout = function(n) {
globalTimeout(n)
}
// # Test runner
var stack = []
var scheduled = false
function cycleStack() {
try {
while (stack.length) stack.shift()()
} finally {
// Don't stop on error, but still let it propagate to the host as usual.
if (stack.length) setTimeout(cycleStack, 0)
else scheduled = false
}
}
var nextTickish = hasProcess
? process.nextTick
: typeof Promise === "function"
? Promise.prototype.then.bind(Promise.resolve())
: function fakeFastNextTick(next) {
if (!scheduled) {
scheduled = true
setTimeout(cycleStack, 0)
}
stack.push(next)
}
o.metadata = function(opts) {
if (arguments.length === 0) {
if (!isRunning()) throw new Error("getting `o.metadata()` is only allowed at test run time")
return {
file: globalTestOrHook.file,
name: globalTestOrHook.context
}
} else {
if (isRunning() || globalContext !== rootSpec) throw new Error("setting `o.metadata()` is only allowed at the root, at test definition time")
globalFile = opts.file
}
}
o.run = function(reporter) {
if (rootSpec !== globalContext) throw new Error("`o.run()` can't be called from within a spec")
if (isRunning()) throw new Error("`o.run()` has already been called")
results = []
start = new Date
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) // eslint-disable-line no-process-exit
}
})
}, null), 200 /*default timeout delay*/)
stats = {
asyncSuccesses: 0,
bailCount: 0,
onlyCalledAt: onlyCalledAt
}
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))
}
return tasks
}, []), spec["\x01after"] || [], finalize), defaultDelay)
if (hasSuiteName) {
var parent = new Spec()
parent.children[name] = rootSpec
}
var finalize = new Task(function() {
timeoutStackName = getStackName({stack: o.cleanStackTrace(ensureStackTrace(new Error))}, /([\w \.]+?:\d+:\d+)/)
if (typeof reporter === "function") reporter(results, stats)
else {
var errCount = o.report(results, stats)
if (hasProcess && errCount !== 0) process.exit(1) // eslint-disable-line no-process-exit
}
}, null, null)
// always async for consistent external behavior
// otherwise, an async test would release Zalgo
// https://blog.izs.me/2013/08/designing-apis-for-asynchrony
nextTickish(function () {
runSpec(hasSuiteName ? parent : rootSpec, [], [], finalize, 200 /*default timeout delay*/)
})
function runSpec(spec, beforeEach, afterEach, finalize, defaultDelay) {
var bailed = false
if (spec.specTimeout) defaultDelay = spec.specTimeout
// stack-managed globals
var previousBail = globalBail
globalBail = function() {bailed = true; stats.bailCount++}
var restoreStack = new Task(function() {
globalBail = previousBail
}, null, null)
beforeEach = [].concat(
beforeEach,
spec.beforeEach
)
afterEach = [].concat(
spec.afterEach,
afterEach
)
series(
[].concat(
spec.before,
Object.keys(spec.children).reduce(function(tasks, key) {
if (
// If in `only` mode, skip the tasks that are not flagged to run.
only.length === 0
|| only.indexOf(spec.children[key].fn) !== -1
// Always run specs though, in case there are `only` tests nested in there.
|| !(spec.children[key] instanceof Task)
) {
tasks.push(new Task(function(done) {
if (bailed) return done()
o.timeout(Infinity)
subjects.push(key)
var popSubjects = new Task(function pop() {subjects.pop(), done()}, null, null)
if (spec.children[key] instanceof Task) {
// this is a test
series(
[].concat(beforeEach, spec.children[key], afterEach, popSubjects),
defaultDelay
)
} else {
// a spec...
runSpec(spec.children[key], beforeEach, afterEach, popSubjects, defaultDelay)
}
}, null, null))
}
return tasks
}, []),
spec.after,
restoreStack,
finalize
),
defaultDelay
)
}
// Executes a list of tasks in series.
// This is quite convoluted because we handle both sync and async tasks.
// Async tasks can either use a legacy `done(error?)` API, or return a
// thenable, which may or may not behave like a Promise
function series(tasks, defaultDelay) {

@@ -131,98 +348,174 @@ var cursor = 0

// const
var task = tasks[cursor++]
var fn = task.fn
currentTestError = task.err
var timeout = 0, delay = defaultDelay, s = new Date
var current = cursor
var arg
var isHook = task.hookName != null
var isInternal = task.error == null
var taskStartTime = new Date
globalTimeout = setDelay
// let
var delay = defaultDelay
var isAsync = false
var isDone = false
var isFinalized = false
var timeout
var isDone = false
// public API, may only be called once from use code (or after returned Promise resolution)
function done(err) {
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)
if (!isInternal) {
globalTestOrHook = task
task.context = subjects.join(" > ")
if (isHook) {
task.context = "o." + task.hookName + Array.apply(null, {length: task.depth}).join("*") + "( " + task.context + " )"
}
}
// for internal use only
function finalizeAsync(err) {
if (err == null) {
if (task.err != null) succeed(new Assert)
globalTimeout = function timeout (t) {
if (typeof t !== "number") throw new Error("timeout() and o.timeout() expect a number as argument")
delay = t
}
try {
if (fn.length > 0) {
fn(done, timeoutParamDeprecationNotice)
} else {
if (err instanceof Error) fail(new Assert, err.message, err)
else fail(new Assert, String(err), null)
var p = fn()
if (p && p.then) {
// Use `_done`, not `finalize` here to defend against badly behaved thenables.
// Let it crash if `then()` doesn't work as expected.
p.then(function() { _done(null, false) }, function(e) {_done(e, true)})
} else {
finalize(null, false, false)
}
}
if (timeout !== undefined) timeout = clearTimeout(timeout)
if (current === cursor) next()
if (!isFinalized) {
// done()/_done() haven't been called synchronously
isAsync = true
startTimer()
}
}
catch (e) {
if (isInternal) throw e
else finalize(e, true, false)
}
globalTimeout = noTimeoutRightNow
// public API, may only be called once from user code (or after the resolution
// of a thenable that's been returned at the end of the test)
function done(err) {
// `!!err` would be more correct as far as node callback go, but we've been
// using a `err != null` test for a while and no one complained...
_done(err, err != null)
}
// common abstraction for node-style callbacks and thenables
function _done(err, threw) {
if (isDone) throw new Error(task.doneTwiceError)
isDone = true
if (isAsync && timeout === undefined) {
globalTimedOutAndPendingResolution--
console.warn(
task.context
+ "\n# elapsed: " + Math.round(new Date - taskStartTime)
+ "ms, expected under " + delay + "ms\n"
+ o.cleanStackTrace(task.error))
}
// temporary, for the "old style count" report
if (!threw && task.error != null) {stats.asyncSuccesses++}
if (!isFinalized) finalize(err, threw, false)
}
// called only for async tests
function startTimer() {
timeout = setTimeout(function() {
timeout = undefined
finalizeAsync("async test timed out after " + delay + "ms")
}, Math.min(delay, 2147483647))
globalTimedOutAndPendingResolution++
finalize("async test timed out after " + delay + "ms\nWarning: assertions starting with `???` may not be properly labelled", true, true)
}, Math.min(delay, 0x7fffffff))
}
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()
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
// common test finalization code path, for internal use only
function finalize(err, threw, isTimeout) {
if (isFinalized) {
// failsafe for hacking, should never happen in released code
throw new Error("Multiple finalization")
}
try {
fn(done, setDelay)
}
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
}
if (timeout === 0) {
startTimer()
}
} else {
try{
var p = fn()
if (p && p.then) {
startTimer()
p.then(function() { done() }, done)
} else {
nextTickish(next)
isFinalized = true
if (threw) {
if (err instanceof Error) fail(new Assertion().i, err.message, err)
else fail(new Assertion().i, String(err), null)
if (!isTimeout) {
globalBail()
if (task.hookName === "beforeEach") {
while (tasks[cursor].error != null && tasks[cursor].depth > task.depth) cursor++
}
}
} 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
}
if (timeout !== undefined) timeout = clearTimeout(timeout)
if (isAsync) next()
else nextTickish(next)
}
globalTimeout = noTimeoutRightNow
}
}
}
function unique(subject) {
if (hasOwn.call(ctx, subject)) {
console.warn("A test or a spec named `" + subject + "` was already defined")
while (hasOwn.call(ctx, subject)) subject += "*"
// #Assertions
function Assertion(value) {
this.value = value
this.i = results.length
results.push({
pass: null,
message: "Incomplete assertion in the test definition starting at...",
error: globalTestOrHook.error,
task: globalTestOrHook,
timeoutLimbo: globalTimedOutAndPendingResolution === 0,
// Deprecated
context: (globalTimedOutAndPendingResolution === 0 ? "" : "??? ") + globalTestOrHook.context,
testError: globalTestOrHook.error
})
}
function plainAssertion(verb, compare) {
return function(self, value) {
var success = compare(self.value, value)
var message = serialize(self.value) + "\n " + verb + "\n" + serialize(value)
if (success) succeed(self.i, message, null)
else fail(self.i, message, null)
}
return subject
}
function hook(name) {
return function(predicate) {
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] = new Task(predicate, ensureStackTrace(new Error))
function define(name, assertion) {
Assertion.prototype[name] = function assert(value) {
var self = this
assertion(self, value)
return function(message) {
results[self.i].message = message + "\n\n" + results[self.i].message
}
}
}
define("equals", "should equal", function(a, b) {return a === b})
define("notEquals", "should not equal", function(a, b) {return a !== b})
define("deepEquals", "should deep equal", deepEqual)
define("notDeepEquals", "should not deep equal", function(a, b) {return !deepEqual(a, b)})
define("throws", "should throw a", throws)
define("notThrows", "should not throw a", function(a, b) {return !throws(a, b)})
define("equals", plainAssertion("should equal", function(a, b) {return a === b}))
define("notEquals", plainAssertion("should not equal", function(a, b) {return a !== b}))
define("deepEquals", plainAssertion("should deep equal", deepEqual))
define("notDeepEquals", plainAssertion("should not deep equal", function(a, b) {return !deepEqual(a, b)}))
define("throws", plainAssertion("should throw a", throws))
define("notThrows", plainAssertion("should not throw a", function(a, b) {return !throws(a, b)}))
define("satisfies", function satisfies(self, check) {
try {
var res = check(self.value)
if (res.pass) succeed(self.i, String(res.message), null)
else fail(self.i, String(res.message), null)
} catch (e) {
results.pop()
throw e
}
})
define("notSatisfies", function notSatisfies(self, check) {
try {
var res = check(self.value)
if (!res.pass) succeed(self.i, String(res.message), null)
else fail(self.i, String(res.message), null)
} catch (e) {
results.pop()
throw e
}
})

@@ -235,2 +528,3 @@ function isArguments(a) {

}
function deepEqual(a, b) {

@@ -250,3 +544,3 @@ if (a === b) return true

}
if (a.length === b.length && (a instanceof Array && b instanceof Array || aIsArgs && bIsArgs)) {
if (a.length === b.length && (Array.isArray(a) && Array.isArray(b) || aIsArgs && bIsArgs)) {
var aKeys = Object.getOwnPropertyNames(a), bKeys = Object.getOwnPropertyNames(b)

@@ -260,3 +554,3 @@ if (aKeys.length !== bKeys.length) return false

if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer) {
if (typeof Buffer === "function" && a instanceof Buffer && b instanceof Buffer && a.length === b.length) {
for (var i = 0; i < a.length; i++) {

@@ -271,2 +565,3 @@ if (a[i] !== b[i]) return false

}
function throws(a, b){

@@ -285,34 +580,17 @@ try{

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 succeed(i, message, error) {
var result = results[i]
result.pass = true
result.message = message
// for notSatisfies. Use the task.error for other passing assertions
if (error != null) result.error = error
}
function Task(fn, err) {
this.fn = fn
this.err = err
function fail(i, message, error) {
var result = results[i]
result.pass = false
result.message = message
result.error = error != null ? error : ensureStackTrace(new Error)
}
function define(name, verb, compare) {
Assert.prototype[name] = function assert(value) {
var self = this
var message = serialize(self.value) + "\n " + verb + "\n" + serialize(value)
if (compare(self.value, value)) succeed(self, message)
else fail(self, message)
return function(message) {
if (!self.pass) self.message = message + "\n\n" + self.message
}
}
}
function succeed(assertion, message) {
results[assertion.i].pass = true
results[assertion.i].context = subjects.join(" > ")
results[assertion.i].message = message
}
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) {

@@ -324,5 +602,83 @@ if (hasProcess) return require("util").inspect(value) // eslint-disable-line global-require

}
function noTimeoutRightNow() {
throw new Error("o.timeout must be called snchronously from within a test definition or a hook")
// o.spy is functionally equivalent to this:
// the extra complexity comes from compatibility issues
// in ES5 environments where you can't overwrite fn.length
// o.spy = function(fn) {
// var spy = function() {
// spy.this = this
// spy.args = [].slice.call(arguments)
// spy.calls.push({this: this, args: spy.args})
// spy.callCount++
// if (fn) return fn.apply(this, arguments)
// }
// if (fn)
// Object.defineProperties(spy, {
// length: {value: fn.length},
// name: {value: fn.name}
// })
// spy.args = []
// spy.calls = []
// spy.callCount = 0
// return spy
// }
var spyFactoryCache = Object.create(null)
function makeSpyFactory(name, length) {
if (spyFactoryCache[name] == null) spyFactoryCache[name] = []
var args = Array.apply(null, {length: length}).map(
function(_, i) {return "_" + i}
).join(", ");
var code =
"'use strict';" +
"var spy = (0, function " + name + "(" + args + ") {" +
" return helper(this, [].slice.call(arguments), fn, spy)" +
"});" +
"return spy"
return spyFactoryCache[name][length] = new Function("fn", "helper", code)
}
function getOrMakeSpyFactory(name, length) {
return spyFactoryCache[name] && spyFactoryCache[name][length] || makeSpyFactory(name, length)
}
function spyHelper(self, args, fn, spy) {
spy.this = self
spy.args = args
spy.calls.push({this: self, args: args})
spy.callCount++
if (fn) return fn.apply(self, args)
}
var supportsFunctionMutations = false;
// eslint-disable-next-line no-empty, no-implicit-coercion
try {supportsFunctionMutations = !!Object.defineProperties(function(){}, {name: {value: "a"},length: {value: 1}})} catch(_){}
var supportsEval = false
// eslint-disable-next-line no-new-func, no-empty
try {supportsEval = Function("return true")()} catch(e){}
o.spy = function spy(fn) {
var name = "", length = 0
if (fn) name = fn.name, length = fn.length
var spy = (!supportsFunctionMutations && supportsEval)
? getOrMakeSpyFactory(name, length)(fn, spyHelper)
: function(){return spyHelper(this, [].slice.call(arguments), fn, spy)}
if (supportsFunctionMutations) Object.defineProperties(spy, {
name: {value: name},
length: {value: length}
})
spy.args = []
spy.calls = []
spy.callCount = 0
return spy
}
// Reporter
var colorCodes = {

@@ -333,2 +689,5 @@ red: "31m",

}
// console style for terminals
// see https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences
function highlight(message, color) {

@@ -338,33 +697,48 @@ var code = colorCodes[color] || colorCodes.red;

}
// console style for the Browsers
// see https://developer.mozilla.org/en-US/docs/Web/API/console#Styling_console_output
function cStyle(color, bold) {
return hasProcess||!color ? "" : "color:"+color+(bold ? ";font-weight:bold" : "")
}
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 onlyWarning(onlyCalledAt) {
var colors = Math.random() > 0.5
? {
term: "red2",
web: cStyle("red", true)
}
: {
term: "re",
web: cStyle("red")
}
if (onlyCalledAt && onlyCalledAt.length !== 0) {
console.warn(
highlight("\n/!\\ WARNING /!\\ o.only() called...\n", colors.term),
colors.web, ""
)
console.warn(onlyCalledAt.join("\n"))
console.warn(
highlight("\n/!\\ WARNING /!\\ o.only()\n", colors.term),
colors.web, ""
)
}
}
function getStackName(e, exp) {
return e.stack && exp.test(e.stack) ? e.stack.match(exp)[1] : null
}
o.report = function (results) {
var errCount = 0
o.report = function (results, stats) {
if (stats == null) stats = {bailCount: 0, asyncSuccesses: 0}
var errCount = -stats.bailCount
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 || ""
var couldHaveABetterStackTrace = !stackTrace || timeoutStackName != null && stackTrace.indexOf(timeoutStackName) !== -1 && stackTrace.indexOf("\n") === -1
if (couldHaveABetterStackTrace) stackTrace = r.task.error != null ? o.cleanStackTrace(r.task.error) : r.error.stack || ""
console.error(
(hasProcess ? "\n" : "") +
highlight(r.context + ":", "red2") + "\n" +
(r.task.timeoutLimbo ? "??? " : "") +
highlight(r.task.context + ":", "red2") + "\n" +
highlight(r.message, "red") +
(stackTrace ? "\n" + stackTrace + "\n" : ""),
cStyle("black", true), "", // reset to default
cStyle("black", true), cStyle(null), // reset to default
cStyle("red"), cStyle("black")

@@ -376,25 +750,34 @@ )

var pl = results.length === 1 ? "" : "s"
var resultSummary = (errCount === 0) ?
highlight((pl ? "All " : "The ") + results.length + " assertion" + pl + " passed", "green"):
highlight(errCount + " out of " + results.length + " assertion" + pl + " failed", "red2")
var runningTime = " in " + Math.round(Date.now() - start) + "ms"
console.log(
(hasProcess ? "––––––\n" : "") +
(name ? name + ": " : "") + resultSummary + runningTime,
cStyle((errCount === 0 ? "green" : "red"), true), ""
)
return errCount
}
var oldTotal = " (old style total: " + (results.length + stats.asyncSuccesses) + ")"
var total = results.length - stats.bailCount
var message = [], log = []
if (hasProcess) {
nextTickish = process.nextTick
} else {
nextTickish = function fakeFastNextTick(next) {
if (stack++ < 5000) next()
else setTimeout(next, stack = 0)
if (hasProcess) message.push("––––––\n")
if (name) message.push(name + ": ")
if (errCount === 0 && stats.bailCount === 0) {
message.push(highlight((pl ? "All " : "The ") + total + " assertion" + pl + " passed" + oldTotal, "green"))
log.push(cStyle("green" , true), cStyle(null))
} else if (errCount === 0) {
message.push((pl ? "All " : "The ") + total + " assertion" + pl + " passed" + oldTotal)
} else {
message.push(highlight(errCount + " out of " + total + " assertion" + pl + " failed" + oldTotal, "red2"))
log.push(cStyle("red" , true), cStyle(null))
}
if (stats.bailCount !== 0) {
message.push(highlight(". Bailed out " + stats.bailCount + (stats.bailCount === 1 ? " time" : " times"), "red"))
log.push(cStyle("red"), cStyle(null))
}
log.unshift(message.join(""))
console.log.apply(console, log)
onlyWarning(stats.onlyCalledAt)
return errCount + stats.bailCount
}
return o
})
{
"name": "ospec",
"version": "4.0.1",
"version": "4.1.0",
"description": "Noiseless testing framework",
"main": "ospec.js",
"directories": {
"test": "tests"
},
"keywords": [ "testing" ],
"unpkg": "ospec.js",
"keywords": [
"testing"
],
"author": "Leo Horie <leohorie@hotmail.com>",
"license": "MIT",
"bin": {
"ospec": "./bin/ospec"
},
"repository": "MithrilJS/mithril.js",
"files": [
"ospec.js",
"bin"
],
"bin": "./bin/ospec",
"repository": "github:MithrilJS/ospec",
"dependencies": {
"glob": "^7.1.3"
},
"scripts": {
"test": "ospec tests/test-*.js",
"test-api": "ospec tests/test-api.js",
"test-cli": "ospec tests/test-cli.js",
"lint": "eslint . bin/ospec"
},
"devDependencies": {
"compose-regexp": "0.4.0",
"eslint": "^6.8.0",
"ospec": "4.0.1"
}
}

@@ -1,4 +0,13 @@

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)
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) [![npm Downloads](https://img.shields.io/npm/dm/ospec.svg)](https://www.npmjs.com/package/ospec) [![Donate at OpenCollective](https://img.shields.io/opencollective/all/mithriljs.svg?colorB=brightgreen)](https://opencollective.com/mithriljs)
=====
<p align="center">
<a href="https://travis-ci.org/MithrilJS/ospec">
<img src="https://img.shields.io/travis/MithrilJS/ospec/master.svg" alt="Build Status">
</a>
<a href="https://gitter.im/mithriljs/mithril.js">
<img src="https://img.shields.io/gitter/room/mithriljs/mithril.js.svg" alt="Gitter" />
</a>
</p>
[About](#about) | [Usage](#usage) | [CLI](#command-line-interface) | [API](#api) | [Goals](#goals)

@@ -10,3 +19,3 @@

- ~360 LOC including the CLI runner
- ~580 LOC including the CLI runner
- terser and faster test code than with mocha, jasmine or tape

@@ -175,3 +184,3 @@ - test code reads like bullet points

```javascript
o.spec("a spec that must timeout quickly", function(done, timeout) {
o.spec("a spec that must timeout quickly", function() {
// wait 20ms before bailing out of the tests of this suite and

@@ -198,3 +207,3 @@ // its descendants

```javascript
o("setTimeout calls callback", function(done, timeout) {
o("setTimeout calls callback", function(done) {
o.timeout(500) //wait 500ms before bailing out of the test

@@ -363,6 +372,6 @@

Finally, you may choose to load files or modules before any tests run (**note:** always add `--require` AFTER match patterns):
Finally, you may choose to load files or modules before any tests run (**note:** always add `--preload` AFTER match patterns):
```
ospec --require esm
ospec --preload esm
```

@@ -373,5 +382,9 @@

```
ospec '**/*.test.js' --ignore 'folder1/**' --require esm ./my-file.js
ospec '**/*.test.js' --ignore 'folder1/**' --preload esm ./my-file.js
```
### native mjs and module support
For Node.js versions >= 13.2, `ospec` supports both ES6 modules and CommonJS packages out of the box. `--preload esm` is thus not needed in that case.
### Run ospec directly from the command line:

@@ -427,3 +440,3 @@

### void o(String title, Function([Function done [, Function timeout]]) assertions)
### void o(String title, Function([Function done]) assertions)

@@ -494,3 +507,3 @@ Defines a test.

### void o.before(Function([Function done [, Function timeout]]) setup)
### void o.before(Function([Function done]) setup)

@@ -503,3 +516,3 @@ Defines code to be run at the beginning of a test group

### void o.after(Function([Function done [, Function timeout]]) teardown)
### void o.after(Function([Function done) teardown)

@@ -512,3 +525,3 @@ Defines code to be run at the end of a test group

### void o.beforeEach(Function([Function done [, Function timeout]]) setup)
### void o.beforeEach(Function([Function done]) setup)

@@ -521,3 +534,3 @@ Defines code to be run before each test in a group

### void o.afterEach(Function([Function done [, Function timeout]]) teardown)
### void o.afterEach(Function([Function done]) teardown)

@@ -530,3 +543,3 @@ Defines code to be run after each test in a group

### void o.only(String title, Function([Function done [, Function timeout]]) assertions)
### void o.only(String title, Function([Function done]) assertions)

@@ -584,2 +597,6 @@ Declares that only a single test should be run, instead of all of them

### throwing Errors
When an error is thrown some tests may be skipped. See the "run time semantics" for a detailed description of the bailout mechanism.
---

@@ -659,4 +676,52 @@

---
## Run time model
### Definitions:
- A **test** is the function passed to `o("description", function test() {})`.
- A **hook** is a function passed to `o.before()`, `o.after()`. `o.beforeEach()` and `o.afterEach()`
- A **task** designates either a test or a hook.
- A given test and its associated `beforeEach` and `afterEach` hooks form a **streak**. The `beforeEach` hooks run outermost first, the `afterEach` run outermost last. The hooks are optional, and are tied at test-definition time in the `o.spec()` calls that enclose the test.
- A **spec** is a collection of streaks, specs, one `before` *hook* and one `after` *hook*. Each component is optional. Specs are defined with the `o.spec("spec name", function specDef() {})` calls.
### The three phases
For a given instance, an `ospec` run goes through three phases:
1) test definitions
1) test execution and results accumulation
1) results presentation
#### Test definition
This phase is synchronous. `o.spec("spec name", function specDef() {})`, `o("test name", function test() {})` and hooks calls generate a tree of specs and tests.
#### Test execution and results accumulation
At test-run time, for each spec, the `before` hook is called if present, then nested specs the streak of each test, in definition order, then the `after` hook, if present.
Test and hooks may contain assertions, which will populate the `results` array.
#### Results presentation
Once all tests have run or timed out, the results are presented.
### Throwing errors and spec bail out
While some testing libraries consider error thrown as assertions failure, `ospec` treats them as super-failures. Throwing will cause the current spec to be aborted, avoiding what can otherwise end up as pages of errors. What this means depends on when the error is thrown. Specifically:
- A syntax error in a file causes the file to be ignored by the runner.
- At test-definition time:
- An error thrown at the root of a file will cause subsequent tests and specs to be ignored
- An error thrown in a spec definition will cause the spec to be ignored.
- At test-run time:
- An error thrown in the `before` hook will cause the streaks and nested specs to be ignored. The `after` hook will run.
- An error thrown in a task...
- ...prevents further streaks and nested specs in the current spec from running. The `after` *hook* of the spec will run.
- ...if thrown in a `beforeEach` hook of a streak, causes the streak to be hollowed out. Hooks defined in nested scopes and the actual test will not run. The `afterEach` hookcorresponding to the one that crashed will run though as will those defined in outer scopes.
For every error thrown, a "bail out" failure is reported.
---

@@ -663,0 +728,0 @@

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc