A comprehensive testing framework for the Doh.js ecosystem, featuring a test harness with rich assertions, a visual test launcher UI, automated test discovery, and full-stack testing with both server and browser components.
The test framework consists of four main components:
tests_harness): A robust assertion library with comprehensive testing utilitiestests_launcher): A visual UI for discovering and running tests in isolationtests_routes): Express routes for web-based test access with auto-discoverydoh test)Run tests directly from the command line without starting a server:
# Run a single test module
doh test test_use_state
# Run multiple test modules
doh test test_use_state test_deep_observe test_use_array_advanced
# Show usage help
doh test
Output:
==================================================
Running: test_use_state
==================================================
=== USE State Test Suite ===
β PASS: State object exists
β PASS: Can set and get values
...
=== USE State Test Summary ===
Total: 15, Passed: 15, Failed: 0
==================================================
TEST SUMMARY
==================================================
Total: 15 | Passed: 15 | Failed: 0
Exit Codes:
0 - All tests passed1 - One or more tests failed (CI-friendly)Note: Some tests require browser APIs (like indexedDB) and will show errors when run via CLI. Use the Web UI for browser-only tests.
/tests in your browser// Load and use the test harness
Doh.Module('my_test', ['tests_harness'], function(tests_harness) {
const { expect, expectEqual, summarizeTests, resetTests } = tests_harness;
resetTests();
// Your tests here
expect(true, 'Basic truth test');
expectEqual(2 + 2, 4, 'Math works');
// Summary with pass/fail counts
summarizeTests('My Test Suite');
});
The test framework supports full-stack testing with both server-side and browser-side test modules in a single test package. This enables testing of complete features including API routes, server logic, and browser interactions.
Use test packages (YAML files) to combine server and browser test modules:
# test_my_feature.doh.yaml
test_my_feature:
load:
- nodejs?? test_my_feature_server # Runs on server first
- test_my_feature_browser # Runs in browser after server
For full-stack testing to work correctly via packages, you must follow these conventions:
Server test modules:
nodejs?? condition in package load array_server suffix in module namenodejs?? test_my_feature_serverBrowser test modules:
nodejs?? condition_browser suffix in module nametest_my_feature_browserThe test framework uses these conventions to determine execution order and environment. Server modules execute first in the Node.js process, while browser modules execute second in the browser iframe.
Server tests execute first (nodejs?? modules)
Browser tests execute second (regular modules)
Results combined in a single test run with unified console output
Server Test (test_server_example_server.doh.js):
Doh.Module('test_server_example_server',
['tests_harness', 'express_router'],
async function(tests_harness, Router) {
const { expect, summarizeTests, resetTests } = tests_harness;
resetTests();
// Server-side tests
expect(typeof process !== 'undefined', 'Node.js process exists');
// Create API route for browser to test
Router.AddRoute('/test-api/hello', async function(data, req, res) {
Router.SendJSON(res, {
success: true,
message: 'Hello from server!',
data: { value: 42 }
});
});
expect(true, 'API route created');
summarizeTests('test_server_example_server');
});
Browser Test (test_server_example_browser.doh.js):
Doh.Module('test_server_example_browser',
['tests_harness'],
async function(tests_harness) {
const { expect, summarizeTests, resetTests } = tests_harness;
resetTests();
// Browser-side tests
expect(typeof document !== 'undefined', 'DOM is available');
// Test the route created by server test
const response = await fetch('/test-api/hello');
const data = await response.json();
expect(response.ok, 'Server route responds');
expect(data.success === true, 'Response has success=true');
expect(data.data.value === 42, 'Response data is correct');
summarizeTests('test_server_example_browser');
});
Package File (test_server_example.doh.yaml):
test_server_example:
load:
- nodejs?? test_server_example_server
- test_server_example_browser
Server tests can:
process, fs, etc.)express_routerBrowser tests can:
/test-api/ for test routesThe test harness automatically detects the environment:
// Server-safe - only runs in browser
if (IsBrowser()) {
// Browser-specific code
}
// Only runs on server
if (IsNode()) {
// Server-specific code
}
This is used internally to prevent postMessage calls on the server and other environment-specific behaviors.
tests_harness)expect(condition, description) - Basic assertion with pass/fail loggingexpectEqual(actual, expected, description) - Deep equality using JSON comparisonexpectArrayEqual(actual, expected, description) - Array comparison with USE system proxy supportexpectGreater(actual, expected, description) - Greater than comparisonexpectAtLeast(actual, expected, description) - Greater than or equal comparisonexpectLess(actual, expected, description) - Less than comparisonexpectAtMost(actual, expected, description) - Less than or equal comparisonexpectTruthy(value, description) - Test for truthinessexpectFalsy(value, description) - Test for falsinessexpectType(value, expectedType, description) - Type checking with array detectionexpectThrows(fn, description) - Verify function throws an exceptionexpectNotThrows(fn, description) - Verify function doesn't throwsummarizeTests(testName) - Print summary and return success booleanresetTests() - Reset test counters for new test rungetTestStats() - Get current test statistics objectsetTestTimeout(timeoutMs) - Set custom timeout for long-running tests (default: 15000ms)startTestHeartbeat(testName) - Start heartbeat monitoring (auto-called)stopTestHeartbeat() - Stop heartbeat monitoring (auto-called)enableDebug(enable) - Enable/disable debug outputdebugLog(...args) - Conditional debug loggingwaitForNotifications() - Wait for microtasks/promiseswaitFor(ms) - Wait for specific durationgetCleanState() - Get clean USE state with all keys removedTests run with a default 15-second timeout. If a test doesn't send heartbeat signals within this time, it's marked as failed. For long-running tests, use setTestTimeout():
Doh.Module('test_long_operation', ['tests_harness'], async function(tests_harness) {
const { expect, resetTests, summarizeTests, setTestTimeout } = tests_harness;
resetTests();
// Set a 30-second timeout for this test
setTestTimeout(30000);
// Run your long operation...
await someLongRunningOperation();
return summarizeTests('Long Operation Test');
});
Timeout Features:
The test launcher automatically discovers test modules using these criteria:
test_ or debug_doh_modules/tests/ directoryDoh.Packages by the Auto-PackagerTest modules should follow this pattern:
Doh.Module('test_my_feature', ['tests_harness'], async function(tests_harness) {
const { expect, expectEqual, summarizeTests, resetTests } = tests_harness;
console.log('\n=== My Feature Test Suite ===');
resetTests();
// Test implementation here
expect(someCondition, 'Feature works as expected');
expectEqual(actualValue, expectedValue, 'Values match');
// Optional: Add async test logic
await tests_harness.waitFor(100);
// Always summarize at the end
const success = summarizeTests('My Feature Tests');
return success;
});
The test launcher provides a modern, comprehensive testing interface with real-time feedback and batch execution capabilities.
doh_modules/tests/Tests display color-coded status badges:
Individual Test Execution:
Batch Test Execution:
Global Statistics:
Real-time console forwarding with:
log - Light gray (#d4d4d4)info - Cyan (#00d4aa)warn - Yellow (#ffcc02)error - Red (#f85149)debug - Gray (#8b949e)Visual countdown timer displays:
After batch execution, a detailed summary shows:
Express routes are automatically created for all test modules:
/tests - Main test launcher interface with folder organization/test/{module_name} - Direct access to individual test executiontest_* and debug_* modules?iframe=true parameter?debug=true to enable verbose debuggingThe framework automatically creates routes for every test module found in Doh.Packages:
// Automatically created routes:
/test/test_use_array_advanced
/test/test_use_state_simple
/test/test_undo_redo_system
// ... etc for all test modules
The framework includes comprehensive example tests in doh_modules/tests/use/:
test_use_state_simple.doh.js
test_use_state_pattern.doh.js
test_use_array_advanced.doh.js (117 assertions)
test_use_array_safe.doh.js
test_undo_redo_system.doh.js
test_use_snapshot.doh.js
test_deep_observation.doh.js
example/test_server_example (Package)
express_routerfetch/test-api/hello routeEvery test module must follow this structure to work properly with the test launcher:
Doh.Module('test_my_feature', ['tests_harness'], async function(tests_harness) {
// 1. Import harness functions (REQUIRED)
const { expect, expectEqual, resetTests, summarizeTests } = tests_harness;
// 2. Reset test counters (REQUIRED)
resetTests();
// 3. Optional: Set custom timeout for long tests
// setTestTimeout(30000); // 30 seconds
// 4. Run your tests - call expect/expectEqual directly
expect(true, 'Basic truth test');
expectEqual(2 + 2, 4, 'Math works');
// 5. Summarize and return result (REQUIRED)
return summarizeTests('My Feature Tests');
});
resetTests() at the start - initializes harness trackingsummarizeTests() at the end - sends completion message to launchersummarizeTests() - enables success trackingresetTests and summarizeTests - both are requiredβ Don't skip resetTests():
// BAD - Test counters not initialized
const { expect } = tests_harness;
expect(true, 'Test'); // Harness not ready!
β Don't skip summarizeTests():
// BAD - Test never completes, times out after 15 seconds
expect(true, 'Test');
// Missing: return summarizeTests('Test');
β Don't create custom test wrappers:
// BAD - Custom tracking instead of harness
let passed = 0, failed = 0;
function runTest(fn) { /* custom logic */ }
runTest(() => expect(true, 'Test'));
β Don't throw errors on failure:
// BAD - Throws instead of using harness
if (failed > 0) {
throw new Error('Tests failed'); // Launcher never gets proper completion
}
await and async utilities for asynchronous operationsconsole.log() for test progress informationdoh_modules/tests/
βββ tests.js # Main test framework (harness + launcher + routes + CLI registration)
βββ cli_test_runner.js # CLI test runner (doh test command)
βββ use/ # USE system tests
β βββ test_*.doh.js # Individual test modules
β βββ debug_*.doh.js # Debug/development tests
βββ README.md # This documentation
The test framework integrates seamlessly with:
=== My Feature Test Suite ===
β PASS: Basic functionality works
β PASS: Values match (expected 42, got 42)
β FAIL: Edge case handling (expected true, got false)
=== My Feature Test Suite Test Summary ===
Total: 3, Passed: 2, Failed: 1
Failures: ['Edge case handling (expected true, got false)']
Running test: test_use_array_advanced
β PASS: Push operation works
β PASS: Pop returns correct value
β PASS: Splice removes correct elements
...
Test "test_use_array_advanced" completed: {total: 117, passed: 117, failed: 0}
Running test: test_undo_redo_system
Test timeout updated to 30000ms (30s)
β PASS: Snapshot should complete
β PASS: First edit should create timeline entry
...
Test "test_undo_redo_system" completed: {total: 25, passed: 25, failed: 0}
Batch run complete: 5 tests processed in 12.3s
Problem: Test shows "ERROR" status with "Test timed out - no response for 15 seconds"
Solutions:
setTestTimeout(30000) for long-running testssummarizeTests() is called at the endawaitProblem: Test shows "RUNNING" status indefinitely
Cause: Missing summarizeTests() call
Solution:
// Add at the end of your test:
return summarizeTests('Your Test Name');
Problem: No pass/fail statistics displayed
Cause: Missing resetTests() at the start
Solution:
// Add at the beginning of your test:
resetTests();
Problem: Built your own test tracking system, but launcher shows no results
Cause: Not using the harness properly
Solution: Remove custom tracking and use harness functions directly:
// Remove: let passed = 0; function runTest() { ... }
// Use: const { expect, resetTests, summarizeTests } = tests_harness;
/test/{module_name}?iframe=trueresetTests()setTestTimeout() for custom timeoutexpect(), expectEqual(), etc.summarizeTests() which sends completion messageTests communicate with the launcher via postMessage:
test_heartbeat - Sent every 2s during test executiontest_set_timeout - Sent when test calls setTestTimeout()test_completed - Sent when test calls summarizeTests()console - Forwarded console output (log, error, warn, etc.)The test framework provides a production-ready foundation for testing all aspects of Doh.js applications with:
β
Comprehensive Harness - Rich assertion library with 15+ test functions
β
Full-Stack Testing - Server and browser tests in single packages with nodejs?? prefix
β
Visual Test Runner - Modern UI with folder organization and batch execution
β
CLI Test Runner - Run tests from command line with doh test (CI-friendly exit codes)
β
Timeout Management - Configurable timeouts with visual countdown (default 15s)
β
Real-time Monitoring - Live console output and heartbeat tracking
β
Batch Execution - Run entire test suites with progress tracking
β
Detailed Reporting - Summary views with error/warning capture
β
Auto-discovery - Automatic test detection and route creation
β
Iframe Isolation - Each test runs in its own sandbox
β
Environment Detection - Automatic server/browser detection with IsBrowser()/IsNode()
The framework is battle-tested with 117+ assertions across multiple test modules covering arrays, state management, snapshots, observers, and complex async scenarios.