323 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # Architecture Overview
 | ||
| 
 | ||
| ## Project Structure
 | ||
| 
 | ||
| This project integrates tstest with tapbundle through a modular architecture:
 | ||
| 
 | ||
| 1. **tstest** (`/ts/`) - The test runner that discovers and executes test files
 | ||
| 2. **tapbundle** (`/ts_tapbundle/`) - The TAP testing framework for writing tests
 | ||
| 3. **tapbundle_node** (`/ts_tapbundle_node/`) - Node.js-specific testing utilities
 | ||
| 
 | ||
| ## How Components Work Together
 | ||
| 
 | ||
| ### Test Execution Flow
 | ||
| 
 | ||
| 1. **CLI Entry Point** (`cli.js` <20> `cli.ts.js` <20> `cli.child.ts`)
 | ||
|    - The CLI uses tsx to run TypeScript files directly
 | ||
|    - Accepts glob patterns to find test files
 | ||
|    - Supports options like `--verbose`, `--quiet`, `--web`
 | ||
| 
 | ||
| 2. **Test Discovery**
 | ||
|    - tstest scans for test files matching the provided pattern
 | ||
|    - Defaults to `test/**/*.ts` when no pattern is specified
 | ||
|    - Supports both file and directory modes
 | ||
| 
 | ||
| 3. **Test Runner**
 | ||
|    - Each test file imports `tap` and `expect` from tapbundle
 | ||
|    - Tests are written using `tap.test()` with async functions
 | ||
|    - Browser tests are compiled with esbuild and run in Chromium via Puppeteer
 | ||
| 
 | ||
| ### Key Integration Points
 | ||
| 
 | ||
| 1. **Import Structure**
 | ||
|    - Test files import from local tapbundle: `import { tap, expect } from '../../ts_tapbundle/index.js'`
 | ||
|    - Node-specific tests also import from tapbundle_node: `import { tapNodeTools } from '../../ts_tapbundle_node/index.js'`
 | ||
| 
 | ||
| 2. **WebHelpers**
 | ||
|    - Browser tests can use webhelpers for DOM manipulation
 | ||
|    - `webhelpers.html` - Template literal for creating HTML strings
 | ||
|    - `webhelpers.fixture` - Creates DOM elements from HTML strings
 | ||
|    - Automatically detects browser environment and only enables in browser context
 | ||
| 
 | ||
| 3. **Build System**
 | ||
|    - Uses `tsbuild tsfolders` to compile TypeScript (invoked by `pnpm build`)
 | ||
|    - Maintains separate output directories: `/dist_ts/`, `/dist_ts_tapbundle/`, `/dist_ts_tapbundle_node/`, `/dist_ts_tapbundle_protocol/`
 | ||
|    - Compilation order is resolved automatically based on dependencies in tspublish.json files
 | ||
|    - Protocol imports use compiled dist directories:
 | ||
|      ```typescript
 | ||
|      // In ts/tstest.classes.tap.parser.ts
 | ||
|      import { ProtocolParser } from '../dist_ts_tapbundle_protocol/index.js';
 | ||
|      
 | ||
|      // In ts_tapbundle/tapbundle.classes.tap.ts  
 | ||
|      import { ProtocolEmitter } from '../dist_ts_tapbundle_protocol/index.js';
 | ||
|      ```
 | ||
| 
 | ||
| ### Test Scripts
 | ||
| 
 | ||
| The package.json defines several test scripts:
 | ||
| - `test` - Builds and runs all tests (tapbundle and tstest)
 | ||
| - `test:tapbundle` - Runs tapbundle framework tests
 | ||
| - `test:tstest` - Runs tstest's own tests
 | ||
| - Both support `:verbose` variants for detailed output
 | ||
| 
 | ||
| ### Environment Detection
 | ||
| 
 | ||
| The framework automatically detects the runtime environment:
 | ||
| - Node.js tests run directly via tsx
 | ||
