@git.zone/tstest 3.1.7 → 3.2.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.
Files changed (33) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/tstest.classes.migration.js +4 -4
  3. package/dist_ts/tstest.classes.runtime.bun.js +5 -5
  4. package/dist_ts/tstest.classes.runtime.chromium.js +12 -10
  5. package/dist_ts/tstest.classes.runtime.deno.js +7 -7
  6. package/dist_ts/tstest.classes.runtime.docker.js +2 -2
  7. package/dist_ts/tstest.classes.runtime.node.js +5 -5
  8. package/dist_ts/tstest.classes.tap.parser.js +6 -7
  9. package/dist_ts/tstest.classes.testdirectory.d.ts +1 -1
  10. package/dist_ts/tstest.classes.testdirectory.js +11 -12
  11. package/dist_ts/tstest.classes.tstest.js +32 -31
  12. package/dist_ts/tstest.plugins.d.ts +6 -3
  13. package/dist_ts/tstest.plugins.js +7 -4
  14. package/dist_ts_tapbundle/tapbundle.classes.taptest.js +2 -2
  15. package/dist_ts_tapbundle_serverside/classes.tapnodetools.d.ts +30 -0
  16. package/dist_ts_tapbundle_serverside/classes.tapnodetools.js +100 -3
  17. package/dist_ts_tapbundle_serverside/classes.testfileprovider.js +3 -3
  18. package/dist_ts_tapbundle_serverside/plugins.d.ts +4 -1
  19. package/dist_ts_tapbundle_serverside/plugins.js +5 -2
  20. package/package.json +14 -13
  21. package/readme.hints.md +1 -1
  22. package/readme.md +273 -868
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/tstest.classes.migration.ts +3 -6
  25. package/ts/tstest.classes.runtime.bun.ts +4 -4
  26. package/ts/tstest.classes.runtime.chromium.ts +8 -12
  27. package/ts/tstest.classes.runtime.deno.ts +6 -6
  28. package/ts/tstest.classes.runtime.docker.ts +1 -1
  29. package/ts/tstest.classes.runtime.node.ts +4 -4
  30. package/ts/tstest.classes.tap.parser.ts +5 -7
  31. package/ts/tstest.classes.testdirectory.ts +19 -20
  32. package/ts/tstest.classes.tstest.ts +36 -41
  33. package/ts/tstest.plugins.ts +8 -3
package/readme.md CHANGED
@@ -1,262 +1,97 @@
1
1
  # @git.zone/tstest
2
- 🧪 **A powerful, modern test runner for TypeScript** - making your test runs beautiful and informative across multiple runtimes!
2
+
3
+ 🧪 **A powerful, modern test runner for TypeScript** — beautiful output, multi-runtime support, and a batteries-included test framework that makes testing actually enjoyable.
3
4
 
4
5
  ## Availability and Links
