Socket
Socket
Sign inDemoInstall

ospec

Package Overview
Dependencies
11
Maintainers
7
Versions
28
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 4.1.7 to 4.2.0

353

ospec.js
"use strict"
// const LOG = console.log
// const p = (...args) => {
// LOG(...args)
// return args.pop()
// }
/*

@@ -43,10 +49,19 @@ Ospec is made of four parts:

// stack-managed globals
var globalBail
var globalContext = rootSpec
var globalDepth = 1
var globalFile
var globalTestOrHook = null
var globalTimeout = noTimeoutRightNow
var globalTimedOutAndPendingResolution = 0
// Are we in the process of baling out?
var $bail = false
// current spec
var $context = rootSpec
// spec nesting level
var $depth = 1
// the current file name
var $file
// the task (test/hook) that is currently running
var $task = null
// the current o.timeout implementation
var $timeout
// count the total amount of tests that timed out and didn't complete after the fact before the run ends
var $timedOutAndPendingResolution = 0
// are we using the v5+ API?
var $localAssertions = false
// Shared state, set only once, but initialization is delayed

@@ -68,5 +83,2 @@ var results, stats, timeoutStackName

function noTimeoutRightNow() {
throw new Error("`o.timeout()` must be called synchronously from within a test definition or a hook")
}

@@ -118,9 +130,11 @@ function timeoutParamDeprecationNotice(n) {

this.context = null
this.file = globalFile
this.file = $file
// 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.depth = $depth + (hookName == null ? 1 : 0)
this.doneTwiceError = !$localAssertions && validateDone(fn, err) || "A thenable should only be resolved once"
this.error = err
this.internal = err == null
this.fn = fn
this.hookName = hookName
this.localAssertions = $localAssertions
}

@@ -130,4 +144,4 @@

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)
if ($context[name].length > 0) throw new Error("Attempt to register o." + name + "() more than once. A spec can only have one hook of each kind")
$context[name][0] = new Task(predicate, ensureStackTrace(new Error), name)
}

@@ -137,6 +151,6 @@ }

function unique(subject) {
if (hasOwn.call(globalContext.children, subject)) {
if (hasOwn.call($context.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 += "*"
while (hasOwn.call($context.children, subject)) subject += "*"
}

@@ -150,8 +164,67 @@ return subject

if (!isRunning()) throw new Error("Assertions should not occur outside test definitions")
if ($task.localAssertions) throw new SyntaxError("Illegal global assertion, use a local `o()`")
return new Assertion(subject)
} else {
subject = String(subject)
globalContext.children[unique(subject)] = new Task(predicate, ensureStackTrace(new Error), null)
$context.children[unique(subject)] = new Task(predicate, ensureStackTrace(new Error), null)
}
}
function noSpecOrTestHasBeenDefined() {
return $context === rootSpec
&& Object.keys(rootSpec).every(k => {
const item = rootSpec[k]
return item == null || (Array.isArray(item) ? item : Object.keys(item)).length === 0
})
}
o.globalAssertions = function(cb) {
if (isRunning()) throw new SyntaxError("local/global modes can only be called before o.run()")
if (cb === "override"){
// escape hatch for the CLI test suite
// ideally we should use --preload that requires
// in depth rethinking of the CLI test suite that I'd rather
// avoid while changing the core at the same time.
$localAssertions = false
return
} else if (cb == null) {
if (noSpecOrTestHasBeenDefined()) {
$localAssertions = false
return
} else {
throw new SyntaxError("local/global mode can only be toggled before defining specs and tests")
}
} else {
const previous = $localAssertions
try {
$localAssertions = false
cb()
} finally {
$localAssertions = previous
}
}
}
o.localAssertions = function(cb) {
if (isRunning()) throw new SyntaxError("local/global modes can only be called before o.run()")
if (cb === "override"){
// escape hatch for the CLI test suite
// ideally we should use --preload that requires
// in depth rethinking of the CLI test suite that I'd rather
// avoid while changing the core at the same time.
$localAssertions = true
return
} if (cb == null) {
if (noSpecOrTestHasBeenDefined()) {
$localAssertions = true
} else {
throw new SyntaxError("local/global mode can only be toggled before defining specs and tests")
}
} else {
const previous = $localAssertions
try {
$localAssertions = true
cb()
} finally {
$localAssertions = previous
}
}
}

@@ -165,5 +238,5 @@ o.before = hook("before")

if (isRunning()) throw new Error("o.specTimeout() can only be called before o.run()")
if (globalContext.specTimeout != null) throw new Error("A default timeout has already been defined in this context")
if ($context.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")
globalContext.specTimeout = t
$context.specTimeout = t
}

@@ -176,16 +249,12 @@

// stack managed globals
var parent = globalContext
var parent = $context
var name = unique(subject)
globalContext = globalContext.children[name] = new Spec()
globalDepth++
$context = $context.children[name] = new Spec()
$depth++
try {
predicate()
} catch(e) {
console.error(e)
globalContext.children[name].children = {"> > BAILED OUT < < <": new Task(function(){
throw e
}, ensureStackTrace(new Error), null)}
} finally {
$depth--
$context = parent
}
globalDepth--
globalContext = parent
}

@@ -217,3 +286,3 @@

o.timeout = function(n) {
globalTimeout(n)
$timeout(n)
}

@@ -250,12 +319,13 @@

return {
file: globalTestOrHook.file,
name: globalTestOrHook.context
file: $task.file,
name: $task.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
if (isRunning() || $context !== rootSpec) throw new Error("setting `o.metadata()` is only allowed at the root, at test definition time")
$file = opts.file
}
}
o.run = function(reporter) {
if (rootSpec !== globalContext) throw new Error("`o.run()` can't be called from within a spec")
if (rootSpec !== $context) throw new Error("`o.run()` can't be called from within a spec")
if (isRunning()) throw new Error("`o.run()` has already been called")

@@ -274,3 +344,3 @@ results = []

var finalize = new Task(function() {
timeoutStackName = getStackName({stack: o.cleanStackTrace(ensureStackTrace(new Error))}, /([\w \.]+?:\d+:\d+)/)
timeoutStackName = getStackName({stack: o.cleanStackTrace(ensureStackTrace(new Error))}, /([w .]+?:d+:d+)/)
if (typeof reporter === "function") reporter(results, stats)

@@ -295,6 +365,6 @@ else {

// stack-managed globals
var previousBail = globalBail
globalBail = function() {bailed = true; stats.bailCount++}
var previousBail = $bail
$bail = function() {bailed = true; stats.bailCount++}
var restoreStack = new Task(function() {
globalBail = previousBail
$bail = previousBail
}, null, null)

@@ -310,3 +380,2 @@

)
series(

@@ -325,5 +394,4 @@ [].concat(

if (bailed) return done()
o.timeout(Infinity)
subjects.push(key)
var popSubjects = new Task(function pop() {subjects.pop(), done()}, null, null)
var popSubjects = new Task(function pop() {subjects.pop(); done()}, null, null)
if (spec.children[key] instanceof Task) {

@@ -366,3 +434,2 @@ // this is a test

var isHook = task.hookName != null
var isInternal = task.error == null
var taskStartTime = new Date

@@ -372,19 +439,116 @@

var delay = defaultDelay
var isAsync = false
var isDone = false
var isFinalized = false
var hasMovedOn = false
var hasConcluded = false
var timeout
if (!isInternal) {
globalTestOrHook = task
task.context = subjects.join(" > ")
if (isHook) {
task.context = "o." + task.hookName + Array.apply(null, {length: task.depth}).join("*") + "( " + task.context + " )"
var isDone = false
var isAsync = false
var promises = []
if (task.internal) {
// internal tasks still use the legacy done() system.
// handled hereafter in a simplified fashion, without timeout
// and bailout handling (let it crash)
if (fn.length === 0) {
fn()
next()
}
else fn(function() {
if (hasMovedOn) throw new Error("Internal Error, done() should only be called once")
hasMovedOn = true
next()
})
return
}
globalTimeout = function timeout (t) {
$task = task
task.context = subjects.join(" > ")
if (isHook) {
task.context = "o." + task.hookName + Array.apply(null, {length: task.depth}).join("*") + "( " + task.context + " )"
}
$timeout = function timeout (t) {
if (isAsync || hasConcluded || isDone) throw new Error("`o.timeout()` must be called synchronously from within a test definition or a hook")
if (typeof t !== "number") throw new Error("timeout() and o.timeout() expect a number as argument")
delay = t
}
if (task.localAssertions) {
var assert = function o(value) {
return new Assertion(value, task)
}
assert.metadata = o.metadata
assert.timeout = o.timeout
assert.spy = createSpy(function(self, args, fn, spy) {
if (hasConcluded) fail(new Assertion().i, "spy ran after its test was concluded\n"+fn.toString())
return globalSpyHelper(self, args, fn, spy)
})
assert.o = assert
Object.defineProperty(assert, "done", {get(){
let f, r
promises.push(new Promise((_f, _r)=>{f = _f, r = _r}))
function done(x){
return x == null ? f() : r(x)
}
return done
}})
// runs when a test had an error or returned a promise
const conclude = (err, threw) => {
if (threw) {
if (err instanceof Error) fail(new Assertion().i, err.message, err)
else fail(new Assertion().i, String(err), null)
$bail()
if (task.hookName === "beforeEach") {
while (!task.internal && tasks[cursor].depth > task.depth) cursor++
}
}
if (timeout !== undefined) {
timeout = clearTimeout(timeout)
}
hasConcluded = true
// if the timeout already expired, the suite has moved on.
// Doing it again would be a bug.
if (!hasMovedOn) moveOn()
}
// hops on to the next task after either conclusion or timeout,
// whichever comes first
const moveOn = () => {
hasMovedOn = true
if (isAsync) next()
else nextTickish(next)
}
const startTimer = () => {
timeout = setTimeout(function() {
timeout = undefined
fail(new Assertion().i, "async test timed out after " + delay + "ms", null)
moveOn()
}, Math.min(delay, 0x7fffffff))
}
try {
var result = fn(assert)
if (result != null && typeof result.then === 'function') {
// normalize thenables so that we only conclude once
promises.push(Promise.resolve(result))
}
if (promises.length > 0) {
Promise.all(promises).then(
function() {conclude()},
function(e) {conclude(e,true)}
)
isAsync = true
startTimer()
} else {
hasConcluded = true
moveOn()
}
} catch(e) {
conclude(e, true)
}
return
}
// for the legacy API
try {

@@ -394,7 +558,7 @@ if (fn.length > 0) {

} else {
var p = fn()
if (p && p.then) {
var prm = fn()
if (prm && prm.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)})
prm.then(function() { _done(null, false) }, function(e) {_done(e, true)})
} else {

@@ -404,3 +568,3 @@ finalize(null, false, false)

}
if (!isFinalized) {
if (!hasMovedOn) {
// done()/_done() haven't been called synchronously

@@ -412,6 +576,4 @@ isAsync = true

catch (e) {
if (isInternal) throw e
else finalize(e, true, false)
finalize(e, true, false)
}
globalTimeout = noTimeoutRightNow

@@ -430,3 +592,3 @@ // public API, may only be called once from user code (or after the resolution

if (isAsync && timeout === undefined) {
globalTimedOutAndPendingResolution--
$timedOutAndPendingResolution--
console.warn(

@@ -440,3 +602,3 @@ task.context

if (!isFinalized) finalize(err, threw, false)
if (!hasMovedOn) finalize(err, threw, false)
}

@@ -447,3 +609,3 @@ // called only for async tests

timeout = undefined
globalTimedOutAndPendingResolution++
$timedOutAndPendingResolution++
finalize("async test timed out after " + delay + "ms\nWarning: assertions starting with `???` may not be properly labelled", true, true)

@@ -454,7 +616,7 @@ }, Math.min(delay, 0x7fffffff))

function finalize(err, threw, isTimeout) {
if (isFinalized) {
if (hasMovedOn) {
// failsafe for hacking, should never happen in released code
throw new Error("Multiple finalization")
}
isFinalized = true
hasMovedOn = true

@@ -465,5 +627,5 @@ if (threw) {

if (!isTimeout) {
globalBail()
$bail()
if (task.hookName === "beforeEach") {
while (tasks[cursor].error != null && tasks[cursor].depth > task.depth) cursor++
while (!task.internal != null && tasks[cursor].depth > task.depth) cursor++
}

@@ -488,8 +650,8 @@ }

message: "Incomplete assertion in the test definition starting at...",
error: globalTestOrHook.error,
task: globalTestOrHook,
timeoutLimbo: globalTimedOutAndPendingResolution === 0,
error: $task.error,
task: $task,
timeoutLimbo: $timedOutAndPendingResolution === 0,
// Deprecated
context: (globalTimedOutAndPendingResolution === 0 ? "" : "??? ") + globalTestOrHook.context,
testError: globalTestOrHook.error
context: ($timedOutAndPendingResolution === 0 ? "" : "??? ") + $task.context,
testError: $task.error
})

@@ -513,2 +675,4 @@ }

if (Array.isArray(message)) {
// We got a tagged template literal,
// we'll interpolate the dynamic values.
var args = arguments

@@ -538,2 +702,3 @@ message = message.reduce(function(acc, v, i) {return acc + args[i] + v})

})
Assertion.prototype._ = Assertion.prototype.satisfies
define("notSatisfies", function notSatisfies(self, check) {

@@ -549,3 +714,2 @@ try {

})
function isArguments(a) {

@@ -561,3 +725,3 @@ if ("callee" in a) {

}
function deepEqual(a, b) {

@@ -681,3 +845,3 @@ if (a === b) return true

function spyHelper(self, args, fn, spy) {
function globalSpyHelper(self, args, fn, spy) {
spy.this = self

@@ -699,19 +863,23 @@ spy.args = args

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
function createSpy(helper) {
return function spy(fn) {
var name = "", length = 0
if (fn) name = fn.name, length = fn.length
var spy = (!supportsFunctionMutations && supportsEval)
? getOrMakeSpyFactory(name, length)(fn, helper)
: function(){return helper(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
}
}
o.spy = createSpy(globalSpyHelper)
// Reporter

@@ -724,2 +892,5 @@ var colorCodes = {

// this is needed to work around the formating done by node see https://nodejs.org/api/util.html#utilformatformat-args
function escapePercent(x){return String(x).replace(/%/g, "%%")}
// console style for terminals

@@ -729,3 +900,3 @@ // see https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences

var code = colorCodes[color] || colorCodes.red;
return hasProcess ? (process.stdout.isTTY ? "\x1b[" + code + message + "\x1b[0m" : message) : "%c" + message + "%c "
return hasProcess ? (process.stdout.isTTY ? "\x1b[" + code + escapePercent(message) + "\x1b[0m" : escapePercent(message)) : "%c" + message + "%c "
}

@@ -732,0 +903,0 @@

{
"name": "ospec",
"version": "4.1.7",
"version": "4.2.0",
"description": "Noiseless testing framework",

@@ -25,6 +25,6 @@ "main": "ospec.js",

"test": "ospec-stable tests/test-*.js",
"test-api": "ospec-stable tests/test-api.js",
"test-api": "ospec-stable tests/test-api-*.js",
"test-cli": "ospec-stable tests/test-cli.js",
"self-test": "node ./bin/ospec tests/test-*.js",
"self-test-api": "node ./bin/ospec tests/test-api.js",
"self-test-api": "node ./bin/ospec tests/test-api-*.js",
"self-test-cli": "node ./bin/ospec tests/test-cli.js",

@@ -38,4 +38,4 @@ "lint": "eslint --cache --ignore-pattern \"tests/fixtures/**/*.*\" . bin/ospec",

