├── doc ├── reporter_dot.png ├── reporter_base.png └── reporter_spec.png ├── .babelrc ├── test ├── cli │ ├── require-a.js │ ├── gc.js │ ├── require-b.js │ ├── no-timeout.js │ ├── sigint.js │ ├── reset_tests.js │ ├── global.js │ ├── timeout.js │ ├── reset.js │ ├── grep-exclude.js │ ├── mocha.js │ ├── reporters.js │ └── test-in-src.js ├── browser │ ├── index.html │ ├── index-error.html │ ├── index-async.html │ ├── test-error.js │ ├── test.js │ └── test-async.js ├── coffee │ └── simple.coffee ├── es2015 │ ├── async-hook.js │ └── async-test.js ├── typings.test.ts ├── index.js └── others.js ├── .travis.yml ├── tsconfig.json ├── .gitignore ├── bin ├── options.js ├── tman └── _tman ├── lib ├── browser.js ├── format.js ├── reporters │ ├── dot.js │ ├── base.js │ ├── spec.js │ ├── browser.js │ └── diff.js ├── tman.js └── core.js ├── browser └── tman.css ├── Makefile ├── example ├── simple.coffee ├── es-next.es ├── test_in_source_code.js ├── mocha.js ├── simple.ts ├── simple.js ├── only-case.js ├── error-case.js └── nested.js ├── LICENSE ├── package.json ├── index.d.ts └── README.md /doc/reporter_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunks/tman/HEAD/doc/reporter_dot.png -------------------------------------------------------------------------------- /doc/reporter_base.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunks/tman/HEAD/doc/reporter_base.png -------------------------------------------------------------------------------- /doc/reporter_spec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thunks/tman/HEAD/doc/reporter_spec.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-async-to-generator"] 4 | } 5 | -------------------------------------------------------------------------------- /test/cli/require-a.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // `bin/tman -r test/cli/require-a test/cli/require-b` 7 | 8 | global.testRequire = 'tman' 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "10" 5 | - "12" 6 | - "13" 7 | sudo: false 8 | script: "npm run test-cov" 9 | after_script: "npm install coveralls@2 && cat ./coverage/lcov.info | coveralls" 10 | -------------------------------------------------------------------------------- /test/cli/gc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global it */ 6 | 7 | // `bin/tman test/cli/gc --gc` 8 | 9 | it('expose gc extension', function () { 10 | global.gc() 11 | }) 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node" 6 | }, 7 | "exclude": [ 8 | "coverage", 9 | "debug", 10 | "node_modules" 11 | ], 12 | "compileOnSave": false 13 | } 14 | -------------------------------------------------------------------------------- /test/browser/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | T-man tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/browser/index-error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | T-man tests with errors 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/browser/index-async.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | T-man tests with async/await 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/cli/require-b.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global suite, it, testRequire */ 6 | 7 | // `bin/tman -r test/cli/require-a test/cli/require-b` 8 | 9 | const assert = require('assert') 10 | 11 | suite('require', function () { 12 | it('require-a should be "tman"', function () { 13 | assert.strictEqual(testRequire, 'tman') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # node-waf configuration 17 | .lock-wscript 18 | 19 | # Dependency directory 20 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 21 | node_modules 22 | 23 | typings 24 | debug 25 | yarn.lock 26 | -------------------------------------------------------------------------------- /test/cli/no-timeout.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global suite, it */ 6 | 7 | // `bin/tman --no-timeout test/cli/no-timeout` 8 | 9 | const thunk = require('thunks')() 10 | 11 | suite('no timeout', function () { 12 | it('test 1100 should ok', function * () { 13 | yield thunk.delay(1100) 14 | }) 15 | 16 | it('test 2100 should ok', function * () { 17 | yield thunk.delay(2100) 18 | }) 19 | 20 | it('test 3000 should ok', function (done) { 21 | setTimeout(done, 3000) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /test/cli/sigint.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global suite, it, after */ 6 | 7 | // `bin/tman test/cli/sigint` 8 | // then `control + c` 9 | 10 | const thunk = require('thunks')() 11 | 12 | suite('Should finish graceful when "SIGINT"', function () { 13 | var i = 0 14 | var count = 0 15 | 16 | after(function () { 17 | console.log('End:', i) 18 | }) 19 | 20 | while (count++ < 1000) { 21 | it('test ' + count, function * () { 22 | yield thunk.delay(200) 23 | i++ 24 | }) 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /test/cli/reset_tests.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global suite, it, before, after */ 6 | 7 | const assert = require('assert') 8 | var count = 0 9 | 10 | before(function () { 11 | assert.strictEqual(count++, 0) 12 | }) 13 | 14 | after(function () { 15 | assert.strictEqual(count++, 4) 16 | }) 17 | 18 | suite('suite', function () { 19 | it('test 1', function () { 20 | assert.strictEqual(count++, 1) 21 | }) 22 | 23 | it('test 2', function () { 24 | assert.strictEqual(count++, 2) 25 | }) 26 | }) 27 | 28 | it('test 3', function () { 29 | assert.strictEqual(count++, 3) 30 | }) 31 | -------------------------------------------------------------------------------- /bin/options.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Modified from https://github.com/mochajs/mocha 3 | 4 | const fs = require('fs') 5 | 6 | module.exports = function () { 7 | const optsPath = process.argv.indexOf('--opts') !== -1 8 | ? process.argv[process.argv.indexOf('--opts') + 1] : 'test/tman.opts' 9 | 10 | try { 11 | const opts = fs.readFileSync(optsPath, 'utf8') 12 | .replace(/\\\s/g, '%20') 13 | .split(/\s/) 14 | .filter(Boolean) 15 | .map((value) => value.replace(/%20/g, ' ')) 16 | 17 | process.argv = process.argv 18 | .slice(0, 2) 19 | .concat(opts.concat(process.argv.slice(2))) 20 | } catch (_) {} 21 | 22 | process.env.LOADED_TMAN_OPTS = true 23 | } 24 | -------------------------------------------------------------------------------- /lib/browser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | const core = require('./core') 7 | const info = require('../package.json') 8 | const Reporter = require('./reporters/base') 9 | require('./reporters/browser') // mount "browser" as default reporter 10 | 11 | const env = {} 12 | const tm = module.exports = tmanFactroy() 13 | tm.NAME = info.name 14 | tm.VERSION = info.version 15 | tm.Test = core.Test 16 | tm.Suite = core.Suite 17 | tm.Reporter = Reporter 18 | tm.createTman = tmanFactroy 19 | tm.tman = tm 20 | tm.env = env 21 | tm.env.TEST = window.TEST 22 | 23 | function tmanFactroy () { 24 | const tman = core.Tman(env) 25 | tman.setReporter(Reporter.defaultReporter) 26 | return tman 27 | } 28 | -------------------------------------------------------------------------------- /test/cli/global.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global tman, it */ 6 | 7 | // `bin/tman test/cli/global --globals suite,it,before,after` 8 | 9 | const assert = require('assert') 10 | const tman1 = require('../..') 11 | 12 | it('assert global tman', function () { 13 | assert.strictEqual(tman, tman1) 14 | assert.strictEqual(global.tman, tman1) 15 | assert.strictEqual(global.describe, undefined) 16 | assert.strictEqual(global.suite, tman1.suite) 17 | assert.strictEqual(global.test, undefined) 18 | assert.strictEqual(global.it, tman1.it) 19 | assert.strictEqual(global.before, tman1.before) 20 | assert.strictEqual(global.after, tman1.after) 21 | assert.strictEqual(global.beforeEach, undefined) 22 | assert.strictEqual(global.afterEach, undefined) 23 | }) 24 | -------------------------------------------------------------------------------- /test/cli/timeout.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global suite, it */ 6 | 7 | // `bin/tman -t 650 test/cli/timeout` 8 | 9 | const thunk = require('thunks')() 10 | 11 | suite('some timeout tests', function () { 12 | it('test 500 should ok', function * () { 13 | yield thunk.delay(500) 14 | }) 15 | 16 | it('test 600 should ok', function * () { 17 | yield thunk.delay(600) 18 | }) 19 | 20 | it('test 700 should timeout', function * () { 21 | yield thunk.delay(700) 22 | }) 23 | 24 | it('test 800 should timeout', function * () { 25 | yield thunk.delay(800) 26 | }) 27 | 28 | it('test 900 should ok', function * () { 29 | this.timeout(1000) 30 | yield thunk.delay(900) 31 | }) 32 | 33 | it('test 1000 should timeout', function * () { 34 | yield thunk.delay(1000) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /browser/tman.css: -------------------------------------------------------------------------------- 1 | #tman { 2 | width: 96%; 3 | max-width: 1024px; 4 | margin: 20px auto 0; 5 | line-height: 28px; 6 | } 7 | 8 | #tman .tman-header { 9 | text-align: center; 10 | } 11 | 12 | #tman .tman-suite h3 { 13 | margin: 0; 14 | font-size: 1.1em; 15 | border-bottom: 1px solid #D0D0D0; 16 | } 17 | 18 | #tman .tman-test {} 19 | 20 | #tman .tman-footer { 21 | margin-top: 20px; 22 | padding-top: 10px; 23 | border-top: 2px solid #9E9E9E; 24 | } 25 | 26 | #tman .tman-statistics { 27 | font-size: 1.2em; 28 | margin-top: 10px; 29 | } 30 | 31 | #tman .tman-statistics span { 32 | padding-right: 12px; 33 | } 34 | 35 | #tman .tman-error h4 { 36 | margin: 0; 37 | } 38 | 39 | #tman .tman-error p { 40 | margin: 0; 41 | } 42 | 43 | #tman .success { 44 | color: #009688; 45 | } 46 | 47 | #tman .error { 48 | color: #E91E63; 49 | } 50 | 51 | #tman .ignored { 52 | color: #03A9F4; 53 | } 54 | -------------------------------------------------------------------------------- /test/coffee/simple.coffee: -------------------------------------------------------------------------------- 1 | # `bin/tman -r coffee-script/register test/coffee` 2 | 3 | assert = require('assert') 4 | tman = require('../..') 5 | 6 | count = 0 7 | 8 | tman.it 'synchronous test', -> 9 | assert.strictEqual(count++, 0) 10 | return 11 | 12 | tman.it 'callback style asynchronous test', (done) -> 13 | assert.strictEqual(count++, 1) 14 | setTimeout(done, 100) 15 | return 16 | 17 | tman.it 'promise style asynchronous test', -> 18 | assert.strictEqual(count++, 2) 19 | return new Promise((resolve) -> 20 | assert.strictEqual(count++, 3) 21 | setTimeout(resolve, 100) 22 | ) 23 | 24 | tman.it 'thunk style asynchronous test', -> 25 | assert.strictEqual(count++, 4) 26 | return (done) -> 27 | assert.strictEqual(count++, 5) 28 | setTimeout(done, 100) 29 | 30 | tman.it 'generator style asynchronous test', -> 31 | assert.strictEqual(count++, 6) 32 | yield (done) -> setTimeout(done, 50) 33 | yield new Promise((resolve) -> setTimeout(resolve, 50)) 34 | assert.strictEqual(count++, 7) 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: 2 | bin/tman 'test/*.js' 3 | bin/tman test/cli/gc -gc 4 | bin/tman test/cli/gc --expose-gc 5 | bin/tman test/cli/global --globals tman,suite,it,before,after 6 | bin/tman -g api -e ignore test/cli/grep-exclude 7 | bin/tman --mocha test/cli/mocha 8 | bin/tman --no-timeout test/cli/no-timeout 9 | !(bin/tman --reporter base test/cli/reporters.js) 10 | !(bin/tman -R dot test/cli/reporters.js) 11 | !(bin/tman --reporter spec test/cli/reporters.js) 12 | bin/tman -r test/cli/require-a test/cli/require-b 13 | bin/tman test/cli/reset 14 | !(bin/tman -t 650 test/cli/timeout) 15 | bin/tman test/cli/test-in-src 16 | node test/cli/test-in-src --test 17 | TEST=* node test/cli/test-in-src 18 | bin/tman -r coffee-script/register test/coffee 19 | bin/tman -r ts-node/register test/typings.test.ts 20 | open test/browser/index-error.html 21 | sleep 2s 22 | open test/browser/index.html 23 | sleep 2s 24 | open test/browser/index-async.html -a '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary' 25 | 26 | .PHONY: test 27 | -------------------------------------------------------------------------------- /test/cli/reset.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global tman */ 6 | 7 | // `bin/tman test/cli/reset` 8 | 9 | const path = require('path') 10 | const assert = require('assert') 11 | const thunk = require('thunks')() 12 | 13 | const tests = path.join(__dirname, './reset_tests.js') 14 | 15 | thunk(function * () { 16 | // process don't exit 17 | tman.setExit(false) 18 | tman.loadFiles(tests) 19 | let res = yield tman.run() 20 | assert.strictEqual(res.passed, 3) 21 | assert.strictEqual(res.ignored, 0) 22 | 23 | tman.reset() 24 | // no test 25 | res = yield tman.run() 26 | assert.strictEqual(res.passed, 0) 27 | assert.strictEqual(res.ignored, 0) 28 | 29 | tman.reset() 30 | tman.loadFiles(tests) 31 | tman.it('should error', function () { 32 | throw new Error('some error') 33 | }) 34 | res = yield tman.run() 35 | assert.strictEqual(res.passed, 3) 36 | assert.strictEqual(res.ignored, 0) 37 | assert.strictEqual(res.errors.length, 1) 38 | 39 | process.exit(0) 40 | })() 41 | -------------------------------------------------------------------------------- /example/simple.coffee: -------------------------------------------------------------------------------- 1 | # **Github:** https://github.com/thunks/tman 2 | # 3 | # **License:** MIT 4 | 5 | # `bin/tman -r coffee-script/register example/simple.coffee` 6 | 7 | assert = require('assert') 8 | tman = require('..') 9 | 10 | count = 0 11 | 12 | tman.it 'synchronous test', -> 13 | assert.strictEqual(count++, 0) 14 | return 15 | 16 | tman.it 'callback style asynchronous test', (done) -> 17 | assert.strictEqual(count++, 1) 18 | setTimeout(done, 100) 19 | return 20 | 21 | tman.it 'promise style asynchronous test', -> 22 | assert.strictEqual(count++, 2) 23 | return new Promise((resolve) -> 24 | assert.strictEqual(count++, 3) 25 | setTimeout(resolve, 100) 26 | ) 27 | 28 | tman.it 'thunk style asynchronous test', -> 29 | assert.strictEqual(count++, 4) 30 | return (done) -> 31 | assert.strictEqual(count++, 5) 32 | setTimeout(done, 100) 33 | 34 | tman.it 'generator style asynchronous test', -> 35 | assert.strictEqual(count++, 6) 36 | yield (done) -> setTimeout(done, 50) 37 | yield new Promise((resolve) -> setTimeout(resolve, 50)) 38 | assert.strictEqual(count++, 7) 39 | -------------------------------------------------------------------------------- /example/es-next.es: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // `babel-node --presets es2015 --plugins transform-async-to-generator bin/tman example/es-next.es` 7 | // or (with .babelrc) 8 | // `tman -r babel-register -r babel-polyfill example/es-next.es` 9 | 10 | import assert from 'assert' 11 | import tman from '..' 12 | 13 | var count = 0 14 | // async "after hook" 15 | tman.after(async () => { 16 | assert.strictEqual(await Promise.resolve(count++), 4) 17 | }) 18 | 19 | tman.it('async/await asynchronous test', async function () { 20 | assert.strictEqual(await Promise.resolve(count++), 0) 21 | assert.strictEqual(await new Promise((resolve, reject) => { 22 | setTimeout(() => { 23 | resolve(count++) 24 | }, 100) 25 | }), 1) 26 | }) 27 | 28 | tman.it('generator asynchronous test', function * () { 29 | // yield Promise 30 | assert.strictEqual(yield Promise.resolve(count++), 2) 31 | // yield thunk function 32 | assert.strictEqual(yield (done) => { 33 | setTimeout(() => { 34 | done(null, count++) 35 | }, 100) 36 | }, 3) 37 | }) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2020 thunks 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/es2015/async-hook.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | const assert = require('assert') 7 | const thunk = require('thunks')() 8 | 9 | module.exports = function (t) { 10 | var count = 0 11 | t.after(function * () { 12 | assert.strictEqual(count++, 6) 13 | yield function (done) { 14 | assert.strictEqual(count++, 7) 15 | setTimeout(function () { 16 | assert.strictEqual(count++, 8) 17 | done() 18 | }, 10) 19 | } 20 | assert.strictEqual(count++, 9) 21 | yield new Promise(function (resolve) { 22 | assert.strictEqual(count++, 10) 23 | setTimeout(function () { 24 | assert.strictEqual(count++, 11) 25 | resolve() 26 | }, 10) 27 | }) 28 | assert.strictEqual(count++, 12) 29 | }) 30 | 31 | t.beforeEach(function * () { 32 | yield thunk.delay(10) 33 | count++ 34 | }) 35 | 36 | t.afterEach(function () { 37 | count++ 38 | return thunk.delay(10) 39 | }) 40 | 41 | t.it('test 1-1', function () { 42 | assert.strictEqual(count++, 1) 43 | }) 44 | 45 | t.it('test 1-2', function () { 46 | assert.strictEqual(count++, 4) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /example/test_in_source_code.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // Run by 3 way: 7 | // tman example/test_in_source_code.js 8 | // node example/test_in_source_code.js --test 9 | // TEST=* node example/test_in_source_code.js 10 | 11 | const tman = require('..') 12 | 13 | // example API 1 14 | exports.indent = function (len) { 15 | var ch = ' ' 16 | var pad = '' 17 | 18 | while (len > 0) { 19 | if (len & 1) pad += ch 20 | if ((len >>= 1)) ch = ch + ch 21 | } 22 | return pad 23 | } 24 | 25 | // example API 2 26 | exports.stringify = function (val) { 27 | return val == null ? '' : String(val) 28 | } 29 | 30 | // T-man tests 31 | tman('test in source code', function () { 32 | const assert = require('assert') 33 | 34 | tman.it('indent', function () { 35 | assert.strictEqual(exports.indent(2), ' ') 36 | }) 37 | 38 | tman.it('stringify', function () { 39 | assert.strictEqual(exports.stringify(), '') 40 | assert.strictEqual(exports.stringify(null), '') 41 | assert.strictEqual(exports.stringify(0), '0') 42 | assert.strictEqual(exports.stringify(false), 'false') 43 | assert.strictEqual(exports.stringify(true), 'true') 44 | assert.strictEqual(exports.stringify(NaN), 'NaN') 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /example/mocha.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global describe, it, before, after */ 6 | 7 | // `tman example/mocha.js` 8 | 9 | const assert = require('assert') 10 | 11 | var count = 0 12 | 13 | describe('mocha style', function () { 14 | before(function () { 15 | assert.strictEqual(count++, 0) 16 | }) 17 | 18 | after(function () { 19 | assert.strictEqual(count++, 9) 20 | }) 21 | 22 | it('synchronous test', function () { 23 | assert.strictEqual(count++, 1) 24 | }) 25 | 26 | it('callback style asynchronous test', function (done) { 27 | assert.strictEqual(count++, 2) 28 | setTimeout(done, 100) 29 | }) 30 | 31 | it('promise style asynchronous test', function () { 32 | assert.strictEqual(count++, 3) 33 | return new Promise(function (resolve) { 34 | assert.strictEqual(count++, 4) 35 | setTimeout(resolve, 100) 36 | }) 37 | }) 38 | 39 | it('thunk style asynchronous test', function () { 40 | assert.strictEqual(count++, 5) 41 | return function (done) { 42 | assert.strictEqual(count++, 6) 43 | setTimeout(done, 100) 44 | } 45 | }) 46 | 47 | it('generator style asynchronous test', function * () { 48 | assert.strictEqual(count++, 7) 49 | yield function (done) { setTimeout(done, 100) } 50 | assert.strictEqual(count++, 8) 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /test/cli/grep-exclude.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global suite, it, before, after */ 6 | 7 | // `bin/tman -g api -e ignore test/cli/grep-exclude` 8 | 9 | const assert = require('assert') 10 | var count = 0 11 | 12 | before(function () { 13 | assert.strictEqual(count++, 0) 14 | }) 15 | 16 | after(function () { 17 | assert.strictEqual(count++, 4) 18 | }) 19 | 20 | it('should not run', function () { 21 | assert.strictEqual(true, false) 22 | }) 23 | 24 | it('api test', function () { 25 | assert.strictEqual(count++, 1) 26 | }) 27 | 28 | it('api should ignore', function () { 29 | assert.strictEqual(true, false) 30 | }) 31 | 32 | suite('suite', function () { 33 | it('test not run', function () { 34 | assert.strictEqual(true, false) 35 | }) 36 | 37 | it('api 1', function () { 38 | assert.strictEqual(count++, 2) 39 | }) 40 | 41 | it('ignore 1', function () { 42 | assert.strictEqual(true, false) 43 | }) 44 | }) 45 | 46 | suite('suite api', function () { 47 | it('test 1', function () { 48 | assert.strictEqual(count++, 3) 49 | }) 50 | 51 | it('ignore', function () { 52 | assert.strictEqual(true, false) 53 | }) 54 | 55 | it('api ignore', function () { 56 | assert.strictEqual(true, false) 57 | }) 58 | }) 59 | 60 | suite('ignore suite', function () { 61 | it('test 2', function () { 62 | assert.strictEqual(true, false) 63 | }) 64 | 65 | it('api 2', function () { 66 | assert.strictEqual(true, false) 67 | }) 68 | }) 69 | 70 | it.skip('skip api', function () { 71 | assert.strictEqual(true, false) 72 | }) 73 | -------------------------------------------------------------------------------- /test/es2015/async-test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | const assert = require('assert') 7 | 8 | module.exports = function (t) { 9 | var count = 0 10 | 11 | t.it('promise style asynchronous test', function () { 12 | assert.strictEqual(count++, 0) 13 | return new Promise(function (resolve) { 14 | assert.strictEqual(count++, 1) 15 | setTimeout(resolve, 10) 16 | }) 17 | }) 18 | 19 | t.it('generator style asynchronous test', function * () { 20 | assert.strictEqual(count++, 2) 21 | yield function (done) { 22 | assert.strictEqual(count++, 3) 23 | setTimeout(function () { 24 | assert.strictEqual(count++, 4) 25 | done() 26 | }, 10) 27 | } 28 | assert.strictEqual(count++, 5) 29 | yield new Promise(function (resolve) { 30 | assert.strictEqual(count++, 6) 31 | setTimeout(function () { 32 | assert.strictEqual(count++, 7) 33 | resolve() 34 | }, 10) 35 | }) 36 | assert.strictEqual(count++, 8) 37 | }) 38 | 39 | t.it('generator style asynchronous test, return generator function', function () { 40 | assert.strictEqual(count++, 9) 41 | 42 | return function * () { 43 | assert.strictEqual(count++, 10) 44 | yield function (done) { 45 | assert.strictEqual(count++, 11) 46 | setTimeout(done, 10) 47 | } 48 | assert.strictEqual(count++, 12) 49 | yield new Promise(function (resolve) { 50 | assert.strictEqual(count++, 13) 51 | setTimeout(resolve, 10) 52 | }) 53 | assert.strictEqual(count++, 14) 54 | } 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /example/simple.ts: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // `ts-node example/simple.ts` 7 | 8 | import * as assert from 'assert' 9 | import { thunk } from 'thunks' 10 | import { run, suite, it } from '..' 11 | 12 | const Rx = require('rxjs') 13 | 14 | var count = 0 15 | 16 | it('synchronous test', function () { 17 | assert.strictEqual(count++, 0) 18 | }) 19 | 20 | it('callback style asynchronous test', function (done) { 21 | assert.strictEqual(count++, 1) 22 | setTimeout(done, 100) 23 | }) 24 | 25 | it('promise style asynchronous test', function () { 26 | assert.strictEqual(count++, 2) 27 | return new Promise(function (resolve) { 28 | assert.strictEqual(count++, 3) 29 | setTimeout(resolve, 100) 30 | }) 31 | }) 32 | 33 | it('thunk style asynchronous test', function () { 34 | assert.strictEqual(count++, 4) 35 | return function (done) { 36 | assert.strictEqual(count++, 5) 37 | setTimeout(done, 100) 38 | } 39 | }) 40 | 41 | it('generator style asynchronous test', function * () { 42 | assert.strictEqual(count++, 6) 43 | yield (done) => setTimeout(done, 50) 44 | assert.strictEqual(count++, 7) 45 | }) 46 | 47 | it('async/await style asynchronous test', async function () { 48 | assert.strictEqual(count++, 8) 49 | await new Promise((resolve) => setTimeout(resolve, 50)) 50 | assert.strictEqual(count++, 9) 51 | }) 52 | 53 | it('Rx.Observable asynchronous test', function () { 54 | assert.strictEqual(count++, 10) 55 | return Rx.Observable.fromPromise(new Promise(function (resolve) { 56 | assert.strictEqual(count++, 11) 57 | setTimeout(resolve, 100) 58 | })) 59 | }) 60 | 61 | run() 62 | -------------------------------------------------------------------------------- /example/simple.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // `tman example/simple.js` 7 | 8 | const assert = require('assert') 9 | const tman = require('..') 10 | const Rx = require('rxjs') 11 | 12 | var count = 0 13 | 14 | tman.it('synchronous test', function () { 15 | assert.strictEqual(count++, 0) 16 | }) 17 | 18 | tman.it('callback style asynchronous test', function (done) { 19 | assert.strictEqual(count++, 1) 20 | setTimeout(done, 100) 21 | }) 22 | 23 | tman.it('promise style asynchronous test', function () { 24 | assert.strictEqual(count++, 2) 25 | return new Promise(function (resolve) { 26 | assert.strictEqual(count++, 3) 27 | setTimeout(resolve, 100) 28 | }) 29 | }) 30 | 31 | tman.it('thunk style asynchronous test', function () { 32 | assert.strictEqual(count++, 4) 33 | return function (done) { 34 | assert.strictEqual(count++, 5) 35 | setTimeout(done, 100) 36 | } 37 | }) 38 | 39 | tman.it('generator style asynchronous test', function * () { 40 | assert.strictEqual(count++, 6) 41 | yield function (done) { setTimeout(done, 50) } 42 | yield new Promise(function (resolve) { setTimeout(resolve, 50) }) 43 | assert.strictEqual(count++, 7) 44 | }) 45 | 46 | tman.it('Rx.Observable asynchronous test', function () { 47 | assert.strictEqual(count++, 8) 48 | return Rx.Observable.fromPromise(new Promise(function (resolve) { 49 | assert.strictEqual(count++, 9) 50 | setTimeout(resolve, 100) 51 | })) 52 | }) 53 | 54 | tman.it('async/await style asynchronous test', async function () { 55 | assert.strictEqual(count++, 10) 56 | await new Promise(function (resolve) { setTimeout(resolve, 50) }) 57 | assert.strictEqual(count++, 11) 58 | }) 59 | 60 | // tman.run() 61 | -------------------------------------------------------------------------------- /test/cli/mocha.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global describe, it, before, after, beforeEach, afterEach */ 6 | 7 | // `bin/tman --mocha test/cli/mocha` 8 | 9 | const assert = require('assert') 10 | var count = 0 11 | 12 | before(function () { 13 | assert.strictEqual(count++, 0) 14 | }) 15 | 16 | after(function () { 17 | assert.strictEqual(count++, 34) 18 | }) 19 | 20 | beforeEach(function () { 21 | count++ 22 | }) 23 | 24 | afterEach(function () { 25 | count++ 26 | }) 27 | 28 | it('test 1-1', function () { 29 | assert.strictEqual(count++, 2) 30 | }) 31 | 32 | it('test 1-2', function () { 33 | assert.strictEqual(count++, 5) 34 | }) 35 | 36 | it.skip('test 1-3', function () { 37 | assert.strictEqual(true, false) 38 | }) 39 | 40 | describe('suite 1-1', function () { 41 | beforeEach(function () { 42 | count++ 43 | }) 44 | 45 | it('test 2-1', function () { 46 | assert.strictEqual(count++, 9) 47 | }) 48 | 49 | it('test 2-2', function () { 50 | assert.strictEqual(count++, 13) 51 | }) 52 | 53 | it('test 2-3', function () { 54 | assert.strictEqual(count++, 17) 55 | }) 56 | }) 57 | 58 | describe('suite 1-2', function () { 59 | it('test 2-1', function () { 60 | assert.strictEqual(count++, 20) 61 | }) 62 | }) 63 | 64 | describe('suite 1-3', function () { 65 | afterEach(function () { 66 | count++ 67 | }) 68 | 69 | it('test 2-1', function () { 70 | assert.strictEqual(count++, 23) 71 | }) 72 | 73 | it('test 2-2', function () { 74 | assert.strictEqual(count++, 27) 75 | }) 76 | 77 | describe('suite 1-3-1', function () { 78 | it('test 3-1', function () { 79 | assert.strictEqual(count++, 31) 80 | }) 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /example/only-case.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // `tman example/only-case.js` 7 | 8 | const assert = require('assert') 9 | const thunk = require('thunks')() 10 | const tman = require('..') 11 | 12 | var count = 0 13 | 14 | tman.before(function () { 15 | assert.strictEqual(count++, 0) 16 | console.log('Start only tests') 17 | }) 18 | 19 | tman.after(function () { 20 | assert.strictEqual(count++, 5) 21 | console.log('End only tests') 22 | }) 23 | 24 | tman.suite('suite 1', function () { 25 | tman.beforeEach(function * () { 26 | yield thunk.delay(10) 27 | }) 28 | 29 | tman.it.only('only test 1-1', function () { 30 | assert.strictEqual(count++, 1) 31 | }) 32 | 33 | tman.it.skip('skip test', function () { 34 | assert.strictEqual(true, false) 35 | }) 36 | 37 | tman.it('assert error', function () { 38 | assert.strictEqual(true, false) 39 | }) 40 | 41 | tman.it('throw error', function () { 42 | throw new Error('throw error') 43 | }) 44 | 45 | tman.suite.only('only suite 1-2', function () { 46 | tman.it.only('only test 1-2-1', function * () { 47 | assert.strictEqual(count++, 2) 48 | }) 49 | 50 | tman.suite('suite 1-2-1', function () { 51 | tman.it('assert error', function () { 52 | assert.strictEqual(true, false) 53 | }) 54 | 55 | tman.it('throw error', function () { 56 | throw new Error('throw error') 57 | }) 58 | 59 | tman.it.only('only test 1-2-1-1', function () { 60 | assert.strictEqual(count++, 3) 61 | }) 62 | }) 63 | }) 64 | }) 65 | 66 | tman.it.only('only test 1-1', function () { 67 | assert.strictEqual(count++, 4) 68 | }) 69 | 70 | tman.it('throw error', function () { 71 | throw new Error('throw error') 72 | }) 73 | 74 | // tman.run() 75 | -------------------------------------------------------------------------------- /lib/format.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | var supportsColor = require('supports-color') 7 | 8 | exports.useColors = function (useColors) { 9 | supportsColor = !!useColors 10 | } 11 | exports.indent = function (len) { 12 | let ch = ' ' 13 | let pad = '' 14 | 15 | while (len > 0) { 16 | if (len & 1) pad += ch 17 | if ((len >>= 1)) ch = ch + ch // avoid "standard" lint 18 | } 19 | return pad 20 | } 21 | 22 | // https://en.wikipedia.org/wiki/ANSI_escape_code 23 | 24 | // 30–37: set text color to one of the colors 0 to 7, 25 | // 40–47: set background color to one of the colors 0 to 7, 26 | // 39: reset text color to default, 27 | // 49: reset background color to default, 28 | // 1: make text bold / bright (this is the standard way to access the bright color variants), 29 | // 22: turn off bold / bright effect, and 30 | // 0: reset all text properties (color, background, brightness, etc.) to their default values. 31 | // For example, one could select bright purple text on a green background (eww!) with the code `\x1B[35;1;42m` 32 | const styles = { red: 31, green: 32, yellow: 33, cyan: 36, white: 37, gray: 90 } 33 | Object.keys(styles).forEach((key) => { 34 | exports[key] = (str, bright) => style(styles[key], str, bright) 35 | }) 36 | 37 | exports.reset = function (str) { 38 | return !supportsColor ? str : ('\x1b[0m' + str) 39 | } 40 | 41 | function style (code, str, bright) { 42 | /* istanbul ignore next */ 43 | if (!supportsColor) return str 44 | if (bright) code += ';1' 45 | return '\x1b[' + code + 'm' + str + '\x1b[39;22m' 46 | } 47 | 48 | /** 49 | * Color lines for `str`, using the color `name`. 50 | * 51 | * @api private 52 | * @param {string} name 53 | * @param {string} str 54 | * @return {string} 55 | */ 56 | exports.colorLines = function (name, str) { 57 | return str.split('\n').map((str) => exports[name](str)).join('\n') 58 | } 59 | -------------------------------------------------------------------------------- /example/error-case.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // `tman example/error-case.js` 7 | 8 | const assert = require('assert') 9 | const thunk = require('thunks')() 10 | const tman = require('..') 11 | 12 | var count = 0 13 | 14 | tman.before(function () { 15 | assert.strictEqual(count++, 0) 16 | console.log('Start error tests') 17 | }) 18 | 19 | tman.after(function () { 20 | assert.strictEqual(count++, 8) 21 | console.log('End error tests') 22 | }) 23 | 24 | tman.suite('error suite 1', function () { 25 | tman.beforeEach(function * () { 26 | count++ 27 | yield thunk.delay(10) 28 | }) 29 | 30 | tman.it('success test', function () { 31 | assert.strictEqual(count++, 2) 32 | }) 33 | 34 | tman.it.skip('skip test', function () { 35 | assert.strictEqual(count++, 4) 36 | }) 37 | 38 | tman.it('assert error', function () { 39 | assert.strictEqual(true, false) 40 | }) 41 | 42 | tman.it('throw error', function () { 43 | throw new Error('throw error') 44 | }) 45 | 46 | tman.suite('error suite 2', function () { 47 | tman.it('assert error', function * () { 48 | assert.strictEqual(true, false) 49 | yield thunk.delay(100) 50 | }) 51 | 52 | tman.suite('error suite 3', function () { 53 | tman.it('assert error', function * () { 54 | yield thunk.delay(100) 55 | assert.strictEqual(true, false) 56 | }) 57 | 58 | tman.it('throw error', function () { 59 | throw new Error('throw error') 60 | }) 61 | 62 | tman.it('time out error', function * () { 63 | this.timeout(100) 64 | yield thunk.delay(1000) 65 | }) 66 | }) 67 | }) 68 | }) 69 | 70 | tman.it('assert error', function () { 71 | assert.strictEqual(true, false) 72 | }) 73 | 74 | tman.it('throw error', function () { 75 | throw new Error('throw error') 76 | }) 77 | 78 | tman.it('success test', function () { 79 | assert.strictEqual(count++, 7) 80 | }) 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tman", 3 | "version": "1.10.0", 4 | "description": "T-man: Super test manager for JavaScript.", 5 | "authors": [ 6 | "Yan Qing " 7 | ], 8 | "main": "lib/tman.js", 9 | "typings": "index.d.ts", 10 | "bin": { 11 | "tman": "./bin/tman", 12 | "_tman": "./bin/_tman" 13 | }, 14 | "scripts": { 15 | "test": "standard && bin/tman 'test/*.js'", 16 | "test-all": "make test", 17 | "test-cov": "standard && istanbul cover bin/_tman 'test/*.js'", 18 | "test-typings": "bin/tman -r ts-node/register test/typings.test.ts", 19 | "browser": "browserify lib/browser.js -s tman -o browser/tman.js" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git@github.com:thunks/tman.git" 24 | }, 25 | "keywords": [ 26 | "T-man", 27 | "tman", 28 | "test", 29 | "thunk", 30 | "bdd", 31 | "tdd", 32 | "ava", 33 | "mocha" 34 | ], 35 | "engines": { 36 | "node": ">= 6" 37 | }, 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/thunks/tman/issues" 41 | }, 42 | "homepage": "https://github.com/thunks/tman", 43 | "dependencies": { 44 | "commander": "^5.0.0", 45 | "diff": "~4.0.2", 46 | "glob": "~7.1.6", 47 | "supports-color": "^7.1.0", 48 | "thunks": "~4.9.6" 49 | }, 50 | "devDependencies": { 51 | "@types/mocha": "^7.0.2", 52 | "@types/node": "^13.9.3", 53 | "babel-plugin-transform-async-to-generator": "^6.24.1", 54 | "babel-polyfill": "^6.26.0", 55 | "babel-preset-es2015": "^6.24.1", 56 | "babel-register": "^6.26.0", 57 | "coffee-script": "^1.12.7", 58 | "istanbul": "^0.4.5", 59 | "minimist": "^1.2.5", 60 | "standard": "^14.3.3", 61 | "ts-node": "^8.8.1", 62 | "typescript": "^3.8.3" 63 | }, 64 | "files": [ 65 | "README.md", 66 | "bin", 67 | "lib", 68 | "browser", 69 | "index.d.ts" 70 | ], 71 | "standard": { 72 | "ignore": [ 73 | "browser" 74 | ] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/reporters/dot.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | const util = require('util') 7 | const Reporter = require('./base') 8 | const Diff = require('./diff') 9 | const format = require('../format') 10 | 11 | module.exports = Dot 12 | Reporter.defaultReporter = Dot 13 | 14 | function Dot (ctx) { 15 | Diff.call(this, ctx) 16 | } 17 | 18 | util.inherits(Dot, Diff) 19 | 20 | Dot.prototype.log = function (str) { 21 | process.stdout.write(str) 22 | } 23 | 24 | Dot.prototype.onStart = function () { 25 | this.log('\n') 26 | } 27 | 28 | Dot.prototype.onTestFinish = function (test) { 29 | let str = '' 30 | if (test.state === null) { 31 | str = format.cyan('-', true) 32 | } else if (test.state === true) { 33 | const time = test.endTime - test.startTime 34 | if (time > 50) str = format.yellow('•', true) 35 | else str = format.green('•', true) 36 | } else { 37 | str = format.red('!', true) 38 | } 39 | this.log(str) 40 | } 41 | 42 | Dot.prototype.onFinish = function (rootSuite) { 43 | let message = '\n' 44 | 45 | if (rootSuite.abort) message += format.yellow('\nTest is terminated by SIGINT!\n', true) 46 | message += format.reset('\nTest ' + (rootSuite.errors.length ? 'failed: ' : 'finished: ')) 47 | message += format[rootSuite.passed ? 'green' : 'gray'](rootSuite.passed + ' passed; ', true) 48 | message += format[rootSuite.errors.length ? 'red' : 'gray'](rootSuite.errors.length + ' failed; ', true) 49 | message += format[rootSuite.ignored ? 'cyan' : 'gray'](rootSuite.ignored + ' ignored.', true) 50 | message += format.yellow(' (' + (rootSuite.endTime - rootSuite.startTime) + 'ms)', true) 51 | this.log(message) 52 | this.log(format.reset('\n\n')) 53 | 54 | this.logError(rootSuite) 55 | if (rootSuite.errors.length) this.log(format.reset('\n')) 56 | if (rootSuite.exit) process.exit((rootSuite.errors.length || !rootSuite.passed) ? 1 : 0) 57 | } 58 | 59 | // Result: 60 | // ``` 61 | // 62 | // ∙∙∙∙∙∙--∙∙-∙∙!∙ 63 | // 64 | // Test failed: 11 passed; 1 failed; 3 ignored. (605ms) 65 | // 66 | // 1) /test level 1-2: AssertionError: 22 === 21 67 | // at Test.fn (/Users/zensh/git/js/thunkjs/tman/example/nested.js:116:10) 68 | // at Test. (/Users/zensh/git/js/thunkjs/tman/lib/core.js:557:37) 69 | // 70 | // ``` 71 | -------------------------------------------------------------------------------- /bin/tman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Modified from https://github.com/mochajs/mocha 4 | * 5 | * This tiny wrapper file checks for known node flags and appends them 6 | * when found, before invoking the "real" _tman(1) executable. 7 | */ 8 | 'use strict' 9 | 10 | const path = require('path') 11 | const spawn = require('child_process').spawn 12 | const getOptions = require('./options') 13 | const args = [path.join(__dirname, '_tman')] 14 | 15 | // Load tman.opts into process.argv 16 | // Must be loaded here to handle node-specific options 17 | getOptions() 18 | 19 | process.argv.slice(2).forEach((arg) => { 20 | let flag = arg.split('=')[0] 21 | 22 | switch (flag) { 23 | case '-d': 24 | args.unshift('--debug') 25 | args.push('--no-timeout') 26 | break 27 | case 'debug': 28 | case '--debug': 29 | case '--debug-brk': 30 | args.unshift(arg) 31 | args.push('--no-timeout') 32 | break 33 | case '--inspect': 34 | case '--inspect-brk': 35 | args.unshift(arg) 36 | args.push('--no-timeout') 37 | break 38 | case '-gc': 39 | case '--expose-gc': 40 | args.unshift('--expose-gc') 41 | break 42 | case '--gc-global': 43 | case '--es_staging': 44 | case '--no-deprecation': 45 | case '--prof': 46 | case '--log-timer-events': 47 | case '--throw-deprecation': 48 | case '--trace-deprecation': 49 | case '--use_strict': 50 | case '--allow-natives-syntax': 51 | case '--perf-basic-prof': 52 | args.unshift(arg) 53 | break 54 | default: 55 | if (arg.indexOf('--harmony') === 0) args.unshift(arg) 56 | else if (arg.indexOf('--trace') === 0) args.unshift(arg) 57 | else if (arg.indexOf('--icu-data-dir') === 0) args.unshift(arg) 58 | else if (arg.indexOf('--max-old-space-size') === 0) args.unshift(arg) 59 | else if (arg.indexOf('--preserve-symlinks') === 0) args.unshift(arg) 60 | else args.push(arg) 61 | break 62 | } 63 | }) 64 | 65 | const proc = spawn(process.execPath, args, {stdio: 'inherit'}) 66 | // listen children exit 67 | proc.once('exit', handleExit) 68 | process.once('exit', handleExit) 69 | function handleExit (code, signal) { 70 | if (signal) process.kill(process.pid, signal) 71 | else process.exit(code) 72 | } 73 | // terminate children. 74 | process.once('SIGINT', () => { 75 | // force to exit in 3.1 seconds. 76 | setTimeout(() => process.exit(1), 3100) 77 | }) 78 | -------------------------------------------------------------------------------- /lib/reporters/base.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | module.exports = Reporter 7 | module.exports.defaultReporter = Reporter 8 | 9 | function Reporter (ctx) { 10 | this.ctx = ctx 11 | this.count = 0 12 | } 13 | 14 | Reporter.prototype.log = function () { 15 | console.log.apply(console, arguments) 16 | } 17 | 18 | Reporter.prototype.onStart = function () { 19 | this.count = 0 20 | this.log('\n') 21 | } 22 | 23 | Reporter.prototype.onSuiteStart = function (suite) {} 24 | 25 | Reporter.prototype.onSuiteFinish = function (suite) {} 26 | 27 | Reporter.prototype.onTestStart = function (test) {} 28 | 29 | Reporter.prototype.onTestFinish = function (test) { 30 | if (test.state) { 31 | const state = test.state === true ? 'pass' : 'fail' 32 | this.log(++this.count + '\t' + test.fullTitle + '\t' + state) 33 | } 34 | } 35 | 36 | Reporter.prototype.onFinish = function (rootSuite) { 37 | let message = '\nTest ' + (rootSuite.errors.length ? 'failed: ' : 'finished: ') 38 | message += rootSuite.passed + ' passed; ' 39 | message += rootSuite.errors.length + ' failed; ' 40 | message += rootSuite.ignored + ' ignored.' 41 | message += ' (' + (rootSuite.endTime - rootSuite.startTime) + 'ms)\n' 42 | this.log(message) 43 | 44 | rootSuite.errors.forEach((err) => { 45 | this.log(err.order + ') ' + err.title + ':') 46 | this.log(err.stack ? err.stack : String(err)) 47 | }) 48 | if (rootSuite.exit && process.exit) process.exit((rootSuite.errors.length || !rootSuite.passed) ? 1 : 0) 49 | } 50 | 51 | // Result: order + TAB + fulltitle + TAB + state 52 | // ``` 53 | // 54 | // 1 /suite level 1-1/test level 2-1 pass 55 | // 2 /suite level 1-1/test level 2-2 pass 56 | // 3 /suite level 1-1/suite level 2-1/test level 3-1 pass 57 | // 4 /suite level 1-1/suite level 2-1/test level 3-2 pass 58 | // 5 /suite level 1-1/suite level 2-2/test level 3-1 pass 59 | // 6 /suite level 1-1/suite level 2-2/test level 3-2 pass 60 | // 7 /suite level 1-1/suite level 2-2/suite level 3-2/test level 4-1 pass 61 | // 8 /suite level 1-1/suite level 2-2/suite level 3-2/test level 4-2 pass 62 | // 9 /suite level 1-1/suite level 2-2/suite level 3-2/test level 4-4 pass 63 | // 10 /test level 1-1 pass 64 | // 11 /test level 1-2 fail 65 | // 12 /test level 1-3 pass 66 | // 67 | // Test failed: 11 passed; 1 failed; 3 ignored. (608ms) 68 | // 69 | // 1) /test level 1-2: 70 | // Expected: 21 71 | // Actual: 22 72 | // AssertionError: 22 === 21 73 | // at Test.fn (/Users/zensh/git/js/thunkjs/tman/example/nested.js:116:10) 74 | // at Test. (/Users/zensh/git/js/thunkjs/tman/lib/core.js:557:37) 75 | // 76 | // ``` 77 | -------------------------------------------------------------------------------- /example/nested.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // `tman example/nested.js` 7 | 8 | const assert = require('assert') 9 | const thunk = require('thunks')() 10 | const tman = require('..') 11 | 12 | var count = 0 13 | 14 | tman.before(function () { 15 | assert.strictEqual(count++, 0) 16 | }) 17 | 18 | tman.after(function () { 19 | assert.strictEqual(count++, 24) 20 | }) 21 | 22 | tman.suite('suite level 1-1', function () { 23 | tman.beforeEach(function * () { 24 | count++ 25 | yield thunk.delay(10) 26 | }) 27 | 28 | tman.it('test level 2-1', function () { 29 | assert.strictEqual(count++, 2) 30 | }) 31 | 32 | tman.it('test level 2-2', function () { 33 | assert.strictEqual(count++, 4) 34 | }) 35 | 36 | tman.suite('suite level 2-1', function () { 37 | tman.beforeEach(function * () { 38 | count++ 39 | yield thunk.delay(20) 40 | }) 41 | 42 | tman.it('test level 3-1', function * () { 43 | assert.strictEqual(count++, 7) 44 | yield thunk.delay(100) 45 | }) 46 | 47 | tman.it('test level 3-2', function () { 48 | assert.strictEqual(count++, 9) 49 | }) 50 | }) 51 | 52 | tman.suite('suite level 2-2', function () { 53 | tman.afterEach(function * () { 54 | count++ 55 | yield thunk.delay(20) 56 | }) 57 | 58 | tman.it('test level 3-1', function * () { 59 | assert.strictEqual(count++, 11) 60 | yield thunk.delay(100) 61 | }) 62 | 63 | tman.it('test level 3-2', function () { 64 | assert.strictEqual(count++, 13) 65 | }) 66 | 67 | tman.suite.skip('suite level 3-1', function () { 68 | tman.afterEach(function * () { 69 | assert.strictEqual('skip', false) 70 | }) 71 | 72 | tman.it('test level 4-1', function * () { 73 | assert.strictEqual('skip', false) 74 | }) 75 | 76 | tman.it('test level 4-2', function () { 77 | assert.strictEqual('skip', false) 78 | }) 79 | }) 80 | 81 | tman.suite('suite level 3-2', function () { 82 | tman.before(function () { 83 | assert.strictEqual(count++, 15) 84 | }) 85 | 86 | tman.after(function () { 87 | assert.strictEqual(count++, 19) 88 | }) 89 | 90 | tman.it('test level 4-1', function * () { 91 | assert.strictEqual(count++, 16) 92 | yield thunk.delay(100) 93 | }) 94 | 95 | tman.it('test level 4-2', function () { 96 | assert.strictEqual(count++, 17) 97 | }) 98 | 99 | tman.it.skip('test level 4-3', function () { 100 | assert.strictEqual('skip', false) 101 | }) 102 | 103 | tman.it('test level 4-4', function () { 104 | assert.strictEqual(count++, 18) 105 | }) 106 | }) 107 | }) 108 | }) 109 | 110 | tman.it('test level 1-1', function * () { 111 | assert.strictEqual(count++, 21) 112 | yield thunk.delay(100) 113 | }) 114 | 115 | tman.it('test level 1-2', function () { 116 | assert.strictEqual(count++, 21) // will error 117 | }) 118 | 119 | tman.it('test level 1-3', function () { 120 | assert.strictEqual(count++, 23) 121 | }) 122 | -------------------------------------------------------------------------------- /test/cli/reporters.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // `tman --reporter dot test/cli/reporters.js` 7 | 8 | const assert = require('assert') 9 | const thunk = require('thunks')() 10 | const tman = require('../..') 11 | 12 | var count = 0 13 | 14 | tman.before(function () { 15 | assert.strictEqual(count++, 0) 16 | }) 17 | 18 | tman.after(function () { 19 | assert.strictEqual(count++, 24) 20 | }) 21 | 22 | tman.suite('suite level 1-1', function () { 23 | tman.beforeEach(function * () { 24 | count++ 25 | yield thunk.delay(10) 26 | }) 27 | 28 | tman.it('test level 2-1', function () { 29 | assert.strictEqual(count++, 2) 30 | }) 31 | 32 | tman.it('test level 2-2', function () { 33 | assert.strictEqual(count++, 4) 34 | }) 35 | 36 | tman.suite('suite level 2-1', function () { 37 | tman.beforeEach(function * () { 38 | count++ 39 | yield thunk.delay(20) 40 | }) 41 | 42 | tman.it('test level 3-1', function * () { 43 | assert.strictEqual(count++, 7) 44 | yield thunk.delay(100) 45 | }) 46 | 47 | tman.it('test level 3-2', function () { 48 | assert.strictEqual(count++, 9) 49 | }) 50 | }) 51 | 52 | tman.suite('suite level 2-2', function () { 53 | tman.afterEach(function * () { 54 | count++ 55 | yield thunk.delay(20) 56 | }) 57 | 58 | tman.it('test level 3-1', function * () { 59 | assert.strictEqual(count++, 11) 60 | yield thunk.delay(100) 61 | }) 62 | 63 | tman.it('test level 3-2', function () { 64 | assert.strictEqual(count++, 13) 65 | }) 66 | 67 | tman.suite.skip('suite level 3-1', function () { 68 | tman.afterEach(function * () { 69 | assert.strictEqual('skip', false) 70 | }) 71 | 72 | tman.it('test level 4-1', function * () { 73 | assert.strictEqual('skip', false) 74 | }) 75 | 76 | tman.it('test level 4-2', function () { 77 | assert.strictEqual('skip', false) 78 | }) 79 | }) 80 | 81 | tman.suite('suite level 3-2', function () { 82 | tman.before(function () { 83 | assert.strictEqual(count++, 15) 84 | }) 85 | 86 | tman.after(function () { 87 | assert.strictEqual(count++, 19) 88 | }) 89 | 90 | tman.it('test level 4-1', function * () { 91 | assert.strictEqual(count++, 16) 92 | yield thunk.delay(100) 93 | }) 94 | 95 | tman.it('test level 4-2', function () { 96 | assert.strictEqual(count++, 17) 97 | }) 98 | 99 | tman.it.skip('test level 4-3', function () { 100 | assert.strictEqual('skip', false) 101 | }) 102 | 103 | tman.it('test level 4-4', function () { 104 | assert.strictEqual(count++, 18) 105 | }) 106 | }) 107 | }) 108 | }) 109 | 110 | tman.it('test level 1-1', function * () { 111 | assert.strictEqual(count++, 20) // will error 112 | yield thunk.delay(100) 113 | }) 114 | 115 | tman.it('test level 1-2', function () { 116 | assert.strictEqual(count++, 21) // will error 117 | }) 118 | 119 | tman.it('test level 1-3', function () { 120 | assert.strictEqual(count++, 23) 121 | }) 122 | -------------------------------------------------------------------------------- /test/browser/test-error.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global tman */ 6 | 7 | var count = 0 8 | var assert = { 9 | strictEqual: function (a, b) { 10 | if (a !== b) throw new Error(String(a) + ' not equal ' + String(b)) 11 | } 12 | } 13 | 14 | function delay (n) { 15 | return function (done) { 16 | setTimeout(done, n) 17 | } 18 | } 19 | 20 | tman.before(function () { 21 | assert.strictEqual(count++, 0) 22 | console.log('Start practical tests') 23 | }) 24 | 25 | tman.after(function () { 26 | assert.strictEqual(count++, 25) 27 | console.log('End practical tests') 28 | }) 29 | 30 | tman.suite('suite level 1-1', function () { 31 | tman.beforeEach(function * () { 32 | count++ 33 | yield delay(10) 34 | }) 35 | 36 | tman.it('test level 2-1', function () { 37 | assert.strictEqual(count++, 2) 38 | }) 39 | 40 | tman.it('test level 2-2', function () { 41 | assert.strictEqual(count++, 4) 42 | }) 43 | 44 | tman.suite('suite level 2-1', function () { 45 | tman.beforeEach(function * () { 46 | count++ 47 | yield delay(20) 48 | }) 49 | 50 | tman.it('test level 3-1', function * () { 51 | assert.strictEqual(count++, 7) 52 | yield delay(100) 53 | }) 54 | 55 | tman.it('test level 3-2', function () { 56 | assert.strictEqual(count++, 9) 57 | }) 58 | }) 59 | 60 | tman.suite('suite level 2-2', function () { 61 | tman.afterEach(function * () { 62 | count++ 63 | yield delay(20) 64 | }) 65 | 66 | tman.it('test level 3-1', function * () { 67 | assert.strictEqual(count++, 11) 68 | yield delay(100) 69 | }) 70 | 71 | tman.it('test level 3-2', function () { 72 | assert.strictEqual(count++, 13) 73 | }) 74 | 75 | tman.suite('suite level 3-1', function () { 76 | tman.afterEach(function * () { 77 | assert.strictEqual('skip', false) 78 | }) 79 | 80 | tman.it('test level 4-1', function * () { 81 | assert.strictEqual('skip', 'skip') 82 | }) 83 | 84 | tman.it('test level 4-2', function () { 85 | assert.strictEqual('skip', false) 86 | }) 87 | }) 88 | 89 | tman.suite('suite level 3-2', function () { 90 | tman.before(function () { 91 | assert.strictEqual(count++, 16) 92 | }) 93 | 94 | tman.after(function () { 95 | assert.strictEqual(count++, 20) 96 | }) 97 | 98 | tman.it('test level 4-1', function * () { 99 | assert.strictEqual(count++, 17) 100 | yield delay(100) 101 | }) 102 | 103 | tman.it('test level 4-2', function () { 104 | assert.strictEqual(count++, 18) 105 | }) 106 | 107 | tman.it('test level 4-3', function () { 108 | assert.strictEqual('test error', false) 109 | }) 110 | 111 | tman.it('test level 4-4', function () { 112 | assert.strictEqual(count++, 19) 113 | }) 114 | }) 115 | }) 116 | }) 117 | 118 | tman.it('test level 1-1', function * () { 119 | assert.strictEqual(count++, 22) 120 | yield delay(100) 121 | }) 122 | 123 | tman.it('test level 1-2', function () { 124 | assert.strictEqual(count++, 23) 125 | }) 126 | 127 | tman.it('test level 1-3', function () { 128 | assert.strictEqual(count++, 24) 129 | }) 130 | 131 | tman.run() 132 | -------------------------------------------------------------------------------- /test/browser/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global tman */ 6 | 7 | var count = 0 8 | var assert = { 9 | strictEqual: function (a, b) { 10 | if (a !== b) throw new Error(String(a) + ' not equal ' + String(b)) 11 | } 12 | } 13 | 14 | function delay (n) { 15 | return function (done) { 16 | setTimeout(done, n) 17 | } 18 | } 19 | 20 | tman.before(function () { 21 | assert.strictEqual(count++, 0) 22 | console.log('Start practical tests') 23 | }) 24 | 25 | tman.after(function () { 26 | assert.strictEqual(count++, 24) 27 | console.log('End practical tests') 28 | }) 29 | 30 | tman.suite('suite level 1-1', function () { 31 | tman.beforeEach(function * () { 32 | count++ 33 | yield delay(10) 34 | }) 35 | 36 | tman.it('test level 2-1', function () { 37 | assert.strictEqual(count++, 2) 38 | }) 39 | 40 | tman.it('test level 2-2', function () { 41 | assert.strictEqual(count++, 4) 42 | }) 43 | 44 | tman.suite('suite level 2-1', function () { 45 | tman.beforeEach(function * () { 46 | count++ 47 | yield delay(20) 48 | }) 49 | 50 | tman.it('test level 3-1', function * () { 51 | assert.strictEqual(count++, 7) 52 | yield delay(100) 53 | }) 54 | 55 | tman.it('test level 3-2', function () { 56 | assert.strictEqual(count++, 9) 57 | }) 58 | }) 59 | 60 | tman.suite('suite level 2-2', function () { 61 | tman.afterEach(function * () { 62 | count++ 63 | yield delay(20) 64 | }) 65 | 66 | tman.it('test level 3-1', function * () { 67 | assert.strictEqual(count++, 11) 68 | yield delay(100) 69 | }) 70 | 71 | tman.it('test level 3-2', function () { 72 | assert.strictEqual(count++, 13) 73 | }) 74 | 75 | tman.suite.skip('suite level 3-1', function () { 76 | tman.afterEach(function * () { 77 | assert.strictEqual('skip', false) 78 | }) 79 | 80 | tman.it('test level 4-1', function * () { 81 | assert.strictEqual('skip', false) 82 | }) 83 | 84 | tman.it('test level 4-2', function () { 85 | assert.strictEqual('skip', false) 86 | }) 87 | }) 88 | 89 | tman.suite('suite level 3-2', function () { 90 | tman.before(function () { 91 | assert.strictEqual(count++, 15) 92 | }) 93 | 94 | tman.after(function () { 95 | assert.strictEqual(count++, 19) 96 | }) 97 | 98 | tman.it('test level 4-1', function * () { 99 | assert.strictEqual(count++, 16) 100 | yield delay(100) 101 | }) 102 | 103 | tman.it('test level 4-2', function () { 104 | assert.strictEqual(count++, 17) 105 | }) 106 | 107 | tman.it.skip('test level 4-3', function () { 108 | assert.strictEqual('skip', false) 109 | }) 110 | 111 | tman.it('test level 4-4', function () { 112 | assert.strictEqual(count++, 18) 113 | }) 114 | }) 115 | }) 116 | }) 117 | 118 | tman.it('test level 1-1', function * () { 119 | assert.strictEqual(count++, 21) 120 | yield delay(100) 121 | }) 122 | 123 | tman.it('test level 1-2', function () { 124 | assert.strictEqual(count++, 22) 125 | }) 126 | 127 | tman.it('test level 1-3', function () { 128 | assert.strictEqual(count++, 23) 129 | }) 130 | 131 | tman.run() 132 | -------------------------------------------------------------------------------- /lib/reporters/spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | const util = require('util') 7 | const Reporter = require('./base') 8 | const Diff = require('./diff') 9 | const format = require('../format') 10 | 11 | module.exports = Spec 12 | Reporter.defaultReporter = Spec 13 | 14 | function Spec (ctx) { 15 | Diff.call(this, ctx) 16 | } 17 | 18 | util.inherits(Spec, Diff) 19 | 20 | Spec.prototype.onSuiteStart = function (suite) { 21 | if (this.ctx === suite.ctx) return // It is rootSuite 22 | let title = '✢ ' + suite.title 23 | title = format[suite.mode === 'skip' ? 'cyan' : 'white'](title, true) 24 | this.log(format.indent(suite.depth) + title) 25 | } 26 | 27 | Spec.prototype.onSuiteFinish = function (suite) { 28 | if (suite.state instanceof Error) { 29 | const title = format.red('✗ ' + suite.state.title + ' (' + suite.state.order + ')', true) 30 | this.log(format.indent(suite.depth + 1) + title) 31 | } 32 | } 33 | 34 | Spec.prototype.onTestFinish = function (test) { 35 | let title = test.title 36 | if (test.state === null) { 37 | title = format.cyan('‒ ' + title, true) 38 | } else if (test.state === true) { 39 | title = format.green('✓ ') + format.gray(title) 40 | const time = test.endTime - test.startTime 41 | if (time > 50) title += format.red(' (' + time + 'ms)') 42 | } else { 43 | title = format.red('✗ ' + title + ' (' + test.state.order + ')', true) 44 | } 45 | this.log(format.indent(test.depth) + title) 46 | } 47 | 48 | Spec.prototype.onFinish = function (rootSuite) { 49 | let message = '' 50 | 51 | if (rootSuite.abort) message += format.yellow('\nTest is terminated by SIGINT!\n', true) 52 | message += format.reset('\nTest ' + (rootSuite.errors.length ? 'failed: ' : 'finished: ')) 53 | message += format[rootSuite.passed ? 'green' : 'gray'](rootSuite.passed + ' passed; ', true) 54 | message += format[rootSuite.errors.length ? 'red' : 'gray'](rootSuite.errors.length + ' failed; ', true) 55 | message += format[rootSuite.ignored ? 'cyan' : 'gray'](rootSuite.ignored + ' ignored.', true) 56 | message += format.yellow(' (' + (rootSuite.endTime - rootSuite.startTime) + 'ms)', true) 57 | this.log(message, format.reset('\n')) 58 | 59 | this.logError(rootSuite) 60 | if (rootSuite.errors.length) this.log(format.reset('\n')) 61 | if (rootSuite.exit) process.exit((rootSuite.errors.length || !rootSuite.passed) ? 1 : 0) 62 | } 63 | 64 | // Result: 65 | // ``` 66 | // 67 | // ✢ suite level 1-1 68 | // ✓ test level 2-1 69 | // ✓ test level 2-2 70 | // ✢ suite level 2-1 71 | // ✓ test level 3-1 (106ms) 72 | // ✓ test level 3-2 73 | // ✢ suite level 2-2 74 | // ✓ test level 3-1 (105ms) 75 | // ✓ test level 3-2 76 | // ✢ suite level 3-1 77 | // ‒ test level 4-1 78 | // ‒ test level 4-2 79 | // ✢ suite level 3-2 80 | // ✓ test level 4-1 (100ms) 81 | // ✓ test level 4-2 82 | // ‒ test level 4-3 83 | // ✓ test level 4-4 84 | // ✓ test level 1-1 (100ms) 85 | // ✗ test level 1-2 (1) 86 | // ✓ test level 1-3 87 | // 88 | // Test failed: 11 passed; 1 failed; 3 ignored. (606ms) 89 | // 90 | // 1) /test level 1-2: 91 | // AssertionError: 22 === 21 92 | // at Test.fn (/Users/zensh/git/js/thunkjs/tman/example/nested.js:116:10) 93 | // at Test. (/Users/zensh/git/js/thunkjs/tman/lib/core.js:557:37) 94 | // 95 | // ``` 96 | -------------------------------------------------------------------------------- /test/browser/test-async.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | /* global tman */ 6 | 7 | var count = 0 8 | var assert = { 9 | strictEqual: function (a, b) { 10 | if (a !== b) throw new Error(String(a) + ' not equal ' + String(b)) 11 | } 12 | } 13 | 14 | function delay (n) { 15 | return new Promise((resolve, reject) => setTimeout(resolve, n)) 16 | } 17 | 18 | tman.before(function () { 19 | assert.strictEqual(count++, 0) 20 | console.log('Start practical tests') 21 | }) 22 | 23 | tman.after(function () { 24 | assert.strictEqual(count++, 24) 25 | console.log('End practical tests') 26 | }) 27 | 28 | tman.suite('suite level 1-1', function () { 29 | tman.beforeEach(function * () { 30 | count++ 31 | yield delay(10) 32 | }) 33 | 34 | tman.it('test level 2-1', function () { 35 | assert.strictEqual(count++, 2) 36 | }) 37 | 38 | tman.it('test level 2-2', function () { 39 | assert.strictEqual(count++, 4) 40 | }) 41 | 42 | tman.suite('suite level 2-1', function () { 43 | tman.beforeEach(async function () { 44 | count++ 45 | await delay(20) 46 | }) 47 | 48 | tman.it('test level 3-1', async () => { 49 | assert.strictEqual(count++, 7) 50 | await delay(100) 51 | }) 52 | 53 | tman.it('test level 3-2', function () { 54 | assert.strictEqual(count++, 9) 55 | }) 56 | }) 57 | 58 | tman.suite('suite level 2-2', function () { 59 | tman.afterEach(async () => { 60 | count++ 61 | await delay(20) 62 | }) 63 | 64 | tman.it('test level 3-1', async function () { 65 | assert.strictEqual(count++, 11) 66 | await delay(100) 67 | }) 68 | 69 | tman.it('test level 3-2', function () { 70 | assert.strictEqual(count++, 13) 71 | }) 72 | 73 | tman.suite.skip('suite level 3-1', function () { 74 | tman.afterEach(async function () { 75 | assert.strictEqual('skip', false) 76 | }) 77 | 78 | tman.it('test level 4-1', async function () { 79 | assert.strictEqual('skip', false) 80 | }) 81 | 82 | tman.it('test level 4-2', function () { 83 | assert.strictEqual('skip', false) 84 | }) 85 | }) 86 | 87 | tman.suite('suite level 3-2', function () { 88 | tman.before(function () { 89 | assert.strictEqual(count++, 15) 90 | }) 91 | 92 | tman.after(function () { 93 | assert.strictEqual(count++, 19) 94 | }) 95 | 96 | tman.it('test level 4-1', async function () { 97 | assert.strictEqual(count++, 16) 98 | await delay(100) 99 | }) 100 | 101 | tman.it('test level 4-2', function () { 102 | assert.strictEqual(count++, 17) 103 | }) 104 | 105 | tman.it.skip('test level 4-3', function () { 106 | assert.strictEqual('skip', false) 107 | }) 108 | 109 | tman.it('test level 4-4', function () { 110 | assert.strictEqual(count++, 18) 111 | }) 112 | }) 113 | }) 114 | }) 115 | 116 | tman.it('test level 1-1', async function () { 117 | assert.strictEqual(count++, 21) 118 | await delay(100) 119 | }) 120 | 121 | tman.it('test level 1-2', function () { 122 | assert.strictEqual(count++, 22) 123 | }) 124 | 125 | tman.it('test level 1-3', function () { 126 | assert.strictEqual(count++, 23) 127 | }) 128 | 129 | tman.run() 130 | -------------------------------------------------------------------------------- /test/cli/test-in-src.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | // `bin/tman test/cli/test-in-src` 7 | // `TEST=* node test/cli/test-in-src` 8 | // `node test/cli/test-in-src --test` 9 | 10 | const assert = require('assert') 11 | const thunk = require('thunks')() 12 | const tman = require('../..') 13 | 14 | var count = 0 15 | 16 | module.exports = atomicCount 17 | function atomicCount () { 18 | return count++ 19 | } 20 | 21 | tman(function () { 22 | tman.before(function () { 23 | assert.strictEqual(atomicCount(), 0) 24 | }) 25 | 26 | tman.after(function () { 27 | assert.strictEqual(atomicCount(), 24) 28 | }) 29 | 30 | tman.suite('suite level 1-1', function () { 31 | tman.beforeEach(function * () { 32 | atomicCount() 33 | yield thunk.delay(10) 34 | }) 35 | 36 | tman.it('test level 2-1', function () { 37 | assert.strictEqual(atomicCount(), 2) 38 | }) 39 | 40 | tman.it('test level 2-2', function () { 41 | assert.strictEqual(atomicCount(), 4) 42 | }) 43 | 44 | tman.suite('suite level 2-1', function () { 45 | tman.beforeEach(function * () { 46 | atomicCount() 47 | yield thunk.delay(20) 48 | }) 49 | 50 | tman.it('test level 3-1', function * () { 51 | assert.strictEqual(atomicCount(), 7) 52 | yield thunk.delay(100) 53 | }) 54 | 55 | tman.it('test level 3-2', function () { 56 | assert.strictEqual(atomicCount(), 9) 57 | }) 58 | }) 59 | 60 | tman.suite('suite level 2-2', function () { 61 | tman.afterEach(function * () { 62 | atomicCount() 63 | yield thunk.delay(20) 64 | }) 65 | 66 | tman.it('test level 3-1', function * () { 67 | assert.strictEqual(atomicCount(), 11) 68 | yield thunk.delay(100) 69 | }) 70 | 71 | tman.it('test level 3-2', function () { 72 | assert.strictEqual(atomicCount(), 13) 73 | }) 74 | 75 | tman.suite.skip('suite level 3-1', function () { 76 | tman.afterEach(function * () { 77 | assert.strictEqual('skip', false) 78 | }) 79 | 80 | tman.it('test level 4-1', function * () { 81 | assert.strictEqual('skip', false) 82 | }) 83 | 84 | tman.it('test level 4-2', function () { 85 | assert.strictEqual('skip', false) 86 | }) 87 | }) 88 | 89 | tman.suite('suite level 3-2', function () { 90 | tman.before(function () { 91 | assert.strictEqual(atomicCount(), 15) 92 | }) 93 | 94 | tman.after(function () { 95 | assert.strictEqual(atomicCount(), 19) 96 | }) 97 | 98 | tman.it('test level 4-1', function * () { 99 | assert.strictEqual(atomicCount(), 16) 100 | yield thunk.delay(100) 101 | }) 102 | 103 | tman.it('test level 4-2', function () { 104 | assert.strictEqual(atomicCount(), 17) 105 | }) 106 | 107 | tman.it.skip('test level 4-3', function () { 108 | assert.strictEqual('skip', false) 109 | }) 110 | 111 | tman.it('test level 4-4', function () { 112 | assert.strictEqual(atomicCount(), 18) 113 | }) 114 | }) 115 | }) 116 | }) 117 | 118 | tman.it('test level 1-1', function * () { 119 | assert.strictEqual(atomicCount(), 21) 120 | yield thunk.delay(100) 121 | }) 122 | 123 | tman.it('test level 1-2', function () { 124 | assert.strictEqual(atomicCount(), 22) 125 | }) 126 | 127 | tman.it('test level 1-3', function () { 128 | assert.strictEqual(atomicCount(), 23) 129 | }) 130 | }) 131 | -------------------------------------------------------------------------------- /lib/tman.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | const fs = require('fs') 7 | const path = require('path') 8 | const glob = require('glob') 9 | const core = require('./core') 10 | const format = require('./format') 11 | const info = require('../package.json') 12 | const Reporter = require('./reporters/base') 13 | require('./reporters/spec') // mount "spec" as default reporter 14 | 15 | const env = {} 16 | const tm = module.exports = tmanFactroy() 17 | tm.NAME = info.name 18 | tm.VERSION = info.version 19 | tm.Test = core.Test 20 | tm.Suite = core.Suite 21 | tm.Reporter = Reporter 22 | tm.format = format 23 | tm.createTman = tmanFactroy 24 | tm.tman = tm 25 | tm.env = env 26 | tm.env.TEST = getProcessEnv() 27 | tm.baseDir = '' 28 | tm.setBaseDir = function (filePath) { 29 | if (!tm.baseDir) tm.baseDir = path.dirname(filePath) 30 | else { 31 | for (let i = 0; i < tm.baseDir.length; i++) { 32 | if (tm.baseDir[i] === filePath[i]) continue 33 | tm.baseDir = tm.baseDir.slice(0, i) 34 | return 35 | } 36 | } 37 | } 38 | tm.loadFiles = function (files, sort) { 39 | if (!Array.isArray(files)) files = [files] 40 | if (tm.baseDir && require.cache) { 41 | // clear test files require cache 42 | Object.keys(require.cache).forEach((id) => { 43 | if (id.indexOf(tm.baseDir) === 0) delete require.cache[id] 44 | }) 45 | } 46 | files = resolveFiles(files) 47 | if (sort !== false) sortFiles(files) 48 | files.forEach((filePath) => { 49 | filePath = path.resolve(filePath) 50 | tm.setBaseDir(filePath) 51 | require(filePath) 52 | }) 53 | } 54 | tm.globals = function (globals) { 55 | globals.forEach((name) => { 56 | if (global[name]) throw new Error('"' + name + '" exists on global') 57 | if (!tm[name]) throw new Error('"' + name + '" not exists on tman') 58 | global[name] = tm[name] 59 | }) 60 | } 61 | tm.useColors = function (useColors) { 62 | format.useColors(useColors) 63 | } 64 | tm.loadReporter = function (reporter) { 65 | const reporterPath = path.join(__dirname, 'reporters', reporter) 66 | try { 67 | const Reporter = require(reporterPath) 68 | tm.setReporter(Reporter) 69 | } catch (err) { 70 | throw new Error('reporter "' + reporter + '" does not exist in ' + reporterPath) 71 | } 72 | } 73 | 74 | function tmanFactroy () { 75 | const tman = core.Tman(env) 76 | tman.setReporter(Reporter.defaultReporter) 77 | return tman 78 | } 79 | 80 | function getProcessEnv () { 81 | let envTest = tm.env.TEST || process.env.TEST 82 | if (envTest) return envTest 83 | for (let i = 2; i < process.argv.length; i++) { 84 | if (process.argv[i].indexOf('--test') === 0) { 85 | envTest = process.argv[i].slice(7) 86 | break 87 | } 88 | } 89 | return envTest == null ? '' : (envTest || 'root') 90 | } 91 | 92 | function resolveFiles (args) { 93 | const files = [] 94 | args.forEach((arg) => { 95 | const result = [] 96 | if (fsStat(arg) === 1) { 97 | files.push(arg) 98 | return 99 | } 100 | const filenames = glob.sync(arg) 101 | if (!filenames.length) filenames.push(arg + '.js') 102 | filenames.forEach((filename) => { 103 | const stat = fsStat(filename) 104 | if (stat === 1) result.push(filename) 105 | else if (stat === 2) { 106 | result.push.apply(result, glob.sync(path.join(filename, '*.{js,ts,es,coffee}'))) 107 | } 108 | }) 109 | files.push.apply(files, result) 110 | }) 111 | return files 112 | } 113 | 114 | function sortFiles (files) { 115 | files.sort((a, b) => (a.split(path.sep).length - b.split(path.sep).length) || Number(a > b) || -Number(a < b)) 116 | } 117 | 118 | function fsStat (filePath) { 119 | try { 120 | const stat = fs.statSync(filePath) 121 | if (stat.isFile()) return 1 122 | else if (stat.isDirectory()) return 2 123 | else return 0 124 | } catch (e) {} 125 | return 0 126 | } 127 | -------------------------------------------------------------------------------- /lib/reporters/browser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | const Reporter = require('./base') 7 | 8 | module.exports = Browser 9 | Reporter.defaultReporter = Browser 10 | 11 | function Browser (ctx) { 12 | Reporter.call(this, ctx) 13 | } 14 | inherits(Browser, Reporter) 15 | 16 | Browser.prototype.onStart = function (suite) { 17 | let rootElement = document.getElementById('tman') 18 | if (!rootElement) { 19 | rootElement = createElement('div', 'tman') 20 | rootElement.setAttribute('id', 'tman') 21 | document.body.appendChild(rootElement) 22 | } 23 | rootElement.appendChild(createElement('h2', 'tman-header', 'T-man')) 24 | this.ctx.$element = rootElement 25 | } 26 | 27 | Browser.prototype.onSuiteStart = function (suite) { 28 | if (this.ctx === suite.ctx) return // It is rootSuite 29 | const title = '✢ ' + suite.title 30 | const $element = suite.ctx.$element = createElement('div', 'tman-suite') 31 | $element.appendChild(createElement('h3', '', indent(suite.depth) + title)) 32 | suite.ctx.parent.$element.appendChild($element) 33 | } 34 | 35 | Browser.prototype.onSuiteFinish = function (suite) { 36 | if (suite.state instanceof Error) { 37 | suite.ctx.$element.setAttribute('class', 'tman-test error') 38 | const $element = createElement('span', 'more-info', 39 | indent(suite.depth + 1) + suite.state.title + ' ✗ (' + suite.state.order + ')') 40 | suite.ctx.$element.appendChild($element) 41 | } 42 | } 43 | 44 | Browser.prototype.onTestStart = function (test) { 45 | test.ctx.$element = createElement('div', 'tman-test', indent(test.depth) + test.title) 46 | test.ctx.parent.$element.appendChild(test.ctx.$element) 47 | } 48 | 49 | Browser.prototype.onTestFinish = function (test) { 50 | let message = '' 51 | let className = 'tman-test ' 52 | if (test.state === null) { 53 | message += ' ‒' 54 | className += 'ignored' 55 | } else if (test.state === true) { 56 | message += ' ✓' 57 | className += 'success' 58 | const time = test.endTime - test.startTime 59 | if (time > 50) message += ' (' + time + 'ms)' 60 | } else { 61 | message += ' ✗ (' + test.state.order + ')' 62 | className += 'error' 63 | } 64 | test.ctx.$element.setAttribute('class', className) 65 | if (message) { 66 | test.ctx.$element.appendChild(createElement('span', 'more-info', message)) 67 | } 68 | } 69 | 70 | Browser.prototype.onFinish = function (rootSuite) { 71 | const resultElement = createElement('div', 'tman-footer') 72 | this.ctx.$element.appendChild(resultElement) 73 | 74 | const statElement = createElement('div', 'tman-statistics') 75 | statElement.appendChild(createElement('span', 76 | 'info', 'Test ' + (rootSuite.errors.length ? 'failed: ' : 'finished: '))) 77 | statElement.appendChild(createElement('span', 78 | rootSuite.passed && 'success', rootSuite.passed + ' passed;')) 79 | statElement.appendChild(createElement('span', 80 | rootSuite.errors.length && 'error', rootSuite.errors.length + ' failed;')) 81 | statElement.appendChild(createElement('span', 82 | rootSuite.errors && 'ignored', rootSuite.ignored + ' ignored.')) 83 | statElement.appendChild(createElement('span', 84 | 'info', '(' + (rootSuite.endTime - rootSuite.startTime) + 'ms)')) 85 | 86 | resultElement.appendChild(statElement) 87 | /* istanbul ignore next */ 88 | rootSuite.errors.forEach((err) => { 89 | const errElement = createElement('div', 'tman-error') 90 | errElement.appendChild(createElement('h4', 'error', err.order + ') ' + err.title + ':')) 91 | let message = err.stack ? err.stack : String(err) 92 | message = message.replace(/^/gm, '
').replace(/ /g, ' ').slice(5) 93 | errElement.appendChild(createElement('p', 'error-stack', message)) 94 | resultElement.appendChild(errElement) 95 | }) 96 | } 97 | 98 | function indent (len) { 99 | let ch = '  ' 100 | let pad = '' 101 | 102 | while (len > 0) { 103 | if (len & 1) pad += ch 104 | if ((len >>= 1)) ch = ch + ch // avoid "standard" lint 105 | } 106 | return pad 107 | } 108 | 109 | function createElement (tag, className, content) { 110 | const el = document.createElement(tag) 111 | if (className) el.setAttribute('class', className) 112 | if (content) el.innerHTML = content 113 | return el 114 | } 115 | 116 | function inherits (Child, Parent) { 117 | function Ctor () { 118 | this.constructor = Child 119 | } 120 | 121 | Ctor.prototype = Parent.prototype 122 | Child.prototype = new Ctor() 123 | return Child 124 | } 125 | -------------------------------------------------------------------------------- /lib/reporters/diff.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | const util = require('util') 7 | const diff = require('diff') 8 | const Reporter = require('./base') 9 | const format = require('../format') 10 | const objToString = Object.prototype.toString 11 | 12 | module.exports = Diff 13 | Reporter.defaultReporter = Diff 14 | 15 | function Diff (ctx) { 16 | Reporter.call(this, ctx) 17 | } 18 | 19 | util.inherits(Diff, Reporter) 20 | 21 | Diff.prototype.onFinish = function (rootSuite) { 22 | let message = '\nTest ' + (rootSuite.errors.length ? 'failed: ' : 'finished: ') 23 | message += rootSuite.passed + ' passed; ' 24 | message += rootSuite.errors.length + ' failed; ' 25 | message += rootSuite.ignored + ' ignored.' 26 | message += ' (' + (rootSuite.endTime - rootSuite.startTime) + 'ms)\n' 27 | this.log(message) 28 | this.logError(rootSuite) 29 | if (rootSuite.exit && process.exit) process.exit((rootSuite.errors.length || !rootSuite.passed) ? 1 : 0) 30 | } 31 | 32 | Diff.prototype.logError = function (rootSuite) { 33 | rootSuite.errors.forEach((err, i) => { 34 | // msg 35 | let msg 36 | let message 37 | if (err.message && typeof err.message.toString === 'function') { 38 | message = err.message + '' 39 | } else if (typeof err.inspect === 'function') { 40 | message = err.inspect() + '' 41 | } else { 42 | message = '' 43 | } 44 | let stack = err.stack || message 45 | let index = message ? stack.indexOf(message) : -1 46 | let actual = err.actual 47 | let expected = err.expected 48 | 49 | if (index === -1) { 50 | msg = message 51 | } else { 52 | index += message.length 53 | msg = stack.slice(0, index) 54 | // remove msg from stack 55 | stack = stack.slice(index + 1) 56 | } 57 | 58 | // uncaught 59 | if (err.uncaught) { 60 | msg = 'Uncaught ' + msg 61 | } 62 | // explicitly show diff 63 | const isShowDiff = err.showDiff !== false && sameType(actual, expected) && expected !== undefined 64 | if (isShowDiff) { 65 | if (!(typeof actual === 'string' && typeof expected === 'string')) { 66 | err._actual = err.actual 67 | err._expected = err.expected 68 | err.actual = actual = stringify(actual) 69 | err.expected = expected = stringify(expected) 70 | } 71 | 72 | const match = message.match(/^([^:]+): expected/) 73 | msg = '\n ' + format.gray(match ? match[1] : msg) 74 | 75 | msg += unifiedDiff(err) 76 | } 77 | 78 | // indent stack trace 79 | stack = stack.replace(/^/gm, ' ') 80 | const result = errMessageFormat(isShowDiff, (i + 1), err.title, msg, stack) 81 | this.log(result) 82 | }) 83 | } 84 | 85 | /** 86 | * Return formated error message 87 | * @private 88 | * @param{boolean} is diff message 89 | * @param{number} index of message in Error queue 90 | * @param{string} test suite title 91 | * @param{string} error message 92 | * @param{string} error stack 93 | */ 94 | function errMessageFormat (showDiff, pos, title, msg, stack) { 95 | if (showDiff) { 96 | return format.red(' ' + pos + ') ' + title + ':\n' + msg) + format.gray('\n' + stack + '\n') 97 | } 98 | return format.red(' ' + pos + ') ' + title + ':\n') + 99 | format.white(' ' + msg) + 100 | format.white('\n' + stack + '\n') 101 | } 102 | 103 | /** 104 | * Returns a unified diff between two strings. 105 | * @private 106 | * @param {Error} err with actual/expected 107 | * @return {string} The diff. 108 | */ 109 | function unifiedDiff (err) { 110 | const indent = ' ' 111 | function cleanUp (line) { 112 | if (line[0] === '+') { 113 | return indent + format.colorLines('green', line) 114 | } 115 | if (line[0] === '-') { 116 | return indent + format.colorLines('red', line) 117 | } 118 | if (line.match(/@@/)) { 119 | return null 120 | } 121 | if (line.match(/\\ No newline/)) { 122 | return null 123 | } 124 | if (line.trim().length) { 125 | line = format.colorLines('white', line) 126 | } 127 | return indent + line 128 | } 129 | function notBlank (line) { 130 | return typeof line !== 'undefined' && line !== null 131 | } 132 | let msg = diff.createPatch('string', err.actual, err.expected) 133 | let lines = msg.split('\n').splice(4) 134 | let diffResult = lines.map(cleanUp).filter(notBlank).join('\n') 135 | if (!diffResult.trim().length) { 136 | msg = diff.createPatch( 137 | 'string', 138 | stringify(Object.keys(err._actual || err.actual).sort()), 139 | stringify(Object.keys(err._expected || err.expected).sort()) 140 | ) 141 | lines = msg.split('\n').splice(4) 142 | diffResult = format.red(' object keys not match: \n') + lines.map(cleanUp).filter(notBlank).join('\n') 143 | } 144 | return '\n ' + 145 | format.colorLines('green', '+ expected') + ' ' + 146 | format.colorLines('red', '- actual') + 147 | '\n\n' + 148 | diffResult 149 | } 150 | 151 | /** 152 | * Check that a / b have the same type. 153 | * 154 | * @private 155 | * @param {Object} a 156 | * @param {Object} b 157 | * @return {boolean} 158 | */ 159 | function sameType (a, b) { 160 | return objToString.call(a) === objToString.call(b) 161 | } 162 | 163 | let CIRCULAR_ERROR_MESSAGE 164 | 165 | function stringify (obj) { 166 | try { 167 | if (obj && typeof obj.toJSON === 'function') { 168 | obj = obj.toJSON() 169 | } 170 | return typeof obj === 'string' 171 | ? obj : JSON.stringify(obj, Object.keys(obj).sort(), 2).replace(/,(\n|$)/g, '$1') 172 | } catch (err) { 173 | // Populate the circular error message lazily 174 | if (!CIRCULAR_ERROR_MESSAGE) { 175 | try { 176 | const a = {}; a.a = a; JSON.stringify(a) 177 | } catch (err) { 178 | CIRCULAR_ERROR_MESSAGE = err.message 179 | } 180 | } 181 | if (err.name === 'TypeError' && err.message === CIRCULAR_ERROR_MESSAGE) { 182 | return '[Circular]' 183 | } 184 | } 185 | return '[object Object]' 186 | } 187 | -------------------------------------------------------------------------------- /bin/_tman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const fs = require('fs') 5 | const path = require('path') 6 | const program = require('commander') 7 | const getOptions = require('./options') 8 | const packageInfo = require('../package.json') 9 | const cwd = process.cwd() 10 | 11 | // Prefer the local installation of T-man 12 | const tman = localTman(path.join(cwd, 'node_modules', 'tman')) || localTman(cwd) || require('..') 13 | if (packageInfo.version !== tman.VERSION) { 14 | console.warn(tman.format.red('\nWarning: T-man version mismatch:', true)) 15 | console.warn(tman.format.red( 16 | ' Global: v' + packageInfo.version + ', update: npm i -g tman', true)) 17 | console.warn(tman.format.red( 18 | ' Local: v' + tman.VERSION + ', update: npm i tman@latest\n', true)) 19 | } 20 | 21 | // options 22 | program._name = tman.NAME 23 | program 24 | .version('v' + tman.VERSION) 25 | .usage('[debug] [options] [files]') 26 | .option('-c, --colors', 'force enabling of colors') 27 | .option('-C, --no-colors', 'force disabling of colors') 28 | .option('-d, --debug', "enable node's debugger, synonym for node --debug") 29 | .option('-e, --exclude ', 'exclude tests matching ') 30 | .option('-g, --grep ', 'run tests matching ') 31 | .option('-gc, --expose-gc', 'expose gc extension') 32 | .option('-r, --require ', 'require the given module') 33 | .option('-R, --reporter ', 'specify the reporter to use [spec]') 34 | .option('-t, --timeout ', 'set test-case timeout in milliseconds [2000]') 35 | .option('--debug-brk', "enable node's debugger breaking on the first line") 36 | .option('--inspect', "enable node's debugger breaking on the first line") 37 | .option('--inspect-brk', "enable node's debugger breaking on the first line") 38 | .option('--es_staging', 'enable all staged features') 39 | .option('--globals ', 'allow the given comma-delimited global [names]') 40 | .option('--harmony<_classes,_generators,...>', 'all node --harmony* flags are available') 41 | .option('--icu-data-dir', 'include ICU data') 42 | .option('--mocha', 'mocha compatible mode') 43 | .option('--no-sort', 'don\'t sort test files') 44 | .option('--no-timeout', 'disables timeouts, given implicitly with --debug') 45 | .option('--no-exit', 46 | 'require a clean shutdown of the event loop: T-man will not call process.exit') 47 | .option('--opts ', 'specify opts path', 'test/tman.opts') 48 | .option('--perf-basic-prof', 'enable perf linux profiler (basic support)') 49 | .option('--preserve-symlinks', 'Instructs the module loader to preserve symbolic links when resolving and caching modules') 50 | .option('--reporters', 'display available reporters') 51 | .option('--throw-deprecation', 'throw an exception anytime a deprecated function is used') 52 | .option('--trace', 'trace function calls') 53 | .option('--trace-deprecation', 'show stack traces on deprecations') 54 | .option('--use_strict', 'enforce strict mode') 55 | 56 | module.paths.push(cwd, path.join(cwd, 'node_modules')) 57 | // -r, --require 58 | const requires = [] 59 | program.on('option:require', (name) => { 60 | let stat = fsStat(name + '.js') || fsStat(name) 61 | if (stat) name = path.resolve(name) 62 | requires.push(name) 63 | }) 64 | // --globals 65 | const globals = [] 66 | program.on('option:globals', (val) => { 67 | globals.push.apply(globals, parseList(val)) 68 | }) 69 | program.on('option:reporters', () => { 70 | console.log() 71 | console.log(' dot - dot matrix') 72 | console.log(' spec - hierarchical spec list') 73 | console.log(' base - spec-style listing with TAB') 74 | console.log() 75 | process.exit() 76 | }) 77 | 78 | // If not already done, load mocha.opts 79 | if (!process.env.LOADED_TMAN_OPTS) getOptions() 80 | // parse args 81 | program.parse(process.argv) 82 | 83 | // --no-colors | --colors 84 | if (~process.argv.indexOf('--no-colors') || ~process.argv.indexOf('-C')) tman.useColors(false) 85 | else if (~process.argv.indexOf('--colors') || ~process.argv.indexOf('-c')) tman.useColors(true) 86 | // --exclude 87 | if (program.exclude) tman.exclude(program.exclude) 88 | // --grep 89 | if (program.grep) tman.grep(program.grep) 90 | // --mocha 91 | if (program.mocha) tman.mocha() 92 | // --no-exit 93 | tman.setExit(program.exit) 94 | // --timeout 95 | const timeout = program.timeout === false ? -1 : parseInt(program.timeout, 10) 96 | if (timeout) tman.timeout(timeout) 97 | 98 | // reporter 99 | if (program.reporter) tman.loadReporter(program.reporter) 100 | 101 | // requires 102 | requires.forEach((mod) => require(mod)) 103 | 104 | // register to global object 105 | const defaultGlobals = ['tman', 'describe', 'suite', 'test', 'it', 'before', 'after', 'beforeEach', 'afterEach'] 106 | tman.globals(globals.length ? globals : defaultGlobals) 107 | 108 | if (!tman.env.TEST) tman.env.TEST = 'root' 109 | if (!process.env.npm_execpath) { 110 | // can't exit when runing with npm. https://github.com/npm/npm/issues/4603 111 | process.once('SIGINT', () => { 112 | tman.abort() 113 | // force to exit in 3 seconds. 114 | setTimeout(() => tman.exit(1), 3000) 115 | }) 116 | } 117 | 118 | // // load test files. 119 | const files = program.args 120 | // default files to `test/*.{js,ts,es,coffee}` 121 | if (!files.length) files.push(path.join('test', '*.{js,ts,es,coffee}')) 122 | tman.loadFiles(files, program.sort !== false) 123 | tman.tryRun() 124 | 125 | function localTman (dirname) { 126 | let tmanId = path.sep + path.join('lib', 'tman.js') 127 | let file = path.join(dirname, 'lib', 'tman.js') 128 | if (file.slice(tmanId.length * -1) !== tmanId) return null 129 | if (fsStat(file) !== 1 || fsStat(path.join(dirname, 'bin', 'tman')) !== 1) return null 130 | return require(file) 131 | } 132 | 133 | // 0: unknown, 1: file, 2: directory 134 | function fsStat (filePath) { 135 | try { 136 | let stat = fs.statSync(filePath) 137 | if (stat.isFile()) return 1 138 | else if (stat.isDirectory()) return 2 139 | else return 0 140 | } catch (e) {} 141 | return 0 142 | } 143 | 144 | // Parse list. 145 | function parseList (str) { 146 | return str.replace(/\W/g, ' ').trim().split(/ +/) 147 | } 148 | -------------------------------------------------------------------------------- /test/typings.test.ts: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // `bin/tman -r ts-node/register test/typings.test.ts` 4 | 5 | /// 6 | 7 | import { thunk, thunks, Scope } from 'thunks' 8 | import * as assert from 'assert' 9 | import * as tman from '../' 10 | import { tman as tman1, Suite, Test, Reporter, suite, it } from '../' 11 | 12 | tman(function () {}) // should ok 13 | tman(function (done) {}) // should error 14 | tman('test', function () {}) // should ok 15 | 16 | tman.suite('tman typings', () => { 17 | tman.it('tman', function () { 18 | assert.ok(tman.suite instanceof Function) 19 | assert.ok(tman.it instanceof Function) 20 | assert.ok(tman.before instanceof Function) 21 | assert.ok(tman.after instanceof Function) 22 | assert.ok(tman.beforeEach instanceof Function) 23 | assert.ok(tman.afterEach instanceof Function) 24 | assert.ok(tman.only instanceof Function) 25 | assert.ok(tman.skip instanceof Function) 26 | 27 | assert.ok(tman.Suite instanceof Function) 28 | assert.ok(tman.Test instanceof Function) 29 | assert.ok(tman.setBaseDir instanceof Function) 30 | assert.ok(tman.grep instanceof Function) 31 | assert.ok(tman.exclude instanceof Function) 32 | assert.ok(tman.mocha instanceof Function) 33 | assert.ok(tman.reset instanceof Function) 34 | assert.ok(tman.setExit instanceof Function) 35 | assert.ok(tman.tryRun instanceof Function) 36 | assert.ok(tman.run instanceof Function) 37 | assert.ok(tman.createTman instanceof Function) 38 | assert.ok(tman.loadFiles instanceof Function) 39 | assert.ok(tman.loadReporter instanceof Function) 40 | assert.ok(tman.useColors instanceof Function) 41 | assert.ok(tman.globals instanceof Function) 42 | assert.ok(tman.rootSuite instanceof Suite) 43 | assert.ok(tman.rootSuite.reporter instanceof Reporter) 44 | 45 | assert.ok(tman.Reporter.prototype.log instanceof Function) 46 | assert.ok(tman.Reporter.prototype.onSuiteStart instanceof Function) 47 | assert.ok(tman.Reporter.prototype.onSuiteFinish instanceof Function) 48 | assert.ok(tman.Reporter.prototype.onTestStart instanceof Function) 49 | assert.ok(tman.Reporter.prototype.onTestFinish instanceof Function) 50 | assert.ok(tman.Reporter.prototype.onStart instanceof Function) 51 | assert.ok(tman.Reporter.prototype.onFinish instanceof Function) 52 | 53 | assert.strictEqual(tman, tman1) 54 | assert.strictEqual(tman.Suite, Suite) 55 | assert.strictEqual(tman.Test, Test) 56 | assert.strictEqual(tman.it, it) 57 | assert.strictEqual(tman.it, tman.test) 58 | assert.strictEqual(tman.suite, suite) 59 | assert.strictEqual(tman.suite, tman.describe) 60 | }) 61 | 62 | tman.it('tman(suite)', function () { 63 | let tm = tman.createTman() 64 | assert.ok(tm(function () {}) instanceof tman.Suite) 65 | assert.ok(tm('test', function () {}) instanceof tman.Suite) 66 | tm.setExit(false) 67 | return tm.run(function () {}) 68 | }) 69 | }) 70 | 71 | tman.suite('run with typings', () => { 72 | let count = 0 73 | 74 | tman.before(() => { 75 | assert.strictEqual(count++, 0) 76 | }) 77 | 78 | tman.after((done) => { 79 | assert.strictEqual(count++, 24) 80 | done() 81 | }) 82 | 83 | tman.suite('suite level 1-1', () => { 84 | tman.beforeEach(function () { 85 | count++ 86 | return thunk.delay(10) 87 | }) 88 | 89 | tman.it('test level 2-1', function () { 90 | assert.strictEqual(count++, 2) 91 | return Promise.resolve() 92 | }) 93 | 94 | tman.it('test level 2-2', function * () { 95 | yield thunk.delay(10) 96 | assert.strictEqual(count++, 4) 97 | }) 98 | 99 | tman.suite('suite level 2-1', () => { 100 | tman.beforeEach(function () { 101 | count++ 102 | return { 103 | then: function (resolve, reject) { 104 | return Promise.resolve(resolve()) 105 | } 106 | } 107 | }) 108 | 109 | tman.it('test level 3-1', function () { 110 | assert.strictEqual(count++, 7) 111 | return { 112 | toThunk: function () { 113 | return function (done) { setTimeout(done, 10)} 114 | } 115 | } 116 | }) 117 | 118 | tman.it('test level 3-2', () => { 119 | assert.strictEqual(count++, 9) 120 | 121 | return { 122 | toPromise: function () { 123 | return Promise.resolve() 124 | } 125 | } 126 | }) 127 | }) 128 | 129 | tman.suite('suite level 2-2', () => { 130 | tman.afterEach(function () { 131 | count++ 132 | return (function *() { yield thunk.delay(20) })() 133 | }) 134 | 135 | tman.it('test level 3-1', function () { 136 | assert.strictEqual(count++, 11) 137 | return thunks(new Scope())(1) 138 | }) 139 | 140 | tman.it('test level 3-2', () => { 141 | assert.strictEqual(count++, 13) 142 | }) 143 | 144 | tman.suite.skip('suite level 3-1', () => { 145 | tman.afterEach(function () { 146 | assert.strictEqual('skip', false) 147 | }) 148 | 149 | tman.it('test level 4-1', function () { 150 | assert.strictEqual('skip', false) 151 | }) 152 | 153 | tman.it('test level 4-2', () => { 154 | assert.strictEqual('skip', false) 155 | }) 156 | }) 157 | 158 | tman.suite('suite level 3-2', function () { 159 | tman.before(function () { 160 | assert.strictEqual(count++, 15) 161 | }) 162 | 163 | tman.after(function () { 164 | assert.strictEqual(count++, 19) 165 | }) 166 | 167 | tman.it('test level 4-1', function () { 168 | assert.strictEqual(count++, 16) 169 | return thunk.delay(100) 170 | }) 171 | 172 | tman.it('test level 4-2', function () { 173 | assert.strictEqual(count++, 17) 174 | }) 175 | 176 | tman.it.skip('test level 4-3', function () { 177 | assert.strictEqual('skip', false) 178 | }) 179 | 180 | tman.it('test level 4-4', function () { 181 | assert.strictEqual(count++, 18) 182 | }) 183 | }) 184 | }) 185 | }) 186 | 187 | tman.it('test level 1-1', function () { 188 | this.timeout(1000) 189 | assert.strictEqual(count++, 21) 190 | return thunk.delay(100) 191 | }) 192 | 193 | tman.it('test level 1-2', function () { 194 | assert.strictEqual(count++, 22) 195 | }) 196 | 197 | tman.it('test level 1-3', function () { 198 | assert.strictEqual(count++, 23) 199 | }) 200 | }) 201 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for tman 2 | // Project: https://github.com/thunks/tman 3 | // Definitions by: zensh 4 | 5 | // Import: `import * as tman from 'tman'` 6 | // Import: `import { suite, it, before, after, beforeEach, afterEach } from 'tman'` 7 | 8 | interface Callback { 9 | (err?: Error): void; 10 | } 11 | 12 | interface ThunkLikeFunction { 13 | (fn: Callback): void; 14 | } 15 | 16 | interface ThunkFunction { 17 | (fn?: Callback): ThunkFunction; 18 | } 19 | 20 | interface AsyncFunction extends Function { 21 | (): PromiseLike; 22 | } 23 | 24 | interface AsyncFunctionConstructor { 25 | new (...args: string[]): AsyncFunction; 26 | (...args: string[]): AsyncFunction; 27 | prototype: AsyncFunction; 28 | } 29 | 30 | interface PromiseLike { 31 | then(onfulfilled?: (value: any) => any, onrejected?: (reason: Error) => any): PromiseLike; 32 | } 33 | 34 | interface ToThunk { 35 | toThunk(): ThunkLikeFunction; 36 | } 37 | 38 | interface ToPromise { 39 | toPromise(): PromiseLike; 40 | } 41 | 42 | interface SuiteAction { 43 | (done?: SuitDone): void; 44 | } 45 | 46 | type SuitDone = (error?: any) => any; 47 | 48 | type TestAction = (done: SuitDone) => any | PromiseLike; 49 | 50 | interface SuiteFn { 51 | (title: string, fn: SuiteAction): tman.Suite; 52 | only(title: string, fn: SuiteAction): tman.Suite; 53 | skip(title: string, fn: SuiteAction): tman.Suite; 54 | } 55 | 56 | interface TestFn { 57 | (title: string, fn: TestAction): tman.Test; 58 | only(title: string, fn: TestAction): tman.Test; 59 | skip(title: string, fn: TestAction): tman.Test; 60 | } 61 | 62 | interface SuiteResult { 63 | ctx: tman.Suite; 64 | title: string; 65 | fullTitle: string; 66 | depth: number; 67 | startTime: number; 68 | endTime: number; 69 | state: Error | boolean; 70 | mode: 'skip' | 'only' | 'hasOnly'; 71 | } 72 | 73 | interface RootSuiteResult extends SuiteResult { 74 | ctx: RootSuite; 75 | abort: boolean; 76 | passed: number; 77 | ignored: number; 78 | errors: Array; 79 | } 80 | 81 | interface TestResult { 82 | ctx: tman.Test; 83 | title: string; 84 | fullTitle: string; 85 | depth: number; 86 | startTime: number; 87 | endTime: number; 88 | state: Error | boolean; 89 | mode: 'skip' | 'only'; 90 | } 91 | 92 | interface RootSuite extends tman.Suite { 93 | abort: boolean; 94 | passed: number; 95 | ignored: number; 96 | errors: Array; 97 | reporter: tman.Reporter; 98 | } 99 | 100 | interface Tman { 101 | (suite: SuiteAction): tman.Suite; 102 | (title: string, suite: SuiteAction): tman.Suite; 103 | rootSuite: RootSuite; 104 | suite: SuiteFn; 105 | describe: SuiteFn; 106 | test: TestFn; 107 | it: TestFn; 108 | only(suite: SuiteAction): tman.Suite; 109 | skip(suite: SuiteAction): tman.Suite; 110 | only(title: string, fn: SuiteAction): tman.Suite; 111 | skip(title: string, fn: SuiteAction): tman.Suite; 112 | before(test: TestAction): void; 113 | after(test: TestAction): void; 114 | beforeEach(test: TestAction): void; 115 | afterEach(test: TestAction): void; 116 | grep(pattern: string): void; 117 | exclude(pattern: string): void; 118 | mocha(): void; 119 | reset(): void; 120 | setExit(shouldExit: boolean): void; 121 | setReporter(reporter: tman.Reporter, options?: any): void; 122 | timeout(duration: number): void; 123 | tryRun(delay?: number): ThunkFunction; 124 | run(callback?: Callback): ThunkFunction; 125 | } 126 | 127 | declare function tman (suite: SuiteAction): tman.Suite; 128 | declare function tman (title: string, suite: SuiteAction): tman.Suite; 129 | declare namespace tman { 130 | export const NAME: string; 131 | export const VERSION: string; 132 | export const TEST: string; 133 | export var baseDir: string; 134 | // method in Tman interface 135 | export const suite: SuiteFn; 136 | export const describe: SuiteFn; 137 | export const test: TestFn; 138 | export const it: TestFn; 139 | export const rootSuite: RootSuite; 140 | 141 | export function tman (suite: SuiteAction): tman.Suite; 142 | export function tman (title: string, suite: SuiteAction): tman.Suite; 143 | export function only(suite: SuiteAction): Suite; 144 | export function skip(suite: SuiteAction): Suite; 145 | export function only(title: string, fn: SuiteAction): Suite; 146 | export function skip(title: string, fn: SuiteAction): Suite; 147 | export function before(test: TestAction): void; 148 | export function after(test: TestAction): void; 149 | export function beforeEach(test: TestAction): void; 150 | export function afterEach(test: TestAction): void; 151 | export function grep(pattern: string): void; 152 | export function exclude(pattern: string): void; 153 | export function mocha(): void; 154 | export function reset(): void; 155 | export function setExit(shouldExit: boolean): void; 156 | export function timeout(duration: number): void; 157 | export function tryRun(delay?: number): ThunkFunction; 158 | export function run(callback?: Callback): ThunkFunction; 159 | 160 | // extra method 161 | export function createTman (): Tman; 162 | export function setBaseDir(path: string): void; 163 | export function globals(args: Array): void; 164 | export function useColors(args: boolean): void; 165 | export function loadReporter(reporter: string): void; 166 | export function loadFiles(files: string | Array, sort?: boolean): void; 167 | 168 | export class Test { 169 | title: string; 170 | parent: Suite; 171 | root: Suite; 172 | startTime: number; 173 | endTime: number; 174 | state: boolean | Error | void; 175 | depth: number; 176 | mode: 'skip' | 'only'; 177 | constructor(title: string, parent: Suite, mode: 'only' | 'skip' | ''); 178 | onStart(): void; 179 | onFinish(): void; 180 | fullTitle(): string; 181 | timeout(duration: number): void; 182 | toJSON(): TestResult; 183 | toThunk(): ThunkLikeFunction; 184 | } 185 | 186 | export class Suite { 187 | title: string; 188 | parent: Suite; 189 | root: Suite; 190 | startTime: number; 191 | endTime: number; 192 | state: boolean | Error | void; 193 | depth: number; 194 | children: Array; 195 | mode: 'skip' | 'only' | 'hasOnly'; 196 | constructor(title: string, parent: Suite, fn: TestAction, mode: 'only' | 'skip' | ''); 197 | reset(): Suite; 198 | onStart(): void; 199 | onFinish(): void; 200 | fullTitle(): string; 201 | timeout(duration: number): void; 202 | toJSON(): SuiteResult; 203 | toThunk(): ThunkLikeFunction; 204 | log(...args: any[]): void; 205 | } 206 | 207 | export class Reporter { 208 | ctx: Suite; 209 | constructor(ctx: Suite, options?: any); 210 | log(...args: any[]): void; 211 | onStart(): void; 212 | onSuiteStart(suiteResult: SuiteResult): void; 213 | onSuiteFinish(suiteResult: SuiteResult): void; 214 | onTestStart(suiteResult: TestResult): void; 215 | onTestFinish(suiteResult: TestResult): void; 216 | onFinish(rootSuiteResult: RootSuiteResult): void; 217 | } 218 | } 219 | 220 | export = tman; 221 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | var path = require('path') 7 | var util = require('util') 8 | var assert = require('assert') 9 | var slice = Array.prototype.slice 10 | 11 | var tman = require('..') 12 | var format = tman.format 13 | var supportES2015 = false 14 | 15 | try { // 检测是否支持 generator,是则加载 generator 测试 16 | supportES2015 = new Function('return function* (){}') // eslint-disable-line 17 | } catch (e) {} 18 | 19 | assert.strictEqual(tman.baseDir, path.join(process.cwd(), 'test')) 20 | 21 | function CustomReporter (ctx, childCtx) { 22 | tman.Reporter.defaultReporter.call(this, ctx) 23 | this.childCtx = childCtx 24 | } 25 | util.inherits(CustomReporter, tman.Reporter.defaultReporter) 26 | CustomReporter.prototype.onFinish = function (res) { 27 | tman.rootSuite.passed += res.passed + res.errors.length + res.ignored 28 | } 29 | CustomReporter.prototype.log = function () { 30 | var args = slice.call(arguments) 31 | args[0] = format.indent(this.childCtx.depth) + args[0] 32 | tman.rootSuite.reporter.log.apply(null, args) 33 | } 34 | 35 | tman.afterEach(function () { 36 | tman.rootSuite.reporter.log('') 37 | }) 38 | 39 | tman.suite('Suites and tests', function () { 40 | tman.it('synchronous and asynchronous test', function () { 41 | var ctx = this 42 | var count = 0 43 | // new child instance for test 44 | var t = tman.createTman() 45 | t.setReporter(CustomReporter, this) 46 | 47 | t.before(function () { 48 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 49 | assert.strictEqual(count++, 0) 50 | }) 51 | 52 | t.after(function () { 53 | assert.strictEqual(count++, 5) 54 | }) 55 | 56 | t.it('synchronous test', function () { 57 | assert.strictEqual(count++, 1) 58 | }) 59 | 60 | t.it('callback style asynchronous test', function (done) { 61 | assert.strictEqual(count++, 2) 62 | setTimeout(done, 10) 63 | }) 64 | 65 | t.it('thunk style asynchronous test', function () { 66 | assert.strictEqual(count++, 3) 67 | return function (done) { 68 | assert.strictEqual(count++, 4) 69 | setTimeout(done, 10) 70 | } 71 | }) 72 | 73 | if (supportES2015) require('./es2015/async-test')(t) 74 | return t.run() 75 | }) 76 | 77 | tman.it('nested suites and tests', function () { 78 | var ctx = this 79 | var count = 0 80 | // new child instance for test 81 | var t = tman.createTman() 82 | t.setReporter(CustomReporter, this) 83 | 84 | t.before(function () { 85 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 86 | assert.strictEqual(count++, 0) 87 | }) 88 | 89 | t.after(function () { 90 | assert.strictEqual(count++, 26) 91 | }) 92 | 93 | t.suite('suite 1-1', function () { 94 | t.beforeEach(function () { 95 | count++ 96 | }) 97 | 98 | t.it('test 2-1', function () { 99 | assert.strictEqual(count++, 2) 100 | }) 101 | 102 | t.it('test 2-2', function () { 103 | assert.strictEqual(count++, 4) 104 | }) 105 | 106 | t.suite('suite 2-1', function () { 107 | t.beforeEach(function () { 108 | count++ 109 | }) 110 | 111 | t.it('test 3-1', function () { 112 | assert.strictEqual(count++, 7) 113 | }) 114 | 115 | t.it('test 3-2', function () { 116 | assert.strictEqual(count++, 9) 117 | }) 118 | }) 119 | 120 | t.suite('suite 2-2', function () { 121 | t.afterEach(function () { 122 | count++ 123 | }) 124 | 125 | t.it('test 3-1', function () { 126 | assert.strictEqual(count++, 11) 127 | }) 128 | 129 | t.it('test 3-2', function () { 130 | assert.strictEqual(count++, 13) 131 | }) 132 | 133 | t.suite('suite 3-1', function () { 134 | t.before(function () { 135 | assert.strictEqual(count++, 15) 136 | }) 137 | 138 | t.after(function () { 139 | assert.strictEqual(count++, 19) 140 | }) 141 | 142 | t.it('test 4-1', function () { 143 | assert.strictEqual(count++, 16) 144 | }) 145 | 146 | t.it('test 4-2', function () { 147 | assert.strictEqual(count++, 17) 148 | }) 149 | 150 | t.it('test 4-3', function () { 151 | assert.strictEqual(count++, 18) 152 | }) 153 | }) 154 | 155 | t.it('test 3-3', function () { 156 | assert.strictEqual(count++, 21) 157 | }) 158 | }) 159 | }) 160 | 161 | t.it('test 1-1', function () { 162 | assert.strictEqual(count++, 23) 163 | }) 164 | 165 | t.it('test 1-2', function () { 166 | assert.strictEqual(count++, 24) 167 | }) 168 | 169 | t.it('test 1-3', function () { 170 | assert.strictEqual(count++, 25) 171 | }) 172 | 173 | return t.run() 174 | }) 175 | 176 | tman.it('invalid suite and test', function () { 177 | var t = tman.createTman() 178 | 179 | assert.throws(function () { 180 | t.suite(function () {}) 181 | }, /invalid string/) 182 | 183 | assert.throws(function () { 184 | t.suite(123, function () {}) 185 | }, /invalid string/) 186 | 187 | assert.throws(function () { 188 | t.it(null, function () {}) 189 | }, /invalid string/) 190 | 191 | assert.throws(function () { 192 | t.it([], function () {}) 193 | }, /invalid string/) 194 | 195 | assert.throws(function () { 196 | t.suite('test') 197 | }, /not function/) 198 | 199 | assert.throws(function () { 200 | t.suite('test', 123) 201 | }, /not function/) 202 | 203 | assert.throws(function () { 204 | t.it('test', {}) 205 | }, /not function/) 206 | }) 207 | }) 208 | 209 | tman.suite('Hooks', function () { 210 | tman.it('work for suites and tests', function () { 211 | var ctx = this 212 | var count = 0 213 | // new child instance for test 214 | var t = tman.createTman() 215 | t.setReporter(CustomReporter, this) 216 | 217 | t.before(function () { 218 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 219 | assert.strictEqual(count++, 0) 220 | }) 221 | 222 | t.after(function () { 223 | assert.strictEqual(count++, 11) 224 | }) 225 | 226 | t.after(function (done) { 227 | assert.strictEqual(count++, 12) 228 | done() 229 | }) 230 | 231 | t.beforeEach(function () { 232 | count++ 233 | }) 234 | 235 | t.afterEach(function () { 236 | count++ 237 | }) 238 | 239 | t.it('test 1-1', function () { 240 | assert.strictEqual(count++, 2) 241 | }) 242 | 243 | t.suite('suite 1-1', function () { 244 | t.it('test 2-1', function () { 245 | assert.strictEqual(count++, 5) 246 | }) 247 | 248 | t.it('test 2-2', function () { 249 | assert.strictEqual(count++, 6) 250 | }) 251 | }) 252 | 253 | t.it('test 1-2', function () { 254 | assert.strictEqual(count++, 9) 255 | }) 256 | 257 | return t.run() 258 | }) 259 | 260 | tman.it('work for nested suites and tests', function () { 261 | var ctx = this 262 | var count = 0 263 | // new child instance for test 264 | var t = tman.createTman() 265 | t.setReporter(CustomReporter, this) 266 | 267 | t.before(function () { 268 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 269 | assert.strictEqual(count++, 0) 270 | assert.strictEqual(this, t.rootSuite) 271 | }) 272 | 273 | t.after(function () { 274 | assert.strictEqual(count++, 18) 275 | assert.strictEqual(this, t.rootSuite) 276 | }) 277 | 278 | t.it('test 1-1', function () { 279 | assert.strictEqual(count++, 1) 280 | }) 281 | 282 | t.suite('suite 1-1', function () { 283 | var suite = this 284 | 285 | t.before(function () { 286 | assert.strictEqual(count++, 2) 287 | assert.strictEqual(this, suite) 288 | }) 289 | 290 | t.after(function () { 291 | assert.strictEqual(count++, 16) 292 | assert.strictEqual(this, suite) 293 | }) 294 | 295 | t.beforeEach(function () { 296 | count++ 297 | assert.strictEqual(this, suite) 298 | }) 299 | 300 | t.beforeEach(function (done) { 301 | count++ 302 | assert.strictEqual(this, suite) 303 | done() 304 | }) 305 | 306 | t.afterEach(function () { 307 | count++ 308 | assert.strictEqual(this, suite) 309 | }) 310 | 311 | t.it('test 2-1', function () { 312 | assert.strictEqual(count++, 5) 313 | }) 314 | 315 | t.it('test 2-2', function () { 316 | assert.strictEqual(count++, 9) 317 | }) 318 | 319 | t.suite('suite 2-1', function () { 320 | t.it('test 3-1', function () { 321 | assert.strictEqual(count++, 13) 322 | }) 323 | 324 | t.it('test 3-2', function () { 325 | assert.strictEqual(count++, 14) 326 | }) 327 | }) 328 | }) 329 | 330 | t.it('test 1-2', function () { 331 | assert.strictEqual(count++, 17) 332 | }) 333 | 334 | return t.run() 335 | }) 336 | 337 | tman.it('invalid hooks', function () { 338 | var t = tman.createTman() 339 | 340 | assert.throws(function () { 341 | t.before('test') 342 | }, /not function/) 343 | 344 | assert.throws(function () { 345 | t.after() 346 | }, /not function/) 347 | 348 | assert.throws(function () { 349 | t.beforeEach([]) 350 | }, /not function/) 351 | 352 | assert.throws(function () { 353 | t.afterEach(new Date()) 354 | }, /not function/) 355 | }) 356 | 357 | tman.it('work with ES2015', function () { 358 | var ctx = this 359 | // new child instance for test 360 | var t = tman.createTman() 361 | t.setReporter(CustomReporter, this) 362 | t.before(function () { 363 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 364 | }) 365 | 366 | if (supportES2015) require('./es2015/async-hook')(t) 367 | 368 | return t.run() 369 | }) 370 | }) 371 | -------------------------------------------------------------------------------- /lib/core.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | const path = require('path') 7 | const thunks = require('thunks') 8 | const thunk = thunks() 9 | // Save timer references to avoid other module (Sinon) interfering. 10 | const $setTimeout = setTimeout 11 | const $clearTimeout = clearTimeout 12 | 13 | function Suite (title, parent, mode) { 14 | this.title = title 15 | this.parent = parent 16 | this.root = parent ? parent.root : this 17 | 18 | this.mode = mode // 'skip', 'only', 'hasOnly' 19 | this.duration = -1 20 | this.startTime = 0 21 | this.endTime = 0 22 | this.children = [] 23 | this.ctxMachine = this 24 | this.state = null // skip: null, passed: true, failed: error 25 | this.cleanHandle = null 26 | this.depth = parent ? (parent.depth + 1) : 0 27 | this.before = new Hooks('before', this) 28 | this.after = new Hooks('after', this) 29 | this.beforeEach = new Hooks('beforeEach', this) 30 | this.afterEach = new Hooks('afterEach', this) 31 | } 32 | 33 | Suite.prototype.reset = function () { 34 | this.startTime = 0 35 | this.endTime = 0 36 | this.children.length = 0 37 | this.ctxMachine = this 38 | this.state = null 39 | this.cleanHandle = null 40 | this.before.hooks.length = 0 41 | this.after.hooks.length = 0 42 | this.beforeEach.hooks.length = 0 43 | this.afterEach.hooks.length = 0 44 | return this 45 | } 46 | 47 | /* istanbul ignore next */ 48 | Suite.prototype.inspect = function () { 49 | return { 50 | title: this.title, 51 | mode: this.mode, 52 | depth: this.depth, 53 | startTime: this.startTime, 54 | endTime: this.endTime, 55 | before: this.before.inspect(), 56 | after: this.after.inspect(), 57 | beforeEach: this.beforeEach.inspect(), 58 | afterEach: this.afterEach.inspect(), 59 | duration: this.getDuration(), 60 | parent: this.parent && '', 61 | children: this.children.map((test) => '<' + test.constructor.name + ': ' + test.title + '>') 62 | } 63 | } 64 | 65 | Suite.prototype.toJSON = function () { 66 | return { 67 | ctx: this, 68 | title: this.title, 69 | fullTitle: this.fullTitle(), 70 | mode: this.mode, // 'skip', 'only', 'hasOnly' 71 | depth: this.depth, 72 | startTime: this.startTime, 73 | endTime: this.endTime, 74 | state: this.state // skip: null, passed: true, failed: error 75 | } 76 | } 77 | 78 | Suite.prototype.addSuite = function (title, fn, mode) { 79 | const ctx = this.ctxMachine 80 | assertStr(title, ctx) 81 | assertFn(fn, ctx) 82 | const suite = new Suite(title, ctx, mode) 83 | if (mode === 'only' && !ctx.isSkip()) ctx.setOnly() 84 | ctx.children.push(suite) 85 | this.ctxMachine = suite 86 | fn.call(suite) 87 | this.ctxMachine = ctx 88 | return suite 89 | } 90 | 91 | Suite.prototype.addTest = function (title, fn, mode) { 92 | const ctx = this.ctxMachine 93 | assertStr(title, ctx) 94 | assertFn(fn, ctx) 95 | const test = new Test(title, ctx, fn, mode) 96 | if (mode === 'only' && !ctx.isSkip()) ctx.setOnly() 97 | ctx.children.push(test) 98 | return test 99 | } 100 | 101 | Suite.prototype.addBefore = function (fn) { 102 | const ctx = this.ctxMachine 103 | assertFn(fn, ctx) 104 | ctx.before.add(fn) 105 | } 106 | 107 | Suite.prototype.addAfter = function (fn) { 108 | const ctx = this.ctxMachine 109 | assertFn(fn, ctx) 110 | ctx.after.add(fn) 111 | } 112 | 113 | Suite.prototype.addBeforeEach = function (fn) { 114 | const ctx = this.ctxMachine 115 | assertFn(fn, ctx) 116 | ctx.beforeEach.add(fn) 117 | } 118 | 119 | Suite.prototype.addAfterEach = function (fn) { 120 | const ctx = this.ctxMachine 121 | assertFn(fn, ctx) 122 | ctx.afterEach.add(fn) 123 | } 124 | 125 | Suite.prototype.setOnly = function () { 126 | this.mode = 'hasOnly' 127 | if (this.parent) this.parent.setOnly() 128 | } 129 | 130 | Suite.prototype.hasOnly = function () { 131 | if (this.mode === 'hasOnly') return true 132 | return this.parent ? this.parent.hasOnly() : false 133 | } 134 | 135 | Suite.prototype.isOnly = function () { 136 | if (this.mode === 'only') return true 137 | return this.parent ? this.parent.isOnly() : false 138 | } 139 | 140 | Suite.prototype.isSkip = function () { 141 | if (this.mode === 'skip') return true 142 | return this.parent ? this.parent.isSkip() : false 143 | } 144 | 145 | Suite.prototype.timeout = function (duration) { 146 | this.duration = duration >= 0 ? +duration : -1 147 | } 148 | 149 | Suite.prototype.getDuration = function () { 150 | if (this.duration >= 0) return this.duration 151 | return this.parent ? this.parent.getDuration() : 0 152 | } 153 | 154 | Suite.prototype.fullTitle = function () { 155 | return this.parent ? path.join(this.parent.fullTitle(), this.title) : path.sep 156 | } 157 | 158 | Suite.prototype.toThunk = function () { 159 | const ctx = this 160 | const hasOnly = this.hasOnly() 161 | 162 | return function (done) { 163 | /* istanbul ignore next */ 164 | if (ctx.root.abort) return done() 165 | if (hasOnly && ctx.mode !== 'hasOnly' && !ctx.isOnly()) return done() 166 | 167 | ctx.root.reporter.onSuiteStart(ctx.toJSON()) 168 | if (ctx.mode === 'skip') { 169 | return thunk.seq(ctx.children.map((test) => { 170 | test.mode = 'skip' 171 | return test 172 | }))(function () { 173 | ctx.root.reporter.onSuiteFinish(ctx.toJSON()) 174 | })(done) 175 | } 176 | 177 | ctx.cleanHandle = clearSuite 178 | function clearSuite (err) { 179 | if (clearSuite.called) return 180 | clearSuite.called = true 181 | ctx.root.runnerMachine = null 182 | if (err == null) ctx.state = true 183 | else { 184 | ctx.state = err 185 | ctx.root.errors.push(err) 186 | err.order = ctx.root.errors.length 187 | err.title = ctx.fullTitle() + ' ' + (err.title || clearSuite.hookTitle || '') 188 | } 189 | ctx.endTime = Date.now() 190 | ctx.root.reporter.onSuiteFinish(ctx.toJSON()) 191 | done() 192 | } 193 | 194 | const tasks = [] 195 | tasks.push(ctx.before) 196 | ctx.children.forEach((test) => { 197 | if (test instanceof Test) { 198 | const fullTitle = test.fullTitle() 199 | if (ctx.root.exclude.test(fullTitle) || !ctx.root.grep.test(fullTitle)) return 200 | } 201 | if (hasOnly && test.mode !== 'hasOnly' && !test.isOnly()) return 202 | if (test.mode === 'skip') tasks.push(test) 203 | // Mocha compatible mode 204 | else if (ctx.root.mocha && test instanceof Suite) tasks.push(thunk.delay(), test) 205 | else tasks.push(thunk.delay(), ctx.beforeEach, test, ctx.afterEach) 206 | }) 207 | tasks.push(ctx.after) 208 | ctx.startTime = Date.now() 209 | thunk.seq(tasks)(clearSuite) 210 | } 211 | } 212 | 213 | function Hooks (title, parent) { 214 | this.title = title 215 | this.parent = parent 216 | this.hooks = [] 217 | } 218 | 219 | Hooks.prototype.add = function (fn) { 220 | this.hooks.push(fn) 221 | } 222 | 223 | /* istanbul ignore next */ 224 | Hooks.prototype.inspect = function () { 225 | return { 226 | title: this.title, 227 | hooks: this.hooks.map((hook) => '<' + hook.constructor.name + '>') 228 | } 229 | } 230 | 231 | // Mocha compatible mode 232 | Hooks.prototype.getParentHooks = function () { 233 | const suite = this.parent 234 | if (suite.parent && (this.title === 'beforeEach' || this.title === 'afterEach')) { 235 | return suite.parent[this.title] 236 | } 237 | return null 238 | } 239 | 240 | Hooks.prototype.toThunk = function () { 241 | const ctx = this 242 | const suite = ctx.parent 243 | 244 | return function (done) { 245 | const hooks = ctx.hooks.map((hook) => toThunkableFn(hook, suite)) 246 | // Mocha compatible mode 247 | if (suite.root.mocha) { 248 | const parentHooks = ctx.getParentHooks() 249 | if (parentHooks) hooks.unshift(parentHooks) 250 | } 251 | 252 | if (!hooks.length) return done() 253 | const title = '"' + ctx.title + '" Hook' 254 | if (!suite.cleanHandle.called) { 255 | suite.cleanHandle.hookTitle = title 256 | suite.root.runnerMachine = suite.cleanHandle 257 | } 258 | 259 | thunk.seq.call(suite, hooks)(function (err) { 260 | if (err != null) { 261 | err.title = title 262 | throw err 263 | } 264 | })(done) 265 | } 266 | } 267 | 268 | function Test (title, parent, fn, mode) { 269 | this.title = title 270 | this.parent = parent 271 | this.root = parent.root 272 | 273 | this.fn = fn 274 | this.mode = mode // 'skip', 'only' 275 | this.duration = -1 276 | this.startTime = 0 277 | this.endTime = 0 278 | this.timer = null 279 | this.state = null // skip: null, passed: true, failed: error 280 | this.cleanHandle = null 281 | this.depth = parent.depth + 1 282 | } 283 | 284 | /* istanbul ignore next */ 285 | Test.prototype.inspect = function () { 286 | return { 287 | title: this.title, 288 | mode: this.mode, 289 | depth: this.depth, 290 | startTime: this.startTime, 291 | endTime: this.endTime, 292 | state: this.state, 293 | duration: this.getDuration(), 294 | fn: this.fn && '', 295 | parent: this.parent && '' 296 | } 297 | } 298 | 299 | Test.prototype.toJSON = function () { 300 | return { 301 | ctx: this, 302 | title: this.title, 303 | fullTitle: this.fullTitle(), 304 | mode: this.mode, // 'skip', 'only' 305 | depth: this.depth, 306 | startTime: this.startTime, 307 | endTime: this.endTime, 308 | state: this.state // skip: null, passed: true, failed: error 309 | } 310 | } 311 | 312 | Test.prototype.isOnly = function () { 313 | return this.mode === 'only' || this.parent.isOnly() 314 | } 315 | 316 | Test.prototype.timeout = function (duration) { 317 | this.duration = duration >= 0 ? +duration : -1 318 | } 319 | 320 | Test.prototype.getDuration = function () { 321 | return this.duration >= 0 ? this.duration : this.parent.getDuration() 322 | } 323 | 324 | Test.prototype.fullTitle = function () { 325 | return path.join(this.parent.fullTitle(), this.title) 326 | } 327 | 328 | Test.prototype.toThunk = function () { 329 | const ctx = this 330 | 331 | return function (done) { 332 | /* istanbul ignore next */ 333 | if (ctx.root.abort) return done() 334 | if (ctx.parent.hasOnly() && !ctx.isOnly()) return done() 335 | ctx.root.reporter.onTestStart(ctx.toJSON()) 336 | if (ctx.mode === 'skip') { 337 | ctx.root.ignored++ 338 | ctx.root.reporter.onTestFinish(ctx.toJSON()) 339 | return done() 340 | } 341 | 342 | ctx.cleanHandle = clearTest 343 | function clearTest (err) { 344 | if (clearTest.called) return 345 | clearTest.called = true 346 | $clearTimeout(ctx.timer) 347 | ctx.root.runnerMachine = null 348 | if (err == null) { 349 | ctx.state = true 350 | ctx.root.passed++ 351 | } else { 352 | ctx.state = err 353 | ctx.root.errors.push(err) 354 | err.order = ctx.root.errors.length 355 | err.title = ctx.fullTitle() 356 | } 357 | ctx.endTime = Date.now() 358 | ctx.root.reporter.onTestFinish(ctx.toJSON()) 359 | done() 360 | } 361 | 362 | ctx.startTime = Date.now() 363 | ctx.root.runnerMachine = clearTest 364 | thunk.race.call(ctx, [ 365 | toThunkableFn(ctx.fn, ctx), 366 | function (callback) { 367 | thunk.delay()(function () { 368 | const duration = ctx.getDuration() 369 | if (ctx.endTime || !duration) return 370 | ctx.timer = $setTimeout(function () { 371 | callback(new Error('timeout of ' + duration + 'ms exceeded.')) 372 | }, duration) 373 | }) 374 | } 375 | ])(clearTest) 376 | } 377 | } 378 | 379 | exports.Suite = Suite 380 | exports.Test = Test 381 | exports.Tman = function (env) { 382 | const tm = _tman('') 383 | const rootSuite = tm.rootSuite = new Suite('root', null, '') 384 | rootSuite.exit = true 385 | rootSuite.grep = /.*/ 386 | rootSuite.exclude = /.{-1}/ 387 | rootSuite.timeout(2000) 388 | 389 | tm.only = _tman('only') 390 | tm.skip = _tman('skip') 391 | function _tman (mode) { 392 | return function tman (title, fn) { 393 | if (!env.TEST) return 394 | if (typeof title === 'function') { 395 | fn = title 396 | title = 'T-man' 397 | } 398 | const suite = rootSuite.addSuite(title, fn, mode) 399 | tm.tryRun(10) 400 | return suite 401 | } 402 | } 403 | 404 | tm.describe = tm.suite = function (title, fn) { 405 | return rootSuite.addSuite(title, fn, '') 406 | } 407 | tm.suite.only = function (title, fn) { 408 | return rootSuite.addSuite(title, fn, 'only') 409 | } 410 | tm.suite.skip = function (title, fn) { 411 | return rootSuite.addSuite(title, fn, 'skip') 412 | } 413 | 414 | tm.it = tm.test = function (title, fn) { 415 | return rootSuite.addTest(title, fn, '') 416 | } 417 | tm.test.only = function (title, fn) { 418 | return rootSuite.addTest(title, fn, 'only') 419 | } 420 | tm.test.skip = function (title, fn) { 421 | return rootSuite.addTest(title, fn, 'skip') 422 | } 423 | 424 | tm.before = function (fn) { 425 | rootSuite.addBefore(fn) 426 | } 427 | 428 | tm.after = function (fn) { 429 | rootSuite.addAfter(fn) 430 | } 431 | 432 | tm.beforeEach = function (fn) { 433 | rootSuite.addBeforeEach(fn) 434 | } 435 | 436 | tm.afterEach = function (fn) { 437 | rootSuite.addAfterEach(fn) 438 | } 439 | 440 | tm.grep = function (str) { 441 | rootSuite.grep = parseRegExp(str) 442 | } 443 | 444 | tm.exclude = function (str) { 445 | rootSuite.exclude = parseRegExp(str) 446 | } 447 | 448 | tm.mocha = function () { 449 | rootSuite.mocha = true 450 | } 451 | 452 | tm.reset = function () { 453 | rootSuite.reset() 454 | } 455 | 456 | tm.abort = function () { 457 | rootSuite.abort = true 458 | } 459 | 460 | tm.setExit = function (exit) { 461 | rootSuite.exit = !!exit 462 | } 463 | 464 | tm.setReporter = function (CustomReporter, options) { 465 | rootSuite.reporter = new CustomReporter(rootSuite, options) 466 | } 467 | 468 | tm.timeout = function (duration) { 469 | rootSuite.timeout(duration) 470 | } 471 | 472 | var timer = null 473 | var running = false 474 | tm.tryRun = function (delay) { 475 | if (timer) $clearTimeout(timer) 476 | return thunk(function (done) { 477 | timer = $setTimeout(function () { 478 | if (!running) tm.run()(done) 479 | }, delay > 0 ? delay : 0) 480 | })(function (err, res) { 481 | if (err) throw err 482 | return res 483 | }) 484 | } 485 | 486 | tm.run = function (hook) { 487 | /* istanbul ignore next */ 488 | if (running) throw new Error('T-man is running!') 489 | 490 | function endTest (err) { 491 | running = false 492 | process.removeListener('uncaughtException', uncaught) 493 | endTest.called = true 494 | let suite 495 | if (err == null) { 496 | suite = rootSuite.toJSON() 497 | suite.exit = rootSuite.exit 498 | suite.abort = rootSuite.abort 499 | suite.passed = rootSuite.passed 500 | suite.ignored = rootSuite.ignored 501 | suite.errors = rootSuite.errors.slice() 502 | } 503 | 504 | return thunk.call(tm, hook && hook.call(tm, err, suite))(function (error) { 505 | err = err || error 506 | if (err || !suite) throw err 507 | rootSuite.reporter.onFinish(suite) 508 | return suite 509 | }) 510 | } 511 | 512 | tm.uncaught = uncaught 513 | process.on('uncaughtException', uncaught) 514 | function uncaught (err) { 515 | const uncaughtHandle = rootSuite.runnerMachine || endTest 516 | err = err || new Error('uncaught exception') 517 | err.uncaught = true 518 | 519 | const stack = err.stack 520 | err.stack = stack 521 | if (uncaughtHandle.called) rootSuite.reporter.log(String(err)) 522 | else uncaughtHandle(err) 523 | } 524 | 525 | running = true 526 | rootSuite.abort = false 527 | rootSuite.passed = 0 528 | rootSuite.ignored = 0 529 | rootSuite.errors = [] 530 | rootSuite.runnerMachine = null 531 | rootSuite.reporter.onStart() 532 | return thunk.delay.call(tm)(function () { 533 | return rootSuite 534 | })(endTest) 535 | } 536 | 537 | return tm 538 | } 539 | 540 | function assertFn (fn, ctx) { 541 | if (typeof fn !== 'function') { 542 | throw new Error(String(fn) + ' is not function in "' + ctx.fullTitle() + '"') 543 | } 544 | } 545 | 546 | function assertStr (str, ctx) { 547 | if (!str || typeof str !== 'string') { 548 | throw new Error(String(str) + ' is invalid string in "' + ctx.fullTitle() + '"') 549 | } 550 | } 551 | 552 | function toThunkableFn (fn, ctx) { 553 | if (thunks.isThunkableFn(fn)) return fn 554 | return function (done) { thunk(fn.call(ctx))(done) } 555 | } 556 | 557 | // extract args if it's regex-like, i.e: [string, pattern, flag] 558 | function parseRegExp (str) { 559 | if (str instanceof RegExp) return str 560 | const arg = String(str).match(/^\/(.*)\/(g|i|)$|.*/) 561 | return new RegExp(arg[1] || arg[0], arg[2]) 562 | } 563 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | T-man 2 | ==== 3 | Super test manager for JavaScript. 4 | 5 | [![NPM version][npm-image]][npm-url] 6 | [![Build Status][travis-image]][travis-url] 7 | [![Coverage Status][coveralls-image]][coveralls-url] 8 | [![Downloads][downloads-image]][downloads-url] 9 | 10 | T-man is a refactor version of [mocha](http://mochajs.org/), but more lightweight, more flexible. In most case, you can use `tman` replace of `mocha` directly. 11 | 12 | Summary 13 | ------- 14 | - [T-man](#t-man) 15 | - [Summary](#summary) 16 | - [Examples](#examples) 17 | - [Simple tests](#simple-tests) 18 | - [Mocha style tests](#mocha-style-tests) 19 | - [Es-next tests with babel](#es-next-tests-with-babel) 20 | - [Tests in source code](#tests-in-source-code) 21 | - [Practical tests](#practical-tests) 22 | - [Complex tests](#complex-tests) 23 | - [Usage](#usage) 24 | - [Use as CLI](#use-as-cli) 25 | - [Use with npm package.json](#use-with-npm-packagejson) 26 | - [Assertions](#assertions) 27 | - [Suites and tests](#suites-and-tests) 28 | - [tman.suite(title, fn), tman.describe(title, fn)](#tmansuitetitle-fn-tmandescribetitle-fn) 29 | - [tman.test(title, fn), tman.it(title, fn)](#tmantesttitle-fn-tmanittitle-fn) 30 | - [Hooks](#hooks) 31 | - [tman.before(fn)](#tmanbeforefn) 32 | - [tman.after(fn)](#tmanafterfn) 33 | - [tman.beforeEach(fn)](#tmanbeforeeachfn) 34 | - [tman.afterEach(fn)](#tmanaftereachfn) 35 | - [Exclusive or inclusive tests](#exclusive-or-inclusive-tests) 36 | - [tman.suite.only(title, fn)](#tmansuiteonlytitle-fn) 37 | - [tman.it.only(title, fn)](#tmanitonlytitle-fn) 38 | - [tman.suite.skip(title, fn)](#tmansuiteskiptitle-fn) 39 | - [tman.it.skip(title, fn)](#tmanitskiptitle-fn) 40 | - [tman.grep(pattern)](#tmangreppattern) 41 | - [tman.exclude(pattern)](#tmanexcludepattern) 42 | - [Timeouts](#timeouts) 43 | - [Write tests in source code](#write-tests-in-source-code) 44 | - [tman(title, fn)](#tmantitle-fn) 45 | - [tman.only(title, fn)](#tmanonlytitle-fn) 46 | - [tman.skip(title, fn)](#tmanskiptitle-fn) 47 | - [Run tests](#run-tests) 48 | - [tman.run([callback])](#tmanruncallback) 49 | - [tman.mocha()](#tmanmocha) 50 | - [tman.reset()](#tmanreset) 51 | - [tman.loadFiles(filePath, sort)](#tmanloadfilesfilepath-sort) 52 | - [tman.globals(globals)](#tmanglobalsglobals) 53 | - [T-man CLI](#t-man-cli) 54 | - [T-man test mode](#t-man-test-mode) 55 | - [TypeScript Typings](#typescript-typings) 56 | - [Reporters](#reporters) 57 | - [spec](#spec) 58 | - [dot](#dot) 59 | - [base](#base) 60 | - [FAQ](#faq) 61 | - [How to run CoffeeScript (or TypeScript) tests?](#how-to-run-coffeescript-or-typescript-tests) 62 | - [License](#license) 63 | 64 | ## Examples 65 | 66 | ### [Simple tests](https://github.com/thunks/tman/tree/master/example/simple.js) 67 | It define test cases in top level, and no suites. 68 | 69 | ```js 70 | const assert = require('assert') 71 | const tman = require('tman') 72 | const Rx = require('rxjs') 73 | 74 | var count = 0 75 | 76 | tman.it('synchronous test', function () { 77 | assert.strictEqual(count++, 0) 78 | }) 79 | 80 | tman.it('callback style asynchronous test', function (done) { 81 | assert.strictEqual(count++, 1) 82 | setTimeout(done, 100) 83 | }) 84 | 85 | tman.it('promise style asynchronous test', function () { 86 | assert.strictEqual(count++, 2) 87 | return new Promise(function (resolve) { 88 | assert.strictEqual(count++, 3) 89 | setTimeout(resolve, 100) 90 | }) 91 | }) 92 | 93 | tman.it('thunk style asynchronous test', function () { 94 | assert.strictEqual(count++, 4) 95 | return function (done) { 96 | assert.strictEqual(count++, 5) 97 | setTimeout(done, 100) 98 | } 99 | }) 100 | 101 | tman.it('generator style asynchronous test', function * () { 102 | assert.strictEqual(count++, 6) 103 | yield function (done) { setTimeout(done, 50) } 104 | yield new Promise(function (resolve) { setTimeout(resolve, 50) }) 105 | assert.strictEqual(count++, 7) 106 | }) 107 | 108 | tman.it('Rx.Observable asynchronous test', function () { 109 | assert.strictEqual(count++, 8) 110 | return Rx.Observable.fromPromise(new Promise(function (resolve) { 111 | assert.strictEqual(count++, 9) 112 | setTimeout(resolve, 100) 113 | })) 114 | }) 115 | 116 | // Node.js v8 117 | tman.it('async/await style asynchronous test', async function () { 118 | assert.strictEqual(count++, 10) 119 | await new Promise(function (resolve) { setTimeout(resolve, 50) }) 120 | assert.strictEqual(count++, 11) 121 | }) 122 | ``` 123 | 124 | Run by T-man CLI (need `npm i tman -g`): 125 | ```sh 126 | tman example/simple 127 | ``` 128 | 129 | ### [Mocha style tests](https://github.com/thunks/tman/tree/master/example/mocha.js) 130 | It is a mocha style tests. It only can be run by T-man CLI: `tman example/mocha`. 131 | Through T-man CLI, some method are registered to node global object. 132 | And you can use generator as well, it is equal to `mocha` + `thunk-mocha`. 133 | 134 | ```js 135 | const assert = require('assert') 136 | 137 | var count = 0 138 | 139 | describe('mocha style', function () { 140 | before(function () { 141 | assert.strictEqual(count++, 0) 142 | }) 143 | 144 | after(function () { 145 | assert.strictEqual(count++, 9) 146 | }) 147 | 148 | it('synchronous test', function () { 149 | assert.strictEqual(count++, 1) 150 | }) 151 | 152 | it('callback style asynchronous test', function (done) { 153 | assert.strictEqual(count++, 2) 154 | setTimeout(done, 100) 155 | }) 156 | 157 | it('promise style asynchronous test', function () { 158 | assert.strictEqual(count++, 3) 159 | return new Promise(function (resolve) { 160 | assert.strictEqual(count++, 4) 161 | setTimeout(resolve, 100) 162 | }) 163 | }) 164 | 165 | it('thunk style asynchronous test', function () { 166 | assert.strictEqual(count++, 5) 167 | return function (done) { 168 | assert.strictEqual(count++, 6) 169 | setTimeout(done, 100) 170 | } 171 | }) 172 | 173 | it('generator style asynchronous test', function * () { 174 | assert.strictEqual(count++, 7) 175 | yield function (done) { setTimeout(done, 100) } 176 | assert.strictEqual(count++, 8) 177 | }) 178 | }) 179 | ``` 180 | 181 | ### [Es-next tests with babel](https://github.com/thunks/tman/tree/master/example/es-next.es) 182 | `tman -r babel-register -r babel-polyfill example/es-next.es`: 183 | ```js 184 | import assert from 'assert' 185 | import tman from 'tman' 186 | 187 | var count = 0 188 | // async "after hook" 189 | tman.after(async () => { 190 | assert.strictEqual(await Promise.resolve(count++), 4) 191 | }) 192 | 193 | tman.it('async/await asynchronous test', async function () { 194 | assert.strictEqual(await Promise.resolve(count++), 0) 195 | assert.strictEqual(await new Promise((resolve, reject) => { 196 | setTimeout(() => { 197 | resolve(count++) 198 | }, 100) 199 | }), 1) 200 | }) 201 | 202 | tman.it('generator asynchronous test', function * () { 203 | // yield Promise 204 | assert.strictEqual(yield Promise.resolve(count++), 2) 205 | // yield thunk function 206 | assert.strictEqual(yield (done) => { 207 | setTimeout(() => { 208 | done(null, count++) 209 | }, 100) 210 | }, 3) 211 | }) 212 | ``` 213 | 214 | ### [Tests in source code](https://github.com/thunks/tman/tree/master/example/tests-in-source-code.js) 215 | It shows writing tests in source code. The tests will run in [test mode](#t-man-test-mode). 216 | 217 | ### [Practical tests](https://github.com/thunks/tman/tree/master/example/nested.js) 218 | It includes nested suites and tests, just simulate practical use case. 219 | 220 | ### [Complex tests](https://github.com/thunks/tman/tree/master/test/index.js) 221 | It is the test of `tman`, not only nested suites and tests, but also several `tman` instance compose! 222 | 223 | ## Usage 224 | 225 | ### Use as CLI 226 | T-man is easiest to use when installed with [npm](https://www.npmjs.com/package/tman): 227 | ```sh 228 | npm install tman -g 229 | ``` 230 | Run test in `myproject_dir`: 231 | ```sh 232 | cd myproject_dir && tman 233 | ``` 234 | T-man will try to load `myproject_dir/test/*.{js,ts,es,coffee}` and run it. 235 | 236 | ### Use with npm package.json 237 | npm script in `package.json`(, also with `istanbul`): 238 | ```json 239 | "scripts": { 240 | "test": "tman", 241 | "test-cov": "istanbul cover _tman" 242 | } 243 | ``` 244 | 245 | Then run: 246 | ```sh 247 | npm test 248 | ``` 249 | or 250 | ```sh 251 | npm run test-cov 252 | ``` 253 | 254 | The `tman` will try to load tests with glob `test/*.js` and run them. 255 | 256 | You may also run tests with your own globs: `tman test/index.js test/service/*.js test/api/*.js`. 257 | 258 | ### Assertions 259 | T-man has no built-in assertion method, but allows you to use any assertion library you want, if it throws an error, it will work! You can utilize libraries such as: 260 | 261 | - [assert](https://nodejs.org/api/assert.html) Node.js built-in assertion module 262 | - [should.js](https://github.com/shouldjs/should.js) BDD style shown throughout these docs 263 | - [expect.js](https://github.com/LearnBoost/expect.js) expect() style assertions 264 | - [chai](http://chaijs.com/) expect(), assert() and should style assertions 265 | 266 | ### Suites and tests 267 | 268 | #### tman.suite(title, fn), tman.describe(title, fn) 269 | You may use `suite` to organize huge scale tests. `describe` is an alias of `suite`. You can define any level of nested suites and test cases. 270 | 271 | ```js 272 | tman.suite('User', function () { 273 | tman.suite('#save()', function () { 274 | tman.it('should save without error', function * () { 275 | yield new User('Tman').save() 276 | }) 277 | }) 278 | }) 279 | ``` 280 | 281 | #### tman.test(title, fn), tman.it(title, fn) 282 | Define test logic, support synchronous or asynchronous test. `it` is an alias of `test`. 283 | 284 | ```js 285 | tman.it('synchronous test', function () { 286 | // test body 287 | }) 288 | 289 | tman.it('callback style asynchronous test', function (done) { 290 | // test body 291 | setTimeout(done, 100) 292 | }) 293 | 294 | tman.it('promise style asynchronous test', function () { 295 | // test body 296 | return new Promise(function (resolve) { 297 | // test body 298 | setTimeout(resolve, 100) 299 | }) 300 | }) 301 | 302 | tman.it('thunk style asynchronous test', function () { 303 | // test body 304 | return function (done) { 305 | // test body 306 | setTimeout(done, 100) 307 | } 308 | }) 309 | 310 | tman.it('generator style asynchronous test', function * () { 311 | // test body 312 | yield thunk.delay(100) 313 | // test body 314 | }) 315 | ``` 316 | 317 | ### Hooks 318 | This hooks can be used to set up preconditions and clean up after your tests. All of them support synchronous or asynchronous function, just like `tman.it`. You can define any level hooks for `suite` or `test`. 319 | 320 | #### tman.before(fn) 321 | #### tman.after(fn) 322 | #### tman.beforeEach(fn) 323 | #### tman.afterEach(fn) 324 | 325 | ```js 326 | tman.suite('hooks', function () { 327 | 328 | tman.before(function () { 329 | // runs before all tests in this block 330 | }) 331 | 332 | tman.after(function () { 333 | // runs after all tests in this block 334 | }) 335 | 336 | tman.beforeEach(function () { 337 | // runs before each test in this block 338 | }) 339 | 340 | tman.afterEach(function () { 341 | // runs after each test in this block 342 | }) 343 | 344 | // test cases 345 | tman.it('test', function () { 346 | // ... 347 | }) 348 | }) 349 | ``` 350 | 351 | ### Exclusive or inclusive tests 352 | `only` and `skip` will work as your expectation. If you have more than one `only` in your tests or suites, only the first `only` will take effect, all other will not be read. 353 | 354 | #### tman.suite.only(title, fn) 355 | #### tman.it.only(title, fn) 356 | #### tman.suite.skip(title, fn) 357 | #### tman.it.skip(title, fn) 358 | 359 | ```js 360 | tman.suite('Array', function () { 361 | tman.suite('#indexOf()', function () { 362 | tman.it.only('should return -1 unless present', function () { 363 | // ... 364 | }) 365 | 366 | tman.it('should return the index when present', function () { 367 | // ... 368 | }) 369 | }) 370 | }) 371 | ``` 372 | 373 | #### tman.grep(pattern) 374 | Sets grep pattern and run tests matching pattern, same as `--grep ` CLI option. 375 | 376 | #### tman.exclude(pattern) 377 | Sets exclude pattern and exclude tests matching pattern, same as `--exclude ` CLI option. 378 | 379 | ### Timeouts 380 | Default timeout is `2000ms`. 381 | 382 | Suite-level timeouts may be applied to entire test "suites", or disabled via this.timeout(0). This will be inherited by all nested suites and test-cases that do not override the value. 383 | 384 | ```js 385 | tman.suite('a suite of tests', function () { 386 | this.timeout(500) 387 | 388 | tman.it('should take less than 500ms', function (done) { 389 | setTimeout(done, 300) 390 | }) 391 | 392 | tman.it('should take less than 500ms as well', function (done) { 393 | setTimeout(done, 200) 394 | }) 395 | }) 396 | ``` 397 | 398 | Test-specific timeouts may also be applied, or the use of this.timeout(0) to disable timeouts all together. 399 | 400 | ```js 401 | tman.it('should take less than 500ms', function (done) { 402 | this.timeout(500) 403 | setTimeout(done, 300) 404 | }); 405 | ``` 406 | 407 | ### Write tests in source code 408 | 409 | #### tman(title, fn) 410 | #### tman.only(title, fn) 411 | #### tman.skip(title, fn) 412 | You can write tests in your source code: 413 | 414 | ```js 415 | exports.stringify = function (val) { 416 | return val == null ? '' : String(val) 417 | } 418 | 419 | tman('test in source code', function () { 420 | const assert = require('assert') 421 | 422 | tman.it('stringify', function () { 423 | assert.strictEqual(exports.stringify(), '') 424 | assert.strictEqual(exports.stringify(null), '') 425 | assert.strictEqual(exports.stringify(0), '0') 426 | assert.strictEqual(exports.stringify(false), 'false') 427 | assert.strictEqual(exports.stringify(NaN), 'NaN') 428 | }) 429 | }) 430 | ``` 431 | 432 | The tests will only run in `test mode`. 433 | 434 | ### Run tests 435 | 436 | #### tman.run([callback]) 437 | You can run the tests programmatically: 438 | 439 | ```js 440 | // Run: `node example.js` 441 | tman.suite('User', function () { 442 | tman.suite('#save()', function () { 443 | tman.it('should save without error', function * () { 444 | yield new User('Tman').save() 445 | }) 446 | }) 447 | // others 448 | }) 449 | 450 | tman.run() 451 | ``` 452 | 453 | **If you run tests with CLI, you will not need to use `tman.run`,** the `tman` command will run tests automatically. 454 | 455 | #### tman.mocha() 456 | Enable mocha compatible mode, same as `--mocha` CLI option. 457 | 458 | #### tman.reset() 459 | Clear all tests of tman instance. 460 | 461 | #### tman.loadFiles(filePath, sort) 462 | Load test files to tman, it will clear previous tests file that in `require.cache`. 463 | 464 | #### tman.globals(globals) 465 | Set the given globals. 466 | 467 | #### T-man CLI 468 | 469 | ```sh 470 | $ tman --help 471 | 472 | Usage: tman [debug] [options] [files] 473 | 474 | Options: 475 | 476 | -h, --help output usage information 477 | -V, --version output the version number 478 | -c, --colors force enabling of colors 479 | -C, --no-colors force disabling of colors 480 | -d, --debug enable node\'s debugger, synonym for node --debug 481 | -e, --exclude exclude tests matching 482 | -g, --grep run tests matching 483 | -gc, --expose-gc expose gc extension 484 | -r, --require require the given module 485 | -R, --reporter specify the reporter to use [spec] 486 | -t, --timeout set test-case timeout in milliseconds [2000] 487 | --debug-brk enable node\'s debugger breaking on the first line 488 | --es_staging enable all staged features 489 | --globals allow the given comma-delimited global [names] 490 | --harmony<_classes,_generators,...> all node --harmony* flags are available 491 | --icu-data-dir include ICU data 492 | --mocha Mocha compatible mode 493 | --no-sort don\'t sort test files 494 | --no-timeout disables timeouts, given implicitly with --debug 495 | --no-exit require a clean shutdown of the event loop: T-man will not call process.exit 496 | --opts specify opts path 497 | --perf-basic-prof enable perf linux profiler (basic support) 498 | --preserve-symlinks Instructs the module loader to preserve symbolic links when resolving and caching modules 499 | --reporters display available reporters 500 | --throw-deprecation throw an exception anytime a deprecated function is used 501 | --trace trace function calls 502 | --trace-deprecation show stack traces on deprecations 503 | --use_strict enforce strict mode 504 | ``` 505 | 506 | #### T-man test mode 507 | There are 3 ways to run with `test mode`: 508 | 509 | 1. `tman example/test_in_source_code.js` 510 | 2. `node example/test_in_source_code.js --test` 511 | 3. `TEST=* node example/test_in_source_code.js` 512 | 513 | ### TypeScript Typings 514 | 515 | ```typescript 516 | import * as tman from 'tman' 517 | import { tman, suite, it, before, after, beforeEach, afterEach } from 'tman' 518 | ``` 519 | 520 | ### Reporters 521 | 522 | #### spec 523 | 524 | ![spec reporter](https://raw.githubusercontent.com/thunks/tman/master/doc/reporter_spec.png) 525 | 526 | #### dot 527 | 528 | ![dot reporter](https://raw.githubusercontent.com/thunks/tman/master/doc/reporter_dot.png) 529 | 530 | #### base 531 | 532 | ![base reporter](https://raw.githubusercontent.com/thunks/tman/master/doc/reporter_base.png) 533 | 534 | ### FAQ 535 | 536 | ### How to run CoffeeScript (or TypeScript) tests? 537 | Use `--require` option: 538 | 539 | 1. `tman -r coffee-script/register test/*.coffee` 540 | 2. `tman -r ts-node/register test/*.ts` 541 | 542 | [Here](https://github.com/thunks/tman/tree/master/example/simple.coffee) is a simple example. You can require one more modules. 543 | 544 | ### License 545 | T-man is licensed under the [MIT](https://github.com/thunks/tman/blob/master/LICENSE) license. 546 | Copyright © 2016-2020 thunks. 547 | 548 | [npm-url]: https://npmjs.org/package/tman 549 | [npm-image]: http://img.shields.io/npm/v/tman.svg 550 | 551 | [travis-url]: https://travis-ci.org/thunks/tman 552 | [travis-image]: http://img.shields.io/travis/thunks/tman.svg 553 | 554 | [coveralls-url]: https://coveralls.io/github/thunks/tman?branch=master 555 | [coveralls-image]: https://coveralls.io/repos/github/thunks/tman/badge.svg?branch=master 556 | 557 | [downloads-url]: https://npmjs.org/package/tman 558 | [downloads-image]: http://img.shields.io/npm/dm/tman.svg?style=flat-square 559 | -------------------------------------------------------------------------------- /test/others.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // **Github:** https://github.com/thunks/tman 3 | // 4 | // **License:** MIT 5 | 6 | var path = require('path') 7 | var util = require('util') 8 | var assert = require('assert') 9 | var thunk = require('thunks')() 10 | var slice = Array.prototype.slice 11 | 12 | var tman = require('..') 13 | var format = tman.format 14 | 15 | assert.strictEqual(tman.baseDir, path.join(process.cwd(), 'test')) 16 | 17 | function CustomReporter (ctx, childCtx) { 18 | tman.Reporter.defaultReporter.call(this, ctx) 19 | this.childCtx = childCtx 20 | } 21 | util.inherits(CustomReporter, tman.Reporter.defaultReporter) 22 | CustomReporter.prototype.onFinish = function (res) { 23 | tman.rootSuite.passed += res.passed + res.errors.length + res.ignored 24 | this.logError(res) 25 | } 26 | CustomReporter.prototype.log = function () { 27 | var args = slice.call(arguments) 28 | Array.prototype.push.apply(this.childCtx.messages, args) 29 | args[0] = format.indent(this.childCtx.depth) + args[0] 30 | tman.rootSuite.reporter.log.apply(null, args) 31 | } 32 | 33 | tman.suite('Exclusive or inclusive tests', function () { 34 | tman.it('"skip" test', function () { 35 | var ctx = this 36 | var count = 0 37 | // new child instance for test 38 | var t = tman.createTman() 39 | this.messages = [] 40 | t.setReporter(CustomReporter, this) 41 | 42 | t.before(function () { 43 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 44 | assert.strictEqual(count++, 0) 45 | }) 46 | 47 | t.after(function () { 48 | assert.strictEqual(count++, 4) 49 | }) 50 | 51 | t.it('test 1-1', function () { 52 | assert.strictEqual(count++, 1) 53 | }) 54 | 55 | t.it.skip('test 1-2', function (done) { 56 | assert.strictEqual(true, false) 57 | done() 58 | }) 59 | 60 | t.suite('suite 1-1', function () { 61 | t.it.skip('test 2-1', function () { 62 | assert.strictEqual(true, false) 63 | }) 64 | 65 | t.suite.skip('suite 2-1', function () { 66 | t.it('test 3-1', function () { 67 | assert.strictEqual(true, false) 68 | }) 69 | 70 | t.it('test 3-2', function () { 71 | assert.strictEqual(true, false) 72 | }) 73 | }) 74 | 75 | t.it('test 2-2', function () { 76 | assert.strictEqual(count++, 2) 77 | }) 78 | 79 | t.it('test 2-3', function () { 80 | assert.deepStrictEqual({ 81 | a: 1, 82 | b: 2, 83 | d: 5 84 | }, { 85 | a: 4, 86 | b: 2, 87 | c: 3 88 | }) 89 | }) 90 | 91 | t.it('test 2-4', function () { 92 | assert.deepStrictEqual({ 93 | a: undefined, 94 | b: 2, 95 | c: 3 96 | }, { 97 | b: 2, 98 | c: 3 99 | }) 100 | }) 101 | 102 | t.it('test 2-5', function () { 103 | assert.deepStrictEqual({ 104 | toJSON: function () { 105 | return { 106 | a: 5 107 | } 108 | } 109 | }, { 110 | a: 5 111 | }) 112 | }) 113 | }) 114 | 115 | t.it('test 1-3', function () { 116 | assert.strictEqual(count++, 3) 117 | }) 118 | 119 | return t.run(function (err, res) { 120 | if (err) throw err 121 | assert.strictEqual(res.passed, 3) 122 | assert.strictEqual(res.ignored, 4) 123 | 124 | var messages = ctx.messages.join('') 125 | assert.ok(messages.indexOf('test 1-2') > 0) 126 | assert.ok(messages.indexOf('test 2-1') > 0) 127 | assert.ok(messages.indexOf('suite 2-1') > 0) 128 | assert.ok(messages.indexOf('test 3-1') > 0) 129 | assert.ok(messages.indexOf('test 3-2') > 0) 130 | }) 131 | }) 132 | 133 | tman.it('"only" test', function () { 134 | var ctx = this 135 | var count = 0 136 | // new child instance for test 137 | var t = tman.createTman() 138 | this.messages = [] 139 | t.setReporter(CustomReporter, this) 140 | 141 | t.before(function () { 142 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 143 | assert.strictEqual(count++, 0) 144 | }) 145 | 146 | t.after(function () { 147 | assert.strictEqual(count++, 2) 148 | }) 149 | 150 | t.it.only('test 1-1', function () { 151 | assert.strictEqual(count++, 1) 152 | }) 153 | 154 | t.it('test 1-2', function () { 155 | assert.strictEqual(true, false) 156 | }) 157 | 158 | t.it('test 1-3', function () { 159 | assert.strictEqual(true, false) 160 | }) 161 | 162 | return t.run(function (err, res) { 163 | if (err) throw err 164 | assert.strictEqual(res.passed, 1) 165 | assert.strictEqual(res.ignored, 0) 166 | 167 | var messages = ctx.messages.join('') 168 | assert.ok(messages.indexOf('test 1-1') > 0) 169 | assert.ok(messages.indexOf('test 1-2') === -1) 170 | assert.ok(messages.indexOf('test 1-3') === -1) 171 | }) 172 | }) 173 | 174 | tman.it('"only" suite and "skip" test', function () { 175 | var ctx = this 176 | var count = 0 177 | // new child instance for test 178 | var t = tman.createTman() 179 | this.messages = [] 180 | t.setReporter(CustomReporter, this) 181 | 182 | t.before(function () { 183 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 184 | assert.strictEqual(count++, 0) 185 | }) 186 | 187 | t.after(function () { 188 | assert.strictEqual(count++, 4) 189 | }) 190 | 191 | t.suite('suite 1-1', function () { 192 | t.it('test 2-1', function () { 193 | assert.strictEqual(true, false) 194 | }) 195 | 196 | t.it('test 2-2', function () { 197 | assert.strictEqual(true, false) 198 | }) 199 | }) 200 | 201 | t.suite.only('suite 1-2', function () { 202 | t.it('test 2-1', function () { 203 | assert.strictEqual(count++, 1) 204 | }) 205 | 206 | t.it('test 2-2', function () { 207 | assert.strictEqual(count++, 2) 208 | }) 209 | 210 | t.suite('suite 2-1', function () { 211 | t.it('test 3-1', function () { 212 | assert.strictEqual(count++, 3) 213 | }) 214 | 215 | t.it.skip('test 3-2', function () { 216 | assert.strictEqual(true, false) 217 | }) 218 | }) 219 | }) 220 | 221 | t.it('test 1-1', function () { 222 | assert.strictEqual(true, false) 223 | }) 224 | 225 | return t.run(function (err, res) { 226 | if (err) throw err 227 | assert.strictEqual(res.passed, 3) 228 | assert.strictEqual(res.ignored, 1) 229 | 230 | var messages = ctx.messages.join('') 231 | assert.ok(messages.indexOf('suite 1-1') === -1) 232 | assert.ok(messages.indexOf('suite 1-2') > 0) 233 | assert.ok(messages.indexOf('test 2-1') > 0) 234 | assert.ok(messages.indexOf('test 2-2') > 0) 235 | assert.ok(messages.indexOf('suite 2-1') > 0) 236 | assert.ok(messages.indexOf('test 3-1') > 0) 237 | assert.ok(messages.indexOf('test 3-2') > 0) 238 | assert.ok(messages.indexOf('test 1-1') === -1) 239 | }) 240 | }) 241 | 242 | tman.it('muilt "only"', function () { 243 | var ctx = this 244 | var count = 0 245 | // new child instance for test 246 | var t = tman.createTman() 247 | this.messages = [] 248 | t.setReporter(CustomReporter, this) 249 | 250 | t.before(function () { 251 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 252 | assert.strictEqual(count++, 0) 253 | }) 254 | 255 | t.after(function () { 256 | assert.strictEqual(count++, 7) 257 | }) 258 | 259 | t.suite('suite 1-1', function () { 260 | t.it('test 1-2-1', function () { 261 | assert.strictEqual(true, false) 262 | }) 263 | 264 | t.it('test 1-2-2', function () { 265 | assert.strictEqual(true, false) 266 | }) 267 | }) 268 | 269 | t.it.only('test 1-1', function () { 270 | assert.strictEqual(count++, 1) 271 | }) 272 | 273 | t.it('test 1-2', function () { 274 | assert.strictEqual(true, false) 275 | }) 276 | 277 | t.suite.only('suite 1-2', function () { 278 | t.it('test 2-2-1', function () { 279 | assert.strictEqual(count++, 2) 280 | }) 281 | 282 | t.it('test 2-2-2', function () { 283 | assert.strictEqual(count++, 3) 284 | }) 285 | 286 | t.it.skip('test 2-2-3', function () { 287 | assert.strictEqual(true, false) 288 | }) 289 | 290 | t.it('test 2-2-4', function () { 291 | assert.notStrictEqual('same string', 'same string') 292 | }) 293 | }) 294 | 295 | t.suite.only('suite 1-3', function () { 296 | t.it('test 3-2-1', function () { 297 | assert.strictEqual(true, false) 298 | }) 299 | 300 | t.it.only('test 3-2-2', function () { 301 | assert.strictEqual(count++, 4) 302 | }) 303 | 304 | t.it.only('test 3-2-3', function () { 305 | assert.strictEqual(count++, 5) 306 | }) 307 | }) 308 | 309 | t.it.only('test 1-3', function () { 310 | assert.strictEqual(count++, 6) 311 | }) 312 | 313 | t.it('test 1-4', function () { 314 | assert.strictEqual(true, false) 315 | }) 316 | 317 | return t.run(function (err, res) { 318 | if (err) throw err 319 | assert.strictEqual(res.passed, 6) 320 | assert.strictEqual(res.ignored, 1) 321 | 322 | var messages = ctx.messages.join('') 323 | assert.ok(messages.indexOf('suite 1-1') === -1) 324 | assert.ok(messages.indexOf('test 1-1') > 0) 325 | assert.ok(messages.indexOf('test 1-2') === -1) 326 | assert.ok(messages.indexOf('suite 1-2') > 0) 327 | assert.ok(messages.indexOf('test 2-2-1') > 0) 328 | assert.ok(messages.indexOf('test 2-2-2') > 0) 329 | assert.ok(messages.indexOf('test 2-2-3') > 0) 330 | assert.ok(messages.indexOf('suite 1-3') > 0) 331 | assert.ok(messages.indexOf('test 3-2-1') === -1) 332 | assert.ok(messages.indexOf('test 3-2-2') > 0) 333 | assert.ok(messages.indexOf('test 3-2-3') > 0) 334 | assert.ok(messages.indexOf('test 1-3') > 0) 335 | assert.ok(messages.indexOf('test 1-4') === -1) 336 | }) 337 | }) 338 | 339 | tman.it('"only" in "skip" mode should not take effect', function () { 340 | var ctx = this 341 | var count = 0 342 | // new child instance for test 343 | var t = tman.createTman() 344 | this.messages = [] 345 | t.setReporter(CustomReporter, this) 346 | 347 | t.before(function () { 348 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 349 | assert.strictEqual(count++, 0) 350 | }) 351 | 352 | t.after(function () { 353 | assert.strictEqual(count++, 4) 354 | }) 355 | 356 | t.it('test 1-1', function () { 357 | assert.strictEqual(count++, 1) 358 | }) 359 | 360 | t.it('test 1-2', function () { 361 | assert.strictEqual(count++, 2) 362 | }) 363 | 364 | t.suite.skip('suite 1-1', function () { 365 | t.it.only('test 2-1', function () { 366 | assert.strictEqual(true, false) 367 | }) 368 | 369 | t.suite.only('suite 2-1', function () { 370 | t.it('test 3-1', function () { 371 | assert.strictEqual(true, false) 372 | }) 373 | 374 | t.it('test 3-2', function () { 375 | assert.strictEqual(true, false) 376 | }) 377 | }) 378 | }) 379 | 380 | t.it('test 1-3', function () { 381 | assert.strictEqual(count++, 3) 382 | }) 383 | 384 | return t.run(function (err, res) { 385 | if (err) throw err 386 | assert.strictEqual(res.passed, 3) 387 | assert.strictEqual(res.ignored, 3) 388 | 389 | var messages = ctx.messages.join('') 390 | assert.ok(messages.indexOf('test 1-1') > 0) 391 | assert.ok(messages.indexOf('test 1-2') > 0) 392 | assert.ok(messages.indexOf('suite 1-1') > 0) 393 | assert.ok(messages.indexOf('test 2-1') > 0) 394 | assert.ok(messages.indexOf('suite 2-1') > 0) 395 | assert.ok(messages.indexOf('test 3-1') > 0) 396 | assert.ok(messages.indexOf('test 3-2') > 0) 397 | assert.ok(messages.indexOf('test 1-3') > 0) 398 | }) 399 | }) 400 | }) 401 | 402 | tman.suite('grep and exclude', function () { 403 | tman.it('grep', function () { 404 | // new child instance for test 405 | var t = tman.createTman() 406 | assert.ok(t.rootSuite.grep.test('')) 407 | assert.ok(t.rootSuite.grep.test('*')) 408 | assert.ok(t.rootSuite.grep.test('abc, 123')) 409 | 410 | t.grep('api') 411 | assert.ok(t.rootSuite.grep.test('api')) 412 | assert.ok(t.rootSuite.grep.test('/api/user')) 413 | assert.ok(t.rootSuite.grep.test('#api, /user')) 414 | assert.ok(!t.rootSuite.grep.test('')) 415 | assert.ok(!t.rootSuite.grep.test('apji')) 416 | 417 | t.grep('/aa|BB/') 418 | assert.ok(!t.rootSuite.grep.test('')) 419 | assert.ok(!t.rootSuite.grep.test('abc')) 420 | assert.ok(!t.rootSuite.grep.test('bb')) 421 | assert.ok(t.rootSuite.grep.test('aa')) 422 | assert.ok(t.rootSuite.grep.test('BB')) 423 | assert.ok(t.rootSuite.grep.test('aaBB')) 424 | 425 | t.grep('/aa|BB/i') 426 | assert.ok(t.rootSuite.grep.test('bb')) 427 | }) 428 | 429 | tman.it('exclude', function () { 430 | // new child instance for test 431 | var t = tman.createTman() 432 | assert.ok(!t.rootSuite.exclude.test('')) 433 | assert.ok(!t.rootSuite.exclude.test('*')) 434 | assert.ok(!t.rootSuite.exclude.test('abc, 123')) 435 | 436 | t.exclude('api') 437 | assert.ok(t.rootSuite.exclude.test('api')) 438 | assert.ok(t.rootSuite.exclude.test('/api/user')) 439 | assert.ok(t.rootSuite.exclude.test('#api, /user')) 440 | assert.ok(!t.rootSuite.exclude.test('')) 441 | assert.ok(!t.rootSuite.exclude.test('apji')) 442 | 443 | t.exclude('/aa|BB/') 444 | assert.ok(!t.rootSuite.exclude.test('')) 445 | assert.ok(!t.rootSuite.exclude.test('abc')) 446 | assert.ok(!t.rootSuite.exclude.test('bb')) 447 | assert.ok(t.rootSuite.exclude.test('aa')) 448 | assert.ok(t.rootSuite.exclude.test('BB')) 449 | assert.ok(t.rootSuite.exclude.test('aaBB')) 450 | 451 | t.exclude('/aa|BB/i') 452 | assert.ok(t.rootSuite.exclude.test('bb')) 453 | }) 454 | 455 | tman.it('grep and exclude in tests', function () { 456 | var ctx = this 457 | var count = 0 458 | // new child instance for test 459 | var t = tman.createTman() 460 | this.messages = [] 461 | t.setReporter(CustomReporter, this) 462 | 463 | t.grep('api') 464 | t.before(function () { 465 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 466 | assert.strictEqual(count++, 0) 467 | }) 468 | 469 | t.after(function () { 470 | assert.strictEqual(count++, 4) 471 | }) 472 | 473 | t.it('should not run', function () { 474 | assert.strictEqual(true, false) 475 | }) 476 | 477 | t.it('api test', function () { 478 | assert.strictEqual(count++, 1) 479 | }) 480 | 481 | t.it('api should ignore', function () { 482 | assert.strictEqual(true, false) 483 | }) 484 | 485 | t.suite('suite', function () { 486 | t.it('test not run', function () { 487 | assert.strictEqual(true, false) 488 | }) 489 | 490 | t.it('api 1', function () { 491 | assert.strictEqual(count++, 2) 492 | }) 493 | 494 | t.it('ignore 1', function () { 495 | assert.strictEqual(true, false) 496 | }) 497 | }) 498 | 499 | t.suite('suite api', function () { 500 | t.it('test 1', function () { 501 | assert.strictEqual(count++, 3) 502 | }) 503 | 504 | t.it('ignore', function () { 505 | assert.strictEqual(true, false) 506 | }) 507 | 508 | t.it('api ignore', function () { 509 | assert.strictEqual(true, false) 510 | }) 511 | }) 512 | 513 | t.suite('ignore suite', function () { 514 | t.it('test 2', function () { 515 | assert.strictEqual(true, false) 516 | }) 517 | 518 | t.it('api 2', function () { 519 | assert.strictEqual(true, false) 520 | }) 521 | }) 522 | 523 | t.it.skip('skip api', function () { 524 | assert.strictEqual(true, false) 525 | }) 526 | 527 | t.exclude('ignore') 528 | return t.run(function (err, res) { 529 | if (err) throw err 530 | assert.strictEqual(res.passed, 3) 531 | assert.strictEqual(res.ignored, 1) 532 | 533 | var messages = ctx.messages.join('') 534 | assert.ok(messages.indexOf('should not run') < 0) 535 | assert.ok(messages.indexOf('api test') > 0) 536 | assert.ok(messages.indexOf('api should ignore') < 0) 537 | assert.ok(messages.indexOf('test not run') < 0) 538 | assert.ok(messages.indexOf('api 1') > 0) 539 | assert.ok(messages.indexOf('ignore 1') < 0) 540 | assert.ok(messages.indexOf('test 1') > 0) 541 | assert.ok(messages.indexOf('api ignore') < 0) 542 | assert.ok(messages.indexOf('test 2') < 0) 543 | assert.ok(messages.indexOf('api 2') < 0) 544 | assert.ok(messages.indexOf('skip api') > 0) 545 | }) 546 | }) 547 | }) 548 | 549 | tman.suite('Timeouts and errors', function () { 550 | tman.it('suite timeouts and test timeouts', function () { 551 | var ctx = this 552 | var count = 0 553 | // new child instance for test 554 | var t = tman.createTman() 555 | this.messages = [] 556 | t.setReporter(CustomReporter, this) 557 | 558 | t.before(function () { 559 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 560 | assert.strictEqual(count++, 0) 561 | }) 562 | 563 | t.after(function () { 564 | assert.strictEqual(count++, 6) 565 | }) 566 | 567 | t.it('test 1-1', function (done) { 568 | assert.strictEqual(count++, 1) 569 | setTimeout(done, 100) 570 | }) 571 | 572 | t.it('test 1-2, timeout', function (done) { 573 | this.timeout(50) 574 | assert.strictEqual(count++, 2) 575 | setTimeout(done, 100) 576 | }) 577 | 578 | t.suite('suite 1-1, timeout', function () { 579 | this.timeout(90) 580 | 581 | t.it('test 2-1', function (done) { 582 | this.timeout(110) 583 | assert.strictEqual(count++, 3) 584 | setTimeout(done, 100) 585 | }) 586 | 587 | t.it('test 2-2, timeout', function (done) { 588 | assert.strictEqual(count++, 4) 589 | setTimeout(done, 100) 590 | }) 591 | 592 | t.it('test 2-2, no-timeout', function (done) { 593 | this.timeout(0) 594 | assert.strictEqual(count++, 5) 595 | setTimeout(done, 100) 596 | }) 597 | }) 598 | 599 | return t.run(function (err, res) { 600 | if (err) throw err 601 | assert.strictEqual(res.passed, 3) 602 | assert.strictEqual(res.ignored, 0) 603 | 604 | var messages = ctx.messages.join('') 605 | assert.ok(messages.indexOf('test 1-1') > 0) 606 | assert.ok(messages.indexOf('test 1-2, timeout') > 0) 607 | assert.ok(messages.indexOf('suite 1-1, timeout') > 0) 608 | assert.ok(messages.indexOf('test 2-1') > 0) 609 | assert.ok(messages.indexOf('test 2-2, timeout') > 0) 610 | 611 | assert.ok(res.errors[0] instanceof Error) 612 | assert.ok(res.errors[0].message.indexOf('50ms') > 0) 613 | assert.strictEqual(res.errors[0].order, 1) 614 | assert.strictEqual(res.errors[0].title, '/test 1-2, timeout') 615 | 616 | assert.ok(res.errors[1] instanceof Error) 617 | assert.ok(res.errors[1].message.indexOf('90ms') > 0) 618 | assert.strictEqual(res.errors[1].order, 2) 619 | assert.strictEqual(res.errors[1].title, '/suite 1-1, timeout/test 2-2, timeout') 620 | }) 621 | }) 622 | 623 | tman.it('record errors', function () { 624 | var ctx = this 625 | var count = 0 626 | // new child instance for test 627 | var t = tman.createTman() 628 | this.messages = [] 629 | t.setReporter(CustomReporter, this) 630 | 631 | t.before(function () { 632 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 633 | assert.strictEqual(count++, 0) 634 | }) 635 | 636 | t.after(function () { 637 | assert.strictEqual(count++, 5) 638 | }) 639 | 640 | t.it('test 1-1', function (done) { 641 | assert.strictEqual(count++, 1) 642 | throw new Error('error') 643 | }) 644 | 645 | t.it('test 1-2', function () { 646 | assert.strictEqual(count++, 2) 647 | return thunk(function (done) { 648 | throw new Error('error from thunk') 649 | }) 650 | }) 651 | 652 | t.it.skip('test 1-3', function (done) { 653 | assert.strictEqual(true, false) 654 | throw new Error('error') 655 | }) 656 | 657 | t.suite('suite 1-1', function () { 658 | t.it('test 2-1', function (done) { 659 | assert.strictEqual(count++, 3) 660 | setTimeout(function () { 661 | done(new Error('error')) 662 | }, 100) 663 | }) 664 | 665 | t.it('test 2-2', function (done) { 666 | assert.strictEqual(count++, 4) 667 | setTimeout(done, 100) 668 | }) 669 | }) 670 | 671 | return t.run(function (err, res) { 672 | if (err) throw err 673 | assert.strictEqual(res.passed, 1) 674 | assert.strictEqual(res.ignored, 1) 675 | 676 | var messages = ctx.messages.join('') 677 | assert.ok(messages.indexOf('test 1-1 (1)') > 0) 678 | assert.ok(messages.indexOf('test 1-2 (2)') > 0) 679 | assert.ok(messages.indexOf('test 1-3') > 0) 680 | assert.ok(messages.indexOf('suite 1-1') > 0) 681 | assert.ok(messages.indexOf('test 2-1 (3)') > 0) 682 | assert.ok(messages.indexOf('test 2-2') > 0) 683 | 684 | assert.ok(res.errors[0] instanceof Error) 685 | assert.strictEqual(res.errors[0].order, 1) 686 | assert.strictEqual(res.errors[0].title, '/test 1-1') 687 | 688 | assert.ok(res.errors[1] instanceof Error) 689 | assert.strictEqual(res.errors[1].order, 2) 690 | assert.strictEqual(res.errors[1].title, '/test 1-2') 691 | 692 | assert.ok(res.errors[2] instanceof Error) 693 | assert.strictEqual(res.errors[2].order, 3) 694 | assert.strictEqual(res.errors[2].title, '/suite 1-1/test 2-1') 695 | }) 696 | }) 697 | 698 | tman.it('hook errors', function () { 699 | var ctx = this 700 | var count = 0 701 | // new child instance for test 702 | var t = tman.createTman() 703 | this.messages = [] 704 | t.setReporter(CustomReporter, this) 705 | 706 | t.before(function () { 707 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 708 | assert.strictEqual(count++, 0) 709 | }) 710 | 711 | t.after(function () { 712 | assert.strictEqual(count++, 5) 713 | }) 714 | 715 | t.it('test 1-1 with error', function (done) { 716 | assert.strictEqual(count++, 1) 717 | throw new Error('error') 718 | }) 719 | 720 | t.it('test 1-2', function () { 721 | assert.strictEqual(count++, 2) 722 | }) 723 | 724 | t.suite('suite 1-1', function () { 725 | t.before(function () { 726 | throw new Error('before hook error') 727 | }) 728 | 729 | t.it('test 2-1 not run', function () { 730 | assert.strictEqual(count++, 0) 731 | }) 732 | }) 733 | 734 | t.suite('suite 1-2', function () { 735 | t.afterEach(function () { 736 | throw new Error('afterEach hook error') 737 | }) 738 | 739 | t.it('test 2-1 run', function () { 740 | assert.strictEqual(count++, 3) 741 | }) 742 | 743 | t.it('test 2-2 not run', function () { 744 | assert.strictEqual(count++, 0) 745 | }) 746 | }) 747 | 748 | t.it('test 1-3', function () { 749 | assert.strictEqual(count++, 4) 750 | }) 751 | 752 | return t.run(function (err, res) { 753 | if (err) throw err 754 | assert.strictEqual(res.passed, 3) 755 | assert.strictEqual(res.ignored, 0) 756 | 757 | var messages = ctx.messages.join('') 758 | assert.ok(messages.indexOf('test 1-1 with error (1)') > 0) 759 | assert.ok(messages.indexOf('/suite 1-1 "before" Hook (2)') > 0) 760 | assert.ok(messages.indexOf('/suite 1-2 "afterEach" Hook (3)') > 0) 761 | 762 | assert.ok(res.errors[0] instanceof Error) 763 | assert.strictEqual(res.errors[0].order, 1) 764 | assert.strictEqual(res.errors[0].title, '/test 1-1 with error') 765 | 766 | assert.ok(res.errors[1] instanceof Error) 767 | assert.strictEqual(res.errors[1].order, 2) 768 | assert.strictEqual(res.errors[1].title, '/suite 1-1 "before" Hook') 769 | 770 | assert.ok(res.errors[2] instanceof Error) 771 | assert.strictEqual(res.errors[2].order, 3) 772 | assert.strictEqual(res.errors[2].title, '/suite 1-2 "afterEach" Hook') 773 | }) 774 | }) 775 | 776 | tman.it('uncaughtException errors', function () { 777 | var ctx = this 778 | var count = 0 779 | // new child instance for test 780 | var t = tman.createTman() 781 | this.messages = [] 782 | t.setReporter(CustomReporter, this) 783 | // remove parent uncaughtException handle 784 | process.removeListener('uncaughtException', tman.uncaught) 785 | 786 | t.before(function () { 787 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 788 | assert.strictEqual(count++, 0) 789 | }) 790 | 791 | t.after(function () { 792 | assert.strictEqual(count++, 5) 793 | // add parent uncaughtException handle 794 | process.on('uncaughtException', tman.uncaught) 795 | }) 796 | 797 | t.it('test 1-1 with error', function (done) { 798 | assert.strictEqual(count++, 1) 799 | setTimeout(function () { 800 | throw new Error('error') 801 | }) 802 | }) 803 | 804 | t.it('test 1-2', function () { 805 | assert.strictEqual(count++, 2) 806 | }) 807 | 808 | t.suite('suite 1-1', function () { 809 | t.before(function (done) { 810 | setTimeout(function () { 811 | throw new Error('before hook error') 812 | }) 813 | }) 814 | 815 | t.it('test 2-1 not run', function () { 816 | assert.strictEqual(count++, 0) 817 | }) 818 | }) 819 | 820 | t.suite('suite 1-2', function () { 821 | t.afterEach(function (done) { 822 | setTimeout(function () { 823 | throw new Error('afterEach hook error') 824 | }) 825 | }) 826 | 827 | t.it('test 2-1 run', function () { 828 | assert.strictEqual(count++, 3) 829 | }) 830 | 831 | t.it('test 2-2 not run', function () { 832 | assert.strictEqual(count++, 0) 833 | }) 834 | }) 835 | 836 | t.it('test 1-3', function () { 837 | assert.strictEqual(count++, 4) 838 | }) 839 | 840 | return t.run(function (err, res) { 841 | if (err) throw err 842 | assert.strictEqual(res.passed, 3) 843 | assert.strictEqual(res.ignored, 0) 844 | 845 | var messages = ctx.messages.join('') 846 | assert.ok(messages.indexOf('test 1-1 with error (1)') > 0) 847 | assert.ok(messages.indexOf('/suite 1-1 "before" Hook (2)') > 0) 848 | assert.ok(messages.indexOf('/suite 1-2 "afterEach" Hook (3)') > 0) 849 | 850 | assert.ok(res.errors[0] instanceof Error) 851 | assert.strictEqual(res.errors[0].order, 1) 852 | assert.strictEqual(res.errors[0].title, '/test 1-1 with error') 853 | 854 | assert.ok(res.errors[1] instanceof Error) 855 | assert.strictEqual(res.errors[1].order, 2) 856 | assert.strictEqual(res.errors[1].title, '/suite 1-1 "before" Hook') 857 | 858 | assert.ok(res.errors[2] instanceof Error) 859 | assert.strictEqual(res.errors[2].order, 3) 860 | assert.strictEqual(res.errors[2].title, '/suite 1-2 "afterEach" Hook') 861 | }) 862 | }) 863 | }) 864 | 865 | tman.suite('mocha compatible mode', function () { 866 | tman.it('enable', function () { 867 | var ctx = this 868 | var count = 0 869 | // new child instance for test 870 | var t = tman.createTman() 871 | this.messages = [] 872 | t.setReporter(CustomReporter, this) 873 | 874 | t.mocha() 875 | t.before(function () { 876 | t.rootSuite.reporter.log(format.yellow('↓ ' + ctx.title + ':', true)) 877 | assert.strictEqual(count++, 0) 878 | }) 879 | 880 | t.after(function () { 881 | assert.strictEqual(count++, 37) 882 | }) 883 | 884 | t.beforeEach(function () { 885 | count++ 886 | }) 887 | 888 | t.afterEach(function () { 889 | count++ 890 | }) 891 | 892 | t.it('test 1-1', function () { 893 | assert.strictEqual(count++, 2) 894 | }) 895 | 896 | t.it('test 1-2', function () { 897 | assert.strictEqual(count++, 5) 898 | }) 899 | 900 | t.suite('suite 1-1', function () { 901 | t.beforeEach(function () { 902 | count++ 903 | }) 904 | 905 | t.it('test 2-1', function () { 906 | assert.strictEqual(count++, 9) 907 | }) 908 | 909 | t.it('test 2-2', function () { 910 | assert.strictEqual(count++, 13) 911 | }) 912 | 913 | t.it('test 2-3', function () { 914 | assert.strictEqual(count++, 17) 915 | }) 916 | }) 917 | 918 | t.suite('suite 1-2', function () { 919 | t.it('test 2-1', function () { 920 | assert.strictEqual(count++, 20) 921 | }) 922 | }) 923 | 924 | t.suite('suite 1-3', function () { 925 | t.afterEach(function () { 926 | count++ 927 | }) 928 | 929 | t.it('test 2-1', function () { 930 | assert.strictEqual(count++, 23) 931 | }) 932 | 933 | t.it('test 2-2', function () { 934 | assert.strictEqual(count++, 27) 935 | }) 936 | 937 | t.suite('suite 1-3-1', function () { 938 | t.it('test 3-1', function () { 939 | assert.strictEqual(count++, 31) 940 | }) 941 | }) 942 | }) 943 | 944 | t.it.skip('test 1-3', function () { 945 | assert.strictEqual(true, false) 946 | }) 947 | 948 | t.it('test 1-4', function () { 949 | assert.strictEqual(count++, 35) 950 | }) 951 | 952 | return t.run(function (err, res) { 953 | if (err) throw err 954 | assert.strictEqual(res.passed, 10) 955 | assert.strictEqual(res.ignored, 1) 956 | }) 957 | }) 958 | }) 959 | 960 | tman.suite('reset', function () { 961 | tman.it('suite.reset', function () { 962 | var count = 0 963 | var t = tman.createTman() 964 | this.messages = [] 965 | t.setReporter(CustomReporter, this) 966 | 967 | t.before(function () { 968 | assert.strictEqual(count++, 0) 969 | }) 970 | 971 | t.after(function () { 972 | assert.strictEqual(count++, 2) 973 | }) 974 | 975 | var suite = t.suite('suite', function () { 976 | t.before(function () { 977 | assert.strictEqual(true, false) 978 | }) 979 | 980 | t.it('test 1', function () { 981 | assert.strictEqual(true, false) 982 | }) 983 | 984 | t.it('test 2', function () { 985 | assert.strictEqual(true, false) 986 | }) 987 | }) 988 | 989 | t.it('test', function () { 990 | assert.strictEqual(count++, 1) 991 | }) 992 | 993 | assert.strictEqual(t.rootSuite.children[0], suite) 994 | assert.strictEqual(suite.before.hooks.length, 1) 995 | assert.strictEqual(suite.after.hooks.length, 0) 996 | assert.strictEqual(suite.beforeEach.hooks.length, 0) 997 | assert.strictEqual(suite.afterEach.hooks.length, 0) 998 | assert.strictEqual(suite.children.length, 2) 999 | 1000 | suite.reset() 1001 | assert.strictEqual(suite.before.hooks.length, 0) 1002 | assert.strictEqual(suite.children.length, 0) 1003 | 1004 | return t.run(function (err, res) { 1005 | if (err) throw err 1006 | assert.strictEqual(res.passed, 1) 1007 | assert.strictEqual(res.ignored, 0) 1008 | }) 1009 | }) 1010 | 1011 | tman.it('tman.reset', function () { 1012 | var t = tman.createTman() 1013 | this.messages = [] 1014 | t.setReporter(CustomReporter, this) 1015 | 1016 | t.before(function () { 1017 | assert.strictEqual(true, false) 1018 | }) 1019 | 1020 | t.after(function () { 1021 | assert.strictEqual(true, false) 1022 | }) 1023 | 1024 | t.suite('suite', function () { 1025 | t.before(function () { 1026 | assert.strictEqual(true, false) 1027 | }) 1028 | 1029 | t.it('test 1', function () { 1030 | assert.strictEqual(true, false) 1031 | }) 1032 | 1033 | t.it('test 2', function () { 1034 | assert.strictEqual(true, false) 1035 | }) 1036 | }) 1037 | 1038 | t.it('test', function () { 1039 | assert.strictEqual(true, false) 1040 | }) 1041 | 1042 | t.reset() 1043 | 1044 | return t.run(function (err, res) { 1045 | if (err) throw err 1046 | assert.strictEqual(res.passed, 0) 1047 | assert.strictEqual(res.ignored, 0) 1048 | 1049 | t.it('test', function () { 1050 | assert.strictEqual(true, true) 1051 | }) 1052 | 1053 | return t.run(function (err, res) { 1054 | if (err) throw err 1055 | assert.strictEqual(res.passed, 1) 1056 | assert.strictEqual(res.ignored, 0) 1057 | }) 1058 | }) 1059 | }) 1060 | }) 1061 | 1062 | tman.suite('tman.tryRun', function () { 1063 | tman.it('should clear previous tryRun', function () { 1064 | var time = Date.now() 1065 | var t = tman.createTman() 1066 | t.setExit(false) 1067 | this.messages = [] 1068 | t.setReporter(CustomReporter, this) 1069 | 1070 | t.it('test tryRun', function () { 1071 | assert.strictEqual(true, true) 1072 | }) 1073 | 1074 | t.tryRun(100)(function () { 1075 | assert.strictEqual('will be cleared', false) 1076 | }) 1077 | 1078 | return thunk.delay(50)(function () { 1079 | return t.tryRun(50)(function (err, res) { 1080 | assert.strictEqual(err, null) 1081 | assert.strictEqual(res.passed, 1) 1082 | assert.strictEqual(res.ignored, 0) 1083 | assert.ok(Date.now() - time >= 100) 1084 | }) 1085 | }) 1086 | }) 1087 | 1088 | tman.it('should not run if running', function () { 1089 | var time = Date.now() 1090 | var t = tman.createTman() 1091 | t.setExit(false) 1092 | this.messages = [] 1093 | t.setReporter(CustomReporter, this) 1094 | 1095 | t.it('test tryRun', function () { 1096 | return thunk.delay(100)(function () { 1097 | assert.strictEqual(true, true) 1098 | }) 1099 | }) 1100 | 1101 | t.tryRun(50)(function () { 1102 | assert.strictEqual('should not run', false) 1103 | }) 1104 | return t.tryRun(10)(function (err, res) { 1105 | assert.strictEqual(err, null) 1106 | assert.strictEqual(res.passed, 1) 1107 | assert.strictEqual(res.ignored, 0) 1108 | assert.ok(Date.now() - time > 100) 1109 | }) 1110 | }) 1111 | }) 1112 | --------------------------------------------------------------------------------