5
- * [npmjs.org (npm package)](https://www.npmjs.com/package/@git.zone/tstest)
6
- * [code.foss.global (source)](https://code.foss.global/git.zone/tstest)
6
+
7
+ - [npmjs.org (npm package)](https://www.npmjs.com/package/@git.zone/tstest)
8
+ - [code.foss.global (source)](https://code.foss.global/git.zone/tstest)
7
9
 
8
10
  ## Issue Reporting and Security
9
11
 
10
- For reporting bugs, issues, or security vulnerabilities, please visit https://community.foss.global/. This is the central community hub for all issue reporting. Developers who want to sign a contribution agreement and go through identification can also get a code.foss.global account to submit Pull Requests directly.
12
+ For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
11
13
 
12
14
  ## Why tstest?
13
15
 
14
- **tstest** is a TypeScript test runner that makes testing delightful. It's designed for modern development workflows with beautiful output, flexible test execution, and powerful features that make debugging a breeze.
15
-
16
- ### Key Features
17
-
18
- - 🎯 **Smart Test Execution** - Run all tests, single files, or use glob patterns
19
- - 🚀 **Multi-Runtime Support** - Run tests in Node.js, Deno, Bun, and Chromium
20
- - 🎨 **Beautiful Output** - Color-coded results with emojis and clean formatting
21
- - 📊 **Multiple Output Modes** - Choose from normal, quiet, verbose, or JSON output
22
- - 🔍 **Automatic Discovery** - Finds all your test files automatically
23
- - 📝 **Detailed Logging** - Optional file logging for debugging
24
- - ⚡ **Performance Metrics** - See which tests are slow
25
- - 🤖 **CI/CD Ready** - JSON output mode for automation
26
- - 🏷️ **Tag-based Filtering** - Run only tests with specific tags
27
- - 🎯 **Parallel Test Execution** - Run tests in parallel groups
28
- - 🔧 **Test Lifecycle Hooks** - beforeEach/afterEach support
29
- - 📸 **Snapshot Testing** - Compare test outputs with saved snapshots
30
- - ⏳ **Timeout Control** - Set custom timeouts for tests
31
- - 🔁 **Retry Logic** - Automatically retry failing tests
32
- - 🛠️ **Test Fixtures** - Create reusable test data
33
- - 👀 **Watch Mode** - Automatically re-run tests on file changes
34
- - 📊 **Real-time Progress** - Live test execution progress updates
35
- - 🎨 **Visual Diffs** - Beautiful side-by-side diffs for failed assertions
36
- - 🔄 **Event-based Reporting** - Real-time test lifecycle events
16
+ Most TypeScript test runners feel like an afterthought clunky configuration, ugly output, and poor TypeScript support. **tstest** was built from the ground up for TypeScript developers who want:
17
+
18
+ - 🎯 **Zero config** — Point it at your test directory and go
19
+ - 🚀 **Multi-runtime** — Run the same tests on Node.js, Deno, Bun, and Chromium
20
+ - 🎨 **Beautiful output** — Color-coded results with emojis, progress bars, and visual diffs
21
+ - **Built-in everything** Assertions, snapshots, fixtures, retries, timeouts, parallel execution
22
+ - 🔧 **Server-side tooling** Free ports, HTTPS certs, ephemeral databases, S3 storage — all out of the box
37
23
 
38
24
  ## Installation
39
25
 
40
26
  ```bash
41
- npm install --save-dev @git.zone/tstest
42
- # or with pnpm
43
- pnpm add -D @git.zone/tstest
27
+ pnpm install --save-dev @git.zone/tstest
44
28
  ```
45
29
 
46
- ## Multi-Runtime Architecture
30
+ ## Module Exports
47
31
 
48
- tstest supports running your tests across multiple JavaScript runtimes, allowing you to verify cross-platform compatibility easily.
32
+ tstest ships as four modules, each optimized for a different use case:
49
33
 
50
- ### Supported Runtimes
34
+ | Export Path | Environment | Purpose |
35
+ |---|---|---|
36
+ | `@git.zone/tstest` | CLI | Test runner — discovers and executes test files |
37
+ | `@git.zone/tstest/tapbundle` | Browser + Node | Core test framework — `tap`, `expect`, lifecycle hooks |
38
+ | `@git.zone/tstest/tapbundle_serverside` | Node.js only | Server-side utilities — ports, certs, databases, shell |
39
+ | `@git.zone/tstest/tapbundle_protocol` | Isomorphic | TAP Protocol V2 — structured metadata, events, diffs |
51
40
 
52
- - **Node.js** - Default runtime, uses tsrun for TypeScript execution
53
- - **Chromium** - Browser environment testing with full DOM support
54
- - **Deno** - Secure TypeScript/JavaScript runtime with modern features
55
- - **Bun** - Ultra-fast all-in-one JavaScript runtime
41
+ ## Quick Start
56
42
 
57
- ### Test File Naming Convention
58
-
59
- Name your test files with runtime specifiers to control where they run:
60
-
61
- | Pattern | Runtimes | Example |
62
- |---------|----------|---------|
63
- | `*.ts` | Node.js only (default) | `test.api.ts` |
64
- | `*.node.ts` | Node.js only | `test.server.node.ts` |
65
- | `*.chromium.ts` | Chromium browser | `test.dom.chromium.ts` |
66
- | `*.deno.ts` | Deno runtime | `test.http.deno.ts` |
67
- | `*.bun.ts` | Bun runtime | `test.fast.bun.ts` |
68
- | `*.all.ts` | All runtimes (Node, Chromium, Deno, Bun) | `test.universal.all.ts` |
69
- | `*.node+chromium.ts` | Both Node.js and Chromium | `test.isomorphic.node+chromium.ts` |
70
- | `*.node+deno.ts` | Both Node.js and Deno | `test.cross.node+deno.ts` |
71
- | `*.deno+bun.ts` | Both Deno and Bun | `test.modern.deno+bun.ts` |
72
- | `*.chromium.nonci.ts` | Chromium, skip in CI | `test.visual.chromium.nonci.ts` |
73
- | `*.all.nonci.ts` | All runtimes, skip in CI | `test.comprehensive.all.nonci.ts` |
74
-
75
- **Multi-Runtime Examples:**
43
+ ### 1. Write a test
76
44
 
77
45
  ```typescript
78
- // test.api.all.ts - runs in all runtimes (Node, Chromium, Deno, Bun)
79
- import { expect, tap } from '@git.zone/tstest/tapbundle';
46
+ // test/test.math.ts
47
+ import { tap, expect } from '@git.zone/tstest/tapbundle';
80
48
 
81
- tap.test('universal HTTP test', async () => {
82
- const response = await fetch('https://api.example.com/test');
83
- expect(response.status).toEqual(200);
49
+ tap.test('should add numbers', async () => {
50
+ expect(2 + 2).toEqual(4);
84
51
  });
85
52
 
86
- export default tap.start();
87
- ```
88
-
89
- ```typescript
90
- // test.api.node+deno+bun.ts - runs in specific runtimes
91
- import { expect, tap } from '@git.zone/tstest/tapbundle';
92
-
93
- tap.test('cross-runtime HTTP test', async () => {
94
- const response = await fetch('https://api.example.com/test');
95
- expect(response.status).toEqual(200);
53
+ tap.test('should handle async operations', async (tools) => {
54
+ await tools.delayFor(100);
55
+ const result = await fetchData();
56
+ expect(result).toBeTruthy();
96
57
  });
97
58
 
98
59
  export default tap.start();
99
60
  ```
100
61
 
101
- ### Runtime Execution Order
102
-
103
- When multiple runtimes are specified, tests execute in this order:
104
- 1. Node.js
105
- 2. Bun
106
- 3. Deno
107
- 4. Chromium
108
-
109
- ### Legacy Naming (Deprecated)
110
-
111
- The following patterns are still supported but deprecated. Use the migration tool to update:
112
-
113
- | Legacy Pattern | Modern Equivalent | Migration Command |
114
- |----------------|-------------------|-------------------|
115
- | `*.browser.ts` | `*.chromium.ts` | `tstest migrate` |
116
- | `*.both.ts` | `*.node+chromium.ts` | `tstest migrate` |
117
-
118
- When running legacy files, tstest shows a deprecation warning with the suggested new name.
119
-
120
- ### Migration Tool
121
-
122
- Migrate your test files from legacy naming to the new convention:
123
-
124
- ```bash
125
- # Dry run - see what would change
126
- tstest migrate --dry-run
127
-
128
- # Apply migrations (uses git mv to preserve history)
129
- tstest migrate --write
130
- ```
131
-
132
- **Migration Features:**
133
- - ✅ Uses `git mv` to preserve file history
134
- - ✅ Idempotent - safe to run multiple times
135
- - ✅ Dry-run by default for safety
136
- - ✅ Colored output showing all changes
137
- - ✅ Handles modifiers like `.nonci` correctly
138
-
139
- Example output:
140
- ```
141
- ============================================================
142
- Test File Migration Tool
143
- ============================================================
144
-
145
- 🔍 DRY RUN MODE - No files will be modified
146
-
147
- Found 3 legacy test file(s)
148
-
149
- Would migrate:
150
- test.browser.ts
151
- → test.chromium.ts
152
-
153
- Would migrate:
154
- test.both.ts
155
- → test.node+chromium.ts
156
-
157
- Would migrate:
158
- test.auth.browser.nonci.ts
159
- → test.auth.chromium.nonci.ts
160
-
161
- ============================================================
162
- Summary:
163
- Total legacy files: 3
164
- Successfully migrated: 3
165
- Errors: 0
166
- ============================================================
167
-
168
- To apply these changes, run:
169
- tstest migrate --write
170
- ```
171
-
172
- ### Runtime-Specific Permissions
173
-
174
- #### Deno Runtime
62
+ ### 2. Run it
175
63
 
176
- Tests run with these permissions by default:
177
64
  ```bash
178
- --allow-read
179
- --allow-env
180
- --allow-net
181
- --allow-write
182
- --allow-sys
183
- --allow-import # Enables npm packages and Node.js built-ins
184
- --node-modules-dir # Node.js compatibility mode
185
- --sloppy-imports # Allows .js imports to resolve to .ts files
186
- ```
187
-
188
- Configure custom permissions in your test file or via environment variables.
189
-
190
- #### Bun Runtime
191
-
192
- Bun runs with its native TypeScript support and full access to Node.js APIs.
193
-
194
- ## Usage
195
-
196
- ### Basic Test Execution
197
-
198
- ```bash
199
- # Run all tests in a directory
65
+ # Run all tests
200
66
  tstest test/
201
67
 
202
- # Run a specific test file
203
- tstest test/test.mycomponent.ts
68
+ # Run a specific file
69
+ tstest test/test.math.ts
204
70
 
205
71
  # Use glob patterns
206
- tstest "test/**/*.spec.ts"
207
- tstest "test/unit/*.ts"
208
- ```
209
-
210
- ### Execution Modes
72
+ tstest "test/**/*.ts"
211
73
 
212
- **tstest** intelligently detects how you want to run your tests:
74
+ # Verbose mode (shows console output)
75
+ tstest test/ --verbose
213
76
 
214
- 1. **Directory mode** - Recursively finds all test files
215
- 2. **File mode** - Runs a single test file
216
- 3. **Glob mode** - Uses pattern matching for flexible test selection
77
+ # Watch mode
78
+ tstest test/ --watch
79
+ ```
217
80
 
218
- ### Command Line Options
81
+ ### 3. See beautiful output
219
82
 
220
- | Option | Description |
221
- |--------|-------------|
222
- | `--quiet`, `-q` | Minimal output - perfect for CI environments |
223
- | `--verbose`, `-v` | Show all console output from tests |
224
- | `--no-color` | Disable colored output |
225
- | `--json` | Output results as JSON |
226
- | `--logfile` | Save detailed logs with automatic error and diff tracking |
227
- | `--tags <tags>` | Run only tests with specific tags (comma-separated) |
228
- | `--timeout <seconds>` | Timeout test files after specified seconds |
229
- | `--startFrom <n>` | Start running from test file number n |
230
- | `--stopAt <n>` | Stop running at test file number n |
231
- | `--watch`, `-w` | Watch for file changes and re-run tests |
232
- | `--watch-ignore <patterns>` | Ignore patterns in watch mode (comma-separated) |
233
- | `--only` | Run only tests marked with .only |
234
-
235
- ### Example Outputs
236
-
237
- #### Normal Output (Default)
238
83
  ```
239
84
  🔍 Test Discovery
240
85
  Mode: directory
241
86
  Pattern: test
242
87
  Found: 4 test file(s)
243
88
 
244
- ━━━ Part 1: Node.js ━━━
245
-
246
- ▶️ test/test.node+deno.ts (1/4)
89
+ ▶️ test/test.math.ts (1/4)
247
90
  Runtime: Node.js
248
- HTTP request works (12ms)
249
- JSON parsing works (3ms)
91
+ should add numbers (2ms)
92
+ should handle async operations (105ms)
250
93
  Summary: 2/2 PASSED in 1.2s
251
94
 
252
- ━━━ Part 2: Deno ━━━
253
-
254
- ▶️ test/test.node+deno.ts (1/4)
255
- Runtime: Deno
256
- ✅ HTTP request works (15ms)
257
- ✅ JSON parsing works (2ms)
258
- Summary: 2/2 PASSED in 1.1s
259
-
260
95
  📊 Test Summary
261
96
  ┌────────────────────────────────┐
262
97
  │ Total Files: 4 │
@@ -269,331 +104,171 @@ tstest "test/unit/*.ts"
269
104
  ALL TESTS PASSED! 🎉
270
105
  ```
271
106
 
272
- #### Quiet Mode
273
- ```
274
- Found 4 tests
275
- ✅ test functionality works
276
- ✅ api calls return expected data
277
- ✅ error handling works correctly
278
- ✅ performance is within limits
279
-
280
- Summary: 4/4 | 542ms | PASSED
281
- ```
282
-
283
- #### Verbose Mode
284
- Shows all console output from your tests, making debugging easier:
285
- ```
286
- ▶️ test/api.test.ts (1/1)
287
- Runtime: node.js
288
- Making API call to /users...
289
- Response received: 200 OK
290
- Processing user data...
291
- ✅ api calls return expected data (145ms)
292
- Summary: 1/1 PASSED
293
- ```
294
-
295
- #### JSON Mode
296
- Perfect for CI/CD pipelines:
297
- ```json
298
- {"event":"discovery","count":4,"pattern":"test","executionMode":"directory"}
299
- {"event":"fileStart","filename":"test/test.ts","runtime":"node.js","index":1,"total":4}
300
- {"event":"testResult","testName":"prepare test","passed":true,"duration":1}
301
- {"event":"summary","summary":{"totalFiles":4,"totalTests":4,"totalPassed":4,"totalFailed":0,"totalDuration":542}}
302
- ```
303
-
304
- ## Writing Tests with tapbundle
305
-
306
- tstest includes tapbundle, a powerful TAP-based test framework. Import it from the embedded tapbundle:
307
-
308
- ```typescript
309
- import { expect, tap } from '@git.zone/tstest/tapbundle';
310
-
311
- tap.test('my awesome test', async () => {
312
- const result = await myFunction();
313
- expect(result).toEqual('expected value');
314
- });
315
-
316
- export default tap.start();
317
- ```
318
-
319
- **Module Exports**
320
-
321
- tstest provides multiple exports for different use cases:
322
-
323
- - `@git.zone/tstest` - Main CLI and test runner functionality
324
- - `@git.zone/tstest/tapbundle` - Browser-compatible test framework
325
- - `@git.zone/tstest/tapbundle_serverside` - Server-side testing utilities for Node.js-only tests (*.node.ts files)
326
- - Execute shell commands during tests
327
- - Manage environment variables on-demand with secure storage
328
- - Generate self-signed HTTPS certificates for testing secure connections
329
- - Create ephemeral MongoDB instances for database testing
330
- - Create local S3-compatible storage for object storage testing
331
- - Download and manage test assets (e.g., Docker images)
332
- - `@git.zone/tstest/tapbundle_protocol` - Protocol V2 emitter and parser for TAP extensions
333
-
334
- ### When to Use tapbundle_serverside
335
-
336
- Use `@git.zone/tstest/tapbundle_serverside` when your tests:
337
-
338
- - Run exclusively on Node.js server-side (*.node.ts test files)
339
- - Need to execute shell commands or interact with the file system
340
- - Require environment variable management with secure on-demand prompts
341
- - Test HTTPS servers and need self-signed certificates
342
- - Interact with databases (MongoDB) and need ephemeral test instances
343
- - Work with object storage (S3-compatible) and need local testing
344
- - Require test assets like Docker images or other downloadable files
345
-
346
- **Important:** tapbundle_serverside utilities are NOT available in:
347
- - Browser environments
348
- - Deno runtime
349
- - Bun runtime
350
-
351
- For cross-runtime tests, only import tapbundle_serverside in `.node.ts` files where you need server-side specific functionality.
352
-
353
- ## tapbundle Protocol V2
354
-
355
- tstest includes an enhanced TAP protocol (Protocol V2) that extends standard TAP 13 with additional metadata while maintaining backwards compatibility.
356
-
357
- ### Overview
358
-
359
- Protocol V2 adds structured metadata to TAP output using Unicode markers (`⟦TSTEST:...⟧`) that standard TAP parsers safely ignore. This allows for:
360
-
361
- - **Timing information** - Test execution duration in milliseconds
362
- - **Structured errors** - Stack traces, diffs, and detailed error data
363
- - **Test events** - Real-time progress and lifecycle events
364
- - **Snapshots** - Snapshot testing data exchange
365
- - **Custom metadata** - Tags, retry counts, file locations
366
-
367
- ### Using the Protocol
368
-
369
- ```typescript
370
- import {
371
- ProtocolEmitter,
372
- ProtocolParser,
373
- PROTOCOL_MARKERS,
374
- PROTOCOL_VERSION
375
- } from '@git.zone/tstest/tapbundle_protocol';
376
-
377
- // Create an emitter
378
- const emitter = new ProtocolEmitter();
379
-
380
- // Emit protocol header
381
- console.log(emitter.emitProtocolHeader());
382
- // Output: ⟦TSTEST:PROTOCOL:2.0.0⟧
383
-
384
- // Emit TAP version
385
- console.log(emitter.emitTapVersion(13));
386
- // Output: TAP version 13
387
-
388
- // Emit a test result with metadata
389
- const testResult = {
390
- ok: true,
391
- testNumber: 1,
392
- description: 'user authentication works',
393
- metadata: {
394
- time: 123,
395
- tags: ['auth', 'unit']
396
- }
397
- };
398
- console.log(emitter.emitTest(testResult).join('\n'));
399
- // Output: ok 1 - user authentication works ⟦TSTEST:time:123⟧
400
- // ⟦TSTEST:META:{"tags":["auth","unit"]}⟧
401
- ```
402
-
403
- ### Protocol Markers
404
-
405
- ```typescript
406
- PROTOCOL_MARKERS = {
407
- START: '⟦TSTEST:',
408
- END: '⟧',
409
- META_PREFIX: 'META:',
410
- ERROR_PREFIX: 'ERROR',
411
- SNAPSHOT_PREFIX: 'SNAPSHOT:',
412
- SKIP_PREFIX: 'SKIP:',
413
- TODO_PREFIX: 'TODO:',
414
- EVENT_PREFIX: 'EVENT:'
415
- }
416
- ```
417
-
418
- ### Use Cases
419
-
420
- #### Creating Custom Test Runners
421
-
422
- ```typescript
423
- import { ProtocolEmitter } from '@git.zone/tstest/tapbundle_protocol';
107
+ ## Multi-Runtime Architecture
424
108
 
425
- const emitter = new ProtocolEmitter();
109
+ tstest supports running your tests across **four JavaScript runtimes**, letting you verify cross-platform compatibility with zero extra effort.
426
110
 
427
- // Emit header and version
428
- console.log(emitter.emitProtocolHeader());
429
- console.log(emitter.emitTapVersion(13));
430
- console.log(emitter.emitPlan({ start: 1, end: 2 }));
111
+ ### Test File Naming Convention
431
112
 
432
- // Run your tests and emit results
433
- const startTime = Date.now();
434
- // ... run test ...
435
- const duration = Date.now() - startTime;
113
+ Name your test files with runtime specifiers to control where they run:
436
114
 
437
- console.log(emitter.emitTest({
438
- ok: true,
439
- testNumber: 1,
440
- description: 'my custom test',
441
- metadata: { time: duration }
442
- }).join('\n'));
443
- ```
115
+ | Pattern | Runtimes | Example |
116
+ |---------|----------|---------|
117
+ | `*.ts` | Node.js (default) | `test.api.ts` |
118
+ | `*.node.ts` | Node.js only | `test.server.node.ts` |
119
+ | `*.chromium.ts` | Chromium browser | `test.dom.chromium.ts` |
120
+ | `*.deno.ts` | Deno | `test.http.deno.ts` |
121
+ | `*.bun.ts` | Bun | `test.fast.bun.ts` |
122
+ | `*.all.ts` | All runtimes | `test.universal.all.ts` |
123
+ | `*.node+chromium.ts` | Node.js + Chromium | `test.isomorphic.node+chromium.ts` |
124
+ | `*.node+deno.ts` | Node.js + Deno | `test.cross.node+deno.ts` |
125
+ | `*.chromium.nonci.ts` | Chromium, skip in CI | `test.visual.chromium.nonci.ts` |
444
126
 
445
- #### Parsing tapbundle Output
127
+ ### Runtime Execution Order
446
128
 
447
- ```typescript
448
- import { ProtocolParser } from '@git.zone/tstest/tapbundle_protocol';
129
+ When multiple runtimes are specified, tests execute in this order: **Node.js → Bun → Deno → Chromium**
449
130
 
450
- const parser = new ProtocolParser();
131
+ ### Migration from Legacy Naming
451
132
 
452
- // Parse TAP output line by line
453
- parser.parseLine('⟦TSTEST:PROTOCOL:2.0.0⟧');
454
- parser.parseLine('TAP version 13');
455
- parser.parseLine('1..1');
456
- parser.parseLine('ok 1 - test name ⟦TSTEST:time:123⟧');
133
+ ```bash
134
+ # Dry run — see what would change
135
+ tstest migrate --dry-run
457
136
 
458
- // Get parsed results
459
- const results = parser.getResults();
460
- console.log(results);
137
+ # Apply migrations (uses git mv to preserve history)
138
+ tstest migrate --write
461
139
  ```
462
140
 
463
- ### Backwards Compatibility
141
+ | Legacy Pattern | Modern Equivalent |
142
+ |---|---|
143
+ | `*.browser.ts` | `*.chromium.ts` |
144
+ | `*.both.ts` | `*.node+chromium.ts` |
464
145
 
465
- Protocol V2 is fully backwards compatible with standard TAP 13. The Unicode markers are treated as comments by standard TAP parsers, so Protocol V2 output can be consumed by any TAP-compliant tool:
146
+ ## CLI Options
466
147
 
467
- ```
468
- ⟦TSTEST:PROTOCOL:2.0.0⟧ ← Ignored by standard TAP parsers
469
- TAP version 13 ← Standard TAP
470
- 1..2 ← Standard TAP
471
- ok 1 - test ⟦TSTEST:time:45⟧ TAP parsers see: "ok 1 - test"
472
- ok 2 - another test ← Standard TAP
473
- ```
148
+ | Option | Description |
149
+ |---|---|
150
+ | `--quiet`, `-q` | Minimal output — perfect for CI |
151
+ | `--verbose`, `-v` | Show all console output from tests |
152
+ | `--no-color` | Disable colored output |
153
+ | `--json` | Output results as JSON (CI/CD pipelines) |
154
+ | `--logfile` | Save detailed logs with error/diff tracking |
155
+ | `--tags <tags>` | Run only tests with specific tags |
156
+ | `--timeout <seconds>` | Timeout test files after N seconds |
157
+ | `--startFrom <n>` | Start from test file number N |
158
+ | `--stopAt <n>` | Stop at test file number N |
159
+ | `--watch`, `-w` | Re-run tests on file changes |
160
+ | `--watch-ignore <patterns>` | Ignore patterns in watch mode |
161
+ | `--only` | Run only tests marked with `.only` |
474
162
 
475
- ## tapbundle Test Framework
163
+ ## Writing Tests with tapbundle
476
164
 
477
- ### Basic Test Syntax
165
+ ### Basic Syntax
478
166
 
479
167
  ```typescript
480
168
  import { tap, expect } from '@git.zone/tstest/tapbundle';
481
169
 
482
- // Basic test
483
- tap.test('should perform basic arithmetic', async () => {
170
+ tap.test('basic test', async () => {
484
171
  expect(2 + 2).toEqual(4);
485
172
  });
486
173
 
487
- // Async test with tools
488
- tap.test('async operations', async (tools) => {
489
- await tools.delayFor(100); // delay for 100ms
490
- const result = await fetchData();
491
- expect(result).toBeDefined();
174
+ tap.test('with tools', async (tools) => {
175
+ await tools.delayFor(100);
176
+ tools.timeout(5000);
177
+ expect(true).toBeTrue();
492
178
  });
493
179
 
494
- // Start test execution
495
180
  export default tap.start();
496
181
  ```
497
182
 
498
- ### Test Modifiers and Chaining
183
+ ### Test Modifiers
499
184
 
500
185
  ```typescript
501
- // Skip a test
502
- tap.skip.test('not ready yet', async () => {
503
- // This test will be skipped
504
- });
186
+ // Skip
187
+ tap.skip.test('not ready yet', async () => { /* skipped */ });
505
188
 
506
- // Run only this test (exclusive)
507
- tap.only.test('focus on this', async () => {
508
- // Only this test will run
509
- });
189
+ // Only (exclusive)
190
+ tap.only.test('focus on this', async () => { /* only this runs */ });
510
191
 
511
- // Todo test - creates actual test object marked as todo
512
- tap.todo.test('implement later', async () => {
513
- // This test will be counted but marked as todo
514
- });
192
+ // Todo
193
+ tap.todo.test('implement later', async () => { /* marked as todo */ });
515
194
 
516
- // Chaining modifiers
195
+ // Fluent chaining
517
196
  tap.timeout(5000)
518
197
  .retry(3)
519
198
  .tags('api', 'integration')
520
- .test('complex test', async (tools) => {
521
- // Test with 5s timeout, 3 retries, and tags
522
- });
199
+ .test('complex test', async (tools) => { /* configured */ });
523
200
  ```
524
201
 
525
202
  ### Test Organization with describe()
526
203
 
527
204
  ```typescript
528
205
  tap.describe('User Management', () => {
529
- let testDatabase;
530
-
531
206
  tap.beforeEach(async () => {
532
- testDatabase = await createTestDB();
207
+ // setup before each test
533
208
  });
534
209
 
535
210
  tap.afterEach(async () => {
536
- await testDatabase.cleanup();
211
+ // cleanup after each test
537
212
  });
538
213
 
539
- tap.test('should create user', async () => {
540
- const user = await testDatabase.createUser({ name: 'John' });
541
- expect(user.id).toBeDefined();
542
- });
214
+ tap.test('should create user', async () => { /* ... */ });
215
+ tap.test('should delete user', async () => { /* ... */ });
543
216
 
544
- tap.describe('User Permissions', () => {
545
- tap.test('should set admin role', async () => {
546
- // Nested describe blocks
547
- });
217
+ tap.describe('Permissions', () => {
218
+ tap.test('should set admin role', async () => { /* ... */ });
548
219
  });
549
220
  });
550
221
  ```
551
222
 
552
- ### Test Tools (Available in Test Function)
553
-
554
- Every test function receives a `tools` parameter with utilities:
223
+ ### Pre-Tasks and Post-Tasks
555
224
 
556
225
  ```typescript
557
- tap.test('using test tools', async (tools) => {
558
- // Delay utilities
559
- await tools.delayFor(1000); // delay for 1000ms
560
- await tools.delayForRandom(100, 500); // random delay between 100-500ms
226
+ tap.preTask('setup database', async () => {
227
+ await initializeDatabase();
228
+ });
561
229
 
562
- // Skip test conditionally
563
- tools.skipIf(process.env.CI === 'true', 'Skipping in CI');
230
+ tap.test('uses the database', async () => { /* ... */ });
231
+
232
+ tap.postTask('cleanup database', async () => {
233
+ await cleanupDatabase();
234
+ });
235
+ ```
564
236
 
565
- // Skip test unconditionally
566
- if (!apiKeyAvailable) {
567
- tools.skip('API key not available');
568
- }
237
+ ### Test Tools
569
238
 
570
- // Mark as todo
571
- tools.todo('Needs implementation');
239
+ Every test function receives a `tools` parameter packed with utilities:
240
+
241
+ ```typescript
242
+ tap.test('tools demo', async (tools) => {
243
+ // ⏱️ Delays
244
+ await tools.delayFor(1000);
245
+ await tools.delayForRandom(100, 500);
572
246
 
573
- // Retry configuration
574
- tools.retry(3); // Set retry count
247
+ // ⏭️ Skip
248
+ tools.skipIf(process.env.CI === 'true', 'Skipping in CI');
249
+ tools.skip('reason');
575
250
 
576
- // Timeout configuration
577
- tools.timeout(10000); // Set timeout to 10s
251
+ // 🔁 Retry & timeout
252
+ tools.retry(3);
253
+ tools.timeout(10000);
578
254
 
579
- // Context sharing between tests
255
+ // 📦 Context sharing between tests
580
256
  tools.context.set('userId', 12345);
581
257
  const userId = tools.context.get('userId');
582
258
 
583
- // Deferred promises
259
+ // 🔮 Deferred promises
584
260
  const deferred = tools.defer();
585
261
  setTimeout(() => deferred.resolve('done'), 100);
586
262
  await deferred.promise;
587
263
 
588
- // Colored console output
589
- const coloredString = await tools.coloredString('Success!', 'green');
590
- console.log(coloredString);
591
-
592
- // Error handling helper
264
+ // 🎯 Error capture
593
265
  const error = await tools.returnError(async () => {
594
266
  throw new Error('Expected error');
595
267
  });
596
268
  expect(error).toBeInstanceOf(Error);
269
+
270
+ // ✅ Allow failure (test won't fail the suite)
271
+ tools.allowFailure();
597
272
  });
598
273
  ```
599
274
 
@@ -602,41 +277,25 @@ tap.test('using test tools', async (tools) => {
602
277
  ```typescript
603
278
  tap.test('snapshot test', async (tools) => {
604
279
  const output = generateComplexOutput();
605
-
606
- // Compare with saved snapshot
607
280
  await tools.matchSnapshot(output);
608
-
609
- // Named snapshots for multiple checks in one test
610
281
  await tools.matchSnapshot(output.header, 'header');
611
- await tools.matchSnapshot(output.body, 'body');
612
282
  });
613
283
 
614
- // Update snapshots with: UPDATE_SNAPSHOTS=true tstest test/
284
+ // Update snapshots: UPDATE_SNAPSHOTS=true tstest test/
615
285
  ```
616
286
 
617
287
  ### Test Fixtures
618
288
 
619
289
  ```typescript
620
- // Define reusable fixtures
621
290
  tap.defineFixture('testUser', async (data) => ({
622
291
  id: Date.now(),
623
292
  name: data?.name || 'Test User',
624
293
  email: data?.email || 'test@example.com',
625
- created: new Date()
626
- }));
627
-
628
- tap.defineFixture('testPost', async (data) => ({
629
- id: Date.now(),
630
- title: data?.title || 'Test Post',
631
- authorId: data?.authorId || 1
632
294
  }));
633
295
 
634
- // Use fixtures in tests
635
296
  tap.test('fixture test', async (tools) => {
636
297
  const user = await tools.fixture('testUser', { name: 'John' });
637
- const post = await tools.fixture('testPost', { authorId: user.id });
638
-
639
- expect(post.authorId).toEqual(user.id);
298
+ expect(user.name).toEqual('John');
640
299
 
641
300
  // Factory pattern for multiple instances
642
301
  const users = await tools.factory('testUser').createMany(5);
@@ -644,274 +303,165 @@ tap.test('fixture test', async (tools) => {
644
303
  });
645
304
  ```
646
305
 
647
- ### Parallel Test Execution
306
+ ### Parallel Execution
648
307
 
649
308
  ```typescript
650
- // Parallel tests within a file
651
- tap.testParallel('parallel test 1', async () => {
652
- await heavyOperation();
653
- });
309
+ // Within a file
310
+ tap.parallel().test('parallel test 1', async () => { /* ... */ });
311
+ tap.parallel().test('parallel test 2', async () => { /* ... */ });
654
312
 
655
- tap.testParallel('parallel test 2', async () => {
656
- await anotherHeavyOperation();
657
- });
658
-
659
- // File naming for parallel groups
660
- // test.api.para__1.ts - runs in parallel with other para__1 files
661
- // test.db.para__1.ts - runs in parallel with other para__1 files
662
- // test.auth.para__2.ts - runs after para__1 group completes
313
+ // Across files same suffix = parallel group
314
+ // test.api.para__1.ts ←─ run together
315
+ // test.db.para__1.ts ←─ run together
316
+ // test.auth.para__2.ts ←─ runs after para__1 completes
663
317
  ```
664
318
 
665
- ### Assertions with expect()
319
+ ### Assertions (expect)
666
320
 
667
- tapbundle uses @push.rocks/smartexpect for assertions:
321
+ tapbundle uses [@push.rocks/smartexpect](https://code.foss.global/push.rocks/smartexpect) for assertions with automatic diff generation on failures:
668
322
 
669
323
  ```typescript
670
- // Basic assertions
324
+ // Equality
671
325
  expect(value).toEqual(5);
672
- expect(value).not.toEqual(10);
673
326
  expect(obj).toDeepEqual({ a: 1, b: 2 });
674
327
 
675
- // Type assertions
328
+ // Types
676
329
  expect('hello').toBeTypeofString();
677
330
  expect(42).toBeTypeofNumber();
678
- expect(true).toBeTypeofBoolean();
679
331
  expect([]).toBeArray();
680
- expect({}).toBeTypeOf('object');
681
332
 
682
- // Comparison assertions
333
+ // Comparisons
683
334
  expect(5).toBeGreaterThan(3);
684
- expect(3).toBeLessThan(5);
685
- expect(5).toBeGreaterThanOrEqual(5);
686
- expect(5).toBeLessThanOrEqual(5);
687
335
  expect(0.1 + 0.2).toBeCloseTo(0.3, 10);
688
336
 
689
337
  // Truthiness
690
338
  expect(true).toBeTrue();
691
- expect(false).toBeFalse();
692
- expect('text').toBeTruthy();
693
- expect(0).toBeFalsy();
694
339
  expect(null).toBeNull();
695
340
  expect(undefined).toBeUndefined();
696
- expect(null).toBeNullOrUndefined();
697
341
 
698
- // String assertions
342
+ // Strings
699
343
  expect('hello world').toStartWith('hello');
700
344
  expect('hello world').toEndWith('world');
701
345
  expect('hello world').toInclude('lo wo');
702
346
  expect('hello world').toMatch(/^hello/);
703
- expect('option').toBeOneOf(['choice', 'option', 'alternative']);
704
347
 
705
- // Array assertions
348
+ // Arrays
706
349
  expect([1, 2, 3]).toContain(2);
707
350
  expect([1, 2, 3]).toContainAll([1, 3]);
708
- expect([1, 2, 3]).toExclude(4);
709
351
  expect([1, 2, 3]).toHaveLength(3);
710
- expect([]).toBeEmptyArray();
711
- expect([{ id: 1 }]).toContainEqual({ id: 1 });
712
352
 
713
- // Object assertions
353
+ // Objects
714
354
  expect(obj).toHaveProperty('name');
715
- expect(obj).toHaveProperty('user.email', 'test@example.com');
716
- expect(obj).toHaveDeepProperty(['level1', 'level2']);
717
355
  expect(obj).toMatchObject({ name: 'John' });
718
356
 
719
- // Function assertions
720
- expect(() => { throw new Error('test'); }).toThrow();
721
- expect(() => { throw new Error('test'); }).toThrow(Error);
722
- expect(() => { throw new Error('test error'); }).toThrowErrorMatching(/test/);
723
- expect(myFunction).not.toThrow();
724
-
725
- // Promise assertions
726
- await expect(Promise.resolve('value')).resolves.toEqual('value');
727
- await expect(Promise.reject(new Error('fail'))).rejects.toThrow();
728
-
729
- // Custom assertions
730
- expect(7).customAssertion(
731
- value => value % 2 === 1,
732
- 'Value is not odd'
733
- );
357
+ // Functions & Promises
358
+ expect(() => { throw new Error(); }).toThrow();
359
+ await expect(Promise.resolve('val')).resolves.toEqual('val');
360
+ await expect(Promise.reject(new Error())).rejects.toThrow();
361
+
362
+ // Custom
363
+ expect(7).customAssertion(v => v % 2 === 1, 'Value is not odd');
734
364
  ```
735
365
 
736
- ### Pre-tasks
366
+ ## Server-Side Tools (tapbundle_serverside)
737
367
 
738
- Run setup tasks before tests start:
368
+ For Node.js-only tests, import server-side utilities:
739
369
 
740
370
  ```typescript
741
- tap.preTask('setup database', async () => {
742
- await initializeTestDatabase();
743
- console.log('Database initialized');
744
- });
745
-
746
- tap.preTask('load environment', async () => {
747
- await loadTestEnvironment();
748
- });
749
-
750
- // Pre-tasks run in order before any tests
371
+ import { tapNodeTools } from '@git.zone/tstest/tapbundle_serverside';
372
+ import { tap, expect } from '@git.zone/tstest/tapbundle';
751
373
  ```
752
374
 
753
- ### Tag-based Test Filtering
375
+ ### 🌐 Network Utilities
754
376
 
755
- ```typescript
756
- // Tag individual tests
757
- tap.tags('unit', 'api')
758
- .test('api unit test', async () => {
759
- // Test code
760
- });
761
-
762
- tap.tags('integration', 'slow')
763
- .test('database integration', async () => {
764
- // Test code
765
- });
766
-
767
- // Run only tests with specific tags
768
- // tstest test/ --tags unit,api
769
- ```
377
+ Find free local ports for test servers — no more port conflicts:
770
378
 
771
- ### Context Sharing
379
+ ```typescript
380
+ tap.test('should start server on free port', async () => {
381
+ // Single free port (random in range 3000–60000)
382
+ const port = await tapNodeTools.findFreePort();
772
383
 
773
- Share data between tests:
384
+ // Custom range
385
+ const port2 = await tapNodeTools.findFreePort({ startPort: 8000, endPort: 9000 });
774
386
 
775
- ```typescript
776
- tap.test('first test', async (tools) => {
777
- const sessionId = await createSession();
778
- tools.context.set('sessionId', sessionId);
387
+ // With exclusions
388
+ const port3 = await tapNodeTools.findFreePort({ exclude: [8080, 8443] });
779
389
  });
780
390
 
781
- tap.test('second test', async (tools) => {
782
- const sessionId = tools.context.get('sessionId');
783
- expect(sessionId).toBeDefined();
391
+ tap.test('should allocate multiple ports', async () => {
392
+ // Multiple distinct ports
393
+ const [httpPort, wsPort, adminPort] = await tapNodeTools.findFreePorts(3);
784
394
 
785
- // Cleanup
786
- tools.context.delete('sessionId');
395
+ // Consecutive port range (e.g., 4000, 4001, 4002)
396
+ const portRange = await tapNodeTools.findFreePortRange(3, {
397
+ startPort: 20000,
398
+ endPort: 30000,
399
+ });
787
400
  });
788
401
  ```
789
402
 
790
- ### Browser Testing with webhelpers
403
+ ### 🔒 HTTPS Certificates
791
404
 
792
- For browser-specific tests:
405
+ Generate self-signed certs for testing secure connections:
793
406
 
794
407
  ```typescript
795
- import { tap, webhelpers } from '@git.zone/tstest/tapbundle';
796
-
797
- tap.test('DOM manipulation', async () => {
798
- // Create DOM elements from HTML strings
799
- const element = await webhelpers.fixture(webhelpers.html`
800
- <div class="test-container">
801
- <h1>Test Title</h1>
802
- <button id="test-btn">Click Me</button>
803
- </div>
804
- `);
805
-
806
- expect(element.querySelector('h1').textContent).toEqual('Test Title');
807
-
808
- // Simulate interactions
809
- const button = element.querySelector('#test-btn');
810
- button.click();
408
+ tap.test('should serve over HTTPS', async () => {
409
+ const { key, cert } = await tapNodeTools.createHttpsCert('localhost');
410
+ const server = https.createServer({ key, cert }, handler);
411
+ server.listen(port);
811
412
  });
413
+ ```
812
414
 
813
- tap.test('CSS testing', async () => {
814
- const styles = webhelpers.css`
815
- .test-class {
816
- color: red;
817
- font-size: 16px;
818
- }
819
- `;
415
+ ### 💻 Shell Commands
820
416
 
821
- // styles is a string that can be injected into the page
822
- expect(styles).toInclude('color: red');
823
- });
417
+ ```typescript
418
+ const result = await tapNodeTools.runCommand('ls -la');
419
+ console.log(result.exitCode); // 0
824
420
  ```
825
421
 
826
- ### Advanced Error Handling
422
+ ### 🔐 Environment Variables
827
423
 
828
424
  ```typescript
829
- tap.test('error handling', async (tools) => {
830
- // Capture errors without failing the test
831
- const error = await tools.returnError(async () => {
832
- await functionThatThrows();
833
- });
834
-
835
- expect(error).toBeInstanceOf(Error);
836
- expect(error.message).toEqual('Expected error message');
837
- });
425
+ const apiKey = await tapNodeTools.getEnvVarOnDemand('GITHUB_API_KEY');
426
+ // Prompts if not set, stores in .nogit/.env for future use
838
427
  ```
839
428
 
840
- ### Test Wrap
841
-
842
- Create wrapped test environments:
429
+ ### 🗄️ Ephemeral MongoDB
843
430
 
844
431
  ```typescript
845
- import { TapWrap } from '@git.zone/tstest/tapbundle';
432
+ const mongo = await tapNodeTools.createSmartmongo();
433
+ // ... run database tests ...
434
+ await mongo.stop();
435
+ ```
846
436
 
847
- const tapWrap = new TapWrap({
848
- before: async () => {
849
- console.log('Before all tests');
850
- await globalSetup();
851
- },
852
- after: async () => {
853
- console.log('After all tests');
854
- await globalCleanup();
855
- }
856
- });
437
+ ### 📦 Local S3 Storage
857
438
 
858
- // Tests registered here will have the wrap lifecycle
859
- tapWrap.tap.test('wrapped test', async () => {
860
- // This test runs with the wrap setup/teardown
861
- });
439
+ ```typescript
440
+ const s3 = await tapNodeTools.createSmarts3();
441
+ // ... run object storage tests ...
442
+ await s3.stop();
862
443
  ```
863
444
 
864
445
  ## Advanced Features
865
446
 
866
447
  ### Watch Mode
867
448
 
868
- Automatically re-run tests when files change:
869
-
870
449
  ```bash
871
- # Watch all files in the project
872
450
  tstest test/ --watch
873
-
874
- # Watch with custom ignore patterns
875
451
  tstest test/ --watch --watch-ignore "dist/**,coverage/**"
876
-
877
- # Short form
878
- tstest test/ -w
879
452
  ```
880
453
 
881
- **Features:**
882
454
  - 👀 Shows which files triggered the re-run
883
455
  - ⏱️ 300ms debouncing to batch rapid changes
884
- - 🔄 Clears console between runs for clean output
885
- - 📁 Intelligently ignores common non-source files
456
+ - 🔄 Clears console between runs
886
457
 
887
- ### Real-time Test Progress
458
+ ### Visual Diffs
888
459
 
889
- tstest provides real-time updates during test execution:
890
-
891
- ```
892
- ▶️ test/api.test.ts (1/4)
893
- Runtime: node.js
894
- ⏳ Running: api endpoint validation...
895
- ✅ api endpoint validation (145ms)
896
- ⏳ Running: error handling...
897
- ✅ error handling (23ms)
898
- Summary: 2/2 PASSED
899
- ```
900
-
901
- ### Visual Diffs for Failed Assertions
902
-
903
- When assertions fail, tstest shows beautiful side-by-side diffs:
460
+ When assertions fail, you get beautiful diffs:
904
461
 
905
462
  ```
906
463
  ❌ should return correct user data
907
464
 
908
- String Diff:
909
- - Expected
910
- + Received
911
-
912
- - Hello World
913
- + Hello Universe
914
-
915
465
  Object Diff:
916
466
  {
917
467
  name: "John",
@@ -921,255 +471,110 @@ When assertions fail, tstest shows beautiful side-by-side diffs:
921
471
  }
922
472
  ```
923
473
 
924
- ### Enhanced Test Logging
925
-
926
- The `--logfile` option provides intelligent test logging with automatic organization:
474
+ ### Enhanced Logging
927
475
 
928
476
  ```bash
929
477
  tstest test/ --logfile
930
478
  ```
931
479
 
932
- **Log Organization:**
933
- - **Current Run**: `.nogit/testlogs/[testname].log`
934
- - **Previous Run**: `.nogit/testlogs/previous/[testname].log`
935
- - **Failed Tests**: `.nogit/testlogs/00err/[testname].log`
936
- - **Changed Output**: `.nogit/testlogs/00diff/[testname].log`
480
+ | Folder | Contents |
481
+ |---|---|
482
+ | `.nogit/testlogs/` | Current run logs |
483
+ | `.nogit/testlogs/previous/` | Previous run logs |
484
+ | `.nogit/testlogs/00err/` | Failed test logs |
485
+ | `.nogit/testlogs/00diff/` | Changed output diffs |
937
486
 
938
- **Features:**
939
- - Previous logs are automatically moved to the `previous/` folder
940
- - Failed tests create copies in `00err/` for quick identification
941
- - Tests with changed output create diff reports in `00diff/`
942
- - The `00err/` and `00diff/` folders are cleared on each run
487
+ ### JSON Output (CI/CD)
943
488
 
944
- **Example Diff Report:**
489
+ ```bash
490
+ tstest test/ --json > test-results.json
945
491
  ```
946
- DIFF REPORT: test__api__integration.log
947
- Generated: 2025-05-24T01:29:13.847Z
948
- ================================================================================
949
492
 
950
- - [Line 8] ✅ api test passes (150ms)
951
- + [Line 8] ✅ api test passes (165ms)
952
-
953
- ================================================================================
954
- Previous version had 40 lines
955
- Current version has 40 lines
493
+ ```json
494
+ {"event":"discovery","count":4,"pattern":"test","executionMode":"directory"}
495
+ {"event":"testResult","testName":"prepare test","passed":true,"duration":1}
496
+ {"event":"summary","summary":{"totalFiles":4,"totalTests":4,"totalPassed":4,"totalFailed":0}}
956
497
  ```
957
498
 
958
- ### Test Timeout Protection
959
-
960
- Prevent runaway tests with the `--timeout` option:
961
-
962
- ```bash
963
- # Timeout any test file that runs longer than 60 seconds
964
- tstest test/ --timeout 60
499
+ ### Tag Filtering
965
500
 
966
- # Shorter timeout for unit tests
967
- tstest test/unit/ --timeout 10
501
+ ```typescript
502
+ tap.tags('unit', 'api').test('api unit test', async () => { /* ... */ });
968
503
  ```
969
504
 
970
- When a test exceeds the timeout:
971
- - The test process is terminated (SIGTERM)
972
- - The test is marked as failed
973
- - An error log is created in `.nogit/testlogs/00err/`
974
- - Clear error message shows the timeout duration
975
-
976
- ### Test File Range Control
977
-
978
- Run specific ranges of test files using `--startFrom` and `--stopAt`:
979
-
980
505
  ```bash
981
- # Run tests starting from the 5th file
982
- tstest test/ --startFrom 5
983
-
984
- # Run only files 5 through 10
985
- tstest test/ --startFrom 5 --stopAt 10
986
-
987
- # Run only the first 3 test files
988
- tstest test/ --stopAt 3
989
- ```
990
-
991
- This is particularly useful for:
992
- - Debugging specific test failures in large test suites
993
- - Running tests in chunks on different CI runners
994
- - Quickly testing changes to specific test files
995
-
996
- The output shows which files are skipped:
997
- ```
998
- ⏭️ test/auth.test.ts (1/10)
999
- Skipped: before start range (5)
1000
- ⏭️ test/user.test.ts (2/10)
1001
- Skipped: before start range (5)
1002
- ▶️ test/api.test.ts (5/10)
1003
- Runtime: node.js
1004
- ✅ api endpoints work (145ms)
506
+ tstest test/ --tags unit,api
1005
507
  ```
1006
508
 
1007
- ### Performance Analysis
509
+ ### Test File Range
1008
510
 
1009
- In verbose mode, see performance metrics:
1010
- ```
1011
- ⏱️ Performance Metrics:
1012
- Average per test: 135ms
1013
- Slowest test: api integration test (486ms)
511
+ ```bash
512
+ tstest test/ --startFrom 5 --stopAt 10 # Run files 5-10 only
1014
513
  ```
1015
514
 
1016
- ### Parallel Test Groups
515
+ ### Browser Testing with webhelpers
1017
516
 
1018
- Tests can be organized into parallel groups for concurrent execution:
517
+ ```typescript
518
+ import { tap, webhelpers } from '@git.zone/tstest/tapbundle';
1019
519
 
1020
- ```
1021
- ━━━ Parallel Group: para__1 ━━━
1022
- ▶️ test/auth.para__1.ts
1023
- ▶️ test/user.para__1.ts
1024
- ... tests run concurrently ...
1025
- ──────────────────────────────────
1026
-
1027
- ━━━ Parallel Group: para__2 ━━━
1028
- ▶️ test/db.para__2.ts
1029
- ▶️ test/api.para__2.ts
1030
- ... tests run concurrently ...
1031
- ──────────────────────────────────
520
+ tap.test('DOM test', async () => {
521
+ const element = await webhelpers.fixture(webhelpers.html`
522
+ <div class="container">
523
+ <h1>Hello</h1>
524
+ </div>
525
+ `);
526
+ expect(element.querySelector('h1').textContent).toEqual('Hello');
527
+ });
1032
528
  ```
1033
529
 
1034
- Files with the same parallel group suffix (e.g., `para__1`) run simultaneously, while different groups run sequentially.
1035
-
1036
- ### CI/CD Integration
1037
-
1038
- For continuous integration, combine quiet and JSON modes:
1039
- ```bash
1040
- # GitHub Actions example
1041
- tstest test/ --json > test-results.json
530
+ ### TapWrap (Global Lifecycle)
1042
531
 
1043
- # Or minimal output
1044
- tstest test/ --quiet
1045
- ```
532
+ ```typescript
533
+ import { TapWrap } from '@git.zone/tstest/tapbundle';
1046
534
 
1047
- **Advanced CI Example:**
1048
- ```bash
1049
- # Run tests with comprehensive logging and safety features
1050
- tstest test/ \
1051
- --timeout 300 \
1052
- --logfile \
1053
- --json > test-results.json
1054
-
1055
- # Run specific test chunks in parallel CI jobs
1056
- tstest test/ --startFrom 1 --stopAt 10 # Job 1
1057
- tstest test/ --startFrom 11 --stopAt 20 # Job 2
1058
- tstest test/ --startFrom 21 # Job 3
535
+ const tapWrap = new TapWrap({
536
+ before: async () => { await globalSetup(); },
537
+ after: async () => { await globalCleanup(); },
538
+ });
1059
539
  ```
1060
540
 
1061
- ### Debugging Failed Tests
1062
-
1063
- When tests fail, use the enhanced logging features:
541
+ ## tapbundle Protocol V2
1064
542
 
1065
- ```bash
1066
- # Run with logging to capture detailed output
1067
- tstest test/ --logfile --verbose
543
+ tstest includes an enhanced TAP protocol that extends TAP 13 with structured metadata while staying backwards compatible. Protocol markers (`⟦TSTEST:...⟧`) are invisible to standard TAP parsers.
1068
544
 
1069
- # Check error logs
1070
- ls .nogit/testlogs/00err/
545
+ ```typescript
546
+ import { ProtocolEmitter, ProtocolParser } from '@git.zone/tstest/tapbundle_protocol';
1071
547
 
1072
- # Review diffs for flaky tests
1073
- cat .nogit/testlogs/00diff/test__api__endpoints.log
548
+ // Emit
549
+ const emitter = new ProtocolEmitter();
550
+ console.log(emitter.emitProtocolHeader()); // ⟦TSTEST:PROTOCOL:2.0.0⟧
551
+ console.log(emitter.emitTest({
552
+ ok: true, testNumber: 1, description: 'test',
553
+ metadata: { time: 42, tags: ['unit'] }
554
+ }).join('\n'));
1074
555
 
1075
- # Re-run specific failed tests
1076
- tstest test/api/endpoints.test.ts --verbose --timeout 60
556
+ // Parse
557
+ const parser = new ProtocolParser();
558
+ const messages = parser.parseLine('ok 1 - test ⟦TSTEST:time:42⟧');
1077
559
  ```
1078
560
 
1079
- ## Changelog
1080
-
1081
- ### Version 3.1.1
1082
- - 🐛 Fixed TapTools parameter passing to suite lifecycle hooks (beforeAll/afterAll)
1083
- - 📦 Updated @push.rocks/smarts3 dependency to ^3.0.0
1084
-
1085
- ### Version 3.1.0
1086
- - 🎯 **postTask() API** - Global teardown method for cleanup after all tests
1087
- - 🏗️ **Suite beforeAll/afterAll** - Lifecycle hooks that run once per describe block
1088
- - ⚡ **parallel() Fluent API** - New fluent entry point for parallel tests
1089
- - 📚 Enhanced tapbundle documentation with complete API reference
1090
-
1091
- ### Version 3.0.0
1092
- - 🔥 **BREAKING:** Renamed tapbundle_node to tapbundle_serverside for clarity
1093
- - 🔧 Migrated all server-side utilities to tapbundle_serverside
1094
- - 📦 Improved module separation and organization
1095
-
1096
- ### Version 2.4.0
1097
- - 🚀 **Multi-Runtime Architecture** - Support for Deno, Bun, Node.js, and Chromium
1098
- - 🔀 **New Naming Convention** - Flexible `.runtime1+runtime2.ts` pattern
1099
- - 🌐 **Universal Testing** - `.all.ts` pattern runs tests on all supported runtimes
1100
- - 🔄 **Migration Tool** - Easy migration from legacy naming (`.browser.ts`, `.both.ts`)
1101
- - 🦕 **Deno Support** - Full Deno runtime with Node.js compatibility
1102
- - 🐰 **Bun Support** - Ultra-fast Bun runtime integration
1103
- - ⚡ **Dynamic Port Selection** - Random port allocation (30000-40000) prevents conflicts
1104
- - 🏗️ **Runtime Adapter Pattern** - Extensible architecture for adding new runtimes
1105
- - 📝 **Deprecation Warnings** - Helpful migration suggestions for legacy naming
1106
- - ✅ **Comprehensive Tests** - Full test coverage for parser and migration tool
1107
-
1108
- ### Version 1.11.0
1109
- - 👀 Added Watch Mode with `--watch`/`-w` flag for automatic test re-runs
1110
- - 📊 Implemented real-time test progress updates with event streaming
1111
- - 🎨 Added visual diffs for failed assertions with side-by-side comparison
1112
- - 🔄 Enhanced event-based test lifecycle reporting
1113
- - ⚙️ Added test configuration system with `.tstest.json` files
1114
- - 🚀 Implemented Protocol V2 with Unicode delimiters for better TAP parsing
1115
- - 🐛 Fixed `tap.todo()` to create proper test objects
1116
- - 🐛 Fixed `tap.skip.test()` to correctly create and count test objects
1117
- - 🐛 Fixed `tap.only.test()` implementation with `--only` flag support
1118
- - 📁 Added settings inheritance for cascading test configuration
1119
- - ⏱️ Added debouncing for file change events in watch mode
1120
-
1121
- ### Version 1.10.0
1122
- - ⏱️ Added `--timeout <seconds>` option for test file timeout protection
1123
- - 🎯 Added `--startFrom <n>` and `--stopAt <n>` options for test file range control
1124
- - 📁 Enhanced `--logfile` with intelligent log organization:
1125
- - Previous logs moved to `previous/` folder
1126
- - Failed tests copied to `00err/` folder
1127
- - Changed tests create diff reports in `00diff/` folder
1128
- - 🔍 Improved test discovery to show skipped files with clear reasons
1129
- - 🐛 Fixed TypeScript compilation warnings and unused variables
1130
- - 📊 Test summaries now include skipped file counts
1131
-
1132
- ### Version 1.9.2
1133
- - 🐛 Fixed test timing display issue (removed duplicate timing in output)
1134
- - 📝 Improved internal protocol design documentation
1135
- - 🔧 Added protocol v2 utilities for future improvements
1136
-
1137
- ### Version 1.9.1
1138
- - 🐛 Fixed log file naming to preserve directory structure
1139
- - 📁 Log files now prevent collisions: `test__dir__file.log`
1140
-
1141
- ### Version 1.9.0
1142
- - 📚 Comprehensive documentation update
1143
- - 🏗️ Embedded tapbundle for better integration
1144
- - 🌐 Full browser compatibility
1145
-
1146
- ### Version 1.8.0
1147
- - 📦 Embedded tapbundle directly into tstest project
1148
- - 🌐 Made tapbundle fully browser-compatible
1149
- - 📸 Added snapshot testing with base64-encoded communication protocol
1150
- - 🏷️ Introduced tag-based test filtering
1151
- - 🔧 Enhanced test lifecycle hooks (beforeEach/afterEach)
1152
- - 🎯 Fixed parallel test execution and grouping
1153
- - ⏳ Improved timeout and retry mechanisms
1154
- - 🛠️ Added test fixtures for reusable test data
1155
- - 📊 Enhanced TAP parser for better test reporting
1156
- - 🐛 Fixed glob pattern handling in shell scripts
1157
-
1158
561
  ## License and Legal Information
1159
562
 
1160
- This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
563
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
1161
564
 
1162
565
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
1163
566
 
1164
567
  ### Trademarks
1165
568
 
1166
- This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
569
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
570
+
571
+ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
1167
572
 
1168
573
  ### Company Information
1169
574
 
1170
575
  Task Venture Capital GmbH
1171
- Registered at District court Bremen HRB 35230 HB, Germany
576
+ Registered at District Court Bremen HRB 35230 HB, Germany
1172
577
 
1173
- For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
578
+ For any legal inquiries or further information, please contact us via email at hello@task.vc.
1174
579
 
1175
580
  By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.