| - Browser tests are compiled and served via a local server
 | ||
| - WebHelpers are only enabled in browser environment
 | ||
| 
 | ||
| This architecture allows for seamless testing across both Node.js and browser environments while maintaining a clean separation of concerns.
 | ||
| 
 | ||
| ## Logging System
 | ||
| 
 | ||
| ### Log File Naming (Fixed in v1.9.1)
 | ||
| 
 | ||
| When using the `--logfile` flag, tstest creates log files in `.nogit/testlogs/`. The log file naming was updated to preserve directory structure and prevent collisions:
 | ||
| 
 | ||
| - **Old behavior**: `test/tapbundle/test.ts` → `.nogit/testlogs/test.log`
 | ||
| - **New behavior**: `test/tapbundle/test.ts` → `.nogit/testlogs/test__tapbundle__test.log`
 | ||
| 
 | ||
| This fix ensures that test files with the same basename in different directories don't overwrite each other's logs. The implementation:
 | ||
| 1. Takes the relative path from the current working directory
 | ||
| 2. Replaces path separators (`/`) with double underscores (`__`)
 | ||
| 3. Removes the `.ts` extension
 | ||
| 4. Creates a flat filename that preserves the directory structure
 | ||
| 
 | ||
| ### Test Timing Display (Fixed in v1.9.2)
 | ||
| 
 | ||
| Fixed an issue where test timing was displayed incorrectly with duplicate values like:
 | ||
| - Before: `✅ test name # time=133ms (0ms)`
 | ||
| - After: `✅ test name (133ms)`
 | ||
| 
 | ||
| The issue was in the TAP parser regex which was greedily capturing the entire line including the TAP timing comment. Changed the regex from `(.*)` to `(.*?)` to make it non-greedy, properly separating the test name from the timing metadata.
 | ||
| 
 | ||
| ## Protocol Limitations and Improvements
 | ||
| 
 | ||
| ### Current TAP Protocol Issues
 | ||
| The current implementation uses standard TAP format with metadata in comments:
 | ||
| ```
 | ||
| ok 1 - test name # time=123ms
 | ||
| ```
 | ||
| 
 | ||
| This has several limitations:
 | ||
| 1. **Delimiter Conflict**: Test descriptions containing `#` can break parsing
 | ||
| 2. **Regex Fragility**: Complex regex patterns that are hard to maintain
 | ||
| 3. **Limited Metadata**: Difficult to add rich error information or custom data
 | ||
| 
 | ||
| ### Planned Protocol V2
 | ||
| A new internal protocol is being designed that will:
 | ||
| - Use Unicode delimiters `⟦TSTEST:⟧` that won't conflict with test content
 | ||
| - Support structured JSON metadata
 | ||
| - Allow rich error reporting with stack traces and diffs
 | ||
| - Completely replace v1 protocol (no backwards compatibility)
 | ||
| 
 | ||
| ### ts_tapbundle_protocol Directory
 | ||
| The protocol v2 implementation is contained in a separate `ts_tapbundle_protocol` directory:
 | ||
| - **Isomorphic Code**: All protocol code works in both browser and Node.js environments
 | ||
| - **No Platform Dependencies**: No Node.js-specific imports, ensuring true cross-platform compatibility
 | ||
| - **Clean Separation**: Protocol logic is isolated from platform-specific code in tstest and tapbundle
 | ||
| - **Shared Implementation**: Both tstest (parser) and tapbundle (emitter) use the same protocol classes
 | ||
| - **Build Process**: 
 | ||
|   - Compiled by `pnpm build` via tsbuild to `dist_ts_tapbundle_protocol/`
 | ||
|   - Build order managed through tspublish.json files
 | ||
|   - Other modules import from the compiled dist directory, not source
 | ||
| 
 | ||
| This architectural decision ensures the protocol can be used in any JavaScript environment without modification and maintains proper build dependencies.
 | ||
