diff --git a/docs/change-log.md b/docs/change-log.md index 0fccbf1d..cfaa74e9 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -56,6 +56,7 @@ - Added support for custom reporters ([#2009](https://github.com/MithrilJS/mithril.js/pull/2020)) - Error handling for async tests with `done` callbacks supports error as first argument - Error messages which include newline characters do not swallow the stack trace [#1495](https://github.com/MithrilJS/mithril.js/issues/1495) ([#1984](https://github.com/MithrilJS/mithril.js/pull/1984), [@ RodericDay](https://github.com/RodericDay)) +- Make Ospec more [Flems](https://flems.io)-friendly (#2034) --- diff --git a/ospec/README.md b/ospec/README.md index f2610e0f..9b0b416c 100644 --- a/ospec/README.md +++ b/ospec/README.md @@ -7,7 +7,7 @@ Noiseless testing framework ## About -- ~180 LOC +- ~330 LOC including the CLI runner - terser and faster test code than with mocha, jasmine or tape - test code reads like bullet points - assertion code follows [SVO](https://en.wikipedia.org/wiki/Subject–verb–object) structure in present tense for terseness and readability @@ -311,7 +311,9 @@ ospec will automatically evaluate all `*.js` files in any folder named `/tests`. Ospec doesn't work when installed globally. Using global scripts is generally a bad idea since you can end up with different, incompatible versions of the same package installed locally and globally. -To work around this limitation, you can use [`npm-run`](https://www.npmjs.com/package/npm-run) which enables one to run the binaries of locally installed packages. +If you're using a recent version of npm (v5+), you can use run `npx ospec` from your project folder. + +Otherwise, to work around this limitation, you can use [`npm-run`](https://www.npmjs.com/package/npm-run) which enables one to run the binaries of locally installed packages. ``` npm install npm-run -g @@ -449,6 +451,13 @@ If running in Node.js, ospec will call `process.exit` after reporting results by default. If you specify a reporter, ospec will not do this and allow your reporter to respond to results in its own way. + +--- + +### Number o.report(results) + +The default reporter used by `o.run()` when none are provided. Returns the number of failures, doesn't exit Node.js by itself. It expects an array of [test result data](#result-data) as argument. + --- ### Function o.new() @@ -490,7 +499,7 @@ True if the test passed. **No other keys will exist on the result if this value ### Error result.error -The value of the stack property from the `Error` object explaining the reason behind a failure. +The `Error` object explaining the reason behind a failure. --- diff --git a/ospec/ospec.js b/ospec/ospec.js index 5b29ec84..9769bccd 100644 --- a/ospec/ospec.js +++ b/ospec/ospec.js @@ -1,11 +1,16 @@ /* eslint-disable global-require, no-bitwise, no-process-exit */ "use strict" - -module.exports = new function init(name) { - var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", reporter, hasOwn = ({}).hasOwnProperty +;(function(m) { +if (typeof module !== "undefined") module["exports"] = m() +else window.o = m() +})(function init(name) { + var spec = {}, subjects = [], results, only = null, ctx = spec, start, stack = 0, nextTickish, hasProcess = typeof process === "object", hasOwn = ({}).hasOwnProperty 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") @@ -49,14 +54,35 @@ module.exports = new function init(name) { spy.callCount = 0 return spy } - o.cleanStackTrace = function(stack) { - return stack.match(/^(?:(?!Error|[\/\\]ospec[\/\\]ospec\.js).)*$/gm).pop() + 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 + // some environments add the name and message to the stack trace + if (error.stack.indexOf(header) === 0) { + stack = error.stack.slice(header.length).split(/\r?\n/) + stack.shift() // drop the initial empty string + } else { + stack = error.stack.split(/\r?\n/) + } + if (ospecFileName == null) return stack.join("\n") + // skip ospec-related entries on the stack + while (stack[i].indexOf(ospecFileName) !== -1) i++ + // now we're in user code + return stack[i] } - o.run = function(_reporter) { + o.run = function(reporter) { results = [] start = new Date - reporter = _reporter - test(spec, [], [], report) + test(spec, [], [], function() { + setTimeout(function () { + if (typeof reporter === "function") reporter(results) + else { + var errCount = o.report(results) + if (hasProcess && errCount !== 0) process.exit(1) + } + }) + }) function test(spec, pre, post, finalize) { pre = [].concat(pre, spec["__beforeEach"] || []) @@ -221,7 +247,8 @@ module.exports = new function init(name) { } result.context = subjects.join(" > ") result.message = message - result.error = error.stack + result.error = error + } results.push(result) } @@ -235,16 +262,13 @@ module.exports = new function init(name) { return hasProcess ? "\x1b[31m" + message + "\x1b[0m" : "%c" + message + "%c " } - function report() { - var status = 0 - - if (typeof reporter === "function") return reporter(results) - + o.report = function (results) { + var errCount = 0 for (var i = 0, r; r = results[i]; i++) { if (!r.pass) { var stackTrace = o.cleanStackTrace(r.error) console.error(r.context + ":\n" + highlight(r.message) + (stackTrace ? "\n\n" + stackTrace + "\n\n" : ""), hasProcess ? "" : "color:red", hasProcess ? "" : "color:black") - status = 1 + errCount++ } } console.log( @@ -252,10 +276,10 @@ module.exports = new function init(name) { results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " + "of which " + results.filter(function(result){return result.error}).length + " failed" ) - if (hasProcess && status === 1) process.exit(1) + return errCount } - if(hasProcess) { + if (hasProcess) { nextTickish = process.nextTick } else { nextTickish = function fakeFastNextTick(next) { @@ -265,4 +289,4 @@ module.exports = new function init(name) { } return o -} +}) diff --git a/ospec/tests/test-ospec.js b/ospec/tests/test-ospec.js index e837049d..526671c6 100644 --- a/ospec/tests/test-ospec.js +++ b/ospec/tests/test-ospec.js @@ -49,6 +49,43 @@ new function(o) { 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() + + function makeError(msg) {try{throw msg ? new Error(msg) : new Error} catch(e){return e}} + try { + var errCount = o.report([{pass: true}, {pass: true}]) + + o(errCount).equals(0) + o(console.log.callCount).equals(1) + o(console.error.callCount).equals(0) + + errCount = o.report([ + {pass: false, error: makeError("hey"), message: "hey"} + ]) + + o(errCount).equals(1) + o(console.log.callCount).equals(2) + o(console.error.callCount).equals(1) + + errCount = o.report([ + {pass: false, error: makeError("hey"), message: "hey"}, + {pass: true}, + {pass: false, error: makeError("ho"), message: "ho"} + ]) + + o(errCount).equals(2) + o(console.log.callCount).equals(3) + o(console.error.callCount).equals(3) + } catch (e) { + o(1).equals(0)("Error while testing the reporter") + } + + console.log = log + console.error = error + }) }) }(o) @@ -188,7 +225,7 @@ o.spec("ospec", function() { try { throw new Error("line\nbreak") } catch(error) { - var trace = o.cleanStackTrace(error.stack) + var trace = o.cleanStackTrace(error) o(trace).notEquals("break") o(trace.includes("test-ospec.js")).equals(true) }