tape-six 1.8.0 → 1.9.0
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 +4 -3
- package/TESTING.md +1 -1
- package/index.d.ts +30 -2
- package/index.js +2 -0
- package/llms-full.txt +47 -12
- package/llms.txt +5 -1
- package/package.json +5 -5
- package/src/OK.js +3 -2
- package/src/State.js +3 -0
- package/src/Tester.js +26 -4
- package/src/test.js +13 -0
package/README.md
CHANGED
|
@@ -319,7 +319,7 @@ The following methods are available (all `msg` arguments are optional):
|
|
|
319
319
|
- `any` — a symbol that can be used in deep equivalency asserts to match any value.
|
|
320
320
|
See [deep6's any](https://github.com/uhop/deep6/wiki/any) for details.
|
|
321
321
|
- `_` — an alias of `any`.
|
|
322
|
-
- `plan(n)` —
|
|
322
|
+
- `plan(n)` — records the expected number of direct assertions; emits a TAP-comment diagnostic at test end if the count diverges (does not fail the test).
|
|
323
323
|
- `comment(msg)` — sends a comment to the test harness. Rarely used.
|
|
324
324
|
- `skipTest(...args, msg)` — skips the current test yet sends a message to the test harness.
|
|
325
325
|
- `bailOut(msg)` — stops the test suite and sends a message to the test harness.
|
|
@@ -425,8 +425,9 @@ Test output can be controlled by flags. See [Supported flags](https://github.com
|
|
|
425
425
|
|
|
426
426
|
The most recent releases:
|
|
427
427
|
|
|
428
|
-
- 1.
|
|
429
|
-
- 1.
|
|
428
|
+
- 1.9.0 _New features: `t.plan(n)` emits a TAP-comment diagnostic on mismatch, `registerTesterMethod(name, fn)` registers tester plugins idempotently. New wiki: 3rd-party library catalog and "Writing plugins" page. Removed `chai` from dev dependencies._
|
|
429
|
+
- 1.8.0 _New subpath modules: `tape-six/server` (HTTP server harness) and `tape-six/response` (response reading helpers for `Response` and `IncomingMessage`)._
|
|
430
|
+
- 1.7.14 _Updated vendored `deep6` to 1.2.0._
|
|
430
431
|
- 1.7.13 _Replaced `process.exit()` / `Deno.exit()` with `process.exitCode` in test runners and `index.js` for graceful stdout flushing. Updated dependencies, GitHub Actions._
|
|
431
432
|
- 1.7.12 _Added `--help`/`-h` and `--version`/`-v` options to all CLI utilities. Added flag documentation to help output._
|
|
432
433
|
- 1.7.11 _Documentation consistency: added missing sequential test commands to AI docs, fixed test glob references._
|
package/TESTING.md
CHANGED
|
@@ -221,7 +221,7 @@ By default tests have **no timeout**. Set `timeout` per test, or wrap a fixture
|
|
|
221
221
|
- `t.comment(msg)` — emit a TAP comment line.
|
|
222
222
|
- `t.skipTest(msg)` — skip the **current** test from inside it (e.g. when a precondition isn't met at runtime).
|
|
223
223
|
- `t.bailOut(msg)` — stop the entire run. Catastrophic; use sparingly.
|
|
224
|
-
- `t.plan(n)` —
|
|
224
|
+
- `t.plan(n)` — record the expected number of direct assertions. If the count diverges at test end, a `# plan != count: expected N, ran M` TAP comment is emitted (diagnostic only, doesn't fail the test). Subtest assertions don't count toward the parent's plan.
|
|
225
225
|
- `t.OK(expr, msg)` (aliases `t.TRUE`, `t.ASSERT`) — returns a code string for `eval()` that asserts an expression and dumps the values of the top-level identifiers in the expression on failure. Useful for compact arithmetic/state checks. Not usable in CSP-restricted contexts (uses `eval`).
|
|
226
226
|
|
|
227
227
|
```js
|
package/index.d.ts
CHANGED
|
@@ -95,8 +95,11 @@ export declare interface Tester {
|
|
|
95
95
|
signal: AbortSignal;
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
98
|
+
* Records the expected number of direct assertions. When the test ends, a
|
|
99
|
+
* `# plan != count: expected N, ran M` TAP comment is emitted if the count
|
|
100
|
+
* diverges. Diagnostic only — does not fail the test. Subtest assertions
|
|
101
|
+
* don't count toward the parent's plan.
|
|
102
|
+
* @param n - The expected number of assertions (non-negative integer)
|
|
100
103
|
*/
|
|
101
104
|
plan(n: number): void;
|
|
102
105
|
|
|
@@ -1251,4 +1254,29 @@ export declare const before: typeof test.before;
|
|
|
1251
1254
|
*/
|
|
1252
1255
|
export declare const after: typeof test.after;
|
|
1253
1256
|
|
|
1257
|
+
/**
|
|
1258
|
+
* Idempotently install a method on `Tester.prototype` so it appears as a
|
|
1259
|
+
* method on every tester instance (`t.<name>(...)`). Same name + same function
|
|
1260
|
+
* is a no-op (allows duplicate side-effect imports). Same name + a different
|
|
1261
|
+
* function throws so collisions surface loudly.
|
|
1262
|
+
*
|
|
1263
|
+
* Usage from a plugin module (e.g., `tape-six-spawn`):
|
|
1264
|
+
*
|
|
1265
|
+
* ```ts
|
|
1266
|
+
* import {registerTesterMethod} from 'tape-six/Tester.js';
|
|
1267
|
+
*
|
|
1268
|
+
* declare module 'tape-six' {
|
|
1269
|
+
* interface Tester {
|
|
1270
|
+
* spawnBin(bin: string, args: string[]): Promise<{code: number; stdout: string; stderr: string}>;
|
|
1271
|
+
* }
|
|
1272
|
+
* }
|
|
1273
|
+
*
|
|
1274
|
+
* registerTesterMethod('spawnBin', async function (bin, args) { ... });
|
|
1275
|
+
* ```
|
|
1276
|
+
*
|
|
1277
|
+
* @param name - non-empty method name
|
|
1278
|
+
* @param fn - function to install on `Tester.prototype`
|
|
1279
|
+
*/
|
|
1280
|
+
export declare const registerTesterMethod: (name: string, fn: (...args: any[]) => any) => void;
|
|
1281
|
+
|
|
1254
1282
|
export default test;
|
package/index.js
CHANGED
package/llms-full.txt
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- Before/after hooks: `beforeAll`, `afterAll`, `beforeEach`, `afterEach`
|
|
11
11
|
- `test()` is aliased as `suite()`, `describe()`, and `it()` for easy migration
|
|
12
12
|
- When called inside a test body, top-level functions auto-delegate to the current tester
|
|
13
|
-
-
|
|
13
|
+
- BYO assertion / mocking / property-based libs: `node:assert`, `chai`, `expect`, `node:test` mock, `sinon`, `fast-check`
|
|
14
14
|
|
|
15
15
|
## Quick start
|
|
16
16
|
|
|
@@ -192,7 +192,7 @@ The `Tester` object is passed to test functions. All `msg` arguments are optiona
|
|
|
192
192
|
|
|
193
193
|
### Miscellaneous
|
|
194
194
|
|
|
195
|
-
- `plan(n)` —
|
|
195
|
+
- `plan(n)` — record expected number of direct assertions. On test end, emits a `# plan != count: expected N, ran M` TAP comment if the count diverges (diagnostic only, doesn't fail the test). Subtest assertions don't count toward the parent's plan.
|
|
196
196
|
- `comment(msg)` — send a comment to the reporter.
|
|
197
197
|
- `skipTest(...args, msg)` — skip current test with a message.
|
|
198
198
|
- `bailOut(msg)` — abort the test suite.
|
|
@@ -257,6 +257,28 @@ test('suite', opts, async t => {
|
|
|
257
257
|
|
|
258
258
|
Multiple hooks of the same type run in registration order (before) or reverse order (after).
|
|
259
259
|
|
|
260
|
+
## Plugins
|
|
261
|
+
|
|
262
|
+
Tester methods can be added via `registerTesterMethod(name, fn)`. Same name + same fn → no-op (idempotent re-import); same name + different fn → throws (loud collision detection).
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
import {registerTesterMethod} from 'tape-six';
|
|
266
|
+
|
|
267
|
+
registerTesterMethod('spawnBin', async function (bin, args) {
|
|
268
|
+
// ... implementation, can call this.equal(...), this.reporter.report({...}), etc.
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// then in tests:
|
|
272
|
+
test('cli', async t => {
|
|
273
|
+
const {code} = await t.spawnBin('node', ['-v']);
|
|
274
|
+
t.equal(code, 0);
|
|
275
|
+
});
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`Tester` is declared as an `interface` in `index.d.ts` to make TS module augmentation natural — plugins extend the interface, no full class shape needed. The built-in `t.OK()` evaluator (`src/OK.js`) uses this exact pattern.
|
|
279
|
+
|
|
280
|
+
Plugin installation is **per-file**. `tape6-seq` runs all files in one process; `tape6` uses workers; `tape6-proc` (from the sister package `tape-six-proc`) uses subprocesses; browser tests run in iframes. Each isolation context has its own module graph, so a plugin import in file A is invisible to file B when they're in different contexts. Every test file that uses a plugin must import it directly. `registerTesterMethod`'s idempotency makes repeated imports safe. See [Writing plugins](https://github.com/uhop/tape-six/wiki/Writing-plugins) for the full guide.
|
|
281
|
+
|
|
260
282
|
## Subpath modules
|
|
261
283
|
|
|
262
284
|
Two helper modules ship as separate subpath imports for tests that need HTTP-shaped fixtures. Both are cross-runtime (Node, Bun, Deno via `node:http`); browsers don't need them because running an HTTP server inside a webpage isn't a use case.
|
|
@@ -473,19 +495,18 @@ Browser: `http://localhost:3000/?flags=FO`
|
|
|
473
495
|
- tape-six-puppeteer (https://www.npmjs.com/package/tape-six-puppeteer) — automates browser testing with Puppeteer.
|
|
474
496
|
- tape-six-playwright (https://www.npmjs.com/package/tape-six-playwright) — automates browser testing with Playwright.
|
|
475
497
|
|
|
476
|
-
## 3rd-party
|
|
498
|
+
## 3rd-party libraries (bring-your-own)
|
|
477
499
|
|
|
478
|
-
`tape-six`
|
|
500
|
+
`tape-six` doesn't bundle assertion / mock / property-based libraries. It catches whatever the test throws — anything throwing a node-compatible `AssertionError` (with `name`, `operator`, `actual`, `expected`) is reported as a regular assertion; other errors are reported as `UNEXPECTED EXCEPTION` (test still fails).
|
|
479
501
|
|
|
480
|
-
|
|
481
|
-
import test from 'tape-six';
|
|
482
|
-
import {assert, expect} from 'chai';
|
|
502
|
+
**Verified candidates** (full per-lib examples and browser importmap snippets in the wiki):
|
|
483
503
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
|
|
504
|
+
- `node:assert` — built-in to Node/Bun/Deno, throws `AssertionError`. Cleanest path.
|
|
505
|
+
- `chai` — `expect`/`should`/`assert` styles, throws `AssertionError`. Works in browsers via importmap entry.
|
|
506
|
+
- `expect` (Jest's standalone) — throws plain `Error`, not `AssertionError`. Works but failures render as `UNEXPECTED EXCEPTION` with ANSI-colored messages. Wrap negative cases in `t.throws()`.
|
|
507
|
+
- `node:test` mock — built-in spies/stubs/timers via `import {mock} from 'node:test'`. Doesn't throw; assert on `spy.mock.calls` with `t.*`.
|
|
508
|
+
- `sinon` — same pattern as `node:test` mock. Assert on `spy.callCount` / `spy.firstCall.args`.
|
|
509
|
+
- `fast-check` — property-based testing. Throws plain `Error` on counterexample (with seed + path for repro). Wrap negative cases in `t.throws()`.
|
|
489
510
|
|
|
490
511
|
```js
|
|
491
512
|
import test from 'tape-six';
|
|
@@ -497,4 +518,18 @@ test('with node:assert', t => {
|
|
|
497
518
|
});
|
|
498
519
|
```
|
|
499
520
|
|
|
521
|
+
```js
|
|
522
|
+
import test from 'tape-six';
|
|
523
|
+
import {mock} from 'node:test';
|
|
524
|
+
|
|
525
|
+
test('with node:test mock', t => {
|
|
526
|
+
const spy = mock.fn();
|
|
527
|
+
spy(1, 2);
|
|
528
|
+
t.equal(spy.mock.calls.length, 1);
|
|
529
|
+
t.deepEqual(spy.mock.calls[0].arguments, [1, 2]);
|
|
530
|
+
});
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Wiki: [3rd-party assertion libraries](https://github.com/uhop/tape-six/wiki/3rd%E2%80%90party-assertion-libraries), [mock libraries](https://github.com/uhop/tape-six/wiki/3rd%E2%80%90party-mock-libraries), [property-based testing](https://github.com/uhop/tape-six/wiki/3rd%E2%80%90party-property-based-testing).
|
|
534
|
+
|
|
500
535
|
Assertions that throw `AssertionError` are automatically caught and reported.
|
package/llms.txt
CHANGED
|
@@ -105,11 +105,15 @@ The object passed to test functions. Provides assertions and test control.
|
|
|
105
105
|
|
|
106
106
|
#### Utilities
|
|
107
107
|
|
|
108
|
-
- `t.plan(n)` —
|
|
108
|
+
- `t.plan(n)` — record expected direct-assertion count; emits a `# plan != count: expected N, ran M` TAP comment at test end on mismatch (diagnostic, doesn't fail).
|
|
109
109
|
- `t.comment(msg)` — emit a comment.
|
|
110
110
|
- `t.skipTest(msg)` — skip current test with a message.
|
|
111
111
|
- `t.bailOut(msg)` — abort entire test suite.
|
|
112
112
|
|
|
113
|
+
### Plugins
|
|
114
|
+
|
|
115
|
+
- `registerTesterMethod(name, fn)` — install a method on `Tester.prototype` from a plugin module. Idempotent for same `fn`; throws on collision with a different `fn`. Plugin imports are per-file (workers / subprocesses / iframes don't share module graphs).
|
|
116
|
+
|
|
113
117
|
### Hooks
|
|
114
118
|
|
|
115
119
|
Hooks can be registered as standalone functions or via test options or Tester methods.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tape-six",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "TAP-inspired unit test library for Node, Deno, Bun, and browsers. ES modules, TypeScript, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -98,21 +98,21 @@
|
|
|
98
98
|
"/tests/test-*.cjs",
|
|
99
99
|
"/tests/cli/test-*.js"
|
|
100
100
|
],
|
|
101
|
+
"node": [
|
|
102
|
+
"/tests/node/test-*.js"
|
|
103
|
+
],
|
|
101
104
|
"browser": [
|
|
102
105
|
"/tests/browser/test-*.html"
|
|
103
106
|
],
|
|
104
107
|
"importmap": {
|
|
105
108
|
"imports": {
|
|
106
109
|
"tape-six": "../index.js",
|
|
107
|
-
"tape-six/": "../src/"
|
|
108
|
-
"chai": "../node_modules/chai/index.js"
|
|
110
|
+
"tape-six/": "../src/"
|
|
109
111
|
}
|
|
110
112
|
}
|
|
111
113
|
},
|
|
112
114
|
"devDependencies": {
|
|
113
|
-
"@types/chai": "^5.2.3",
|
|
114
115
|
"@types/node": "^25.6.0",
|
|
115
|
-
"chai": "^6.2.2",
|
|
116
116
|
"typescript": "^6.0.3"
|
|
117
117
|
}
|
|
118
118
|
}
|
package/src/OK.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {registerTesterMethod, setAliases} from './Tester.js';
|
|
2
2
|
|
|
3
3
|
// code mostly borrowed from https://github.com/heya/ice/blob/master/assert.js under BSD-3
|
|
4
4
|
|
|
@@ -22,7 +22,7 @@ const listVariables = (code, self) => {
|
|
|
22
22
|
return '{' + result.join(',') + '}';
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
const OK = function OK(condition, msg, options) {
|
|
26
26
|
if (typeof condition != 'string') {
|
|
27
27
|
throw new TypeError('Condition must be a string');
|
|
28
28
|
}
|
|
@@ -44,4 +44,5 @@ Tester.prototype.OK = function OK(condition, msg, options) {
|
|
|
44
44
|
}))`;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
registerTesterMethod('OK', OK);
|
|
47
48
|
setAliases('OK', 'TRUE, ASSERT');
|
package/src/State.js
CHANGED
|
@@ -101,6 +101,8 @@ export class State {
|
|
|
101
101
|
this.failOnce = failOnce || parent.failOnce;
|
|
102
102
|
this.offset = parent.asserts || 0;
|
|
103
103
|
this.asserts = this.skipped = this.failed = 0;
|
|
104
|
+
// direct assertions in this state only — not bumped by updateParent, used by t.plan()
|
|
105
|
+
this.localAsserts = 0;
|
|
104
106
|
this.stopTest = false;
|
|
105
107
|
this.timer = timer || parent.timer || getTimer();
|
|
106
108
|
this.startTime = this.time = time || this.timer.now();
|
|
@@ -178,6 +180,7 @@ export class State {
|
|
|
178
180
|
|
|
179
181
|
if (event.type === 'assert' || event.type === 'assertion-error') {
|
|
180
182
|
++this.asserts;
|
|
183
|
+
++this.localAsserts;
|
|
181
184
|
event.skip && ++this.skipped;
|
|
182
185
|
isFailed && ++this.failed;
|
|
183
186
|
event.id = this.asserts + this.offset;
|
package/src/Tester.js
CHANGED
|
@@ -48,8 +48,10 @@ export class Tester {
|
|
|
48
48
|
return this.reporter.state;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
plan(
|
|
52
|
-
|
|
51
|
+
plan(n) {
|
|
52
|
+
if (typeof n !== 'number' || !Number.isInteger(n) || n < 0)
|
|
53
|
+
throw new TypeError('plan(n) requires a non-negative integer');
|
|
54
|
+
this.planned = n;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
comment(msg) {
|
|
@@ -448,8 +450,28 @@ export class Tester {
|
|
|
448
450
|
}
|
|
449
451
|
Tester.prototype.any = Tester.prototype._ = any;
|
|
450
452
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
+
// Idempotent registration of a method on Tester.prototype. Same name + same
|
|
454
|
+
// function → no-op; same name + different function → throws. Lets plugins
|
|
455
|
+
// extend the tester (e.g., spawnBin, withTempDir, waitFor) without colliding
|
|
456
|
+
// silently when two of them claim the same name.
|
|
457
|
+
export const registerTesterMethod = (name, fn) => {
|
|
458
|
+
if (typeof name !== 'string' || !name)
|
|
459
|
+
throw new TypeError('registerTesterMethod: name must be a non-empty string');
|
|
460
|
+
if (typeof fn !== 'function') throw new TypeError('registerTesterMethod: fn must be a function');
|
|
461
|
+
if (Object.prototype.hasOwnProperty.call(Tester.prototype, name)) {
|
|
462
|
+
if (Tester.prototype[name] !== fn)
|
|
463
|
+
throw new Error(
|
|
464
|
+
`registerTesterMethod: '${name}' is already registered with a different implementation`
|
|
465
|
+
);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
Tester.prototype[name] = fn;
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
export const setAliases = (source, aliases) => {
|
|
472
|
+
const fn = Tester.prototype[source];
|
|
473
|
+
aliases.split(', ').forEach(alias => registerTesterMethod(alias, fn));
|
|
474
|
+
};
|
|
453
475
|
|
|
454
476
|
setAliases('ok', 'true, assert');
|
|
455
477
|
setAliases('notOk', 'false, notok');
|
package/src/test.js
CHANGED
|
@@ -227,6 +227,19 @@ export const runTests = async tests => {
|
|
|
227
227
|
await tester.dispose();
|
|
228
228
|
await tester.state?.runAfterAll();
|
|
229
229
|
testers.pop();
|
|
230
|
+
if (
|
|
231
|
+
tester.planned !== undefined &&
|
|
232
|
+
!tester.state?.skip &&
|
|
233
|
+
tester.state?.localAsserts !== tester.planned
|
|
234
|
+
) {
|
|
235
|
+
tester.reporter.report({
|
|
236
|
+
type: 'comment',
|
|
237
|
+
name: `plan != count: expected ${tester.planned}, ran ${tester.state.localAsserts}`,
|
|
238
|
+
test: testNumber,
|
|
239
|
+
marker: new Error(),
|
|
240
|
+
time: tester.timer.now()
|
|
241
|
+
});
|
|
242
|
+
}
|
|
230
243
|
tester.reporter.report({
|
|
231
244
|
type: 'end',
|
|
232
245
|
name: options.name,
|