Merge branch 'next'
This commit is contained in:
commit
2635070734
79 changed files with 7993 additions and 3507 deletions
148
ospec/README.md
148
ospec/README.md
|
|
@ -1,13 +1,13 @@
|
|||
ospec [](https://www.npmjs.com/package/ospec) [](https://www.npmjs.com/package/ospec)
|
||||
=====
|
||||
|
||||
[About](#about) | [Usage](#usage) | [API](#api) | [Goals](#goals)
|
||||
[About](#about) | [Usage](#usage) | [CLI](#command-line-interface) | [API](#api) | [Goals](#goals)
|
||||
|
||||
Noiseless testing framework
|
||||
|
||||
## About
|
||||
|
||||
- ~330 LOC including the CLI runner
|
||||
- ~360 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
|
||||
|
|
@ -111,6 +111,7 @@ o.spec("call()", function() {
|
|||
|
||||
o(spy.callCount).equals(1)
|
||||
o(spy.args[0]).equals(1)
|
||||
o(spy.calls[0]).deepEquals([1])
|
||||
})
|
||||
})
|
||||
```
|
||||
|
|
@ -164,30 +165,54 @@ o("promise test", async function() {
|
|||
})
|
||||
```
|
||||
|
||||
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()
|
||||
})
|
||||
```
|
||||
|
||||
```javascript
|
||||
o("promise test", async function(done, timeout) {
|
||||
timeout(1000)
|
||||
o("promise test", async function() {
|
||||
o.timeout(1000)
|
||||
await someOtherAsyncFunctionThatTakes900ms()
|
||||
done()
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -247,20 +272,31 @@ o.spec("math", function() {
|
|||
})
|
||||
```
|
||||
|
||||
### 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() {
|
||||
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)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
|
|
@ -288,28 +324,64 @@ _o("a test", function() {
|
|||
_o.run()
|
||||
```
|
||||
|
||||
### Running the test suite from the command-line
|
||||
## Command Line Interface
|
||||
|
||||
ospec will automatically evaluate all `*.js` files in any folder named `/tests`.
|
||||
|
||||
`o.run()` is automatically called by the cli - no need to call it in your test code.
|
||||
|
||||
#### Create an npm script in your package:
|
||||
Create a script in your package.json:
|
||||
```
|
||||
"scripts": {
|
||||
...
|
||||
"test": "ospec",
|
||||
...
|
||||
}
|
||||
```
|
||||
...and run it from the command line:
|
||||
|
||||
```
|
||||
$ npm test
|
||||
$ npm test
|
||||
```
|
||||
|
||||
#### Direct use from the command line
|
||||
**NOTE:** `o.run()` is automatically called by the cli - no need to call it in your test code.
|
||||
|
||||
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.
|
||||
### CLI Options
|
||||
|
||||
Running ospec without arguments is equivalent to running `ospec '**/tests/**/*.js'`. In english, this tells ospec to evaluate all `*.js` files in any sub-folder named `tests/` (the `node_modules` folder is always excluded).
|
||||
|
||||
If you wish to change this behavior, just provide one or more glob match patterns:
|
||||
|
||||
```
|
||||
ospec '**/spec/**/*.js' '**/*.spec.js'
|
||||
```
|
||||
|
||||
You can also provide ignore patterns (note: always add `--ignore` AFTER match patterns):
|
||||
|
||||
```
|
||||
ospec --ignore 'folder1/**' 'folder2/**'
|
||||
```
|
||||
|
||||
Finally, you may choose to load files or modules before any tests run (**note:** always add `--require` AFTER match patterns):
|
||||
|
||||
```
|
||||
ospec --require esm
|
||||
```
|
||||
|
||||
Here's an example of mixing them all together:
|
||||
|
||||
```
|
||||
ospec '**/*.test.js' --ignore 'folder1/**' --require esm ./my-file.js
|
||||
```
|
||||
|
||||
### Run ospec directly from the command line:
|
||||
|
||||
ospec comes with an executable named `ospec`. NPM auto-installs local binaries to `./node_modules/.bin/`. You can run ospec by running `./node_modules/.bin/ospec` from your project root, but there are more convenient methods to do so that we will soon describe.
|
||||
|
||||
ospec doesn't work when installed globally (`npm install -g`). 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.
|
||||
|
||||
Here are different ways of running ospec from the command line. This knowledge applies to not just ospec, but any locally installed npm binary.
|
||||
|
||||
#### npx
|
||||
|
||||
If you're using a recent version of npm (v5+), you can use run `npx ospec` from your project folder.
|
||||
|
||||
#### npm-run
|
||||
|
||||
If you're using a recent version of npm (v5+), you can use run `npx ospec` from your project folder.
|
||||
|
||||
|
|
@ -325,6 +397,16 @@ Then, from a project that has ospec installed as a (dev) dependency:
|
|||
npm-run ospec
|
||||
```
|
||||
|
||||
#### PATH
|
||||
|
||||
If you understand how your system's PATH works (e.g. for [OSX](https://coolestguidesontheplanet.com/add-shell-path-osx/)), then you can add the following to your PATH...
|
||||
|
||||
```
|
||||
export PATH=./node_modules/.bin:$PATH
|
||||
```
|
||||
|
||||
...and you'll be able to run `ospec` without npx, npm, etc. This one-time setup will also work with other binaries across all your node projects, as long as you run binaries from the root of your projects.
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
|
@ -491,15 +573,23 @@ o.run(function(results) {
|
|||
|
||||
---
|
||||
|
||||
### 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`).
|
||||
|
||||
---
|
||||
|
||||
### Error result.error
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -527,7 +617,7 @@ o.spec("message", function() {
|
|||
|
||||
### String result.context
|
||||
|
||||
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`.
|
||||
|
||||
```javascript
|
||||
|
|
|
|||
70
ospec/bin/ospec
Normal file → Executable file
70
ospec/bin/ospec
Normal file → Executable file
|
|
@ -1,48 +1,42 @@
|
|||
#!/usr/bin/env node
|
||||
"use strict"
|
||||
|
||||
var fs = require("fs")
|
||||
var path = require("path")
|
||||
|
||||
var o = require("../ospec")
|
||||
var path = require("path")
|
||||
var glob = require("glob")
|
||||
|
||||
function traverseDirectory(pathname, callback) {
|
||||
pathname = pathname.replace(/\\/g, "/")
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.lstat(pathname, function(err, stat) {
|
||||
if (err) reject(err)
|
||||
if (stat && stat.isDirectory()) {
|
||||
fs.readdir(pathname, function(err, pathnames) {
|
||||
if (err) reject(err)
|
||||
var promises = []
|
||||
for (var i = 0; i < pathnames.length; i++) {
|
||||
if (pathnames[i] === "node_modules") continue
|
||||
if (pathnames[i][0] === ".") continue
|
||||
pathnames[i] = path.join(pathname, pathnames[i])
|
||||
promises.push(traverseDirectory(pathnames[i], callback))
|
||||
}
|
||||
callback(pathname, stat, pathnames)
|
||||
resolve(Promise.all(promises))
|
||||
})
|
||||
}
|
||||
else {
|
||||
callback(pathname, stat)
|
||||
resolve(pathname)
|
||||
}
|
||||
})
|
||||
|
||||
function parseArgs(argv) {
|
||||
argv = ["--globs"].concat(argv.slice(2))
|
||||
var args = {}
|
||||
var name
|
||||
argv.forEach(function(arg) {
|
||||
if (/^--/.test(arg)) {
|
||||
name = arg.substr(2)
|
||||
args[name] = args[name] || []
|
||||
} else {
|
||||
args[name].push(arg)
|
||||
}
|
||||
})
|
||||
return args
|
||||
}
|
||||
|
||||
traverseDirectory(".", function(pathname) {
|
||||
if (pathname.match(/(?:^|\/)tests\/.*\.js$/)) {
|
||||
require(path.normalize(process.cwd()) + "/" + pathname) // eslint-disable-line global-require
|
||||
}
|
||||
})
|
||||
.then(o.run)
|
||||
.catch(function(e) {
|
||||
console.log(e.stack)
|
||||
|
||||
var args = parseArgs(process.argv)
|
||||
var globList = args.globs && args.globs.length ? args.globs : ["**/tests/**/*.js"]
|
||||
var ignore = ["**/node_modules/**"].concat(args.ignore||[])
|
||||
var cwd = process.cwd()
|
||||
|
||||
args.require && args.require.forEach(function(module) {
|
||||
module && require(require.resolve(module, { basedir: cwd }))
|
||||
})
|
||||
|
||||
process.on("unhandledRejection", function(e) {
|
||||
console.log("Uncaught (in promise) " + e.stack)
|
||||
})
|
||||
var pending = globList.length
|
||||
globList.forEach(function(globPattern) {
|
||||
glob(globPattern, {ignore: ignore})
|
||||
.on("match", function(fileName) { require(path.join(cwd, fileName)) }) // eslint-disable-line global-require
|
||||
.on("error", function(e) { console.error(e) })
|
||||
.on("end", function() { if (--pending === 0) o.run()})
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", function(e) { console.error("Uncaught (in promise) " + e.stack) })
|
||||
|
|
|
|||
77
ospec/change-log.md
Normal file
77
ospec/change-log.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# Change Log for ospec
|
||||
|
||||
|
||||
## Upcoming...
|
||||
_2018-xx-yy_
|
||||
<!-- Add new lines here. Version number will be decided later -->
|
||||
- Add `spy.calls` array property to get the `this` and `arguments` values for any arbitrary call.
|
||||
|
||||
## 3.0.1
|
||||
_2018-06-30_
|
||||
|
||||
### Bug fix
|
||||
- Move `glob` from `devDependencies` to `dependencies`, fix the test runner ([#2186](https://github.com/MithrilJS/mithril.js/pull/2186) [@porsager](https://github.com/porsager)
|
||||
|
||||
## 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
|
||||
- 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))
|
||||
- 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))
|
||||
|
||||
### Bug fixes
|
||||
- Convert the exectuable back to plain ES5 [#2160](https://github.com/MithrilJS/mithril.js/issues/2160) ([#2161](https://github.com/MithrilJS/mithril.js/pull/2161))
|
||||
|
||||
|
||||
## 2.0.0
|
||||
_2018-05-09_
|
||||
- Added `--require` feature to the ospec executable ([#2144](https://github.com/MithrilJS/mithril.js/pull/2144), [@gilbert](https://github.com/gilbert))
|
||||
- In Node.js, ospec only uses colors when the output is sent to a terminal ([#2143](https://github.com/MithrilJS/mithril.js/pull/2143))
|
||||
- the CLI runner now accepts globs as arguments ([#2141](https://github.com/MithrilJS/mithril.js/pull/2141), [@maranomynet](https://github.com/maranomynet))
|
||||
- Added support for custom reporters ([#2020](https://github.com/MithrilJS/mithril.js/pull/2020), [@zyrolasting](https://github.com/zyrolasting))
|
||||
- Make ospec more [Flems](https://flems.io)-friendly ([#2034](https://github.com/MithrilJS/mithril.js/pull/2034))
|
||||
- Works either as a global or in CommonJS environments
|
||||
- the o.run() report is always printed asynchronously (it could be synchronous before if none of the tests were async).
|
||||
- Properly point to the assertion location of async errors [#2036](https://github.com/MithrilJS/mithril.js/issues/2036)
|
||||
- expose the default reporter as `o.report(results)`
|
||||
- Don't try to access the stack traces in IE9
|
||||
|
||||
|
||||
|
||||
## 1.4.1
|
||||
_2018-05-03_
|
||||
- Identical to v1.4.0, but with UNIX-style line endings so that BASH is happy.
|
||||
|
||||
|
||||
|
||||
## 1.4.0
|
||||
_2017-12-01_
|
||||
- Added support for async functions and promises in tests ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928), [@StephanHoyer](https://github.com/StephanHoyer))
|
||||
- Error handling for async tests with `done` callbacks supports error as first argument ([#1928](https://github.com/MithrilJS/mithril.js/pull/1928))
|
||||
- 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))
|
||||
|
||||
|
||||
|
||||
## 1.3 and earlier
|
||||
- Log using util.inspect to show object content instead of "[object Object]" ([#1661](https://github.com/MithrilJS/mithril.js/issues/1661), [@porsager](https://github.com/porsager))
|
||||
- Shell command: Ignore hidden directories and files ([#1855](https://github.com/MithrilJS/mithril.js/pull/1855) [@pdfernhout)](https://github.com/pdfernhout))
|
||||
- Library: Add the possibility to name new test suites ([#1529](https://github.com/MithrilJS/mithril.js/pull/1529))
|
||||
279
ospec/ospec.js
279
ospec/ospec.js
|
|
@ -1,11 +1,12 @@
|
|||
/* eslint-disable global-require, no-bitwise, no-process-exit */
|
||||
"use strict"
|
||||
;(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
|
||||
|
||||
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) {
|
||||
|
|
@ -13,19 +14,25 @@ else window.o = m()
|
|||
}
|
||||
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
|
||||
o.spec = function(subject, predicate) {
|
||||
var parent = ctx
|
||||
|
|
@ -34,13 +41,18 @@ else window.o = m()
|
|||
ctx = parent
|
||||
}
|
||||
o.only = function(subject, predicate, silent) {
|
||||
if (!silent) console.log(highlight("/!\\ WARNING /!\\ o.only() mode"))
|
||||
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)
|
||||
}
|
||||
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)
|
||||
|
|
@ -51,6 +63,7 @@ else window.o = m()
|
|||
name: {value: fn.name}
|
||||
})
|
||||
spy.args = []
|
||||
spy.calls = []
|
||||
spy.callCount = 0
|
||||
return spy
|
||||
}
|
||||
|
|
@ -67,104 +80,125 @@ else window.o = m()
|
|||
}
|
||||
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
|
||||
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
|
||||
next()
|
||||
|
||||
function next() {
|
||||
if (cursor === fns.length) return
|
||||
if (cursor === tasks.length) return
|
||||
|
||||
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 fn = fns[cursor++]
|
||||
var timeout = 0, delay = 200, s = new Date
|
||||
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.message) record(err.message, err)
|
||||
else record(err)
|
||||
subjects.pop()
|
||||
next()
|
||||
}
|
||||
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 (!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 (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
|
||||
}
|
||||
if (timeout === 0) {
|
||||
startTimer()
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -178,7 +212,7 @@ else window.o = m()
|
|||
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] = predicate
|
||||
ctx[name] = new Task(predicate, ensureStackTrace(new Error))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +229,7 @@ else window.o = m()
|
|||
}
|
||||
function deepEqual(a, b) {
|
||||
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") {
|
||||
var aIsArgs = isArguments(a), bIsArgs = isArguments(b)
|
||||
if (a.constructor === Object && b.constructor === Object && !aIsArgs && !bIsArgs) {
|
||||
|
|
@ -227,54 +261,99 @@ else window.o = m()
|
|||
return false
|
||||
}
|
||||
|
||||
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)
|
||||
else if (typeof value === "function") return value.name || "<anonymous function>"
|
||||
try {return JSON.stringify(value)} catch (e) {return String(value)}
|
||||
}
|
||||
function highlight(message) {
|
||||
return hasProcess ? "\x1b[31m" + message + "\x1b[0m" : "%c" + message + "%c "
|
||||
function noTimeoutRightNow() {
|
||||
throw new Error("o.timeout must be called snchronously from within a test definition or a hook")
|
||||
}
|
||||
var colorCodes = {
|
||||
red: "31m",
|
||||
red2: "31;1m",
|
||||
green: "32;1m"
|
||||
}
|
||||
function highlight(message, color) {
|
||||
var code = colorCodes[color] || colorCodes.red;
|
||||
return hasProcess ? (process.stdout.isTTY ? "\x1b[" + code + message + "\x1b[0m" : message) : "%c" + message + "%c "
|
||||
}
|
||||
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 getStackName(e, exp) {
|
||||
return e.stack && exp.test(e.stack) ? e.stack.match(exp)[1] : null
|
||||
}
|
||||
|
||||
o.report = function (results) {
|
||||
var errCount = 0
|
||||
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)
|
||||
console.error(r.context + ":\n" + highlight(r.message) + (stackTrace ? "\n\n" + stackTrace + "\n\n" : ""), hasProcess ? "" : "color:red", hasProcess ? "" : "color:black")
|
||||
var couldHaveABetterStackTrace = !stackTrace || timeoutStackName != null && stackTrace.indexOf(timeoutStackName) !== -1
|
||||
if (couldHaveABetterStackTrace) stackTrace = r.testError != null ? o.cleanStackTrace(r.testError) : r.error.stack || ""
|
||||
console.error(
|
||||
(hasProcess ? "\n" : "") +
|
||||
highlight(r.context + ":", "red2") + "\n" +
|
||||
highlight(r.message, "red") +
|
||||
(stackTrace ? "\n" + stackTrace + "\n" : ""),
|
||||
|
||||
cStyle("black", true), "", // reset to default
|
||||
cStyle("red"), cStyle("black")
|
||||
)
|
||||
errCount++
|
||||
}
|
||||
}
|
||||
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(
|
||||
(name ? name + ": " : "") +
|
||||
results.length + " assertions completed in " + Math.round(new Date - start) + "ms, " +
|
||||
"of which " + results.filter(function(result){return result.error}).length + " failed"
|
||||
(hasProcess ? "––––––\n" : "") +
|
||||
(name ? name + ": " : "") + resultSummary + runningTime,
|
||||
cStyle((errCount === 0 ? "green" : "red"), true), ""
|
||||
)
|
||||
return errCount
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "ospec",
|
||||
"version": "1.4.0",
|
||||
"version": "3.0.1",
|
||||
"description": "Noiseless testing framework",
|
||||
"main": "ospec.js",
|
||||
"directories": {
|
||||
|
|
@ -12,5 +12,8 @@
|
|||
"bin": {
|
||||
"ospec": "./bin/ospec"
|
||||
},
|
||||
"repository": "MithrilJS/mithril.js"
|
||||
"repository": "MithrilJS/mithril.js",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,20 +3,137 @@
|
|||
var callAsync = require("../../test-utils/callAsync")
|
||||
var o = require("../ospec")
|
||||
|
||||
new function(o) {
|
||||
o = o.new()
|
||||
|
||||
o.spec("ospec", function() {
|
||||
o("skipped", function() {
|
||||
// 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")
|
||||
})
|
||||
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("o.only", function(done) {
|
||||
var oo = o.new()
|
||||
|
||||
oo.spec("won't run", function() {
|
||||
oo("nope, skipped", function() {
|
||||
o(true).equals(false)
|
||||
})
|
||||
o.only(".only()", function() {
|
||||
o(2).equals(2)
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
o.run()
|
||||
}(o)
|
||||
oo.run(function(results){
|
||||
o(results.length).equals(2)
|
||||
o(results[0].pass).equals(true)
|
||||
o(results[1].pass).equals(false)
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
// Predicate test passing on clone results
|
||||
o.spec("reporting", function() {
|
||||
var oo
|
||||
o.beforeEach(function(){
|
||||
oo = o.new()
|
||||
|
||||
oo.spec("clone", function() {
|
||||
oo("fail", function() {
|
||||
oo(true).equals(false)
|
||||
})
|
||||
|
||||
oo("pass", function() {
|
||||
oo(true).equals(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
o("reports per instance", function(done, timeout) {
|
||||
timeout(100) // Waiting on clone
|
||||
|
||||
oo.run(function(results) {
|
||||
o(typeof results).equals("object")
|
||||
o("length" in results).equals(true)
|
||||
o(results.length).equals(2)("Two results")
|
||||
|
||||
o("error" in results[0] && "pass" in results[0]).equals(true)("error and pass keys present in failing result")
|
||||
o(results[0].pass).equals(false)("Test meant to fail has failed")
|
||||
o(results[1].pass).equals(true)("Test meant to pass has passed")
|
||||
|
||||
done()
|
||||
})
|
||||
})
|
||||
o("o.report() returns the number of failures", function () {
|
||||
var log = console.log, error = console.error
|
||||
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
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
new function(o) {
|
||||
var clone = o.new()
|
||||
|
|
@ -75,7 +192,7 @@ new function(o) {
|
|||
{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)
|
||||
|
|
@ -92,6 +209,7 @@ new function(o) {
|
|||
o.spec("ospec", function() {
|
||||
o.spec("sync", function() {
|
||||
var a = 0, b = 0, illegalAssertionThrows = false
|
||||
var reservedTestNameTrows = false
|
||||
|
||||
o.before(function() {a = 1})
|
||||
o.after(function() {a = 0})
|
||||
|
|
@ -100,6 +218,7 @@ o.spec("ospec", function() {
|
|||
o.afterEach(function() {b = 0})
|
||||
|
||||
try {o("illegal assertion")} catch (e) {illegalAssertionThrows = true}
|
||||
try {o("\x01reserved test name", function(){})} catch (e) {reservedTestNameTrows = true}
|
||||
|
||||
o("assertions", function() {
|
||||
var nestedTestDeclarationThrows = false
|
||||
|
|
@ -107,6 +226,7 @@ o.spec("ospec", function() {
|
|||
|
||||
o(illegalAssertionThrows).equals(true)
|
||||
o(nestedTestDeclarationThrows).equals(true)
|
||||
o(reservedTestNameTrows).equals(true)
|
||||
|
||||
var spy = o.spy()
|
||||
spy(a)
|
||||
|
|
@ -157,6 +277,8 @@ o.spec("ospec", function() {
|
|||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(1)
|
||||
o(spy.args[0]).equals(1)
|
||||
o(spy.calls.length).equals(1)
|
||||
o(spy.calls[0]).deepEquals({this: undefined, args: [1]})
|
||||
})
|
||||
o("spy wrapping", function() {
|
||||
var spy = o.spy(function view(vnode){
|
||||
|
|
@ -174,47 +296,368 @@ o.spec("ospec", function() {
|
|||
o(spy.callCount).equals(1)
|
||||
o(spy.args.length).equals(1)
|
||||
o(spy.args[0]).deepEquals({children: children})
|
||||
o(spy.calls.length).equals(1)
|
||||
o(spy.calls[0]).deepEquals({this: state, args: [{children: children}]})
|
||||
o(state).deepEquals({drawn: true})
|
||||
o(output).deepEquals({tag: "div", children: children})
|
||||
})
|
||||
})
|
||||
o.spec("async callback", function() {
|
||||
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.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)
|
||||
|
||||
o.before(function(done) {
|
||||
callAsync(function() {
|
||||
a = 1
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
|
@ -227,7 +670,7 @@ o.spec("ospec", function() {
|
|||
} catch(error) {
|
||||
var trace = o.cleanStackTrace(error)
|
||||
o(trace).notEquals("break")
|
||||
o(trace.includes("test-ospec.js")).equals(true)
|
||||
o(trace.indexOf("test-ospec.js") !== -1).equals(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -279,3 +722,79 @@ o.spec("ospec", function() {
|
|||
})
|
||||
})
|
||||
})
|
||||
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*/
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue