├── .github └── workflows │ ├── latest.yml │ └── release.yml ├── .gitignore ├── .npmignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── bin ├── tape-es.js └── tape-watch-es.js ├── package.json └── src ├── runners.js └── util ├── eachLimit.js ├── index.js ├── match.js └── readPkg.js /.github/workflows/latest.yml: -------------------------------------------------------------------------------- 1 | name: Latest 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - 'v*' 9 | pull_request: 10 | 11 | jobs: 12 | verify: 13 | name: Verify 14 | runs-on: ubuntu-latest 15 | strategy: 16 | matrix: 17 | node: [14, 16] 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | - name: Setup 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: ${{ matrix.node }} 25 | - name: Cache 26 | uses: actions/cache@v2 27 | with: 28 | path: node_modules 29 | key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package.json') }} 30 | restore-keys: | 31 | ${{ runner.OS }}-npm-cache- 32 | - name: Install 33 | run: npm i 34 | - name: Test 35 | run: npm t --if-present 36 | - name: Lint 37 | run: npm run lint --if-present 38 | - name: Types 39 | run: npm run types --if-present 40 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | node: [14, 16] 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | - name: Setup 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: ${{ matrix.node }} 21 | - name: Cache 22 | uses: actions/cache@v2 23 | with: 24 | path: node_modules 25 | key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package.json') }} 26 | restore-keys: | 27 | ${{ runner.OS }}-npm-cache- 28 | - name: Verify 29 | run: | 30 | npm i 31 | npm run preversion 32 | npm: 33 | runs-on: ubuntu-latest 34 | needs: check 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v2 38 | - name: Publish 39 | run: | 40 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc 41 | npm publish --access public 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package/ 3 | .DS_Store 4 | *.tgz 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .vscode/ 3 | *.spec.js 4 | *.tgz 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Current File", 6 | "type": "node", 7 | "request": "launch", 8 | "program": "${file}" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Evan Plaice 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Tape-ES