"eslint": "^6.8.0",
"ospec-stable": "npm:ospec@4.1.5"
"ospec-stable": "npm:ospec@4.1.7"
}
}

@@ -15,3 +15,3 @@ # ospec

- ~660 LOC including the CLI runner
- ~1100 LOC including the CLI runner<sup>1</sup>
- terser and faster test code than with mocha, jasmine or tape

@@ -30,2 +30,4 @@ - test code reads like bullet points

Note: <sup>1</sup> ospec is currently in the process of changing some of its API surface. The legacy and updated APIs are both implemented right now to ease the transition, once legacy code has been removed we'll clock around 800 LOC.
## Usage

@@ -717,6 +719,8 @@

Ospec started as a bare bones test runner optimized for Leo Horie to write Mithril v1 with as little hasle as possible. It has since grown in capabilities and polish, and while we tried to keep some of the original spirit, the current incarnation is not as radically minimalist as the original. The state of the art in testing has also moved with the dominance of Jest over Jasmine and Mocha, and now Vitest coming up the horizon.
- Do the most common things that the mocha/chai/sinon triad does without having to install 3 different libraries and several dozen dependencies
- Disallow configuration in test-space:
- Limit configuration in test-space:
- Disallow ability to pick between API styles (BDD/TDD/Qunit, assert/should/expect, etc)
- Disallow ability to add custom assertion types
- No "magic" plugin system with global reach. Custom assertions need to be imported or defined lexically (e.g. in `o(value)._(matches(refence))`, `matches` can be resolved in file).
- Provide a default simple reporter

@@ -726,3 +730,3 @@ - Make assertion code terse, readable and self-descriptive

Explicitly disallowing modularity and configuration in test-space has a few benefits:
These restrictions have a few benefits:

@@ -729,0 +733,0 @@ - tests always look the same, even across different projects and teams

SocketSocket SOC 2 Logo

Product

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

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc