# @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 ``` ## Issue Reporting and Security 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 want to sign a contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly. ## 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. ## License and Legal Information 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. **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. ### Trademarks 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. ### Company Information Task Venture Capital GmbH Registered at District court Bremen HRB 35230 HB, Germany For any legal inquiries or if you require further information, please contact us via email at hello@task.vc. 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.