├── .babelrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── index.babel.js ├── index.compiled.js ├── package.json ├── tape.js └── tests ├── async.test.js ├── convenience.test.js ├── mixin.test.js ├── only-promise.test.js ├── only.test.js ├── promise.test.js ├── regular.test.js └── rejects-doesNotReject.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": 6 6 | } 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | tests/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | before_install: 3 | - "npm install npm -g" 4 | node_js: 5 | - 10 6 | - 9 7 | - 8 8 | - 7 9 | - 6 10 | sudo: false 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 4.0.0 / 2018-09-27 2 | ------------------ 3 | 4 | - **BREAKING:** Wait for `t.plan()` assertions to complete before ending async tests ([#15](https://github.com/jprichardson/tape-promise/pull/15)) 5 | - Added: `t.rejects()` assertion ([#14](https://github.com/jprichardson/tape-promise/issues/14), [#16](https://github.com/jprichardson/tape-promise/pull/16)) 6 | - Added: `t.doesNotReject()` assertion ([#16](https://github.com/jprichardson/tape-promise/pull/16)) 7 | 8 | 3.0.0 / 2018-03-15 9 | ------------------ 10 | - Drop Node.js 4 & 5 support; Node 6 is now the minimum version 11 | - Add support for argument swapping, tape options, and support `test.only()` [#12](https://github.com/jprichardson/tape-promise/pull/12) 12 | - Update babel config 13 | 14 | 2.0.1 / 2016-11-28 15 | ------------------ 16 | - Babel6 upgrade on `require('tape-promise/tape')` 17 | 18 | 2.0.0 / 2016-10-29 19 | ------------------ 20 | - babel upgrade (breaking for ES5 commonjs) 21 | - fix [#5] 22 | 23 | 1.1.0 / 2015-12-19 24 | ------------------ 25 | - Conveniently import tape/tape-promise. See: https://github.com/jprichardson/tape-promise/pull/2 26 | 27 | 1.0.1 / 2015-11-26 28 | ------------------ 29 | - Resolved promise values were passed `t.end()`, resolving as an error. See: https://github.com/jprichardson/tape-promise/pull/1 30 | 31 | 1.0.0 / 2015-11-06 32 | ------------------ 33 | - initial release 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tape-promise 2 | ============ 3 | 4 | [![build status](https://api.travis-ci.org/jprichardson/tape-promise.svg)](http://travis-ci.org/jprichardson/tape-promise) 5 | 6 | Promise and ES2016 (ES7) `async/await` support for [Tape](https://github.com/substack/tape). 7 | 8 | This module assumes that you're familiar with at least the concepts of Promises 9 | and if you want to use `async/await`, then you must be familiar with [Babel](https://babeljs.io/). 10 | 11 | Install 12 | ------- 13 | 14 | npm i --save-dev tape-promise 15 | 16 | 17 | Usage 18 | ----- 19 | 20 | Make sure that you have `tape` installed: 21 | 22 | npm i --save-dev tape 23 | 24 | Unlike [`blue-tape`](https://www.npmjs.com/package/blue-tape), `tape-promise` is 25 | just a decorator (not an ES7 decorator). That is, it does NOT depend upon `tape` 26 | and requires that you pass it in. 27 | 28 | ### ES5 29 | 30 | ```js 31 | var tape = require('tape') 32 | var _test = require('tape-promise').default // <---- notice 'default' 33 | var test = _test(tape) // decorate tape 34 | ``` 35 | 36 | ### ES6 (ES2015) 37 | 38 | ```js 39 | import tape from 'tape' 40 | import _test from 'tape-promise' 41 | const test = _test(tape) // decorate tape 42 | ``` 43 | 44 | or, for convenience... 45 | 46 | ```js 47 | import test from 'tape-promise/tape' 48 | ``` 49 | 50 | but you must explicitly have `tape` as a dependency. 51 | 52 | 53 | ### Example (promises) 54 | 55 | Just return the promise. 56 | 57 | ```js 58 | // example function that returns a Promise 59 | function delay (time) { 60 | return new Promise(function (resolve, reject) { 61 | setTimeout(function () { 62 | resolve() 63 | }, time) 64 | }) 65 | } 66 | 67 | test('ensure promises works', function (t) { 68 | return delay(100).then(function () { 69 | t.true(true) 70 | }) 71 | }) 72 | ``` 73 | 74 | ### Example (async/await) 75 | 76 | Async/await functions just transform to Promises. 77 | Don't forget to put the `async` keyword on your function. 78 | 79 | ```js 80 | // example function that returns a Promise 81 | // it could also be an async function 82 | function delay (time) { 83 | return new Promise(function (resolve, reject) { 84 | setTimeout(function () { 85 | resolve() 86 | }, time) 87 | }) 88 | } 89 | 90 | // NOTICE 'async'? 91 | test('ensure async works', async function (t) { 92 | await delay(100) 93 | t.true(true) 94 | t.end() // not really necessary 95 | }) 96 | ``` 97 | 98 | ### Example (normal tape tests) 99 | 100 | Of course you can write normal tape tests as you would. 101 | 102 | ```js 103 | test('ensure that regular test functions still work', function (t) { 104 | t.true(true) 105 | t.end() 106 | }) 107 | ``` 108 | 109 | ## New Assertions 110 | 111 | ### t.rejects(promise, expected, msg) 112 | 113 | Assert that the promise will reject. 114 | 115 | If `promise` is a function, then it is called, and the returned promise is used. 116 | 117 | `expected` is the error that should be rejected with, if present, must be a `RegExp` or `Function`. The `RegExp` matches the string representation of the exception, as generated by `err.toString()`. The `Function` is the exception thrown (e.g. `Error`). If the promise rejects, but its error doesn't match `expected`, then the assertion fails. 118 | 119 | `msg` is an optional description of the assertion. 120 | 121 | This function returns a promise that resolves when the assertion is complete (after the promise is settled). The test will not wait for the assertion automatically, so it needs to be `await`ed. 122 | 123 | ### t.doesNotReject(promise, expected, msg) 124 | 125 | Assert that the promise will resolve. 126 | 127 | If `promise` is a function, then it is called, and the returned promise is used. 128 | 129 | `expected` is the error that the promise shouldn't reject with, if present, it must be a `RegExp` or `Function`. The `RegExp` matches the string representation of the exception, as generated by `err.toString()`. The `Function` is the exception thrown (e.g. `Error`). If the promise rejects, but its error doesn't match `expected`, then the assertion passes. 130 | 131 | `msg` is an optional description of the assertion. 132 | 133 | This function returns a promise that resolves when the assertion is complete (after the promise is settled). The test will not wait for the assertion automatically, so it needs to be `await`ed. 134 | 135 | 136 | ### example 137 | 138 | ```js 139 | test('reject and doesNotReject example', async (t) => { 140 | await t.rejects(asyncFunction) 141 | await t.rejects(asyncFunction()) 142 | await t.doesNotReject(Promise.resolve()) 143 | }) 144 | ``` 145 | 146 | License 147 | ------- 148 | 149 | Licensed under MIT 150 | 151 | Copyright (c) [JP Richardson](https://github.com/jprichardson) 152 | -------------------------------------------------------------------------------- /index.babel.js: -------------------------------------------------------------------------------- 1 | import onetime from 'onetime' 2 | import isPromise from 'is-promise' 3 | 4 | // From: https://github.com/substack/tape/blob/17276d7473f9d98e37bab47ebdddf74ca1931f43/lib/test.js#L24 5 | // Modified only for linting 6 | const getTestArgs = function (name_, opts_, cb_) { 7 | let name = '(anonymous)' 8 | let opts = {} 9 | let cb 10 | 11 | for (var i = 0; i < arguments.length; i++) { 12 | var arg = arguments[i] 13 | var t = typeof arg 14 | if (t === 'string') { 15 | name = arg 16 | } else if (t === 'object') { 17 | opts = arg || opts 18 | } else if (t === 'function') { 19 | cb = arg 20 | } 21 | } 22 | return { name, opts, cb } 23 | } 24 | 25 | function registerNewAssertions (Test) { 26 | Test.prototype.rejects = function (promise, expected, message = 'should reject', extra) { 27 | if (typeof promise === 'function') promise = promise() 28 | return promise 29 | .then(() => { 30 | this.throws(() => {}, expected, message, extra) 31 | }) 32 | .catch(err => { 33 | this.throws(() => { throw err }, expected, message, extra) 34 | }) 35 | .then(() => {}) // resolve on failure to not stop execution (assertion is still failing) 36 | } 37 | 38 | Test.prototype.doesNotReject = function (promise, expected, message = 'should resolve', extra) { 39 | if (typeof promise === 'function') promise = promise() 40 | return promise 41 | .then(() => { 42 | this.doesNotThrow(() => {}, expected, message, extra) 43 | }) 44 | .catch(err => { 45 | this.doesNotThrow(() => { throw err }, expected, message, extra) 46 | }) 47 | .then(() => {}) // resolve on failure to not stop execution (assertion is still failing) 48 | } 49 | } 50 | 51 | export default function tapePromiseFactory (tapeTest) { 52 | const Test = tapeTest.Test 53 | // when tapeTest.only() is passed in, Test will be undefined 54 | if (Test) registerNewAssertions(Test) 55 | 56 | function testPromise (...args) { 57 | const { name, opts, cb } = getTestArgs(...args) 58 | tapeTest(name, opts, function (t) { 59 | t.end = onetime(t.end) 60 | 61 | let plan = false 62 | const setPlan = () => { plan = true } 63 | t.once('plan', setPlan) 64 | 65 | process.once('unhandledRejection', t.end) 66 | try { 67 | const p = cb(t) 68 | if (isPromise(p) && !plan) p.then(() => t.end(), t.end) 69 | } catch (e) { 70 | t.end(e) 71 | } finally { 72 | process.removeListener('unhandledRejection', t.end) 73 | t.removeListener('plan', setPlan) 74 | } 75 | }) 76 | } 77 | 78 | Object.keys(tapeTest).forEach((key) => { 79 | if (typeof tapeTest[key] !== 'function') return 80 | if (key === 'only') { 81 | testPromise[key] = tapePromiseFactory(tapeTest[key]) 82 | } else { 83 | testPromise[key] = tapeTest[key] 84 | } 85 | }) 86 | 87 | return testPromise 88 | } 89 | -------------------------------------------------------------------------------- /index.compiled.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = tapePromiseFactory; 7 | 8 | var _onetime = require('onetime'); 9 | 10 | var _onetime2 = _interopRequireDefault(_onetime); 11 | 12 | var _isPromise = require('is-promise'); 13 | 14 | var _isPromise2 = _interopRequireDefault(_isPromise); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | // From: https://github.com/substack/tape/blob/17276d7473f9d98e37bab47ebdddf74ca1931f43/lib/test.js#L24 19 | // Modified only for linting 20 | const getTestArgs = function getTestArgs(name_, opts_, cb_) { 21 | let name = '(anonymous)'; 22 | let opts = {}; 23 | let cb; 24 | 25 | for (var i = 0; i < arguments.length; i++) { 26 | var arg = arguments[i]; 27 | var t = typeof arg; 28 | if (t === 'string') { 29 | name = arg; 30 | } else if (t === 'object') { 31 | opts = arg || opts; 32 | } else if (t === 'function') { 33 | cb = arg; 34 | } 35 | } 36 | return { name: name, opts: opts, cb: cb }; 37 | }; 38 | 39 | function registerNewAssertions(Test) { 40 | Test.prototype.rejects = function (promise, expected, message = 'should reject', extra) { 41 | if (typeof promise === 'function') promise = promise(); 42 | return promise.then(() => { 43 | this.throws(() => {}, expected, message, extra); 44 | }).catch(err => { 45 | this.throws(() => { 46 | throw err; 47 | }, expected, message, extra); 48 | }).then(() => {}); // resolve on failure to not stop execution (assertion is still failing) 49 | }; 50 | 51 | Test.prototype.doesNotReject = function (promise, expected, message = 'should resolve', extra) { 52 | if (typeof promise === 'function') promise = promise(); 53 | return promise.then(() => { 54 | this.doesNotThrow(() => {}, expected, message, extra); 55 | }).catch(err => { 56 | this.doesNotThrow(() => { 57 | throw err; 58 | }, expected, message, extra); 59 | }).then(() => {}); // resolve on failure to not stop execution (assertion is still failing) 60 | }; 61 | } 62 | 63 | function tapePromiseFactory(tapeTest) { 64 | const Test = tapeTest.Test; 65 | // when tapeTest.only() is passed in, Test will be undefined 66 | if (Test) registerNewAssertions(Test); 67 | 68 | function testPromise(...args) { 69 | var _getTestArgs = getTestArgs(...args); 70 | 71 | const name = _getTestArgs.name, 72 | opts = _getTestArgs.opts, 73 | cb = _getTestArgs.cb; 74 | 75 | tapeTest(name, opts, function (t) { 76 | t.end = (0, _onetime2.default)(t.end); 77 | 78 | let plan = false; 79 | const setPlan = () => { 80 | plan = true; 81 | }; 82 | t.once('plan', setPlan); 83 | 84 | process.once('unhandledRejection', t.end); 85 | try { 86 | const p = cb(t); 87 | if ((0, _isPromise2.default)(p) && !plan) p.then(() => t.end(), t.end); 88 | } catch (e) { 89 | t.end(e); 90 | } finally { 91 | process.removeListener('unhandledRejection', t.end); 92 | t.removeListener('plan', setPlan); 93 | } 94 | }); 95 | } 96 | 97 | Object.keys(tapeTest).forEach(key => { 98 | if (typeof tapeTest[key] !== 'function') return; 99 | if (key === 'only') { 100 | testPromise[key] = tapePromiseFactory(tapeTest[key]); 101 | } else { 102 | testPromise[key] = tapeTest[key]; 103 | } 104 | }); 105 | 106 | return testPromise; 107 | } 108 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tape-promise", 3 | "version": "4.0.0", 4 | "description": "Promise/async support for tape.", 5 | "main": "index.compiled.js", 6 | "scripts": { 7 | "build": "babel index.babel.js -o index.compiled.js", 8 | "lint": "standard", 9 | "test": "npm run lint && npm run build && npm run unit", 10 | "unit": "find ./tests -name *.test.js -exec node -r babel-register {} \\; | tap-spec" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/jprichardson/tape-promise.git" 15 | }, 16 | "keywords": [ 17 | "tape", 18 | "promise", 19 | "async", 20 | "test", 21 | "testing", 22 | "tdd", 23 | "unit" 24 | ], 25 | "author": "JP Richardson", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/jprichardson/tape-promise/issues" 29 | }, 30 | "homepage": "https://github.com/jprichardson/tape-promise#readme", 31 | "devDependencies": { 32 | "babel-cli": "^6.18.0", 33 | "babel-eslint": "^8.2.2", 34 | "babel-preset-env": "^1.6.1", 35 | "babel-register": "^6.18.0", 36 | "standard": "*", 37 | "tap-spec": "^4.1.0", 38 | "tape": "^4.2.2" 39 | }, 40 | "dependencies": { 41 | "is-promise": "^2.1.0", 42 | "onetime": "^2.0.0" 43 | }, 44 | "standard": { 45 | "parser": "babel-eslint", 46 | "ignore": [ 47 | "index.compiled.js" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tape.js: -------------------------------------------------------------------------------- 1 | // tape is not a dependency of test-promise and must therefore be 2 | // added as a dependency by the user project. 3 | module.exports = require('./').default(require('tape')) 4 | -------------------------------------------------------------------------------- /tests/async.test.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape' 2 | import _test from '../' 3 | const test = _test(tape) 4 | 5 | const delay = (time) => new Promise((resolve) => setTimeout(resolve, time)) 6 | 7 | test('ensure async works', async (t) => { 8 | await delay(100) 9 | t.true(true) 10 | t.end() 11 | }) 12 | 13 | test('plan should work when using async', async (t) => { 14 | t.plan(2) 15 | delay(100) 16 | .then(() => { 17 | t.true(true) 18 | delay(100).then(() => t.true(true)) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /tests/convenience.test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | test('convenience import', function (t) { 3 | var wrapped = require('../tape') 4 | t.equals(typeof wrapped.skip, 'function', 'skip is a function') 5 | t.end() 6 | }) 7 | -------------------------------------------------------------------------------- /tests/mixin.test.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape' 2 | import _test from '../' 3 | const test = _test(tape) 4 | 5 | test('ensure that regular tape functions are present', (t) => { 6 | t.equals(typeof test.skip, 'function', 'skip is a function') 7 | t.equals(typeof test.only, 'function', 'only is a function') 8 | t.equals(typeof test.createStream, 'function', 'createStream is a function') 9 | t.equals(typeof test.createHarness, 'function', 'createHarness is a function') 10 | t.end() 11 | }) 12 | -------------------------------------------------------------------------------- /tests/only-promise.test.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape' 2 | import _test from '../' 3 | const test = _test(tape) 4 | 5 | const delay = (time) => new Promise((resolve) => setTimeout(resolve, time)) 6 | 7 | test.only('ensure that promise-based test.only works', (t) => { 8 | return delay(100).then(() => t.true(true)) 9 | }) 10 | 11 | test('this should never run', (t) => { 12 | t.fail() 13 | }) 14 | -------------------------------------------------------------------------------- /tests/only.test.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape' 2 | import _test from '../' 3 | const test = _test(tape) 4 | 5 | test.only('ensure that regular test.only works', (t) => { 6 | t.true(true) 7 | t.end() 8 | }) 9 | 10 | test('this should never run', (t) => { 11 | t.fail() 12 | }) 13 | -------------------------------------------------------------------------------- /tests/promise.test.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape' 2 | import _test from '../' 3 | const test = _test(tape) 4 | 5 | const delay = (time) => new Promise((resolve) => setTimeout(resolve, time)) 6 | 7 | test('ensure promises works', (t) => { 8 | return delay(100).then(() => t.true(true)) 9 | }) 10 | -------------------------------------------------------------------------------- /tests/regular.test.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape' 2 | import _test from '../' 3 | const test = _test(tape) 4 | 5 | test('ensure that regular test functions still work', (t) => { 6 | t.true(true) 7 | t.end() 8 | }) 9 | 10 | test('ensure that regular test functions with options still work', {}, (t) => { 11 | t.true(true) 12 | t.end() 13 | }) 14 | 15 | test((t) => { 16 | t.true('ensure that anonymous test functions work') 17 | t.end() 18 | }, 'ensure that regular test functions with swapped args still work', {}) 19 | 20 | test((t) => { 21 | t.true('ensure that anonymous test functions work') 22 | t.end() 23 | }) 24 | -------------------------------------------------------------------------------- /tests/rejects-doesNotReject.test.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape' 2 | import _test from '../' 3 | const test = _test(tape) 4 | 5 | async function reject () { 6 | return new Promise((resolve, reject) => setTimeout(reject(new Error('rejected')), 100)) 7 | } 8 | 9 | async function resolve () { 10 | return new Promise(resolve => setTimeout(resolve, 100)) 11 | } 12 | 13 | test('t.rejects with promise input', async (t) => { 14 | await t.rejects(reject(), /Error: rejected/) 15 | }) 16 | 17 | test('t.rejects with function input', async (t) => { 18 | await t.rejects(reject, /Error: rejected/) 19 | }) 20 | 21 | test('t.doesNotReject with promise input', async (t) => { 22 | await t.doesNotReject(resolve()) 23 | }) 24 | 25 | test('t.doesNotReject with function input', async (t) => { 26 | await t.doesNotReject(resolve) 27 | }) 28 | --------------------------------------------------------------------------------