| 
 | ||
| See `readme.protocol.md` for the full specification and `ts_tapbundle_protocol/` for the implementation.
 | ||
| 
 | ||
| ## Protocol V2 Implementation Status
 | ||
| 
 | ||
| The Protocol V2 has been implemented to fix issues with TAP protocol parsing when test descriptions contain special characters like `#`, `###SNAPSHOT###`, or protocol markers like `⟦TSTEST:ERROR⟧`.
 | ||
| 
 | ||
| ### Implementation Details:
 | ||
| 
 | ||
| 1. **Protocol Components**:
 | ||
|    - `ProtocolEmitter` - Generates protocol v2 messages (used by tapbundle)
 | ||
|    - `ProtocolParser` - Parses protocol v2 messages (used by tstest)
 | ||
|    - Uses Unicode markers `⟦TSTEST:` and `⟧` to avoid conflicts with test content
 | ||
| 
 | ||
| 2. **Current Status**:
 | ||
|    - ✅ Basic protocol emission and parsing works
 | ||
|    - ✅ Handles test descriptions with special characters correctly
 | ||
|    - ✅ Supports metadata for timing, tags, errors
 | ||
|    - ⚠️ Protocol messages sometimes appear in console output (parsing not catching all cases)
 | ||
| 
 | ||
| 3. **Key Findings**:
 | ||
|    - `tap.skip.test()` doesn't create actual test objects, just logs and increments counter
 | ||
|    - `tap.todo()` method is not implemented (no `addTodo` method in Tap class)
 | ||
|    - Protocol parser's `isBlockStart` was fixed to only match exact block markers, not partial matches in test descriptions
 | ||
| 
 | ||
| 4. **Import Paths**:
 | ||
|    - tstest imports from: `import { ProtocolParser } from '../dist_ts_tapbundle_protocol/index.js';`
 | ||
|    - tapbundle imports from: `import { ProtocolEmitter } from '../dist_ts_tapbundle_protocol/index.js';`
 | ||
| 
 | ||
| ## Test Configuration System (Phase 2)
 | ||
| 
 | ||
| The Test Configuration System has been implemented to provide global settings and lifecycle hooks for tests.
 | ||
| 
 | ||
| ### Key Features:
 | ||
| 
 | ||
| 1. **00init.ts Discovery**:
 | ||
|    - Automatically detects `00init.ts` files in the same directory as test files
 | ||
|    - Creates a temporary loader file that imports both `00init.ts` and the test file
 | ||
|    - Loader files are cleaned up automatically after test execution
 | ||
| 
 | ||
| 2. **Settings Inheritance**:
 | ||
|    - Global settings from `00init.ts` → File-level settings → Test-level settings
 | ||
|    - Settings include: timeout, retries, retryDelay, bail, concurrency
 | ||
|    - Lifecycle hooks: beforeAll, afterAll, beforeEach, afterEach
 | ||
| 
 | ||
| 3. **Implementation Details**:
 | ||
|    - `SettingsManager` class handles settings inheritance and merging
 | ||
|    - `tap.settings()` API allows configuration at any level
 | ||
|    - Lifecycle hooks are integrated into test execution flow
 | ||
| 
 | ||
| ### Important Development Notes:
 | ||
| 
 | ||
| 1. **Local Development**: When developing tstest itself, use `node cli.js` instead of globally installed `tstest` to test changes
 | ||
| 
 | ||
| 2. **Console Output Buffering**: Console output from tests is buffered and only displayed for failing tests. TAP-compliant comments (lines starting with `#`) are always shown.
 | ||
| 
 | ||
| 3. **TypeScript Warnings**: Fixed async/await warnings in `movePreviousLogFiles()` by using sync versions of file operations
 | ||
| 
 | ||
| ## Enhanced Communication Features (Phase 3)
 | ||
| 
 | ||
| The Enhanced Communication system has been implemented to provide rich, real-time feedback during test execution.
 | ||
| 
 | ||
| ### Key Features:
 | ||
| 
 | ||
| 1. **Event-Based Test Lifecycle Reporting**:
 | ||
|    - `test:queued` - Test is ready to run
 | ||
|    - `test:started` - Test execution begins
 | ||
|    - `test:completed` - Test finishes (with pass/fail status)
 | ||
|    - `suite:started` - Test suite/describe block begins
 | ||
|    - `suite:completed` - Test suite/describe block ends
 | ||
|    - `hook:started` - Lifecycle hook (beforeEach/afterEach) begins
 | ||
|    - `hook:completed` - Lifecycle hook finishes
 | ||
|    - `assertion:failed` - Assertion failure with detailed information
 | ||
| 
 | ||
| 2. **Visual Diff Output for Assertion Failures**:
 | ||
|    - **String Diffs**: Character-by-character comparison with colored output
 | ||
|    - **Object/Array Diffs**: Deep property comparison showing added/removed/changed properties
 | ||
|    - **Primitive Diffs**: Clear display of expected vs actual values
 | ||
|    - **Colorized Output**: Green for expected, red for actual, yellow for differences
 | ||
|    - **Smart Formatting**: Multi-line strings and complex objects are formatted for readability
 | ||
| 
 | ||
| 3. **Real-Time Test Progress API**:
 | ||
|    - Tests emit progress events as they execute
 | ||
|    - tstest parser processes events and updates display in real-time
 | ||
|    - Structured event format carries rich metadata (timing, errors, diffs)
 | ||
|    - Seamless integration with existing TAP protocol via Protocol V2
 | ||
| 
 | ||
| ### Implementation Details:
 | ||
| - Events are transmitted via Protocol V2's `EVENT` block type
 | ||
| - Event data is JSON-encoded within protocol markers
 | ||
| - Parser handles events asynchronously for real-time updates
 | ||
| - Visual diffs are generated using custom diff algorithms for each data type
 | ||
| 
 | ||
| ## Watch Mode (Phase 4)
 | ||
| 
 | ||
| tstest now supports watch mode for automatic test re-runs on file changes.
 | ||
| 
 | ||
| ### Usage
 | ||
| ```bash
 | ||
| tstest test/**/*.ts --watch
 | ||
| tstest test/specific.ts -w
 | ||
| ```
 | ||
| 
 | ||
| ### Features
 | ||
| - **Automatic Re-runs**: Tests re-run when any watched file changes
 | ||
| - **Debouncing**: Multiple rapid changes are batched (300ms delay)
 | ||
| - **Clear Output**: Console is cleared before each run for clean results
 | ||
| - **Status Updates**: Shows which files triggered the re-run
 | ||
| - **Graceful Exit**: Press Ctrl+C to stop watching
 | ||
| 
 | ||
| ### Options
 | ||
| - `--watch` or `-w`: Enable watch mode
 | ||
| - `--watch-ignore`: Comma-separated patterns to ignore (e.g., `--watch-ignore node_modules,dist`)
 | ||
| 
 | ||
| ### Implementation Details
 | ||
| - Uses `@push.rocks/smartchok` for cross-platform file watching
 | ||
| - Watches the entire project directory from where tests are run
 | ||
| - Ignores changes matching the ignore patterns
 | ||
| - Shows "Waiting for file changes..." between runs
 | ||
| 
 | ||
| ## Fixed Issues
 | ||
| 
 | ||
| ### tap.skip.test(), tap.todo(), and tap.only.test() (Fixed)
 | ||
| 
 | ||
| Previously reported issues with these methods have been resolved:
 | ||
| 
 | ||
| 1. **tap.skip.test()** - Now properly creates test objects that are counted in test results
 | ||
|    - Tests marked with `skip.test()` appear in the test count
 | ||
|    - Shows as passed with skip directive in TAP output
 | ||
|    - `markAsSkipped()` method added to handle pre-test skip marking
 | ||
| 
 | ||
| 2. **tap.todo.test()** - Fully implemented with test object creation
 | ||
|    - Supports both `tap.todo.test('description')` and `tap.todo.test('description', testFunc)`
 | ||
|    - Todo tests are counted and marked with todo directive
 | ||
|    - Both regular and parallel todo tests supported
 | ||
| 
 | ||
| 3. **tap.only.test()** - Works correctly for focused testing
 | ||
|    - When `.only` tests exist, only those tests run
 | ||
|    - Other tests are not executed but still counted
 | ||
|    - Both regular and parallel only tests supported
 | ||
| 
 | ||
| These fixes ensure accurate test counts and proper TAP-compliant output for all test states.
 | ||
| 
 | ||
| ## Test Timing Implementation
 | ||
| 
 | ||
| ### Timing Architecture
 | ||
| 
 | ||
| Test timing is captured using `@push.rocks/smarttime`'s `HrtMeasurement` class, which provides high-resolution timing:
 | ||
| 
 | ||
| 1. **Timing Capture**:
 | ||
|    - Each `TapTest` instance has its own `HrtMeasurement`
 | ||
|    - Timer starts immediately before test function execution
 | ||
|    - Timer stops after test completes (or fails/times out)
 | ||
|    - Millisecond precision is used for reporting
 | ||
| 
 | ||
| 2. **Protocol Integration**:
 | ||
|    - Timing is embedded in TAP output using Protocol V2 markers
 | ||
|    - Inline format for simple timing: `ok 1 - test name ⟦TSTEST:time:123⟧`
 | ||
|    - Block format for complex metadata: `⟦TSTEST:META:{"time":456,"file":"test.ts"}⟧`
 | ||
| 
 | ||
| 3. **Performance Metrics Calculation**:
 | ||
|    - Average is calculated from sum of individual test times, not total runtime
 | ||
|    - Slowest test detection prefers tests with >0ms duration
 | ||
|    - Failed tests still contribute their execution time to metrics
 | ||
| 
 | ||
| ### Edge Cases and Considerations
 | ||
| 
 | ||
| 1. **Sub-millisecond Tests**:
 | ||
|    - Very fast tests may report 0ms due to millisecond rounding
 | ||
|    - Performance metrics handle this by showing "All tests completed in <1ms" when appropriate
 | ||
| 
 | ||
| 2. **Special Test States**:
 | ||
|    - **Skipped tests**: Report 0ms (not executed)
 | ||
|    - **Todo tests**: Report 0ms (not executed)
 | ||
|    - **Failed tests**: Report actual execution time before failure
 | ||
|    - **Timeout tests**: Report time until timeout occurred
 | ||
| 
 | ||
| 3. **Parallel Test Timing**:
 | ||
|    - Each parallel test tracks its own execution time independently
 | ||
|    - Parallel tests may have overlapping execution periods
 | ||
|    - Total suite time reflects wall-clock time, not sum of test times
 | ||
| 
 | ||
| 4. **Hook Timing**:
 | ||
|    - `beforeEach`/`afterEach` hooks are not included in individual test times
 | ||
|    - Only the actual test function execution is measured
 | ||
| 
 | ||
| 5. **Retry Timing**:
 | ||
|    - When tests retry, only the final attempt's duration is reported
 | ||
|    - Each retry attempt emits separate `test:started` events
 | ||
| 
 | ||
| ### Parser Fix for Timing Metadata
 | ||
| 
 | ||
| The protocol parser was fixed to correctly handle inline timing metadata:
 | ||
| - Changed condition from `!simpleMatch[1].includes(':')` to check for simple key:value pairs
 | ||
| - Excludes prefixed formats (META:, SKIP:, TODO:, EVENT:) while parsing simple formats like `time:250`
 | ||
| 
 | ||
| This ensures timing metadata is correctly extracted and displayed in test results. |