tape-six 1.7.2 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -8
- package/TESTING.md +756 -0
- package/bin/tape6-bun.js +4 -3
- package/bin/tape6-node.js +4 -3
- package/bin/tape6-seq.js +4 -3
- package/index.d.ts +28 -21
- package/llms-full.txt +28 -8
- package/llms.txt +22 -7
- package/package.json +7 -4
- package/src/State.js +3 -2
- package/src/Tester.js +2 -2
- package/src/reporters/JSONLReporter.js +2 -2
- package/src/reporters/MinReporter.js +2 -13
- package/src/reporters/ProxyReporter.js +2 -2
- package/src/reporters/Reporter.js +3 -3
- package/src/reporters/TTYReporter.js +4 -4
- package/src/reporters/TapReporter.js +2 -1
- package/src/runners/bun/TestWorker.js +21 -34
- package/src/runners/deno/TestWorker.js +11 -20
- package/src/runners/deno/worker.js +1 -1
- package/src/runners/node/TestWorker.js +21 -34
- package/src/runners/seq/BypassReporter.js +4 -4
- package/src/runners/seq/TestWorker.js +18 -31
- package/src/test.js +1 -0
- package/src/utils/EventServer.js +4 -4
- package/web-app/DashReporter.js +2 -2
- package/web-app/DomReporter.js +2 -2
- package/workflows/write-tests.md +33 -0
package/TESTING.md
ADDED
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
# Testing with tape-six
|
|
2
|
+
|
|
3
|
+
> This guide is for AI agents and developers working on projects that use `tape-six` for testing.
|
|
4
|
+
> It covers how to write tests, run them, and configure test discovery.
|
|
5
|
+
|
|
6
|
+
`tape-six` supports ES modules and CommonJS. TypeScript is supported natively — no transpilation needed (Node 22+, Deno, Bun all run `.ts` files directly).
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm i -D tape-six
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
Create a test file (e.g., `tests/test-example.js`):
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
import test from 'tape-six';
|
|
20
|
+
|
|
21
|
+
test('arithmetic', t => {
|
|
22
|
+
t.equal(2 + 2, 4, 'addition');
|
|
23
|
+
t.ok(10 > 5, 'comparison');
|
|
24
|
+
t.deepEqual({a: 1}, {a: 1}, 'deep equality');
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Run it directly:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
node tests/test-example.js
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Run all configured tests:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx tape6 --flags FO
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Writing tests
|
|
41
|
+
|
|
42
|
+
### Importing
|
|
43
|
+
|
|
44
|
+
ES modules (`.js`, `.mjs`, `.ts`, `.mts`):
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import test from 'tape-six';
|
|
48
|
+
// or named imports:
|
|
49
|
+
import {test, describe, it, beforeAll, afterAll, beforeEach, afterEach} from 'tape-six';
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
CommonJS (`.cjs`, `.cts`):
|
|
53
|
+
|
|
54
|
+
```js
|
|
55
|
+
const {test} = require('tape-six');
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Registering tests
|
|
59
|
+
|
|
60
|
+
`test(name, options, testFn)` — all arguments are optional, recognized by type.
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
test('name', t => {
|
|
64
|
+
t.pass('unconditional pass');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('async test', async t => {
|
|
68
|
+
const result = await fetchData();
|
|
69
|
+
t.equal(result.status, 200, 'status OK');
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Aliases: `suite()`, `describe()`, `it()` — all identical to `test()`.
|
|
74
|
+
|
|
75
|
+
When called inside a test body, top-level functions (`test`, `it`, `describe`, and all hooks) automatically delegate to the current tester. Using `t.test()`, `t.before()`, etc. is still preferred because it makes the delegation explicit. The top-level form is convenient when porting tests from frameworks like Mocha or Jest:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
import {describe, it, before, beforeEach} from 'tape-six';
|
|
79
|
+
|
|
80
|
+
describe('module', () => {
|
|
81
|
+
before(() => {
|
|
82
|
+
/* setup — same as t.before() */
|
|
83
|
+
});
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
/* per-test setup */
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('works', t => {
|
|
89
|
+
t.ok(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('also works', t => {
|
|
93
|
+
t.equal(1 + 1, 2);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Skip and TODO
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
test.skip('not ready', t => {
|
|
102
|
+
t.fail();
|
|
103
|
+
}); // skipped entirely
|
|
104
|
+
test.todo('in progress', t => {
|
|
105
|
+
t.equal(1, 2);
|
|
106
|
+
}); // runs, failures not counted
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Assertions
|
|
110
|
+
|
|
111
|
+
All `msg` arguments are optional. If omitted, a generic message is used.
|
|
112
|
+
|
|
113
|
+
| Method | Checks | Common aliases |
|
|
114
|
+
| ------------------------------------ | ------------------------------- | -------------------------------- |
|
|
115
|
+
| `t.pass(msg)` | Unconditional pass | |
|
|
116
|
+
| `t.fail(msg)` | Unconditional fail | |
|
|
117
|
+
| `t.ok(val, msg)` | `val` is truthy | `true`, `assert` |
|
|
118
|
+
| `t.notOk(val, msg)` | `val` is falsy | `false`, `notok` |
|
|
119
|
+
| `t.error(err, msg)` | `err` is falsy | `ifError`, `ifErr` |
|
|
120
|
+
| `t.equal(a, b, msg)` | `a === b` | `is`, `strictEqual`, `isEqual` |
|
|
121
|
+
| `t.notEqual(a, b, msg)` | `a !== b` | `not`, `notStrictEqual`, `isNot` |
|
|
122
|
+
| `t.deepEqual(a, b, msg)` | Deep strict equality | `same`, `isEquivalent` |
|
|
123
|
+
| `t.notDeepEqual(a, b, msg)` | Not deeply equal | `notSame`, `notEquivalent` |
|
|
124
|
+
| `t.looseEqual(a, b, msg)` | `a == b` | |
|
|
125
|
+
| `t.notLooseEqual(a, b, msg)` | `a != b` | |
|
|
126
|
+
| `t.deepLooseEqual(a, b, msg)` | Deep loose equality | |
|
|
127
|
+
| `t.notDeepLooseEqual(a, b, msg)` | Not deeply loosely equal | |
|
|
128
|
+
| `t.throws(fn, msg)` | `fn()` throws | |
|
|
129
|
+
| `t.doesNotThrow(fn, msg)` | `fn()` does not throw | |
|
|
130
|
+
| `t.matchString(str, re, msg)` | `str` matches `re` | |
|
|
131
|
+
| `t.doesNotMatchString(str, re, msg)` | `str` doesn't match `re` | |
|
|
132
|
+
| `t.match(a, b, msg)` | Structural pattern match | |
|
|
133
|
+
| `t.doesNotMatch(a, b, msg)` | No structural match | |
|
|
134
|
+
| `t.rejects(promise, msg)` | Promise rejects (**await it**) | `doesNotResolve` |
|
|
135
|
+
| `t.resolves(promise, msg)` | Promise resolves (**await it**) | `doesNotReject` |
|
|
136
|
+
|
|
137
|
+
### Async assertions
|
|
138
|
+
|
|
139
|
+
`rejects` and `resolves` are async — always `await` them:
|
|
140
|
+
|
|
141
|
+
```js
|
|
142
|
+
test('async asserts', async t => {
|
|
143
|
+
await t.rejects(Promise.reject(new Error('fail')), 'should reject');
|
|
144
|
+
await t.resolves(Promise.resolve(42), 'should resolve');
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Nested (embedded) tests
|
|
149
|
+
|
|
150
|
+
Tests can be nested. The preferred way is `t.test()` because it makes the delegation explicit. Top-level `test()`/`it()` are equivalent inside a test body — they auto-delegate to the current tester:
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
import {test, it} from 'tape-six';
|
|
154
|
+
|
|
155
|
+
test('suite', async t => {
|
|
156
|
+
// these two forms are equivalent:
|
|
157
|
+
await t.test('using t.test', t => {
|
|
158
|
+
t.pass();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await it('using top-level it()', t => {
|
|
162
|
+
t.ok(true);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Embedded tests must be `await`ed to preserve execution order.
|
|
168
|
+
|
|
169
|
+
### Hooks (setup/teardown)
|
|
170
|
+
|
|
171
|
+
Hooks are scoped — they only affect tests at their registration level.
|
|
172
|
+
|
|
173
|
+
`before` is an alias for `beforeAll`, `after` is an alias for `afterAll`. These aliases work everywhere: as named exports, on `test.before`/`test.after`, on `t.before`/`t.after`, and in options objects.
|
|
174
|
+
|
|
175
|
+
**Top-level hooks** (affect all top-level tests in the file):
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
import {test, beforeAll, afterAll, beforeEach, afterEach} from 'tape-six';
|
|
179
|
+
// or with aliases:
|
|
180
|
+
import {test, before, after, beforeEach, afterEach} from 'tape-six';
|
|
181
|
+
|
|
182
|
+
beforeAll(() => {
|
|
183
|
+
/* once before first test */
|
|
184
|
+
});
|
|
185
|
+
afterAll(() => {
|
|
186
|
+
/* once after last test */
|
|
187
|
+
});
|
|
188
|
+
beforeEach(() => {
|
|
189
|
+
/* before each test */
|
|
190
|
+
});
|
|
191
|
+
afterEach(() => {
|
|
192
|
+
/* after each test */
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Nested hooks** (affect embedded tests within a suite):
|
|
197
|
+
|
|
198
|
+
The preferred way is `t.before()`/`t.after()` because it makes the scope explicit. Top-level `before()`/`after()` are equivalent inside a test body — they auto-delegate to the current tester:
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
import {test, before, after, beforeEach} from 'tape-six';
|
|
202
|
+
|
|
203
|
+
test('database tests', async t => {
|
|
204
|
+
let db;
|
|
205
|
+
// these use top-level functions — they auto-delegate to t:
|
|
206
|
+
before(async () => {
|
|
207
|
+
db = await connect();
|
|
208
|
+
});
|
|
209
|
+
after(async () => {
|
|
210
|
+
await db.close();
|
|
211
|
+
});
|
|
212
|
+
beforeEach(() => {
|
|
213
|
+
/* reset state */
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// equivalent using t. methods:
|
|
217
|
+
// t.before(async () => { db = await connect(); });
|
|
218
|
+
// t.after(async () => { await db.close(); });
|
|
219
|
+
// t.beforeEach(() => { /* reset state */ });
|
|
220
|
+
|
|
221
|
+
await t.test('insert', async t => {
|
|
222
|
+
const result = await db.insert({name: 'Alice'});
|
|
223
|
+
t.ok(result.id, 'got an id');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
await t.test('query', async t => {
|
|
227
|
+
const rows = await db.query('SELECT * FROM users');
|
|
228
|
+
t.ok(rows.length > 0, 'has rows');
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Hooks via options** (reusable across tests):
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
const dbOpts = {
|
|
237
|
+
beforeEach: () => resetFixtures(),
|
|
238
|
+
afterEach: () => cleanupDb()
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
test('suite A', dbOpts, async t => {
|
|
242
|
+
/* ... */
|
|
243
|
+
});
|
|
244
|
+
test('suite B', dbOpts, async t => {
|
|
245
|
+
/* ... */
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Migrating from other test frameworks
|
|
250
|
+
|
|
251
|
+
`tape-six` supports `describe`/`it` and `before`/`after` aliases. When called inside a test body, all top-level functions automatically delegate to the current tester. This means migration from Mocha, Jest, or `node:test` is nearly mechanical — change the import and swap assertions.
|
|
252
|
+
|
|
253
|
+
### Mocha / Jest → tape-six
|
|
254
|
+
|
|
255
|
+
```js
|
|
256
|
+
// Mocha / Jest
|
|
257
|
+
describe('module', () => {
|
|
258
|
+
before(() => {
|
|
259
|
+
/* setup */
|
|
260
|
+
});
|
|
261
|
+
after(() => {
|
|
262
|
+
/* teardown */
|
|
263
|
+
});
|
|
264
|
+
beforeEach(() => {
|
|
265
|
+
/* per-test setup */
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('works', () => {
|
|
269
|
+
expect(1).toBe(1);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
// tape-six — just change the import and swap assertions
|
|
276
|
+
import {describe, it, before, after, beforeEach} from 'tape-six';
|
|
277
|
+
|
|
278
|
+
describe('module', () => {
|
|
279
|
+
before(() => {
|
|
280
|
+
/* setup */
|
|
281
|
+
});
|
|
282
|
+
after(() => {
|
|
283
|
+
/* teardown */
|
|
284
|
+
});
|
|
285
|
+
beforeEach(() => {
|
|
286
|
+
/* per-test setup */
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('works', t => {
|
|
290
|
+
t.equal(1, 1);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Key differences from Mocha/Jest:
|
|
296
|
+
|
|
297
|
+
- **Assertions** use `t.equal`, `t.deepEqual`, etc. instead of `expect`.
|
|
298
|
+
- The test function receives a `t` argument for assertions.
|
|
299
|
+
- No magic globals — everything is imported explicitly.
|
|
300
|
+
- `it()` inside `describe()` is auto-delegated, no need to use `t.test()`.
|
|
301
|
+
- `before()`/`after()` inside `describe()` are auto-delegated, no need to use `t.before()`/`t.after()`.
|
|
302
|
+
|
|
303
|
+
### Chai → tape-six
|
|
304
|
+
|
|
305
|
+
Chai is commonly used with Mocha and other test frameworks. Its `expect`/`should`/`assert` styles all throw `AssertionError`, which `tape-six` catches automatically. You can use Chai assertions directly inside `tape-six` tests, or replace them with `t.*` equivalents:
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
// Chai expect style
|
|
309
|
+
import {expect} from 'chai';
|
|
310
|
+
|
|
311
|
+
describe('module', () => {
|
|
312
|
+
it('works', () => {
|
|
313
|
+
expect(1 + 1).to.equal(2);
|
|
314
|
+
expect([1, 2]).to.deep.equal([1, 2]);
|
|
315
|
+
expect(true).to.be.ok;
|
|
316
|
+
expect(() => badFn()).to.throw();
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
```js
|
|
322
|
+
// tape-six — Chai assertions work as-is, or replace with t.*
|
|
323
|
+
import {describe, it} from 'tape-six';
|
|
324
|
+
|
|
325
|
+
describe('module', () => {
|
|
326
|
+
it('works', t => {
|
|
327
|
+
t.equal(1 + 1, 2);
|
|
328
|
+
t.deepEqual([1, 2], [1, 2]);
|
|
329
|
+
t.ok(true);
|
|
330
|
+
t.throws(() => badFn());
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
You can also keep Chai alongside `tape-six` — failures are reported correctly either way:
|
|
336
|
+
|
|
337
|
+
```js
|
|
338
|
+
import {describe, it} from 'tape-six';
|
|
339
|
+
import {expect} from 'chai';
|
|
340
|
+
|
|
341
|
+
describe('mixed assertions', () => {
|
|
342
|
+
it('uses both', t => {
|
|
343
|
+
expect(1).to.be.lessThan(2); // Chai — caught automatically
|
|
344
|
+
t.equal(1 + 1, 2); // tape-six native
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### node:test → tape-six
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
// node:test
|
|
353
|
+
import {describe, it, before, after} from 'node:test';
|
|
354
|
+
import assert from 'node:assert/strict';
|
|
355
|
+
|
|
356
|
+
describe('module', () => {
|
|
357
|
+
before(() => {
|
|
358
|
+
/* setup */
|
|
359
|
+
});
|
|
360
|
+
it('works', () => {
|
|
361
|
+
assert.equal(1, 1);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
```js
|
|
367
|
+
// tape-six — change import, optionally swap assert for t.*
|
|
368
|
+
import {describe, it, before} from 'tape-six';
|
|
369
|
+
|
|
370
|
+
describe('module', () => {
|
|
371
|
+
before(() => {
|
|
372
|
+
/* setup */
|
|
373
|
+
});
|
|
374
|
+
it('works', t => {
|
|
375
|
+
t.equal(1, 1);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Note: you can also keep using `node:assert` inside tape-six tests — `AssertionError` is caught automatically (see [3rd-party assertion libraries](#3rd-party-assertion-libraries)).
|
|
381
|
+
|
|
382
|
+
### Quick reference
|
|
383
|
+
|
|
384
|
+
| Mocha / Jest / node:test | tape-six equivalent |
|
|
385
|
+
| ----------------------------------- | ------------------------------------------------- |
|
|
386
|
+
| `describe(name, fn)` | `describe(name, fn)` — same |
|
|
387
|
+
| `it(name, fn)` | `it(name, t => { ... })` — same, but receives `t` |
|
|
388
|
+
| `before(fn)` | `before(fn)` — same (alias for `beforeAll`) |
|
|
389
|
+
| `after(fn)` | `after(fn)` — same (alias for `afterAll`) |
|
|
390
|
+
| `beforeEach(fn)` | `beforeEach(fn)` — same |
|
|
391
|
+
| `afterEach(fn)` | `afterEach(fn)` — same |
|
|
392
|
+
| `expect(a).toBe(b)` | `t.equal(a, b)` |
|
|
393
|
+
| `expect(a).toEqual(b)` | `t.deepEqual(a, b)` |
|
|
394
|
+
| `expect(a).toBeTruthy()` | `t.ok(a)` |
|
|
395
|
+
| `expect(fn).toThrow()` | `t.throws(fn)` |
|
|
396
|
+
| `assert.equal(a, b)` | `t.equal(a, b)` or keep `assert.equal` |
|
|
397
|
+
| `assert.deepEqual(a, b)` | `t.deepEqual(a, b)` or keep `assert.deepEqual` |
|
|
398
|
+
| `expect(a).to.equal(b)` (Chai) | `t.equal(a, b)` or keep `expect` |
|
|
399
|
+
| `expect(a).to.deep.equal(b)` (Chai) | `t.deepEqual(a, b)` or keep `expect` |
|
|
400
|
+
| `expect(a).to.be.ok` (Chai) | `t.ok(a)` or keep `expect` |
|
|
401
|
+
| `expect(fn).to.throw()` (Chai) | `t.throws(fn)` or keep `expect` |
|
|
402
|
+
|
|
403
|
+
### Wildcard matching with `t.any`
|
|
404
|
+
|
|
405
|
+
Use `t.any` (or `t._`) in deep equality checks to match any value:
|
|
406
|
+
|
|
407
|
+
```js
|
|
408
|
+
test('partial match', t => {
|
|
409
|
+
const result = {id: 123, name: 'Alice', createdAt: new Date()};
|
|
410
|
+
t.deepEqual(result, {id: 123, name: 'Alice', createdAt: t.any});
|
|
411
|
+
});
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Testing exceptions
|
|
415
|
+
|
|
416
|
+
```js
|
|
417
|
+
test('errors', async t => {
|
|
418
|
+
t.throws(() => {
|
|
419
|
+
throw new Error('boom');
|
|
420
|
+
}, 'should throw');
|
|
421
|
+
t.doesNotThrow(() => 42, 'should not throw');
|
|
422
|
+
await t.rejects(Promise.reject(new Error('fail')), 'should reject');
|
|
423
|
+
await t.resolves(Promise.resolve(42), 'should resolve');
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### 3rd-party assertion libraries
|
|
428
|
+
|
|
429
|
+
`tape-six` catches `AssertionError` automatically. You can use `chai` or `node:assert`:
|
|
430
|
+
|
|
431
|
+
```js
|
|
432
|
+
import test from 'tape-six';
|
|
433
|
+
import {expect} from 'chai';
|
|
434
|
+
|
|
435
|
+
test('with chai', t => {
|
|
436
|
+
expect(1).to.be.lessThan(2);
|
|
437
|
+
expect([1, 2]).to.deep.equal([1, 2]);
|
|
438
|
+
});
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
```js
|
|
442
|
+
import test from 'tape-six';
|
|
443
|
+
import assert from 'node:assert/strict';
|
|
444
|
+
|
|
445
|
+
test('with node:assert', t => {
|
|
446
|
+
assert.equal(1 + 1, 2);
|
|
447
|
+
assert.deepEqual({a: 1}, {a: 1});
|
|
448
|
+
});
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Running tests
|
|
452
|
+
|
|
453
|
+
### Single file
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
node tests/test-example.js # Node.js
|
|
457
|
+
bun run tests/test-example.js # Bun
|
|
458
|
+
deno run -A tests/test-example.js # Deno
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### All configured tests
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
npx tape6 --flags FO # parallel (worker threads)
|
|
465
|
+
npx tape6-seq --flags FO # sequential (in-process, no workers)
|
|
466
|
+
npx tape6 --par 4 --flags FO # limit to 4 workers
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**`tape6` vs `tape6-seq`**: The default `tape6` runner spawns worker threads to run test files in parallel — faster, but each file runs in its own isolated context. `tape6-seq` runs all test files sequentially in a single process — slower, but useful for debugging, for tests that share state, or when worker threads are unavailable.
|
|
470
|
+
|
|
471
|
+
### Selected test files
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
npx tape6 --flags FO tests/test-foo.js tests/test-bar.js
|
|
475
|
+
npx tape6-seq --flags FO tests/test-foo.js tests/test-bar.js
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Typical package.json scripts
|
|
479
|
+
|
|
480
|
+
```json
|
|
481
|
+
{
|
|
482
|
+
"scripts": {
|
|
483
|
+
"test": "tape6 --flags FO",
|
|
484
|
+
"test:bun": "tape6-bun --flags FO",
|
|
485
|
+
"test:deno": "tape6-deno --flags FO",
|
|
486
|
+
"test:seq": "tape6-seq --flags FO",
|
|
487
|
+
"test:seq:bun": "bun run `tape6-seq --self` --flags FO",
|
|
488
|
+
"test:seq:deno": "deno run -A `tape6-seq --self` --flags FO"
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Flags
|
|
494
|
+
|
|
495
|
+
Flags control test output. Uppercase = enabled, lowercase = disabled.
|
|
496
|
+
|
|
497
|
+
| Flag | Meaning |
|
|
498
|
+
| ---- | -------------------------------------- |
|
|
499
|
+
| `F` | **F**ailures only — hide passing tests |
|
|
500
|
+
| `O` | Fail **o**nce — stop at first failure |
|
|
501
|
+
| `T` | Show **t**ime for each test |
|
|
502
|
+
| `D` | Show **d**ata of failed tests |
|
|
503
|
+
| `B` | Show **b**anner with summary |
|
|
504
|
+
| `N` | Show assert **n**umber |
|
|
505
|
+
| `M` | **M**onochrome — no colors |
|
|
506
|
+
| `C` | Don't **c**apture console output |
|
|
507
|
+
| `H` | **H**ide streams and console output |
|
|
508
|
+
|
|
509
|
+
Common combinations: `FO` (failures only + stop at first), `FOT` (+ show time).
|
|
510
|
+
|
|
511
|
+
### Environment variables
|
|
512
|
+
|
|
513
|
+
- `TAPE6_FLAGS` — flags string (alternative to `--flags`).
|
|
514
|
+
- `TAPE6_PAR` — number of parallel workers.
|
|
515
|
+
- `TAPE6_TAP` — force TAP output format.
|
|
516
|
+
- `TAPE6_JSONL` — force JSONL output format.
|
|
517
|
+
|
|
518
|
+
## Configuring test discovery
|
|
519
|
+
|
|
520
|
+
Add to `package.json`:
|
|
521
|
+
|
|
522
|
+
```json
|
|
523
|
+
{
|
|
524
|
+
"tape6": {
|
|
525
|
+
"tests": ["/tests/test-*.*js"],
|
|
526
|
+
"importmap": {
|
|
527
|
+
"imports": {
|
|
528
|
+
"tape-six": "/node_modules/tape-six/index.js",
|
|
529
|
+
"tape-six/": "/node_modules/tape-six/src/",
|
|
530
|
+
"my-package": "/src/index.js",
|
|
531
|
+
"my-package/": "/src/"
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
- `tests` — glob patterns for test files (relative to project root with leading `/`). Common for all environments.
|
|
539
|
+
- `cli` — additional patterns for CLI-only environments (Node, Bun, Deno). Typically used for `.cjs` files.
|
|
540
|
+
- `node`, `deno`, `bun`, `browser` — additional patterns specific to a given environment. These are **not overrides** — they are added to `tests` (and `cli` for non-browser).
|
|
541
|
+
- `importmap` — import map for browser testing (standard [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) format).
|
|
542
|
+
|
|
543
|
+
Example with environment-specific tests:
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
{
|
|
547
|
+
"tape6": {
|
|
548
|
+
"node": ["/tests/node/test-*.js"],
|
|
549
|
+
"deno": ["/tests/deno/test-*.js"],
|
|
550
|
+
"browser": ["/tests/web/test-*.html"],
|
|
551
|
+
"tests": ["/tests/test-*.*js"],
|
|
552
|
+
"importmap": {
|
|
553
|
+
"imports": {
|
|
554
|
+
"tape-six": "/node_modules/tape-six/index.js",
|
|
555
|
+
"tape-six/": "/node_modules/tape-six/src/"
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
In this example, running `tape6` on Node will execute tests matching `/tests/node/test-*.js` + `/tests/test-*.*js`. Running in a browser will execute `/tests/web/test-*.html` + `/tests/test-*.*js`.
|
|
563
|
+
|
|
564
|
+
## Browser testing
|
|
565
|
+
|
|
566
|
+
Browser tests use `tape6-server`, a static file server bundled with `tape-six` that provides a web UI for running tests.
|
|
567
|
+
|
|
568
|
+
### Setup
|
|
569
|
+
|
|
570
|
+
1. Configure `importmap` in `package.json` so the browser can resolve bare imports:
|
|
571
|
+
|
|
572
|
+
```json
|
|
573
|
+
{
|
|
574
|
+
"tape6": {
|
|
575
|
+
"tests": ["/tests/test-*.*js"],
|
|
576
|
+
"browser": ["/tests/web/test-*.html"],
|
|
577
|
+
"importmap": {
|
|
578
|
+
"imports": {
|
|
579
|
+
"tape-six": "/node_modules/tape-six/index.js",
|
|
580
|
+
"tape-six/": "/node_modules/tape-six/src/",
|
|
581
|
+
"my-package": "/src/index.js",
|
|
582
|
+
"my-package/": "/src/"
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
2. Start the server:
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
npx tape6-server --trace
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
3. Open `http://localhost:3000` in a browser to see the web UI and run all configured tests.
|
|
596
|
+
|
|
597
|
+
### Running all configured browser tests
|
|
598
|
+
|
|
599
|
+
Navigate to:
|
|
600
|
+
|
|
601
|
+
```
|
|
602
|
+
http://localhost:3000/
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
The web app fetches the configured test list from the server and runs them.
|
|
606
|
+
|
|
607
|
+
### Running specific test files by name
|
|
608
|
+
|
|
609
|
+
Use the `?q=` query parameter (supports multiple values):
|
|
610
|
+
|
|
611
|
+
```
|
|
612
|
+
http://localhost:3000/?q=/tests/test-foo.js&q=/tests/test-bar.js
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
These are glob patterns resolved by the server, so wildcards work:
|
|
616
|
+
|
|
617
|
+
```
|
|
618
|
+
http://localhost:3000/?q=/tests/test-sample.*js
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Running a single test file (HTML shim)
|
|
622
|
+
|
|
623
|
+
Create an HTML file that loads test scripts directly with an inline import map:
|
|
624
|
+
|
|
625
|
+
```html
|
|
626
|
+
<!DOCTYPE html>
|
|
627
|
+
<html lang="en">
|
|
628
|
+
<head>
|
|
629
|
+
<meta charset="utf-8" />
|
|
630
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
631
|
+
<title>My tests</title>
|
|
632
|
+
<script type="importmap">
|
|
633
|
+
{
|
|
634
|
+
"imports": {
|
|
635
|
+
"tape-six": "/node_modules/tape-six/index.js",
|
|
636
|
+
"tape-six/": "/node_modules/tape-six/src/"
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
</script>
|
|
640
|
+
<script type="module" src="../test-sample.js"></script>
|
|
641
|
+
</head>
|
|
642
|
+
<body>
|
|
643
|
+
<h1>My tests</h1>
|
|
644
|
+
<p>See the console.</p>
|
|
645
|
+
</body>
|
|
646
|
+
</html>
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Navigate to the HTML file directly (e.g., `http://localhost:3000/tests/web/test-simple.html`). Results appear in the browser console. This approach does not use the web UI.
|
|
650
|
+
|
|
651
|
+
### Flags and parallel execution in the browser
|
|
652
|
+
|
|
653
|
+
Append query parameters:
|
|
654
|
+
|
|
655
|
+
```
|
|
656
|
+
http://localhost:3000/?flags=FO&par=3
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
### Browser automation
|
|
660
|
+
|
|
661
|
+
Use Puppeteer or Playwright to run browser tests from the command line:
|
|
662
|
+
|
|
663
|
+
```js
|
|
664
|
+
import puppeteer from 'puppeteer';
|
|
665
|
+
const browser = await puppeteer.launch({headless: true});
|
|
666
|
+
const page = await browser.newPage();
|
|
667
|
+
page.on('console', msg => console.log(msg.text()));
|
|
668
|
+
await page.exposeFunction('__tape6_reportResults', async text => {
|
|
669
|
+
await browser.close();
|
|
670
|
+
process.exit(text === 'success' ? 0 : 1);
|
|
671
|
+
});
|
|
672
|
+
await page.goto('http://localhost:3000/?flags=M');
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### Browser limitations
|
|
676
|
+
|
|
677
|
+
- Browsers cannot run TypeScript files directly — only `.js` and `.mjs`.
|
|
678
|
+
- Browsers cannot run CommonJS (`.cjs`) files.
|
|
679
|
+
- Browsers can run HTML shim files in addition to JS files.
|
|
680
|
+
|
|
681
|
+
## Test file conventions
|
|
682
|
+
|
|
683
|
+
- **Naming**: `test-*.js`, `test-*.mjs`, `test-*.cjs`, `test-*.ts`, `test-*.mts`, `test-*.cts`.
|
|
684
|
+
- **Location**: typically `tests/` directory.
|
|
685
|
+
- **Self-contained**: each test file should be directly executable with `node`.
|
|
686
|
+
- **One concern per file**: group related tests in a single file, use embedded tests for sub-grouping.
|
|
687
|
+
|
|
688
|
+
## Patterns for AI agents writing tests
|
|
689
|
+
|
|
690
|
+
### Testing a new function
|
|
691
|
+
|
|
692
|
+
```js
|
|
693
|
+
import test from 'tape-six';
|
|
694
|
+
import {myFunction} from 'my-package/my-module.js';
|
|
695
|
+
|
|
696
|
+
test('myFunction', async t => {
|
|
697
|
+
await t.test('returns correct result for basic input', t => {
|
|
698
|
+
t.deepEqual(myFunction(1, 2), {sum: 3, product: 2});
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
await t.test('handles edge cases', t => {
|
|
702
|
+
t.deepEqual(myFunction(0, 0), {sum: 0, product: 0});
|
|
703
|
+
t.throws(() => myFunction(null), 'throws on null input');
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
await t.test('async variant', async t => {
|
|
707
|
+
const result = await myFunction.async(1, 2);
|
|
708
|
+
t.equal(result.sum, 3);
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
### Testing a class
|
|
714
|
+
|
|
715
|
+
```js
|
|
716
|
+
import test from 'tape-six';
|
|
717
|
+
import {MyClass} from 'my-package/my-class.js';
|
|
718
|
+
|
|
719
|
+
test('MyClass', async t => {
|
|
720
|
+
let instance;
|
|
721
|
+
t.beforeEach(() => {
|
|
722
|
+
instance = new MyClass();
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
await t.test('constructor', t => {
|
|
726
|
+
t.ok(instance, 'creates instance');
|
|
727
|
+
t.equal(instance.size, 0, 'starts empty');
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
await t.test('add', t => {
|
|
731
|
+
instance.add('item');
|
|
732
|
+
t.equal(instance.size, 1, 'size increases');
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
await t.test('remove', t => {
|
|
736
|
+
instance.add('item');
|
|
737
|
+
instance.remove('item');
|
|
738
|
+
t.equal(instance.size, 0, 'size decreases');
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Verifying after writing tests
|
|
744
|
+
|
|
745
|
+
```bash
|
|
746
|
+
node tests/test-<name>.js # run your new test file directly
|
|
747
|
+
npm test # run full suite to check for regressions
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
## Links
|
|
751
|
+
|
|
752
|
+
- Full API reference: https://github.com/uhop/tape-six/wiki/Tester
|
|
753
|
+
- Hooks documentation: https://github.com/uhop/tape-six/wiki/Before-and-after-hooks
|
|
754
|
+
- Configuration: https://github.com/uhop/tape-six/wiki/Set-up-tests
|
|
755
|
+
- Supported flags: https://github.com/uhop/tape-six/wiki/Supported-flags
|
|
756
|
+
- npm: https://www.npmjs.com/package/tape-six
|