2 | 3 | A **[Tape.js][]** test runner and watcher for modern JavaScript. Works with both ES modules and CommonJS. 4 | 5 |
6 | GitHub Release 7 | NPM Release 8 | Latest Status 9 | Release Status 10 | 11 | Discord 12 |
13 | 14 | ## Features 15 | 16 | - runs ES module tests 17 | - runs tests in parallel for speed 18 | - uses sensible defaults 19 | - works with `type: module` packages 20 | 21 | *Note: Since this is an ES package, it requires Node >= v14.x* 22 | 23 | ## tape-es 24 | 25 | ### Arguments 26 | 27 | `tape-es [pattern] -i [pattern] -r [path] -t [number]` 28 | 29 | - `[pattern]` - the file matcher pattern (default `**/*.spec.js`) 30 | - `-i` | `--ignore` - the ignore matcher pattern (default `**/node_modules/**`) 31 | - `-r` | `--root` - the root path to run the tests from (default `process.cwd()`) 32 | - `-t` | `--threads` - Number of threads to run concurrently (default `10`) 33 | 34 | ### Basic Usage 35 | 36 | Use the defaults 37 | 38 | ```sh 39 | tape-es 40 | ``` 41 | 42 | ### Advanced Usage 43 | 44 | Specify custom parameters 45 | 46 | ```sh 47 | tape-es "**/*.spec.js" -i "node_modules/**" -r ../absurdum/ -t 20 48 | ``` 49 | 50 | **Note: In Linux/OSX the matcher patterns must be delimited in quotes.** 51 | 52 | ## tape-watch-es 53 | 54 | ### Arguments 55 | 56 | `tape-es [pattern] -i [pattern] -r [path]` 57 | 58 | - `[pattern]` - the file matcher pattern (default `**/*.spec.js`) 59 | - `-i` | `--ignore` - the ignore matcher pattern (default `**/node_modules/**`) 60 | - `-r` | `--root` - the root path to run the tests from (default `process.cwd()`) 61 | 62 | ### Basic Usage 63 | 64 | Use the defaults 65 | 66 | ```sh 67 | tape-watch-es 68 | ``` 69 | 70 | ### Advanced Usage 71 | 72 | Specify custom parameters 73 | 74 | ```sh 75 | tape-es "**/*.spec.js" -i "node_modules/**" -r ../absurdum/ 76 | ``` 77 | 78 | **Note: In Linux/OSX the matcher patterns must be delimited in quotes.** 79 | 80 | ## Writing Tests 81 | 82 | Tests are identical to Tape.js, except `import` is used to load modules rather than require. 83 | 84 | ```javascript 85 | import test from 'tape'; 86 | import { arrays } from '../../index.js'; 87 | 88 | test('arrays.chunk(array) - should return a chunk for each item in the array', t => { 89 | // ...test code 90 | }); 91 | ``` 92 | 93 | ## Tap Reporters 94 | 95 | One of the greatest advantages to using Tape, is that it outputs results in the standard [TAP][] format. That means you can pipe the results into a wide array of TAP reporters. 96 | 97 | The parallel nature of this runner will break most reporters. As a General rule. 98 | 99 | 1. if you want speed (ie CI/CD) don't use a reporter 100 | 2. if you want speed and readability use [tap-spec][] 101 | 3. if you want to use any reporter, run tests in parallel with `tape-es -t 1` 102 | 103 | [Tape.js]: https://github.com/substack/tape 104 | [TAP]: https://en.wikipedia.org/wiki/Test_Anything_Protocol 105 | [tap-spec]: https://github.com/scottcorgan/tap-spec 106 | -------------------------------------------------------------------------------- /bin/tape-es.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import cli from 'commander' 3 | import { runAll } from '../src/runners.js' 4 | import { match, readPkg } from '../src/util/index.js' 5 | 6 | const DEFAULT_PATTERN = '**/*.spec.js' 7 | const DEFAULT_IGNORE = '**/node_modules/**' 8 | const DEFAULT_ROOT = process.cwd() 9 | const DEFAULT_THREADS = 10; 10 | 11 | (async () => { 12 | const pkg = await readPkg() 13 | 14 | cli.version(pkg.version) 15 | .arguments('[pattern]') 16 | .option('-i, --ignore [value]', 'Ignore files pattern') 17 | .option('-r, --root [value]', 'The root path') 18 | .option('-t, --threads [number]', 'Number of threads to run tests concurrently', parseInt) 19 | .parse(process.argv) 20 | 21 | const pattern = cli.args[0] ? cli.args[0] : DEFAULT_PATTERN 22 | const ignore = cli.ignore ? cli.ignore : DEFAULT_IGNORE 23 | const root = cli.root ? cli.root : DEFAULT_ROOT 24 | const threads = cli.threads ? cli.threads : DEFAULT_THREADS 25 | 26 | const tests = await match(pattern, ignore, root) 27 | await runAll(tests, threads, root) 28 | })().catch(e => { 29 | console.error(e) 30 | }) 31 | -------------------------------------------------------------------------------- /bin/tape-watch-es.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import cli from 'commander' 3 | import chokidar from 'chokidar' 4 | import { run } from '../src/runners.js' 5 | import { readPkg } from '../src/util/index.js' 6 | 7 | const DEFAULT_PATTERN = '**/*.spec.js' 8 | const DEFAULT_IGNORE = '**/node_modules/**' 9 | const DEFAULT_ROOT = process.cwd(); 10 | 11 | (async () => { 12 | const pkg = await readPkg() 13 | 14 | cli.version(pkg.version) 15 | .arguments('[pattern]') 16 | .option('-i, --ignore [value]', 'Ignore files pattern') 17 | .option('-r, --root [value]', 'The root path') 18 | .parse(process.argv) 19 | 20 | const pattern = cli.pattern ? cli.pattern : DEFAULT_PATTERN 21 | const ignore = cli.ignore ? cli.ignore : DEFAULT_IGNORE 22 | const root = cli.root ? cli.root : DEFAULT_ROOT 23 | 24 | const watcher = chokidar.watch(pattern, { 25 | ignored: [ignore], 26 | persistent: true, 27 | ignoreInitial: true, 28 | cwd: root 29 | }) 30 | watcher.on('all', (event, path, stat) => run(path, root)) 31 | })().catch(e => { 32 | console.error(e) 33 | }) 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tape-es", 3 | "version": "1.2.17", 4 | "description": "ESM-compatible Tape.js test runner", 5 | "keywords": [ 6 | "testing", 7 | "esm", 8 | "esmodules", 9 | "tape", 10 | "nodejs", 11 | "cli" 12 | ], 13 | "repository": "https://github.com/vanillaes/tape-es", 14 | "author": "Evan Plaice (http://evanplaice.com/)", 15 | "license": "MIT", 16 | "type": "module", 17 | "bin": { 18 | "tape-es": "bin/tape-es.js", 19 | "tape-watch-es": "bin/tape-watch-es.js" 20 | }, 21 | "scripts": { 22 | "lint": "esmtk lint", 23 | "package": "npx rimraf package && npm pack | tail -n 1 | xargs tar -xf", 24 | "preversion": "npm run lint", 25 | "postversion": "git push --follow-tags" 26 | }, 27 | "dependencies": { 28 | "chokidar": "^3.5.2", 29 | "commander": "^4.1.1", 30 | "glob": "^7.2.0", 31 | "tape": "^4.14.0" 32 | }, 33 | "devDependencies": { 34 | "esmtk": "^0.5.13" 35 | }, 36 | "engines": { 37 | "node": ">=14" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/runners.js: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process' 2 | import { eachLimit } from './util/index.js' 3 | 4 | export async function run (test, root) { 5 | spawn('node', [test], { 6 | cwd: root, 7 | stdio: ['pipe', process.stdout, process.stderr] 8 | }).on('close', msg => { 9 | if (msg === 1) { console.error('\x1b[31m%s\x1b[0m %s', 'ERR', 'Test failed!') } 10 | }).on('error', err => { 11 | console.error(err) 12 | }) 13 | } 14 | 15 | export async function runAll (tests, max, root) { 16 | await eachLimit(tests, max, function (test) { 17 | spawn('node', [test], { 18 | cwd: root, 19 | stdio: ['pipe', process.stdout, process.stderr] 20 | }).on('close', msg => { 21 | if (msg === 1) { process.exitCode = 1 } 22 | }).on('error', err => { 23 | console.error(err) 24 | }) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /src/util/eachLimit.js: -------------------------------------------------------------------------------- 1 | export async function eachLimit (items, limit, fn) { 2 | Promise.all([...Array(limit)].map(async () => { 3 | while (items.length > 0) { 4 | await fn(items.pop()) 5 | } 6 | })) 7 | } 8 | -------------------------------------------------------------------------------- /src/util/index.js: -------------------------------------------------------------------------------- 1 | export { eachLimit } from './eachLimit.js' 2 | export { match } from './match.js' 3 | export { readPkg } from './readPkg.js' 4 | -------------------------------------------------------------------------------- /src/util/match.js: -------------------------------------------------------------------------------- 1 | import glob from 'glob' 2 | import { promisify } from 'util' 3 | const globAsync = promisify(glob) 4 | 5 | export async function match (pattern, ignore, root) { 6 | // multiple ignore patterns 7 | if (ignore.includes(',')) { 8 | ignore = ignore.split(',') 9 | } 10 | return globAsync(pattern, { cwd: root, ignore }) 11 | } 12 | -------------------------------------------------------------------------------- /src/util/readPkg.js: -------------------------------------------------------------------------------- 1 | import { promises as fs } from 'fs' 2 | 3 | export async function readPkg () { 4 | const PKG_PATH = new URL('../../package.json', import.meta.url) 5 | if (!await fs.stat(PKG_PATH)) { 6 | throw Error('package.json not found, is this a package?') 7 | } 8 | 9 | try { 10 | return JSON.parse(await fs.readFile(PKG_PATH, 'utf-8')) 11 | } catch { 12 | throw Error('Failed to read package.json') 13 | } 14 | } 15 | --------------------------------------------------------------------------------