fix(docs): Update documentation: expand README with multi-runtime architecture, add module READMEs, and add local dev settings
This commit is contained in:
587
ts_tapbundle_protocol/readme.md
Normal file
587
ts_tapbundle_protocol/readme.md
Normal file
@@ -0,0 +1,587 @@
|
||||
# @git.zone/tstest/tapbundle_protocol
|
||||
|
||||
> 📡 Enhanced TAP Protocol V2 implementation for structured test communication
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# tapbundle_protocol is included as part of @git.zone/tstest
|
||||
pnpm install --save-dev @git.zone/tstest
|
||||
```
|
||||
|
||||
## Overview
|
||||
|
||||
`@git.zone/tstest/tapbundle_protocol` implements Protocol V2, an enhanced version of the Test Anything Protocol (TAP) with support for structured metadata, real-time events, error diffs, and isomorphic operation. This protocol enables rich communication between test runners and test consumers while maintaining backward compatibility with standard TAP parsers.
|
||||
|
||||
## Key Features
|
||||
|
||||
- 📋 **TAP v13 Compliant** - Fully compatible with standard TAP consumers
|
||||
- 🎯 **Enhanced Metadata** - Timing, tags, errors, diffs, and custom data
|
||||
- 🔄 **Real-time Events** - Live test execution updates
|
||||
- 🔍 **Structured Errors** - JSON error blocks with stack traces and diffs
|
||||
- 📸 **Snapshot Support** - Built-in snapshot testing protocol
|
||||
- 🌐 **Isomorphic** - Works in Node.js, browsers, Deno, and Bun
|
||||
- 🏷️ **Protocol Markers** - Structured data using Unicode delimiters
|
||||
|
||||
## Protocol V2 Format
|
||||
|
||||
### Protocol Markers
|
||||
|
||||
Protocol V2 uses special Unicode markers to embed structured data within TAP output:
|
||||
|
||||
- `⟦TSTEST:` - Start marker
|
||||
- `⟧` - End marker
|
||||
|
||||
These markers allow structured data to coexist with standard TAP without breaking compatibility.
|
||||
|
||||
### Example Output
|
||||
|
||||
```tap
|
||||
⟦TSTEST:PROTOCOL:2.0.0⟧
|
||||
TAP version 13
|
||||
1..3
|
||||
ok 1 - should add numbers ⟦TSTEST:time:42⟧
|
||||
not ok 2 - should validate input
|
||||
⟦TSTEST:META:{"time":156,"file":"test.ts","line":42}⟧
|
||||
⟦TSTEST:ERROR⟧
|
||||
{
|
||||
"error": {
|
||||
"message": "Expected 5 to equal 6",
|
||||
"diff": {...}
|
||||
}
|
||||
}
|
||||
⟦TSTEST:/ERROR⟧
|
||||
ok 3 - should handle edge cases # SKIP not implemented ⟦TSTEST:SKIP:not implemented⟧
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### ProtocolEmitter
|
||||
|
||||
Generates Protocol V2 messages. Used by tapbundle to emit test results.
|
||||
|
||||
#### `emitProtocolHeader()`
|
||||
|
||||
Emit the protocol version header.
|
||||
|
||||
```typescript
|
||||
import { ProtocolEmitter } from '@git.zone/tstest/tapbundle_protocol';
|
||||
|
||||
const emitter = new ProtocolEmitter();
|
||||
console.log(emitter.emitProtocolHeader());
|
||||
// Output: ⟦TSTEST:PROTOCOL:2.0.0⟧
|
||||
```
|
||||
|
||||
#### `emitTapVersion(version?)`
|
||||
|
||||
Emit TAP version line.
|
||||
|
||||
```typescript
|
||||
console.log(emitter.emitTapVersion(13));
|
||||
// Output: TAP version 13
|
||||
```
|
||||
|
||||
#### `emitPlan(plan)`
|
||||
|
||||
Emit test plan.
|
||||
|
||||
```typescript
|
||||
console.log(emitter.emitPlan({ start: 1, end: 5 }));
|
||||
// Output: 1..5
|
||||
|
||||
console.log(emitter.emitPlan({ start: 1, end: 0, skipAll: 'Not ready' }));
|
||||
// Output: 1..0 # Skipped: Not ready
|
||||
```
|
||||
|
||||
#### `emitTest(result)`
|
||||
|
||||
Emit a test result with optional metadata.
|
||||
|
||||
```typescript
|
||||
const lines = emitter.emitTest({
|
||||
ok: true,
|
||||
testNumber: 1,
|
||||
description: 'should work correctly',
|
||||
metadata: {
|
||||
time: 45,
|
||||
tags: ['unit', 'fast']
|
||||
}
|
||||
});
|
||||
|
||||
lines.forEach(line => console.log(line));
|
||||
// Output:
|
||||
// ok 1 - should work correctly ⟦TSTEST:time:45⟧
|
||||
// ⟦TSTEST:META:{"tags":["unit","fast"]}⟧
|
||||
```
|
||||
|
||||
#### `emitComment(comment)`
|
||||
|
||||
Emit a comment line.
|
||||
|
||||
```typescript
|
||||
console.log(emitter.emitComment('Setup complete'));
|
||||
// Output: # Setup complete
|
||||
```
|
||||
|
||||
#### `emitBailout(reason)`
|
||||
|
||||
Emit a bailout (abort all tests).
|
||||
|
||||
```typescript
|
||||
console.log(emitter.emitBailout('Database connection failed'));
|
||||
// Output: Bail out! Database connection failed
|
||||
```
|
||||
|
||||
#### `emitError(error)`
|
||||
|
||||
Emit a structured error block.
|
||||
|
||||
```typescript
|
||||
const lines = emitter.emitError({
|
||||
testNumber: 2,
|
||||
error: {
|
||||
message: 'Expected 5 to equal 6',
|
||||
stack: 'Error: ...',
|
||||
actual: 5,
|
||||
expected: 6,
|
||||
diff: '...'
|
||||
}
|
||||
});
|
||||
|
||||
lines.forEach(line => console.log(line));
|
||||
// Output:
|
||||
// ⟦TSTEST:ERROR⟧
|
||||
// {
|
||||
// "testNumber": 2,
|
||||
// "error": { ... }
|
||||
// }
|
||||
// ⟦TSTEST:/ERROR⟧
|
||||
```
|
||||
|
||||
#### `emitEvent(event)`
|
||||
|
||||
Emit a real-time test event.
|
||||
|
||||
```typescript
|
||||
console.log(emitter.emitEvent({
|
||||
eventType: 'test:started',
|
||||
timestamp: Date.now(),
|
||||
data: {
|
||||
testNumber: 1,
|
||||
description: 'should work'
|
||||
}
|
||||
}));
|
||||
// Output: ⟦TSTEST:EVENT:{"eventType":"test:started",...}⟧
|
||||
```
|
||||
|
||||
#### `emitSnapshot(snapshot)`
|
||||
|
||||
Emit snapshot data.
|
||||
|
||||
```typescript
|
||||
const lines = emitter.emitSnapshot({
|
||||
name: 'user-data',
|
||||
content: { name: 'Alice', age: 30 },
|
||||
format: 'json'
|
||||
});
|
||||
|
||||
lines.forEach(line => console.log(line));
|
||||
// Output:
|
||||
// ⟦TSTEST:SNAPSHOT:user-data⟧
|
||||
// {
|
||||
// "name": "Alice",
|
||||
// "age": 30
|
||||
// }
|
||||
// ⟦TSTEST:/SNAPSHOT⟧
|
||||
```
|
||||
|
||||
### ProtocolParser
|
||||
|
||||
Parses Protocol V2 messages. Used by tstest to consume test results.
|
||||
|
||||
#### `parseLine(line)`
|
||||
|
||||
Parse a single line and return protocol messages.
|
||||
|
||||
```typescript
|
||||
import { ProtocolParser } from '@git.zone/tstest/tapbundle_protocol';
|
||||
|
||||
const parser = new ProtocolParser();
|
||||
|
||||
const messages = parser.parseLine('ok 1 - test passed ⟦TSTEST:time:42⟧');
|
||||
console.log(messages);
|
||||
// Output:
|
||||
// [{
|
||||
// type: 'test',
|
||||
// content: {
|
||||
// ok: true,
|
||||
// testNumber: 1,
|
||||
// description: 'test passed',
|
||||
// metadata: { time: 42 }
|
||||
// }
|
||||
// }]
|
||||
```
|
||||
|
||||
#### Message Types
|
||||
|
||||
The parser returns different message types:
|
||||
|
||||
```typescript
|
||||
interface IProtocolMessage {
|
||||
type: 'test' | 'plan' | 'comment' | 'version' | 'bailout' | 'protocol' | 'snapshot' | 'error' | 'event';
|
||||
content: any;
|
||||
}
|
||||
```
|
||||
|
||||
**Examples:**
|
||||
|
||||
```typescript
|
||||
// Test result
|
||||
{
|
||||
type: 'test',
|
||||
content: {
|
||||
ok: true,
|
||||
testNumber: 1,
|
||||
description: 'test name',
|
||||
metadata: { ... }
|
||||
}
|
||||
}
|
||||
|
||||
// Plan
|
||||
{
|
||||
type: 'plan',
|
||||
content: {
|
||||
start: 1,
|
||||
end: 5
|
||||
}
|
||||
}
|
||||
|
||||
// Event
|
||||
{
|
||||
type: 'event',
|
||||
content: {
|
||||
eventType: 'test:started',
|
||||
timestamp: 1234567890,
|
||||
data: { ... }
|
||||
}
|
||||
}
|
||||
|
||||
// Error
|
||||
{
|
||||
type: 'error',
|
||||
content: {
|
||||
testNumber: 2,
|
||||
error: {
|
||||
message: '...',
|
||||
stack: '...',
|
||||
diff: '...'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `getProtocolVersion()`
|
||||
|
||||
Get the detected protocol version.
|
||||
|
||||
```typescript
|
||||
const version = parser.getProtocolVersion();
|
||||
console.log(version); // "2.0.0" or null
|
||||
```
|
||||
|
||||
## TypeScript Types
|
||||
|
||||
### ITestResult
|
||||
|
||||
```typescript
|
||||
interface ITestResult {
|
||||
ok: boolean;
|
||||
testNumber: number;
|
||||
description: string;
|
||||
directive?: {
|
||||
type: 'skip' | 'todo';
|
||||
reason?: string;
|
||||
};
|
||||
metadata?: ITestMetadata;
|
||||
}
|
||||
```
|
||||
|
||||
### ITestMetadata
|
||||
|
||||
```typescript
|
||||
interface ITestMetadata {
|
||||
// Timing
|
||||
time?: number; // Test duration in milliseconds
|
||||
startTime?: number; // Unix timestamp
|
||||
endTime?: number; // Unix timestamp
|
||||
|
||||
// Status
|
||||
skip?: string; // Skip reason
|
||||
todo?: string; // Todo reason
|
||||
retry?: number; // Current retry attempt
|
||||
maxRetries?: number; // Max retries allowed
|
||||
|
||||
// Error details
|
||||
error?: {
|
||||
message: string;
|
||||
stack?: string;
|
||||
diff?: string;
|
||||
actual?: any;
|
||||
expected?: any;
|
||||
code?: string;
|
||||
};
|
||||
|
||||
// Test context
|
||||
file?: string; // Source file path
|
||||
line?: number; // Line number
|
||||
column?: number; // Column number
|
||||
|
||||
// Custom data
|
||||
tags?: string[]; // Test tags
|
||||
custom?: Record<string, any>;
|
||||
}
|
||||
```
|
||||
|
||||
### ITestEvent
|
||||
|
||||
```typescript
|
||||
interface ITestEvent {
|
||||
eventType: EventType;
|
||||
timestamp: number;
|
||||
data: {
|
||||
testNumber?: number;
|
||||
description?: string;
|
||||
suiteName?: string;
|
||||
hookName?: string;
|
||||
progress?: number; // 0-100
|
||||
duration?: number;
|
||||
error?: IEnhancedError;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
type EventType =
|
||||
| 'test:queued'
|
||||
| 'test:started'
|
||||
| 'test:progress'
|
||||
| 'test:completed'
|
||||
| 'suite:started'
|
||||
| 'suite:completed'
|
||||
| 'hook:started'
|
||||
| 'hook:completed'
|
||||
| 'assertion:failed';
|
||||
```
|
||||
|
||||
### IEnhancedError
|
||||
|
||||
```typescript
|
||||
interface IEnhancedError {
|
||||
message: string;
|
||||
stack?: string;
|
||||
diff?: IDiffResult;
|
||||
actual?: any;
|
||||
expected?: any;
|
||||
code?: string;
|
||||
type?: 'assertion' | 'timeout' | 'uncaught' | 'syntax' | 'runtime';
|
||||
}
|
||||
```
|
||||
|
||||
### IDiffResult
|
||||
|
||||
```typescript
|
||||
interface IDiffResult {
|
||||
type: 'string' | 'object' | 'array' | 'primitive';
|
||||
changes: IDiffChange[];
|
||||
context?: number; // Lines of context
|
||||
}
|
||||
|
||||
interface IDiffChange {
|
||||
type: 'add' | 'remove' | 'modify';
|
||||
path?: string[]; // For object/array diffs
|
||||
oldValue?: any;
|
||||
newValue?: any;
|
||||
line?: number; // For string diffs
|
||||
content?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Protocol Constants
|
||||
|
||||
```typescript
|
||||
import { PROTOCOL_MARKERS, PROTOCOL_VERSION } from '@git.zone/tstest/tapbundle_protocol';
|
||||
|
||||
console.log(PROTOCOL_VERSION); // "2.0.0"
|
||||
console.log(PROTOCOL_MARKERS.START); // "⟦TSTEST:"
|
||||
console.log(PROTOCOL_MARKERS.END); // "⟧"
|
||||
```
|
||||
|
||||
### Available Markers
|
||||
|
||||
```typescript
|
||||
const PROTOCOL_MARKERS = {
|
||||
START: '⟦TSTEST:',
|
||||
END: '⟧',
|
||||
META_PREFIX: 'META:',
|
||||
ERROR_PREFIX: 'ERROR',
|
||||
ERROR_END: '/ERROR',
|
||||
SNAPSHOT_PREFIX: 'SNAPSHOT:',
|
||||
SNAPSHOT_END: '/SNAPSHOT',
|
||||
PROTOCOL_PREFIX: 'PROTOCOL:',
|
||||
SKIP_PREFIX: 'SKIP:',
|
||||
TODO_PREFIX: 'TODO:',
|
||||
EVENT_PREFIX: 'EVENT:',
|
||||
};
|
||||
```
|
||||
|
||||
## Usage Patterns
|
||||
|
||||
### Creating a Custom Test Runner
|
||||
|
||||
```typescript
|
||||
import { ProtocolEmitter } from '@git.zone/tstest/tapbundle_protocol';
|
||||
|
||||
const emitter = new ProtocolEmitter();
|
||||
|
||||
// Emit protocol header
|
||||
console.log(emitter.emitProtocolHeader());
|
||||
console.log(emitter.emitTapVersion(13));
|
||||
|
||||
// Emit plan
|
||||
console.log(emitter.emitPlan({ start: 1, end: 2 }));
|
||||
|
||||
// Run test 1
|
||||
emitter.emitEvent({
|
||||
eventType: 'test:started',
|
||||
timestamp: Date.now(),
|
||||
data: { testNumber: 1 }
|
||||
}).split('\n').forEach(line => console.log(line));
|
||||
|
||||
const result1 = emitter.emitTest({
|
||||
ok: true,
|
||||
testNumber: 1,
|
||||
description: 'first test',
|
||||
metadata: { time: 45 }
|
||||
});
|
||||
result1.forEach(line => console.log(line));
|
||||
|
||||
// Run test 2
|
||||
const result2 = emitter.emitTest({
|
||||
ok: false,
|
||||
testNumber: 2,
|
||||
description: 'second test',
|
||||
metadata: {
|
||||
time: 120,
|
||||
error: {
|
||||
message: 'Assertion failed',
|
||||
actual: 5,
|
||||
expected: 6
|
||||
}
|
||||
}
|
||||
});
|
||||
result2.forEach(line => console.log(line));
|
||||
```
|
||||
|
||||
### Parsing Test Output
|
||||
|
||||
```typescript
|
||||
import { ProtocolParser } from '@git.zone/tstest/tapbundle_protocol';
|
||||
import * as readline from 'readline';
|
||||
|
||||
const parser = new ProtocolParser();
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
rl.on('line', (line) => {
|
||||
const messages = parser.parseLine(line);
|
||||
|
||||
messages.forEach(message => {
|
||||
switch (message.type) {
|
||||
case 'test':
|
||||
console.log(`Test ${message.content.testNumber}: ${message.content.ok ? 'PASS' : 'FAIL'}`);
|
||||
break;
|
||||
case 'event':
|
||||
console.log(`Event: ${message.content.eventType}`);
|
||||
break;
|
||||
case 'error':
|
||||
console.error(`Error: ${message.content.error.message}`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Building Test Dashboards
|
||||
|
||||
Real-time events enable building live test dashboards:
|
||||
|
||||
```typescript
|
||||
const parser = new ProtocolParser();
|
||||
|
||||
parser.parseLine(line).forEach(message => {
|
||||
if (message.type === 'event') {
|
||||
const event = message.content;
|
||||
|
||||
switch (event.eventType) {
|
||||
case 'test:started':
|
||||
updateUI({ status: 'running', test: event.data.description });
|
||||
break;
|
||||
case 'test:completed':
|
||||
updateUI({ status: 'done', duration: event.data.duration });
|
||||
break;
|
||||
case 'suite:started':
|
||||
createSuiteCard(event.data.suiteName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
Protocol V2 is fully backward compatible with standard TAP parsers:
|
||||
|
||||
- Protocol markers use Unicode characters that TAP parsers ignore
|
||||
- Standard TAP output (ok/not ok, plan, comments) works everywhere
|
||||
- Enhanced features gracefully degrade in standard TAP consumers
|
||||
|
||||
**Standard TAP View:**
|
||||
```tap
|
||||
TAP version 13
|
||||
1..3
|
||||
ok 1 - should add numbers
|
||||
not ok 2 - should validate input
|
||||
ok 3 - should handle edge cases # SKIP not implemented
|
||||
```
|
||||
|
||||
**Protocol V2 View (same output):**
|
||||
```tap
|
||||
⟦TSTEST:PROTOCOL:2.0.0⟧
|
||||
TAP version 13
|
||||
1..3
|
||||
ok 1 - should add numbers ⟦TSTEST:time:42⟧
|
||||
not ok 2 - should validate input
|
||||
⟦TSTEST:META:{"time":156}⟧
|
||||
ok 3 - should handle edge cases # SKIP not implemented ⟦TSTEST:SKIP:not implemented⟧
|
||||
```
|
||||
|
||||
## Isomorphic Design
|
||||
|
||||
This module works in all JavaScript environments:
|
||||
|
||||
- ✅ Node.js
|
||||
- ✅ Browsers (via tapbundle)
|
||||
- ✅ Deno
|
||||
- ✅ Bun
|
||||
- ✅ Web Workers
|
||||
- ✅ Service Workers
|
||||
|
||||
No runtime-specific APIs are used, making it truly portable.
|
||||
|
||||
## Legal
|
||||
|
||||
This project is licensed under MIT.
|
||||
|
||||
© 2025 Task Venture Capital GmbH. All rights reserved.
|
Reference in New Issue
Block a user