├── .babelrc ├── .eslintignore ├── .eslintrc ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── DOCUMENTATION.md ├── LICENSE ├── README.md ├── bin └── painless ├── examples ├── async-await.js ├── callback.js ├── chai-as-promised.js ├── custom-reporter.js ├── es6-sync.js ├── generator.js ├── promise.js ├── sync-error.js └── sync.js ├── harness.js ├── index.js ├── lib ├── create-harness.js ├── execute-groups.js ├── fn-obj.js ├── reporters │ ├── dot.js │ ├── none.js │ ├── spec.js │ └── tap.js ├── run-fn.js └── sequential-tests.js ├── package-lock.json ├── package.json └── test ├── babel-cli.js ├── cli.js ├── fixtures ├── babel-await-error.js ├── babel-promise-error.js ├── babel-sync.js ├── generator.js └── string-error-test.js ├── harness.js └── run-fn.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-syntax-dynamic-import", 7 | "@babel/plugin-syntax-import-meta", 8 | "@babel/plugin-proposal-class-properties", 9 | "@babel/plugin-proposal-json-strings" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/**/*.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-es5", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "rules": { 7 | "one-var": 0, 8 | "no-var": 0, 9 | "no-param-reassign": 0, 10 | "comma-dangle": 0, 11 | "no-eq-null": 0, 12 | "no-process-exit": 0, 13 | "eqeqeq": [2, "allow-null"] 14 | } 15 | } -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [12.x, 14.x, 16.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | cache: 'npm' 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test 32 | -------------------------------------------------------------------------------- /.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 | .nyc_output 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '8' 5 | - '10' 6 | - '12' 7 | after_success: 8 | - '[ -z "$COVERALLS_REPO_TOKEN" ] && echo "running coverage" && tap --coverage-report=text-lcov | ./node_modules/.bin/coveralls' 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.7.0 2 | Initial Release 3 | 4 | ## 0.8.0 5 | Features 6 | - Created generic reporter API 7 | - New spec and dot reporter 8 | - Expose chai directly to allow chai.use 9 | ``` 10 | var painless = require('painless'); 11 | var chai = painless.chai; 12 | ``` 13 | - `--include,-i` to include additional files 14 | - Updated `variable-diff` for better looking diffs 15 | 16 | Changes 17 | - New dot reporter is default. Use `-r=spec` to keep using spec 18 | 19 | Deprecation 20 | - `--tap,-t` will be removed in the next version 21 | 22 | Breaking 23 | - None 24 | 25 | ### 0.8.1 26 | - Updated coloring for spec reporter times 27 | 28 | ### 0.8.2 29 | - Added `none` reporter. Useful for code coverage reports 30 | 31 | ### 0.8.3 32 | - Updated spec reporter to show group names 33 | 34 | ## 0.9.0 35 | Features 36 | - Browser support and karma support with `karma-painless` 37 | 38 | Bugs 39 | - Fixed bugs with beforeEach and afterEach 40 | 41 | ### 0.9.1 42 | - Fixed bug with 0.10 Node not having Promise 43 | 44 | ### 0.9.5 45 | - Fixed bug where afterEach was not executed on test failure 46 | 47 | ### 0.9.7 48 | - Fixed vulnerable dev dependencies (Does not affect dependents) 49 | -------------------------------------------------------------------------------- /DOCUMENTATION.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## createGroup 4 | ```js 5 | const test = createGroup( /* group Name */); 6 | ``` 7 | 8 | returns test 9 | 10 | ## test 11 | ```js 12 | test( /* test name */, /* options */, /* test function */); 13 | ``` 14 | 15 | Order of parameters does not matter. Only mandatory param is the test function. 16 | #### Options Object 17 | ```js 18 | { 19 | timeout: // Number of milliseconds before test fails 20 | } 21 | ``` 22 | 23 | ### test.beforeEach 24 | ```js 25 | test.beforeEach( /* before each name */, /* options */, /* test function */); 26 | ``` 27 | 28 | Order of parameters does not matter. Only mandatory param is the test function. 29 | #### Options Object 30 | ```js 31 | { 32 | timeout: // Number of milliseconds before test fails 33 | } 34 | ``` 35 | 36 | ### test.afterEach 37 | ```js 38 | test.afterEach( /* after each name */, /* options */, /* test function */); 39 | ``` 40 | 41 | Order of parameters does not matter. Only mandatory param is the test function. 42 | #### Options Object 43 | ```js 44 | { 45 | timeout: // Number of milliseconds before test fails 46 | } 47 | ``` 48 | 49 | ## Reporter API 50 | Painless supports creating your own custom reporters 51 | 52 | ### Create a custom reporter 53 | The easiest way to get started is to use `painless-reporter-helper`. Advanced users can use the [advanced api](#advanced-reporters) 54 | ``` 55 | npm install painless-reporter-helper --save 56 | ``` 57 | Create a new reporter file 58 | ```js 59 | var helper = require('painless-reporter-helper'); 60 | module.exports = helper({ 61 | 'test.end': function(test) { 62 | if (test.success) { 63 | return test.name + ' success!\n'; 64 | } 65 | 66 | return test.name + ' failed!\n'; 67 | } 68 | }); 69 | ``` 70 | The helper function takes a single JS Object. The keys are the names of the events you want to listen to. 71 | The values are functions that are passed the data associated with the event. The return value **MUST BE A STRING**. It is sent to the console. 72 | 73 | For other examples, look at the [built-in reporters](lib/reporters). 74 | 75 | #### Event Types 76 | ##### `test.end` - When a test finishes 77 | 78 | Data associated 79 | ```js 80 | { 81 | name: , // name of the test 82 | success: // Whether the test was successful 83 | error: // null if successful, Object if the test failed (see below) 84 | time: , // time the test took to run in milliseconds 85 | cb: , // test function 86 | timeout: , // timeout of the test, shows default if none set 87 | options: // any options passed to the test 88 | } 89 | ``` 90 | error key has the following structure 91 | ```js 92 | { 93 | message: , // error message 94 | stack: , // lines in the stack when error occurred 95 | expected: , // expected value. Key may not exist if not supported by assertion library. 96 | actual: // actual value. Key may not exist if not supported by assertion library. 97 | } 98 | ``` 99 | ##### `group.start` - When a new group starts 100 | 101 | Data associated 102 | ```js 103 | { 104 | name: // name of the group 105 | } 106 | ``` 107 | ##### `group.end` - When a group finishes 108 | 109 | Data associated 110 | ```js 111 | { 112 | name: , // name of the group 113 | success: , // whether all group tests were successful (no errors) 114 | testCount: , // number of tests in the group, 115 | errors: >, // An array of tests that failed in the group 116 | time: // time to run the group in milliseconds 117 | } 118 | ``` 119 | ##### `end` - When all tests have finished 120 | 121 | Data ssociated 122 | ```js 123 | { 124 | success , // whether all tests were successful (no errors) 125 | testCount: , // number of tests total, 126 | errors: >, // An array of all tests that failed 127 | time: // time to run all tests in milliseconds 128 | } 129 | ``` 130 | 131 | #### Troubleshooting 132 | - `TypeError: invalid data` 133 | 134 | This means you are **NOT** returning a string from one of your event functions. Check the return values. 135 | 136 | #### Advanced Reporters 137 | Here is the signature of a reporter function. It takes in a Stream and returns a new Stream. 138 | ```js 139 | /** 140 | * @param {Stream} stream Node object stream 141 | * @return {Stream} 142 | */ 143 | module.exports = function myReporter(stream) { 144 | 145 | 146 | } 147 | ``` 148 | The Stream sends javascript objects with the following signature. 149 | ```js 150 | { type: '', data: { } } 151 | ``` 152 | See [Event Types](#event-types) for message types and data structures. The function must return and new Stream. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Taylor Hakes 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Painless Test Library 2 | [![Coverage Status](https://coveralls.io/repos/github/taylorhakes/painless/badge.svg?branch=master)](https://coveralls.io/github/taylorhakes/painless?branch=master) 3 | 4 | Simple test library that is easy to learn, use and debug. Tests can be run with a standard node command `node test.js` allowing you to use all the existing node tools. Tests are really fast. In some cases 10-20x faster than other libraries. Out of the box tests can use ES6/Babel, Promises, Async/Await, Generators, Observables, Callbacks and more. 5 | 6 | ## Why use painless? 7 | - Easy to learn 8 | - Works with ES6/Babel, Promises, Async/Await, Generators, Callbacks, Observables, Streams, Processes, out of the box 9 | - No Globals 10 | - Great diffs on errors (Shows only differences. Very helpful on large objects) 11 | - Really fast (Has the ability to run multiple tests at the same time) 12 | - Easy to debug (Tests are just basic node processes. No subprocesses or threads) 13 | - Batteries included. Comes bundled with assertions and mocking (Chai and Sinon) 14 | - 100% Test Coverage 15 | - Supports coverage support with [nyc](https://github.com/bcoe/nyc) 16 | 17 | ## Table of Contents 18 | - [Example Tests](#example-tests) 19 | - [How to Use](#how-to-use) 20 | - [FAQs (Comparison to other test libraries)](#faqs) 21 | - [Assertions](#assertions) 22 | - [Mocking/Spy/Stubs](#spyingstubsmocking) 23 | - [Command Line Usage](#command-line-usage) 24 | - [Code Coverage](#code-coverage) 25 | 26 | ## Example Tests 27 | ES6 28 | ```js 29 | import { createGroup, assert } from 'painless'; 30 | 31 | const test = createGroup(); 32 | 33 | // Sync test 34 | test('sync test', () => { 35 | assert.deepEqual({ a: '1' }, { a: '1'}); 36 | }); 37 | 38 | // Promise test. return assert.eventually to wait for Promise 39 | test('promise test', () => { 40 | return assert.eventually.deepEqual(promiseFn(), { a: '1'}); 41 | }); 42 | ``` 43 | 44 | [See more examples here](examples) 45 | 46 | ES5 47 | ```js 48 | var painless = require('painless'); 49 | var test = painless.createGroup(); 50 | var assert = painless.assert; 51 | 52 | // Callback test 53 | test('callback test', function(done) { 54 | setTimeout(function() { 55 | assert.deepEqual({ a: '1' }, { a: '1'}); 56 | done(); 57 | }, 10); 58 | }); 59 | ``` 60 | 61 | ![output](http://i.imgur.com/km7eWcL.png) 62 | 63 | ## How to use 64 | ##### Install 65 | ``` 66 | npm install painless --save-dev 67 | ``` 68 | Execute a single test 69 | ``` 70 | node test/test-file.js 71 | ``` 72 | Or run multiple tests 73 | ``` 74 | ./node_modules/.bin/painless test/**/*.js 75 | ``` 76 | Run tests with Babel 77 | ``` 78 | babel-node ./node_modules/.bin/painless test/**/*.js 79 | ``` 80 | 81 | Add tests to package.json 82 | ```js 83 | { 84 | "scripts": { 85 | "test": "babel-node painless test/**/*.js" 86 | } 87 | } 88 | ``` 89 | 90 | ### Assertions 91 | Painless uses Chai and Chai-as-promised. Get more info on all available assertions here. [Chai Assertions](http://chaijs.com/api/assert/). 92 | [Chai-as-promised](https://github.com/domenic/chai-as-promised) adds Promise assertions. 93 | 94 | Access Chai 95 | ```js 96 | // ES6 97 | import { assert } from 'painless'; 98 | 99 | // ES5 100 | var painless = require('painless'); 101 | var assert = painless.assert; 102 | ``` 103 | It is possible to use any other assertion library as well. You will receive better errors if the library supports [AssertionError](http://wiki.commonjs.org/wiki/Unit_Testing/1.0#Assert) 104 | 105 | ### Spying/Stubs/Mocking 106 | Painless comes bundled with Sinon to allow all types of mocking. Get more info here. [Sinon Library](http://sinonjs.org/docs/) 107 | 108 | Access Spy 109 | ```js 110 | // ES6 111 | import { spy } from 'painless'; 112 | 113 | // ES5 114 | var painless = require('painless'); 115 | var spy = painless.spy; 116 | ``` 117 | 118 | Access Stub 119 | ```js 120 | // ES6 121 | import { stub } from 'painless'; 122 | 123 | // ES5 124 | var painless = require('painless'); 125 | var stub = painless.stub; 126 | ``` 127 | 128 | Access Mocks 129 | ```js 130 | // ES6 131 | import { mock } from 'painless'; 132 | 133 | // ES5 134 | var painless = require('painless'); 135 | var mock = painless.mock; 136 | ``` 137 | 138 | Accessing other Sinon functionality 139 | ```js 140 | // ES6 141 | import { sinon } from 'painless'; 142 | 143 | const xhr = sinon.useFakeXMLHttpRequest(); 144 | 145 | // ES5 146 | var painless = require('painless'); 147 | var sinon = painless.sinon; 148 | 149 | var xhr = sinon.useFakeXMLHttpRequest(); 150 | ``` 151 | ### beforeEach and afterEach 152 | Painless supports beforeEach and afterEach out of the box. You can use all the features available in tests (Promises, Async/Await, etc) 153 | ```js 154 | var painless = require('painless'); 155 | var assert = painless.assert; 156 | var test = painless.createGroup(); 157 | 158 | test.beforeEach(function() { 159 | doSomeSetup(); 160 | return promiseFn(); 161 | }); 162 | 163 | test.afterEach(function() { 164 | doSomeCleanup(); 165 | }); 166 | 167 | test('test that needs setup 1', function() { 168 | assert(true); 169 | }); 170 | 171 | test('test that needs setup 2', function() { 172 | assert(true); 173 | }); 174 | ``` 175 | 176 | 177 | ### Command Line Usage 178 | ``` 179 | ./node_modules/.bin/painless test/**/*.js 180 | ``` 181 | With Babel 182 | ``` 183 | babel-node ./node_modules/.bin/painless test/**/*.js 184 | ``` 185 | - `--async,-a` Run tests async. This will speed up tests that use IO or network. It is not recommended while debugging. It will make tests tough to reason about. 186 | - `--bunch,-b` Bunch size. If using the async flag, it determines the number of tests to run at the same time. By default this is 10. 187 | - `--reporter,-r` Specify a different reporter. By default painlesss use dot. Options are dot, spec and tap. 188 | - `--include, -i` Specify other files to include. NPM modules or local code. Useful for adding polyfills or other global code. 189 | 190 | View docs for [creating custom reporters](DOCUMENTATION.md) 191 | 192 | ### Async 193 | Painless has the ability to run tests async. This will execute tests at the same time, but in a single process and thread. It uses node's standard event system to execute other tests while a test is doing IO or network tasks. This can significantly speed up your tests if you are doing IO or network tests. If your tests are CPU bound, you will not see any gains because everything is running in the same thread. 194 | Enable async using the command options below. While debugging your tests, it is not recommended to use async because it will make execution flow harder to understand. 195 | 196 | ### Code Coverage 197 | Code coverage is really easy to use. Just install [nyc](https://github.com/bcoe/nyc) 198 | ``` 199 | npm install nyc --save-dev 200 | ``` 201 | #### Coverage in your console 202 | ``` 203 | nyc --cache --reporter=text ./node_modules/bin/painless test/**/*.js 204 | ``` 205 | #### Coverage in HTML report 206 | ``` 207 | nyc --cache --reporter=html ./node_modules/bin/painless test/**/*.js 208 | ``` 209 | #### Send coverage to `coveralls` 210 | ``` 211 | npm install coveralls 212 | ``` 213 | then (make sure COVERALLS_REPO_TOKEN is in your environment variables) 214 | ``` 215 | nyc --reporter=text-lcov ./node_modules/bin/painless test/**/*.js | ./node_modules/.bin/coveralls 216 | ``` 217 | 218 | ### Browser Support 219 | Tests can run in browser with [karma-painless](https://github.com/taylorhakes/karma-painless) 220 | 221 | ## FAQS 222 | 1. Why should I use painless over other test libraries? 223 | 224 | Go [here](#compared-to-other-libraries) for comparisons to other test libraries. Painless is not groundbreaking in any way. If you like it, use it. It is more important to test your javascript code. The test framework is a minor detail. 225 | 226 | 2. Why bundle other libraries (Chai, Sinon, etc)? Why not let the user decide? 227 | 228 | Painless tries to achieve a test library that just works. The goal is to be able to start writing tests as soon as possible. It is possible to switch out assertion and mocking libraries, but defaults are available. 229 | 230 | 231 | ## Compared to other libraries 232 | #### Mocha 233 | Painless Advantages 234 | - Has everything you need included (assertions, mocking, etc) 235 | - Supports Generators, Observables, Streams, Processes out of the box 236 | - Doesn't need a separate executable to run (just run `node test.js`) 237 | - Is easier to debug (single process, better error messages, easy to read diffs) 238 | - Has no globals 239 | - Run tests at the same time (speed improvements) 240 | 241 | Mocha Advantages 242 | - More widely used (more battle tested) 243 | 244 | #### AVA 245 | Painless Advantages 246 | - Supports Browser and Node 247 | - Better error messages (beautiful diff when tests fail) 248 | - Easier to debug (single process and thread) 249 | - Has everything you need included (assertions, mocking, etc) 250 | - Doesn't need a separate executable to run (just run `node test.js`) 251 | - Faster for most types of tests (even IO tests with painless --async flag) 252 | - Doesn't require babel (write your tests in ES5, typescript, coffeescript, etc) 253 | - Built-in support for Node streams and processes 254 | 255 | AVA Advantages 256 | - Test files are isolated (provides some isolation) 257 | 258 | #### Jasmine 259 | Painless Advantages 260 | - Supports Promises, Async/Await, Generators, Observables, Streams, Processes out of the box 261 | - Doesn't need a separate executable to run (just run `node test.js`) 262 | - Easier to debug (single process, better error messages, easy to read diffs) 263 | - Has no globals 264 | - Run tests at the same time (speed improvements) 265 | 266 | Jasmine Advantages 267 | - More widely used (more battle tested) 268 | 269 | #### Tape 270 | Painless Advantages 271 | - Exceptions are caught in each test and reported. Your tests don't stop at the first failure. 272 | - Supports Promises, Async/Await, Generators, Observables, Streams, Processes 273 | - Has everything you need included (assertions, mocking, etc) 274 | - Simpler test syntax (No need to call `t.end()`. Your tests end based on the return) 275 | - Better output format out of the box (Easier to read than TAP. painless supports TAP too) 276 | - Supports test groups (`beforeEach` and `afterEach`) 277 | - Better error messages 278 | - Run tests at the same time (speed improvements) 279 | -------------------------------------------------------------------------------- /bin/painless: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var glob = require('glob'); 5 | var resolveCwd = require('resolve-cwd'); 6 | var argv = require('minimist')(process.argv.slice(2), { 7 | alias: { 8 | i: 'include' 9 | } 10 | }); 11 | 12 | if (argv.include) { 13 | var include = resolveCwd(argv.include); 14 | if (!include) { 15 | throw new Error('Unable to find include ' + argv.include); 16 | } 17 | require(include); 18 | } 19 | 20 | process.argv.slice(2).forEach(function onEach(arg) { 21 | glob(arg, function onGLob(err, files) { 22 | files.forEach(function onFile(file) { 23 | require(path.resolve(process.cwd(), file)); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/async-await.js: -------------------------------------------------------------------------------- 1 | /* 2 | Run by: 3 | babel-node --presets es2015 examples/async-await.js 4 | 5 | */ 6 | 7 | import { createGroup, assert } from '..'; 8 | 9 | const test = createGroup(); 10 | 11 | function wait(time) { 12 | return new Promise(function prom(resolve) { 13 | setTimeout(resolve, time); 14 | }); 15 | } 16 | 17 | test('async/await 1', async function gen() { 18 | assert.equal(1 + 2, 3); 19 | await wait(100); 20 | assert.equal(5 + 6, 11); 21 | }); 22 | test('async/await 2', async function gen2() { 23 | assert.equal(1 + 5, 6); 24 | await wait(100); 25 | assert.equal(5 + 9, 14); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/callback.js: -------------------------------------------------------------------------------- 1 | /* 2 | Run by: 3 | node examples/callback.js 4 | */ 5 | 6 | var painless = require('..'); 7 | var test = painless.createGroup(); 8 | var assert = painless.assert; 9 | 10 | test('callback 1', function testFn(done) { 11 | setTimeout(function timeout() { 12 | assert(true); 13 | done(); 14 | }, 20); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/chai-as-promised.js: -------------------------------------------------------------------------------- 1 | /* 2 | Run by: 3 | node examples/chai-as-promised.js 4 | */ 5 | 6 | var painless = require('..'); 7 | var test = painless.createGroup(); 8 | var assert = painless.assert; 9 | 10 | function delayedPromise(val) { 11 | return new Promise(function prom(resolve) { 12 | setTimeout(function timeout() { 13 | resolve(val); 14 | }, 20); 15 | }); 16 | } 17 | 18 | test('Chai-as-promised 1', function testFn() { 19 | var prom = delayedPromise(10); 20 | 21 | // Must return the assert or it will not wait 22 | return assert.eventually.equal(prom, 10); 23 | }); 24 | -------------------------------------------------------------------------------- /examples/custom-reporter.js: -------------------------------------------------------------------------------- 1 | var helper = require('painless-reporter-helper'); 2 | 3 | module.exports = helper({ 4 | 'test.end': function(test) { 5 | if (test.success) { 6 | return test.name + ' success!\n'; 7 | } 8 | 9 | return test.name + ' failed!\n'; 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /examples/es6-sync.js: -------------------------------------------------------------------------------- 1 | /* 2 | Run by: 3 | babel-node --presets es2015 examples/es6-sync.js 4 | */ 5 | 6 | import { createGroup, assert } from '..'; 7 | 8 | const test = createGroup(); 9 | 10 | test('ES6 sync pass 1', () => { 11 | assert.equal(1 + 2, 3); 12 | }); 13 | test('ES6 sync pass 2', () => { 14 | assert.equal(1 + 4, 5); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/generator.js: -------------------------------------------------------------------------------- 1 | /* 2 | Run by: 3 | babel-node --presets es2015 examples/generator.js 4 | 5 | */ 6 | 7 | import { createGroup, assert } from '..'; 8 | const test = createGroup(); 9 | 10 | function wait(time) { 11 | return new Promise(function prom(resolve) { 12 | setTimeout(resolve, time); 13 | }); 14 | } 15 | 16 | test('simple generator 1', function* gen1() { 17 | assert.equal(1 + 2, 3); 18 | yield wait(100); 19 | assert.equal(5 + 6, 11); 20 | }); 21 | test('simple generator 2', function* gen2() { 22 | assert.equal(1 + 5, 6); 23 | yield wait(100); 24 | assert.equal(5 + 9, 14); 25 | }); 26 | -------------------------------------------------------------------------------- /examples/promise.js: -------------------------------------------------------------------------------- 1 | /* 2 | Run by: 3 | node examples/promise.js 4 | */ 5 | 6 | var painless = require('..'); 7 | var test = painless.createGroup(); 8 | var assert = painless.assert; 9 | 10 | test('simple Promise 1', function testFn() { 11 | return new Promise(function prom(resolve) { 12 | setTimeout(function timeout() { 13 | assert.deepEqual({ red: 'blue' }, { red: 'blue' }); 14 | resolve(); 15 | }, 100); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /examples/sync-error.js: -------------------------------------------------------------------------------- 1 | /* 2 | Run by: 3 | node examples/sync-error.js 4 | */ 5 | 6 | var painless = require('..'); 7 | var test = painless.createGroup(); 8 | var assert = painless.assert; 9 | 10 | test('simple sync pass', function sync1() { 11 | assert.deepEqual([1,2,5], [1,2,3]); 12 | }); 13 | 14 | test('simple sync pass 2', function sync2() { 15 | assert.equal(6, 5); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/sync.js: -------------------------------------------------------------------------------- 1 | /* 2 | Run by: 3 | node examples/sync.js 4 | */ 5 | 6 | var painless = require('..'); 7 | var test = painless.createGroup('Sync examples'); 8 | var assert = painless.assert; 9 | 10 | test('simple sync pass', function sync1() { 11 | assert.deepEqual([1,2,3], [1,2,3]); 12 | }); 13 | 14 | test('simple sync pass 2', function sync2() { 15 | assert.equal(5, 5); 16 | }); 17 | -------------------------------------------------------------------------------- /harness.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/create-harness'); 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var setAsap = require('setasap'); 2 | var tap = require('./lib/reporters/tap'); 3 | var dot = require('./lib/reporters/dot'); 4 | var spec = require('./lib/reporters/spec'); 5 | var none = require('./lib/reporters/none'); 6 | var chai = require('chai'); 7 | var chaiAsPromised = require('chai-as-promised'); 8 | var sinon = require('sinon'); 9 | var createHarness = require('./lib/create-harness'); 10 | var path = require('path'); 11 | var minimist = require('minimist'); 12 | var canExit = typeof process !== 'undefined' && process 13 | && typeof process.exit === 'function'; 14 | 15 | /* istanbul ignore next */ 16 | var argv = canExit ? minimist(process.argv.slice(2), { 17 | boolean: ['async'], 18 | alias: { 19 | a: 'async', 20 | b: 'bunch', 21 | r: 'reporter', 22 | t: 'tap' 23 | } 24 | }) : {}; 25 | 26 | /** 27 | @deprecated 28 | */ 29 | /* istanbul ignore next */ 30 | if (argv.tap) { 31 | argv.reporter = 'tap'; 32 | } 33 | 34 | var reporters = { 35 | dot: dot, 36 | tap: tap, 37 | spec: spec, 38 | none: none 39 | }; 40 | 41 | chai.use(chaiAsPromised); 42 | 43 | var harness = createHarness(); 44 | 45 | // Allow browser runners to control painless, like karma 46 | /* istanbul ignore next */ 47 | if (typeof window === 'object' && window && window.__PAINLESS__) { 48 | window.__PAINLESS__.push(harness); 49 | } else { 50 | setAsap(function asap() { 51 | var groupOutput = harness.run(argv); 52 | var processOutput; 53 | var reporter; 54 | if (argv.reporter) { 55 | if (reporters[argv.reporter]) { 56 | reporter = reporters[argv.reporter]; 57 | } else { 58 | reporter = require(argv.reporter.indexOf('.') === 0 ? path.resolve(process.cwd(), argv.reporter) : argv.reporter); 59 | } 60 | processOutput = reporter(groupOutput); 61 | } else { 62 | processOutput = dot(groupOutput); 63 | } 64 | 65 | 66 | var hasError = false; 67 | groupOutput.on('data', function onData(info) { 68 | if (info.type === 'end' && info.data.errors.length) { 69 | hasError = true; 70 | } 71 | }); 72 | 73 | /* istanbul ignore else */ 74 | if (canExit) { 75 | groupOutput.on('end', function onEnd() { 76 | process.exit(hasError ? 1 : 0); 77 | }); 78 | processOutput.pipe(process.stdout); 79 | } else { 80 | processOutput.on('data', function onInfo(result) { 81 | console.log(result); // eslint-disable-line no-console 82 | }); 83 | } 84 | }); 85 | } 86 | 87 | // Ignore coverage, this is just in case there is an error in painless. Not testable 88 | /* istanbul ignore next */ 89 | if (canExit) { 90 | process.on('unhandledRejection', function onUnhandled(reason) { 91 | console.error('Unhandled Rejection:', reason.stack); // eslint-disable-line no-console 92 | }); 93 | } 94 | 95 | module.exports.createGroup = harness.createGroup; 96 | module.exports.assert = chai.assert; 97 | module.exports.spy = sinon.spy; 98 | module.exports.stub = sinon.stub; 99 | module.exports.mock = sinon.mock; 100 | module.exports.sinon = sinon; 101 | module.exports.chai = chai; 102 | 103 | -------------------------------------------------------------------------------- /lib/create-harness.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var FnObj = require('./fn-obj'); 4 | var executeGroups = require('./execute-groups'); 5 | 6 | function createHarness() { 7 | var groups = []; 8 | 9 | function createGroup(name) { 10 | var tests = []; 11 | var beforeEachFns = []; 12 | var afterEachFns = []; 13 | 14 | function test(/* name_, _opts, _cb */) { 15 | var t = FnObj.apply(null, arguments); 16 | t.isTest = true; 17 | tests.push(t); 18 | } 19 | 20 | test.beforeEach = function beforeEach(/* name_, _opts, _cb */) { 21 | var t = FnObj.apply(null, arguments); 22 | beforeEachFns.push(t); 23 | }; 24 | 25 | test.afterEach = function afterEach(/* name_, _opts, _cb */) { 26 | var t = FnObj.apply(null, arguments); 27 | afterEachFns.push(t); 28 | }; 29 | 30 | groups.push({ 31 | beforeEach: beforeEachFns, 32 | afterEach: afterEachFns, 33 | tests: tests, 34 | name: name 35 | }); 36 | 37 | return test; 38 | } 39 | 40 | function run(options) { 41 | return executeGroups(groups, options); 42 | } 43 | 44 | return { 45 | groups: groups, 46 | createGroup: createGroup, 47 | run: run 48 | }; 49 | } 50 | 51 | 52 | module.exports = createHarness; 53 | -------------------------------------------------------------------------------- /lib/execute-groups.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var through = require('through'); 4 | var sequentialTests = require('./sequential-tests'); 5 | var setAsap = require('setasap'); 6 | var GROUP_START = 'group.start'; 7 | var GROUP_END = 'group.end'; 8 | var TEST_END = 'test.end'; 9 | 10 | 11 | function executeGroups(groups, options) { 12 | var output = through(); 13 | var testCount = 0; 14 | var allErrors = []; 15 | var time = 0; 16 | 17 | function endStream() { 18 | output.queue({ 19 | type: 'end', 20 | data: { 21 | time: time, 22 | testCount: testCount, 23 | errors: allErrors, 24 | success: allErrors.length === 0 25 | } 26 | }); 27 | output.queue(null); 28 | } 29 | 30 | function createStream(index) { 31 | var stream; 32 | stream = executeGroup(groups[index], options); 33 | 34 | function onGroupEnd() { 35 | createStream(index + 1); 36 | } 37 | 38 | stream.on('data', function onData(info) { 39 | if (info.type === GROUP_END) { 40 | testCount += info.data.testCount; 41 | if (info.data.errors.length) { 42 | allErrors = allErrors.concat(info.data.errors); 43 | } 44 | time += info.data.time; 45 | } 46 | output.queue(info); 47 | }); 48 | stream.on('end', index < groups.length - 1 ? onGroupEnd : endStream); 49 | } 50 | createStream(0); 51 | return output; 52 | } 53 | 54 | function executeGroup(group, options) { 55 | var testIndex = 0; 56 | var resultIndex = 0; 57 | var len = group.tests.length; 58 | var responses = {}; 59 | var errors = []; 60 | var output = through(); 61 | var changedOptions = options || {}; 62 | var async = changedOptions.async || false; 63 | var bunchSize = changedOptions.bunch || 10; 64 | var time = 0; 65 | 66 | setAsap(function onNextTick() { 67 | output.queue({ type: GROUP_START, data: { name: group.name } }); 68 | }); 69 | 70 | function createOnResult(index) { 71 | return function onResult(info) { 72 | var i, lines, newLines; 73 | 74 | if (!info.success) { 75 | newLines = []; 76 | lines = String(info.error.stack).split('\n'); 77 | for (i = 1; i < lines.length; i++) { 78 | if (lines[i].indexOf('(domain.js') > -1) { 79 | break; 80 | } 81 | newLines.push(lines[i].trim()); 82 | } 83 | info.error.stack = newLines.join('\n'); 84 | } 85 | responses[index] = info; 86 | 87 | while (responses[resultIndex]) { 88 | if (responses[resultIndex].success === false) { 89 | errors.push(responses[resultIndex]); 90 | } 91 | time += responses[resultIndex].time; 92 | output.queue({ type: TEST_END, data: responses[resultIndex] }); 93 | delete responses[resultIndex]; 94 | resultIndex++; 95 | } 96 | 97 | if (resultIndex === len) { 98 | output.queue({ 99 | type: GROUP_END, 100 | data: { 101 | name: group.name, 102 | success: errors.length === 0, 103 | testCount: len, 104 | errors: errors, 105 | time: time 106 | } 107 | }); 108 | output.queue(null); 109 | } 110 | 111 | if (testIndex < len) { 112 | executeNext(); 113 | } 114 | }; 115 | } 116 | 117 | function executeNext() { 118 | if (!len) { 119 | setAsap(function next() { 120 | output.queue(null); 121 | }); 122 | return; 123 | } 124 | 125 | var resultFn = createOnResult(testIndex); 126 | sequentialTests(group.beforeEach, group.tests[testIndex], group.afterEach) 127 | .then(resultFn); 128 | 129 | testIndex++; 130 | } 131 | 132 | function executeBunch(size) { 133 | for (var i = 0; i < size && testIndex < len; i++) { 134 | executeNext(); 135 | } 136 | } 137 | 138 | if (async) { 139 | executeBunch(bunchSize); 140 | } else { 141 | executeNext(); 142 | } 143 | 144 | return output; 145 | } 146 | 147 | module.exports = executeGroups; 148 | -------------------------------------------------------------------------------- /lib/fn-obj.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var DEFAULT_TIMEOUT = 10000; 4 | 5 | function getFnArgs(/* name_, _opts, _cb */) { 6 | var name; 7 | var opts = {}; 8 | var cb; 9 | var i; 10 | var arg; 11 | var t; 12 | 13 | for (i = 0; i < arguments.length; i++) { 14 | arg = arguments[i]; 15 | t = typeof arg; 16 | if (t === 'string') { 17 | name = arg; 18 | } else if (t === 'object') { 19 | opts = arg || opts; 20 | } else if (t === 'function') { 21 | cb = arg; 22 | } 23 | } 24 | return {name: name, opts: opts, cb: cb}; 25 | } 26 | 27 | function Fn(name_, opts_, cb_) { 28 | var args; 29 | 30 | if (!(this instanceof Fn)) { 31 | return new Fn(name_, opts_, cb_); 32 | } 33 | 34 | args = getFnArgs(name_, opts_, cb_); 35 | 36 | this.name = args.name || (args.cb && args.cb.name) || '(anonymous)'; 37 | this.timeout = args.opts.timeout != null ? args.opts.timeout : DEFAULT_TIMEOUT; 38 | this.cb = args.cb; 39 | this.isTest = false; 40 | this.options = args.opts; 41 | 42 | if (!this.cb) { 43 | throw new Error('Invalid test `' + this.name + '`. No test function defined.'); 44 | } 45 | } 46 | 47 | module.exports = Fn; 48 | -------------------------------------------------------------------------------- /lib/reporters/dot.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk'); 2 | var diff = require('variable-diff'); 3 | var helper = require('painless-reporter-helper'); 4 | var INDENT = ' '; 5 | 6 | function indentLine(line) { 7 | return INDENT + ' ' + line; 8 | } 9 | 10 | function handleTest() { 11 | var firstTest = true; 12 | 13 | return function onTest(test) { 14 | var text; 15 | var isFirst = firstTest; 16 | 17 | if (test.success) { 18 | text = chalk.green('.'); 19 | } else { 20 | text = chalk.red('x'); 21 | } 22 | firstTest = false; 23 | return isFirst ? INDENT + text : text; 24 | }; 25 | } 26 | 27 | function handleEnd(info) { 28 | var error, i, len, output = []; 29 | function push(text) { 30 | output.push(text); 31 | } 32 | 33 | push('\n\n' + INDENT + info.time + ' ms\n'); 34 | push(INDENT + info.testCount + ' tests\n'); 35 | push(INDENT + chalk.green(info.testCount - info.errors.length + ' passed\n')); 36 | 37 | if (info.errors.length) { 38 | push(INDENT + chalk.red(info.errors.length + ' failed\n')); 39 | push('\n' + INDENT + chalk.red('Failed Tests:\n')); 40 | for (i = 0, len = info.errors.length; i < len; i++) { 41 | error = info.errors[i]; 42 | push(INDENT + ' ' + chalk.red('x') + ' ' + error.name + '\n'); 43 | push(INDENT + ' ' + error.error.message + '\n'); 44 | if (error.error.actual && error.error.expected) { 45 | push(diff(error.error.expected, error.error.actual).text.split('\n').map(indentLine).join('\n') + '\n'); 46 | } 47 | push(error.error.stack.split('\n').map(indentLine).join('\n') + '\n'); 48 | } 49 | } else { 50 | push('\n' + INDENT + chalk.green('Pass!\n')); 51 | } 52 | return output.join(''); 53 | } 54 | 55 | module.exports = helper({ 56 | 'test.end': handleTest(), 57 | 'end': handleEnd 58 | }); 59 | -------------------------------------------------------------------------------- /lib/reporters/none.js: -------------------------------------------------------------------------------- 1 | var helper = require('painless-reporter-helper'); 2 | 3 | module.exports = helper({}); 4 | -------------------------------------------------------------------------------- /lib/reporters/spec.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk'); 2 | var diff = require('variable-diff'); 3 | var figures = require('figures'); 4 | var helper = require('painless-reporter-helper'); 5 | var INDENT = ' '; 6 | 7 | function indentLine(line) { 8 | return INDENT + ' ' + line; 9 | } 10 | 11 | function handleTest(test) { 12 | var text; 13 | var time = chalk.grey(' ' + test.time + 'ms'); 14 | var output = []; 15 | 16 | if (test.success) { 17 | text = chalk.green(figures.tick); 18 | } else { 19 | text = chalk.red(figures.cross); 20 | } 21 | output.push(INDENT + text + ' ' + test.name + time + '\n'); 22 | 23 | if (test.error) { 24 | output.push(INDENT + ' ' + test.error.message + '\n'); 25 | if (test.error.actual && test.error.expected) { 26 | output.push(diff(test.error.expected, test.error.actual).text.split('\n').map(indentLine).join('\n') + '\n'); 27 | } 28 | output.push(test.error.stack.split('\n').map(indentLine).join('\n') + '\n'); 29 | } 30 | 31 | return output.join(''); 32 | } 33 | 34 | function handleGroupStart(info) { 35 | return info.name ? ('\n' + info.name + '\n') : '\n'; 36 | } 37 | 38 | function handleEnd(info) { 39 | var output = []; 40 | 41 | output.push('\n\n' + INDENT + chalk.grey(info.time + 'ms total\n')); 42 | output.push(INDENT + info.testCount + ' tests\n'); 43 | output.push(INDENT + chalk.green((info.testCount - info.errors.length) + ' passed\n')); 44 | if (info.errors.length) { 45 | output.push(INDENT + chalk.red(info.errors.length + ' failed\n')); 46 | } else { 47 | output.push('\n' + INDENT + chalk.green('Pass!\n')); 48 | } 49 | 50 | return output.join(''); 51 | } 52 | 53 | module.exports = helper({ 54 | 'group.start': handleGroupStart, 55 | 'test.end': handleTest, 56 | 'end': handleEnd 57 | }); 58 | -------------------------------------------------------------------------------- /lib/reporters/tap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var helper = require('painless-reporter-helper'); 4 | var diff = require('variable-diff'); 5 | 6 | function handleEnd(info) { 7 | var ouput = []; 8 | var errLen = info.errors.length; 9 | 10 | function push(text) { 11 | ouput.push(text); 12 | } 13 | 14 | push('\n1..' + info.testCount + '\n'); 15 | push('# tests ' + info.testCount + '\n'); 16 | push('# pass ' + (info.testCount - errLen) + '\n'); 17 | if (errLen) { 18 | push('# fail ' + errLen + '\n'); 19 | } 20 | push('# time ' + info.time + 'ms\n'); 21 | push('# ' + (info.success ? 'passed' : 'failed') + '!\n'); 22 | 23 | return ouput.join(''); 24 | } 25 | 26 | function createhandleTest() { 27 | var count = 1; 28 | return function handleTest(test) { 29 | var output = []; 30 | var outer; 31 | var inner; 32 | var lines; 33 | var i; 34 | 35 | function push(text) { 36 | output.push(text); 37 | } 38 | if (count === 1) { 39 | push('TAP version 13\n'); 40 | } 41 | 42 | push((test.success ? 'ok ' : 'not ok ') + count); 43 | push(' ' + test.name.toString().replace(/\s+/g, ' ') + '\n'); 44 | 45 | if (!test.success) { 46 | outer = ' '; 47 | inner = outer + ' '; 48 | push(outer + '---\n'); 49 | 50 | push(inner + 'error: \n'); 51 | inner += ' '; 52 | push(inner + 'message: ' + test.error.message + '\n'); 53 | if (test.error.actual && test.error.expected) { 54 | push(inner + 'diff: >\n'); 55 | lines = diff(test.error.expected, test.error.actual).text.split('\n'); 56 | for (i = 0; i < lines.length; i++) { 57 | push(inner + ' ' + lines[i] + '\n'); 58 | } 59 | } 60 | if (test.error.stack) { 61 | lines = String(test.error.stack).split('\n'); 62 | push(inner + 'stack:\n'); 63 | for (i = 0; i < lines.length; i++) { 64 | push(inner + ' ' + lines[i] + '\n'); 65 | } 66 | } 67 | 68 | push(outer + '...\n'); 69 | } 70 | count++; 71 | 72 | return output.join(''); 73 | }; 74 | } 75 | 76 | module.exports = helper({ 77 | 'test.end': createhandleTest(), 78 | 'end': handleEnd 79 | }); 80 | -------------------------------------------------------------------------------- /lib/run-fn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fnDone = require('function-done'); 4 | var objectAssign = require('object-assign'); 5 | /* istanbul ignore next */ 6 | var PromiseM = typeof Promise === 'function' ? Promise : require('promise-polyfill'); // eslint-disable-line no-undef 7 | var now = Date.now; 8 | var timeoutFn = setTimeout; 9 | 10 | function getError(fnObj, time, error) { 11 | var res = getSuccess(fnObj, time); 12 | res.success = false; 13 | res.error = { 14 | message: typeof error === 'string' ? error : error.message, 15 | stack: error.stack 16 | }; 17 | if (error.hasOwnProperty('expected')) { 18 | res.error.expected = error.expected; 19 | } 20 | if (error.hasOwnProperty('actual')) { 21 | res.error.actual = error.actual; 22 | } 23 | 24 | return res; 25 | } 26 | 27 | function getSuccess(fnObj, time) { 28 | var info = objectAssign({ time: time, error: null, success: true }, fnObj); 29 | delete info.isTest; 30 | return info; 31 | } 32 | 33 | function runFn(fnObj) { 34 | var timeout; 35 | 36 | return new PromiseM(function prom(resolve) { 37 | timeout = timeoutFn(function onTimeout() { 38 | resolve(getError(fnObj, fnObj.timeout, new Error('test timed out after ' + fnObj.timeout + 'ms'))); 39 | }, fnObj.timeout); 40 | 41 | var start = now(); 42 | fnDone(fnObj.cb, function onFnDone(err) { 43 | clearTimeout(timeout); 44 | var time = now() - start; 45 | if (err) { 46 | resolve(getError(fnObj, time, err)); 47 | } else { 48 | resolve(getSuccess(fnObj, time)); 49 | } 50 | }); 51 | }); 52 | } 53 | 54 | module.exports = runFn; 55 | -------------------------------------------------------------------------------- /lib/sequential-tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var runFn = require('./run-fn'); 4 | 5 | function sequentialTests(beforeTestFns, test, afterTestFns) { 6 | var testResult, fns = []; 7 | var index = 0; 8 | function nextProm(result) { 9 | if (result.success) { 10 | if (index < fns.length - 1) { 11 | index++; 12 | return fns[index]().then(nextProm); 13 | } else if (afterTestFns.length) { 14 | return runAfterFns().then(function afterFns(afterResult) { 15 | if (!afterResult.success) { 16 | return afterResult; 17 | } 18 | 19 | return testResult; 20 | }); 21 | } 22 | 23 | return testResult; 24 | } 25 | 26 | if (afterTestFns.length) { 27 | return runAfterFns().then(function afterFnErrors() { 28 | return testResult; 29 | }); 30 | } 31 | 32 | return result; 33 | } 34 | 35 | function runAfterFns() { 36 | return runSequentialTests(afterTestFns, false) 37 | .then(function onAfter(info) { 38 | if (!info.success) { 39 | info.name = 'afterEach: ' + test.name; 40 | info.afterTest = true; 41 | } 42 | 43 | return info; 44 | }); 45 | } 46 | 47 | if (beforeTestFns.length) { 48 | fns.push(function beforeEachs() { 49 | return runSequentialTests(beforeTestFns, true) 50 | .then(function onBefore(info) { 51 | if (!info.success) { 52 | info.name = 'beforeEach: ' + test.name; 53 | info.beforeTest = true; 54 | 55 | testResult = info; 56 | } 57 | return info; 58 | }); 59 | }); 60 | } 61 | 62 | fns.push(function onTest() { 63 | return runFn(test).then(function onEnd(result) { 64 | testResult = result; 65 | return result; 66 | }); 67 | }); 68 | 69 | return fns[0]().then(nextProm); 70 | } 71 | 72 | function runSequentialTests(tests, stopOnError) { 73 | let firstError = null; 74 | 75 | function innerRunTests(index) { 76 | return runFn(tests[index]).then(function onThen(result) { 77 | if (!result.success) { 78 | if (stopOnError) { 79 | return result; 80 | } 81 | 82 | firstError = result; 83 | } 84 | 85 | if (index < tests.length - 1) { 86 | return innerRunTests(index + 1); 87 | } 88 | return firstError || result; 89 | }); 90 | } 91 | 92 | return innerRunTests(0); 93 | } 94 | 95 | module.exports = sequentialTests; 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "painless", 3 | "version": "0.9.7", 4 | "description": "Super simple test framework for node and the browser", 5 | "main": "index.js", 6 | "bin": "./bin/painless", 7 | "dependencies": { 8 | "chai": "3.5.0", 9 | "chai-as-promised": "5.2.0", 10 | "chalk": "1.1.1", 11 | "figures": "1.4.0", 12 | "function-done": "2.1.1", 13 | "glob": "7.1.3", 14 | "minimist": "1.2.6", 15 | "object-assign": "4.0.1", 16 | "painless-reporter-helper": "1.1.0", 17 | "promise-polyfill": "2.1.0", 18 | "resolve-cwd": "1.0.0", 19 | "setasap": "2.0.0", 20 | "sinon": "1.17.2", 21 | "through": "2.3.8", 22 | "variable-diff": "2.0.1" 23 | }, 24 | "devDependencies": { 25 | "@babel/cli": "^7.0.0", 26 | "@babel/core": "^7.0.0", 27 | "@babel/plugin-proposal-class-properties": "^7.0.0", 28 | "@babel/plugin-proposal-json-strings": "^7.0.0", 29 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 30 | "@babel/plugin-syntax-import-meta": "^7.0.0", 31 | "@babel/preset-env": "^7.0.0", 32 | "acorn": "^6.0.4", 33 | "babel-eslint": "^10.0.1", 34 | "concat-stream": "^1.6.2", 35 | "coveralls": "^3.0.2", 36 | "eslint": "5.8.0", 37 | "eslint-config-airbnb-es5": "^1.2.0", 38 | "eslint-plugin-react": "^7.11.1", 39 | "falafel": "1.0.1", 40 | "js-yaml": "3.13.1", 41 | "nyc": "13.1.0", 42 | "tap": "14.6.7", 43 | "zen-observable": "0.1.8" 44 | }, 45 | "scripts": { 46 | "test": "npm run lint && nyc --cache --reporter=lcov --reporter=text tap --no-cov --timeout=150 test/*.js", 47 | "lint": "eslint lib/** *.js" 48 | }, 49 | "repository": { 50 | "type": "git", 51 | "url": "git://github.com/taylorhakes/painless.git" 52 | }, 53 | "homepage": "https://github.com/taylorhakes/painless", 54 | "keywords": [ 55 | "tap", 56 | "test", 57 | "harness", 58 | "assert", 59 | "browser" 60 | ], 61 | "files": [ 62 | "lib", 63 | "bin", 64 | "index.js", 65 | "LICENSE", 66 | "README.md", 67 | "package.json" 68 | ], 69 | "author": { 70 | "name": "Taylor Hakes" 71 | }, 72 | "license": "MIT" 73 | } 74 | -------------------------------------------------------------------------------- /test/babel-cli.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | var tap = require('tap'); 3 | var test = tap.test; 4 | 5 | test('babel command sync', function(t) { 6 | exec('babel-node test/fixtures/babel-sync', function (err) { 7 | t.notOk(err); 8 | t.end(); 9 | }); 10 | }); 11 | 12 | test('babel command async promise error', function(t) { 13 | exec('babel-node test/fixtures/babel-promise-error', function (err) { 14 | t.ok(err); 15 | t.end(); 16 | }); 17 | }); 18 | 19 | test('babel command await error', function(t) { 20 | exec('babel-node test/fixtures/babel-await-error', function (err) { 21 | t.ok(err); 22 | t.end(); 23 | }); 24 | }); 25 | 26 | test('babel command generator error', function(t) { 27 | exec('babel-node test/fixtures/generator', function (err) { 28 | t.ok(err); 29 | t.end(); 30 | }); 31 | }); -------------------------------------------------------------------------------- /test/cli.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec; 2 | var tap = require('tap'); 3 | var test = tap.test; 4 | 5 | 6 | test('command sync', function(t) { 7 | exec('node examples/sync', function (error, stdout, stderr) { 8 | t.match(stdout, / ..\n\n [0-9]+ ms\n 2 tests\n 2 passed\n\n Pass!/); 9 | t.end(); 10 | }); 11 | }); 12 | 13 | test('command sync -r=tap', function(t) { 14 | exec('node examples/sync -r=tap', function (error, stdout, stderr) { 15 | t.match(stdout, /ok 1 simple sync pass\nok 2 simple sync pass 2\n\n1..2\n# tests 2\n# pass 2\n# time [0-9]+ms\n# passed!\n/); 16 | t.end(); 17 | }); 18 | }); 19 | 20 | test('command sync -r=spec', function(t) { 21 | exec('node examples/sync -r=spec', function (error, stdout, stderr) { 22 | t.match(stdout, /Sync examples\n ✔ simple sync pass [0-9]+ms\n ✔ simple sync pass 2 [0-9]+ms\n\n\n [0-9]+ms total\n 2 tests\n 2 passed\n\n Pass!\n/); 23 | t.end(); 24 | }); 25 | }); 26 | 27 | test('command sync -r=none', function(t) { 28 | exec('node examples/sync -r=none', function (error, stdout, stderr) { 29 | t.equal(stdout, ''); 30 | t.end(); 31 | }); 32 | }); 33 | 34 | test('command sync custom reporter', function(t) { 35 | exec('node examples/sync -r=./examples/custom-reporter', function (error, stdout, stderr) { 36 | t.match(stdout, /simple sync pass success!\nsimple sync pass 2 success!\n/); 37 | t.end(); 38 | }); 39 | }); 40 | 41 | test('command sync custom reporter fail', function(t) { 42 | exec('node examples/sync-error -r=./examples/custom-reporter', function (error, stdout, stderr) { 43 | t.match(stdout, /simple sync pass failed!\nsimple sync pass 2 failed!\n/); 44 | t.end(); 45 | }); 46 | }); 47 | 48 | test('command sync -r=nyan does not exist', function(t) { 49 | exec('node examples/sync -r=nyan', function (error, stdout, stderr) { 50 | t.ok(error); 51 | t.end(); 52 | }); 53 | }); 54 | 55 | test('error sync', function(t) { 56 | exec('node examples/sync-error', function (error, stdout, stderr) { 57 | t.ok(error); 58 | t.end(); 59 | }); 60 | }); 61 | 62 | test('error sync tap', function(t) { 63 | exec('node examples/sync-error -r=tap', function (error, stdout, stderr) { 64 | t.ok(error); 65 | t.end(); 66 | }); 67 | }); 68 | 69 | test('error sync spec', function(t) { 70 | exec('node examples/sync-error -r=spec', function (error, stdout, stderr) { 71 | t.ok(error); 72 | t.end(); 73 | }); 74 | }); 75 | 76 | test('command callback', function(t) { 77 | exec('node examples/callback', function (error, stdout, stderr) { 78 | t.match(stdout, / .\n\n [0-9]+ ms\n 1 tests\n 1 passed\n\n Pass!/); 79 | t.end(); 80 | }); 81 | }); 82 | 83 | test('invalid assertion', function(t) { 84 | exec('node test/fixtures/string-error-test', function (error, stdout, stderr) { 85 | t.ok(error); 86 | t.match(stdout, / x\n\n [0-9]+ ms\n 1 tests\n 0 passed\n 1 failed\n\n Failed Tests:\n x string assertion\n hello\n \n/); 87 | t.end(); 88 | }); 89 | }); 90 | 91 | test('invalid assertion tap', function(t) { 92 | exec('node test/fixtures/string-error-test -r=tap', function (error, stdout, stderr) { 93 | t.ok(error); 94 | t.match(stdout, /not ok 1 string assertion\n ---\n error: |-\n hello\n ...\n\n1..1\n# tests 1\n# pass 0\n# fail 1\n# time [0-9]+ms\n# failed!\n/); 95 | t.end(); 96 | }); 97 | }); 98 | 99 | test('invalid assertion spec', function(t) { 100 | exec('node test/fixtures/string-error-test -r=spec', function (error, stdout, stderr) { 101 | t.ok(error); 102 | t.match(stdout, / ✖ string assertion [0-9]+ms\n hello\n \n\n\n [0-9]+ms total\n 1 tests\n 0 passed\n 1 failed\n/); 103 | t.end(); 104 | }); 105 | }); 106 | -------------------------------------------------------------------------------- /test/fixtures/babel-await-error.js: -------------------------------------------------------------------------------- 1 | import { createGroup, assert } from '../..'; 2 | import Promise from 'promise-polyfill'; 3 | 4 | const test = createGroup(); 5 | const asyncFn = function() { 6 | return new Promise(function(resolve, reject) { 7 | setTimeout(() => { 8 | reject(new Error('This is an error')); 9 | }, 5); 10 | }); 11 | }; 12 | 13 | test('promise', async () => { 14 | await asyncFn(); 15 | }); -------------------------------------------------------------------------------- /test/fixtures/babel-promise-error.js: -------------------------------------------------------------------------------- 1 | import { createGroup, assert } from '../..'; 2 | import Promise from 'promise-polyfill'; 3 | 4 | const test = createGroup(); 5 | 6 | test('promise', () => { 7 | return new Promise(function(resolve) { 8 | setTimeout(() => { 9 | assert.equal(1 + 4, 3); 10 | resolve(); 11 | }, 5); 12 | }); 13 | }); -------------------------------------------------------------------------------- /test/fixtures/babel-sync.js: -------------------------------------------------------------------------------- 1 | import { createGroup, assert } from '../..'; 2 | import Promise from 'promise-polyfill'; 3 | 4 | const test = createGroup(); 5 | 6 | test('sync', () => { 7 | assert(1 + 4, 5); 8 | }); 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/fixtures/generator.js: -------------------------------------------------------------------------------- 1 | import { createGroup, assert } from '../..'; 2 | import Promise from 'promise-polyfill'; 3 | 4 | const test = createGroup(); 5 | const asyncFn = function() { 6 | return new Promise(function(resolve, reject) { 7 | setTimeout(() => { 8 | reject(new Error('This is an error')); 9 | }, 5); 10 | }); 11 | }; 12 | 13 | test('promise', function* () { 14 | yield asyncFn(); 15 | }); -------------------------------------------------------------------------------- /test/fixtures/string-error-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Run by: 3 | node examples/sync-error.js 4 | */ 5 | 6 | var painless = require('../..'); 7 | var test = painless.createGroup(); 8 | 9 | test('string assertion', function sync1() { 10 | throw 'hello'; 11 | }); 12 | -------------------------------------------------------------------------------- /test/harness.js: -------------------------------------------------------------------------------- 1 | var tap = require('tap'); 2 | var Harness = require('../harness'); 3 | var Promise = require('promise-polyfill'); 4 | var through = require('through'); 5 | var assert = require('chai').assert; 6 | var test = tap.test; 7 | var MY_MESSAGE = 'my assertion message'; 8 | 9 | var TEST_END = 'test.end'; 10 | function noop() {} 11 | function error() { 12 | assert(false, MY_MESSAGE); 13 | } 14 | function timeoutTest(time) { 15 | return function(done) { 16 | setTimeout(done, time); 17 | }; 18 | } 19 | function timeoutTestError(time) { 20 | return function(done) { 21 | setTimeout(function() { 22 | throw new Error('invalid'); 23 | done(); 24 | }, time); 25 | }; 26 | } 27 | 28 | test('harness single group all pass', function(t) { 29 | var har = Harness(); 30 | var test = har.createGroup(); 31 | t.plan(4); 32 | 33 | test('test1', noop); 34 | test('test2', noop); 35 | 36 | var output = har.run(); 37 | var index = 0; 38 | output.on('data', function(data) { 39 | if (data.type !== TEST_END) { 40 | return; 41 | } 42 | data = data.data; 43 | if (index === 0) { 44 | t.is(data.name, 'test1'); 45 | t.is(data.success, true); 46 | index++; 47 | } else { 48 | t.is(data.name, 'test2'); 49 | t.is(data.success, true); 50 | } 51 | }); 52 | output.on('end', t.end); 53 | }); 54 | 55 | test('harness single group all pass beforeEach and afterEach', function(t) { 56 | var har = Harness(); 57 | var test = har.createGroup(); 58 | var beforeCalled = 0; 59 | var afterCalled = 0; 60 | t.plan(8); 61 | 62 | test.beforeEach(function() { 63 | return new Promise(function(resolve) { 64 | setTimeout(function() { 65 | beforeCalled++; 66 | resolve(); 67 | }, 10); 68 | }); 69 | }); 70 | 71 | test.beforeEach(function() { 72 | return new Promise(function(resolve) { 73 | setTimeout(function() { 74 | beforeCalled++; 75 | resolve(); 76 | }, 10); 77 | }); 78 | }); 79 | 80 | test.afterEach(function() { 81 | return new Promise(function(resolve) { 82 | setTimeout(function() { 83 | afterCalled++; 84 | resolve(); 85 | }, 10); 86 | }); 87 | }); 88 | 89 | test.afterEach(function() { 90 | return new Promise(function(resolve) { 91 | setTimeout(function() { 92 | afterCalled++; 93 | resolve(); 94 | }, 10); 95 | }); 96 | }); 97 | 98 | test('test1', noop); 99 | test('test2', noop); 100 | 101 | var output = har.run(); 102 | var index = 0; 103 | output.on('data', function(info) { 104 | if (info.type !== TEST_END) { 105 | return; 106 | } 107 | var data = info.data; 108 | 109 | if (index === 0) { 110 | t.is(beforeCalled, 2); 111 | t.is(afterCalled, 2); 112 | t.is(data.name, 'test1'); 113 | t.is(data.success, true); 114 | } else { 115 | t.is(beforeCalled, 4); 116 | t.is(afterCalled, 4); 117 | t.is(data.name, 'test2'); 118 | t.is(data.success, true); 119 | } 120 | index++; 121 | }); 122 | output.on('end', t.end); 123 | }); 124 | 125 | 126 | test('harness single group failing test beforeEach and afterEach', function(t) { 127 | var har = Harness(); 128 | var test = har.createGroup(); 129 | var beforeCalled = 0; 130 | var afterCalled = 0; 131 | t.plan(4); 132 | 133 | test.beforeEach(function() { 134 | return new Promise(function(resolve) { 135 | setTimeout(function() { 136 | beforeCalled++; 137 | resolve(); 138 | }, 10); 139 | }); 140 | }); 141 | 142 | test.beforeEach(function() { 143 | return new Promise(function(resolve) { 144 | setTimeout(function() { 145 | beforeCalled++; 146 | resolve(); 147 | }, 10); 148 | }); 149 | }); 150 | 151 | test.afterEach(function() { 152 | return new Promise(function(resolve) { 153 | setTimeout(function() { 154 | afterCalled++; 155 | resolve(); 156 | }, 10); 157 | }); 158 | }); 159 | 160 | test.afterEach(function() { 161 | return new Promise(function(resolve) { 162 | setTimeout(function() { 163 | afterCalled++; 164 | resolve(); 165 | }, 10); 166 | }); 167 | }); 168 | 169 | test('test1', function() { 170 | throw new Error(); 171 | }); 172 | 173 | var output = har.run(); 174 | output.on('data', function(info) { 175 | 176 | if (info.type !== TEST_END) { 177 | return; 178 | } 179 | 180 | var data = info.data; 181 | 182 | t.is(beforeCalled, 2); 183 | t.is(afterCalled, 2); 184 | t.is(data.name, 'test1'); 185 | t.is(data.success, false); 186 | }); 187 | output.on('end', t.end); 188 | }); 189 | 190 | test('harness single group multiple errors beforeEach and afterEach', function(t) { 191 | var har = Harness(); 192 | var test = har.createGroup(); 193 | t.plan(6); 194 | 195 | test.beforeEach(function() { 196 | return new Promise(function(resolve) { 197 | setTimeout(function() { 198 | assert(false); 199 | resolve(); 200 | }, 10); 201 | }); 202 | }); 203 | 204 | test.afterEach(function() { 205 | return new Promise(function(resolve) { 206 | setTimeout(function() { 207 | assert(false); 208 | resolve(); 209 | }, 10); 210 | }); 211 | }); 212 | 213 | test('test1', noop); 214 | test('test2', noop); 215 | 216 | var output = har.run(); 217 | var index = 0; 218 | output.on('data', function(info) { 219 | if (info.type !== TEST_END) { 220 | return; 221 | } 222 | var data = info.data; 223 | 224 | if (index === 0) { 225 | t.is(data.name, 'beforeEach: test1'); 226 | t.is(data.beforeTest, true); 227 | t.is(data.success, false); 228 | } else { 229 | t.is(data.name, 'beforeEach: test2'); 230 | t.is(data.beforeTest, true); 231 | t.is(data.success, false); 232 | } 233 | index++; 234 | }); 235 | output.on('end', t.end); 236 | }); 237 | 238 | test('harness single group multiple errors afterEach', function(t) { 239 | var har = Harness(); 240 | var test = har.createGroup(); 241 | t.plan(6); 242 | 243 | test.beforeEach(function() { 244 | return new Promise(function(resolve) { 245 | setTimeout(function() { 246 | resolve(); 247 | }, 10); 248 | }); 249 | }); 250 | 251 | test.afterEach(function() { 252 | return new Promise(function(resolve) { 253 | setTimeout(function() { 254 | assert(false); 255 | resolve(); 256 | }, 10); 257 | }); 258 | }); 259 | 260 | test('test1', noop); 261 | test('test2', noop); 262 | 263 | var output = har.run(); 264 | var index = 0; 265 | output.on('data', function(info) { 266 | if (info.type !== TEST_END) { 267 | return; 268 | } 269 | var data = info.data; 270 | 271 | if (index === 0) { 272 | t.is(data.name, 'afterEach: test1'); 273 | t.is(data.afterTest, true); 274 | t.is(data.success, false); 275 | } else { 276 | t.is(data.name, 'afterEach: test2'); 277 | t.is(data.afterTest, true); 278 | t.is(data.success, false); 279 | } 280 | index++; 281 | }); 282 | output.on('end', t.end); 283 | }); 284 | 285 | test('success multiple errors', function(t) { 286 | var har = Harness(); 287 | var test = har.createGroup(); 288 | 289 | test('test1', error); 290 | test('test2', noop); 291 | test('test3', error); 292 | 293 | var output = har.run(); 294 | var index = 0; 295 | output.on('data', function(info) { 296 | if (info.type !== TEST_END) { 297 | return; 298 | } 299 | 300 | var data = info.data; 301 | if (index === 0) { 302 | t.equal(data.name, 'test1'); 303 | t.notOk(data.success); 304 | t.equal(data.error.message, MY_MESSAGE); 305 | } else if (index === 1) { 306 | t.equal(data.name, 'test2'); 307 | t.ok(data.success); 308 | } else { 309 | t.equal(data.name, 'test3'); 310 | t.notOk(data.success); 311 | t.equal(data.error.message, MY_MESSAGE); 312 | } 313 | index++; 314 | }); 315 | output.on('end', t.end); 316 | }); 317 | 318 | test('multiple groups', function(t) { 319 | var har = Harness(); 320 | var test1 = har.createGroup(); 321 | var test2 = har.createGroup(); 322 | 323 | test1('test1', error); 324 | test2('test2', noop); 325 | 326 | var output = har.run(); 327 | var index = 0; 328 | output.on('data', function(info) { 329 | if (info.type !== TEST_END) { 330 | return; 331 | } 332 | 333 | var data = info.data; 334 | if (index === 0) { 335 | t.equal(data.name, 'test1'); 336 | t.notOk(data.success); 337 | t.equal(data.error.message, MY_MESSAGE); 338 | } else { 339 | t.equal(data.name, 'test2'); 340 | t.ok(data.success); 341 | } 342 | index++; 343 | }); 344 | output.on('end', t.end); 345 | }); 346 | 347 | test('group without tests', function(t) { 348 | var har = Harness(); 349 | har.createGroup(); 350 | var output = har.run(); 351 | output.on('end', t.end); 352 | }); 353 | 354 | test('harness single group all pass async', function(t) { 355 | var har = Harness(); 356 | var test = har.createGroup(); 357 | t.plan(4); 358 | 359 | test('test1', noop); 360 | test('test2', noop); 361 | 362 | var output = har.run({ async: true }); 363 | var index = 0; 364 | output.on('data', function(info) { 365 | if (info.type !== TEST_END) { 366 | return; 367 | } 368 | 369 | var data = info.data; 370 | if (index === 0) { 371 | t.equal(data.name, 'test1'); 372 | t.ok(data.success); 373 | index++; 374 | } else { 375 | t.equal(data.name, 'test2'); 376 | t.ok(data.success); 377 | } 378 | }); 379 | output.on('end', t.end); 380 | }); 381 | 382 | test('harness async larger than group size', function(t) { 383 | var har = Harness(); 384 | var test = har.createGroup(); 385 | t.plan(24); 386 | 387 | test('test1', noop); 388 | test('test2', noop); 389 | test('test3', noop); 390 | test('test4', noop); 391 | test('test5', noop); 392 | test('test6', noop); 393 | test('test7', noop); 394 | test('test8', noop); 395 | test('test9', noop); 396 | test('test10', noop); 397 | test('test11', noop); 398 | test('test12', noop); 399 | 400 | var output = har.run({ async: true }); 401 | var index = 1; 402 | output.on('data', function(info) { 403 | if (info.type !== TEST_END) { 404 | return; 405 | } 406 | var data = info.data; 407 | t.equal(data.name, 'test' + index); 408 | t.ok(data.success); 409 | index++; 410 | }); 411 | output.on('end', t.end); 412 | }); 413 | 414 | test('harness async preserves order on success', function(t) { 415 | var har = Harness(); 416 | var test = har.createGroup(); 417 | t.plan(24); 418 | 419 | test('test1', timeoutTest(30)); 420 | test('test2', timeoutTest(50)); 421 | test('test3', timeoutTest(10)); 422 | test('test4', timeoutTest(20)); 423 | test('test5', timeoutTest(40)); 424 | test('test6', timeoutTest(10)); 425 | test('test7', timeoutTest(20)); 426 | test('test8', timeoutTest(30)); 427 | test('test9', timeoutTest(10)); 428 | test('test10', timeoutTest(30)); 429 | test('test11', timeoutTest(20)); 430 | test('test12', timeoutTest(0)); 431 | 432 | var output = har.run({ async: true }); 433 | var index = 1; 434 | output.on('data', function(info) { 435 | if (info.type !== TEST_END) { 436 | return; 437 | } 438 | var data = info.data; 439 | t.equal(data.name, 'test' + index); 440 | t.ok(data.success); 441 | index++; 442 | }); 443 | output.on('end', t.end); 444 | }); 445 | 446 | test('harness async preserves order on errors', function(t) { 447 | var har = Harness(); 448 | var test = har.createGroup(); 449 | t.plan(8); 450 | 451 | test('test1', timeoutTestError(30)); 452 | test('test2', timeoutTestError(50)); 453 | test('test3', timeoutTest(10)); 454 | test('test4', timeoutTestError(0)); 455 | 456 | var output = har.run({ async: true }); 457 | var index = 1; 458 | output.on('data', function(info) { 459 | if (info.type !== TEST_END) { 460 | return; 461 | } 462 | var data = info.data; 463 | if (index == 3) { 464 | t.equal(data.name, 'test' + index); 465 | t.ok(data.success); 466 | } else { 467 | t.equal(data.name, 'test' + index); 468 | t.equal(data.success, false); 469 | } 470 | 471 | index++; 472 | }); 473 | output.on('end', t.end); 474 | }); 475 | 476 | test('harness async bunch size change', function(t) { 477 | var har = Harness(); 478 | var test = har.createGroup(); 479 | t.plan(16); 480 | 481 | test('test1', timeoutTest(30)); 482 | test('test2', timeoutTest(50)); 483 | test('test3', timeoutTest(10)); 484 | test('test4', timeoutTest(20)); 485 | 486 | test = har.createGroup(); 487 | test('test5', timeoutTest(40)); 488 | test('test6', timeoutTest(10)); 489 | test('test7', timeoutTest(20)); 490 | test('test8', timeoutTest(30)); 491 | 492 | var output = har.run({ async: true }); 493 | var index = 1; 494 | output.on('data', function(info) { 495 | if (info.type !== TEST_END) { 496 | return; 497 | } 498 | var data = info.data; 499 | t.equal(data.name, 'test' + index); 500 | t.ok(data.success); 501 | index++; 502 | }); 503 | output.on('end', t.end); 504 | }); 505 | 506 | test('harness async test before/after each order', function(t) { 507 | var har = Harness(); 508 | var test = har.createGroup(); 509 | var before = false; 510 | var after = false; 511 | t.plan(2); 512 | 513 | test.beforeEach(function(done) { 514 | setTimeout(function() { 515 | before = true; 516 | done(); 517 | }, 50); 518 | }); 519 | test.afterEach(function() { 520 | after = true; 521 | }); 522 | 523 | 524 | test('test1', function(done) { 525 | setTimeout(function() { 526 | t.equal(after, false); 527 | t.equal(before, true); 528 | done(); 529 | t.end(); 530 | }, 4); 531 | }); 532 | 533 | har.run({ async: true }); 534 | }); 535 | -------------------------------------------------------------------------------- /test/run-fn.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var tap = require('tap'); 4 | var assert = require('chai').assert; 5 | var Test = require('../lib/fn-obj'); 6 | var Promise = require('promise-polyfill'); 7 | var Observable = require("zen-observable"); 8 | var through = require('through'); 9 | var cp = require('child_process'); 10 | var runFn = require('../lib/run-fn'); 11 | var test = tap.test; 12 | var noop = function() {}; 13 | 14 | test('creates a new instance', function(t) { 15 | var tes = new Test('hello', noop); 16 | t.type(tes, Test); 17 | t.end(); 18 | }); 19 | test('creates a new instance without new', function(t) { 20 | var tes = Test('hello', noop); 21 | t.type(tes, Test); 22 | t.end(); 23 | }); 24 | 25 | test('name correct', function(t) { 26 | var tes = Test('hello', noop); 27 | t.is(tes.name, 'hello'); 28 | t.end(); 29 | }); 30 | 31 | test('options in multiple order', function(t) { 32 | var tes = Test(noop, 'hello', {}); 33 | t.is(tes.name, 'hello'); 34 | t.end(); 35 | }); 36 | 37 | test('options in other order', function(t) { 38 | var tes = Test({}, noop, 'hello'); 39 | t.is(tes.name, 'hello'); 40 | t.end(); 41 | }); 42 | 43 | test('timeout', function(t) { 44 | var tes = Test('hello', function(done) {}, {timeout: 10}); 45 | runFn(tes).then(function(val) { 46 | t.is(val.error.message, 'test timed out after 10ms'); 47 | t.end(); 48 | }); 49 | }); 50 | 51 | test('no callback error', function(t) { 52 | var error = false; 53 | try { 54 | var tes = Test('hello'); 55 | } catch(e) { 56 | error = true; 57 | } 58 | t.is(error, true); 59 | t.end(); 60 | }); 61 | 62 | test('null options', function(t) { 63 | var tes = Test('hello', null, function() { 64 | assert(true); 65 | }); 66 | runFn(tes).then(function() { 67 | t.end(); 68 | }); 69 | }); 70 | 71 | test('use function name', function (t) { 72 | var tes = Test(function useFunctionName() { 73 | assert(true); 74 | }); 75 | runFn(tes).then(function() { 76 | t.is(tes.name, 'useFunctionName'); 77 | t.end(); 78 | }); 79 | }); 80 | 81 | test('anonymous function name', function (t) { 82 | var tes = Test(function () { 83 | assert(true); 84 | }); 85 | runFn(tes).then(function() { 86 | t.is(tes.name, '(anonymous)'); 87 | t.end(); 88 | }); 89 | }); 90 | 91 | test('run test twice', function (t) { 92 | var tes = Test('run test twice', function () { 93 | assert(true); 94 | }); 95 | Promise.all([ 96 | runFn(tes), 97 | runFn(tes) 98 | ]).then(function() { 99 | t.end(); 100 | }); 101 | }); 102 | 103 | test('failing test', function(t) { 104 | var tes = Test('hello', function() { 105 | assert.equal(false, true); 106 | }); 107 | runFn(tes).then(function(val) { 108 | t.is(val.error.actual, false); 109 | t.is(val.error.expected, true); 110 | t.end(); 111 | }); 112 | }); 113 | 114 | test('failing test callback', function(t) { 115 | var tes = Test('hello', function(done) { 116 | setTimeout(function() { 117 | assert.equal(1, 2); 118 | done(); 119 | }, 10); 120 | }); 121 | runFn(tes).then(function(val) { 122 | t.is(val.error.actual, 1); 123 | t.is(val.error.expected, 2); 124 | t.end(); 125 | }); 126 | }); 127 | 128 | test('failing test callback with error', function(t) { 129 | var tes = Test('hello', function(done) { 130 | setTimeout(function() { 131 | done(new Error()); 132 | }, 10); 133 | }); 134 | runFn(tes).then(function() { 135 | t.end(); 136 | }); 137 | }); 138 | 139 | test('passing callback', function(t) { 140 | var tes = Test('hello', function(done) { 141 | setTimeout(function() { 142 | assert(true); 143 | done(); 144 | }, 10); 145 | }); 146 | runFn(tes).then(function() { 147 | t.end(); 148 | }); 149 | }); 150 | 151 | test('failing Promise', function(t) { 152 | var tes = Test('hello', function() { 153 | return Promise.resolve(10).then(function() { 154 | assert.equal(1, 2); 155 | }); 156 | }); 157 | runFn(tes).then(function(val) { 158 | t.is(val.error.actual, 1); 159 | t.is(val.error.expected, 2); 160 | t.end(); 161 | }); 162 | }); 163 | 164 | test('fail before Promise created', function(t) { 165 | var tes = Test('hello', function() { 166 | assert.equal(1, 2); 167 | return Promise.resolve(10); 168 | }); 169 | runFn(tes).then(function(val) { 170 | t.is(val.error.actual, 1); 171 | t.is(val.error.expected, 2); 172 | t.end(); 173 | }); 174 | }); 175 | 176 | test('passing Promise', function(t) { 177 | var value; 178 | var tes = Test('hello', function() { 179 | return Promise.resolve(10).then(function() { 180 | assert(true); 181 | value = 1; 182 | }); 183 | }); 184 | runFn(tes).then(function() { 185 | t.is(value, 1); 186 | t.end(); 187 | }); 188 | }); 189 | 190 | test('failing Observable', function(t) { 191 | var tes = Test('test failing observable', function() { 192 | return new Observable(function(observer) { 193 | observer.next(8); 194 | observer.next(9); 195 | 196 | // Failing assert 197 | assert.equal(1, 2); 198 | observer.complete(); 199 | }); 200 | }); 201 | runFn(tes).then(function(val) { 202 | t.is(val.error.actual, 1); 203 | t.is(val.error.expected, 2); 204 | t.end(); 205 | }); 206 | }); 207 | 208 | test('failing Observable map', function(t) { 209 | var tes = Test('test failing observable', function() { 210 | return Observable.of([1,2,3]).map(function() { 211 | assert.equal(3, 4); 212 | return 1; 213 | }); 214 | }); 215 | runFn(tes).then(function(val) { 216 | t.is(val.error.actual, 3); 217 | t.is(val.error.expected, 4); 218 | t.end(); 219 | }); 220 | }); 221 | 222 | test('passing observable', function(t) { 223 | var tes = Test('test failing observable', function() { 224 | return new Observable(function(observer) { 225 | observer.next(8); 226 | observer.next(9); 227 | 228 | // Passing assert 229 | assert.equal(2, 2); 230 | observer.complete(); 231 | }); 232 | }); 233 | runFn(tes).then(function() { 234 | t.end(); 235 | }); 236 | }); 237 | 238 | test('failing stream', function(t) { 239 | var tes = Test('test failing observable', function() { 240 | var stream = through(); 241 | setTimeout(function() { 242 | stream.emit('error', new Error('invalid stream')); 243 | stream.destroy(); 244 | }, 10); 245 | 246 | return stream; 247 | }); 248 | runFn(tes).then(function(val) { 249 | t.is(val.error.message, 'invalid stream'); 250 | t.end(); 251 | }); 252 | }); 253 | 254 | test('passing stream', function(t) { 255 | var tes = Test('test failing observable', function() { 256 | var stream = through(); 257 | setTimeout(function() { 258 | stream.queue(1); 259 | stream.queue(null); 260 | stream.destroy(); 261 | }, 10); 262 | 263 | return stream; 264 | }); 265 | runFn(tes).then(function() { 266 | t.end(); 267 | }); 268 | }); 269 | 270 | test('failing process', function(t) { 271 | var tes = Test('test failing process', function() { 272 | return cp.exec('foo-bar-baz hello world'); 273 | }); 274 | runFn(tes).then(function() { 275 | t.end(); 276 | }); 277 | }); 278 | 279 | test('passing process', function(t) { 280 | var tes = Test('test passing process', function() { 281 | return cp.exec('echo hello world'); 282 | }); 283 | runFn(tes).then(function() { 284 | t.end(); 285 | }); 286 | }); --------------------------------------------------------------------------------