# @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; } ``` ### 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.