Test Migration Toolkit
Tools for migrating TypeScript test files (.test.ts) to declarative JSON format for the MetaBuilder unified test runner.
Overview
The toolkit consists of three main components:
- Converter - Parses TypeScript test files and converts them to JSON
- Migrator - Batch discovers and migrates all test files
- Validator - Validates JSON test files against the schema
Architecture
TypeScript .test.ts files
↓
[Converter] ← Uses TypeScript AST to parse test structure
↓
JSON test definitions
↓
[Validator] ← Ensures JSON conforms to schema
↓
/packages/*/unit-tests/tests.json
↓
[Unified Test Runner] ← Discovers and executes
Usage
1. Dry-Run Migration (Safe Preview)
# Preview what would be converted
npm --prefix scripts/migrate-tests run migrate -- --dry-run --verbose
# Or directly:
npx ts-node scripts/migrate-tests/migrator.ts --dry-run --verbose
2. Actual Migration
# Migrate all discovered .test.ts files
npm --prefix scripts/migrate-tests run migrate
# Or directly:
npx ts-node scripts/migrate-tests/migrator.ts
3. Validate Converted Tests
# Validate all JSON test files in packages
npm --prefix scripts/migrate-tests run validate
# Or directly:
npx ts-node scripts/migrate-tests/validator.ts packages
# Validate specific directory:
npx ts-node scripts/migrate-tests/validator.ts packages/my_package
How It Works
Conversion Process
The converter uses TypeScript's AST (Abstract Syntax Tree) to understand test structure:
- Parse imports - Extract all import statements into
importsarray - Extract test suites - Find all
describe()blocks - Parse tests - Extract
it()blocks within suites - Parse assertions - Extract
expect()calls and map matchers to JSON types - Build JSON - Construct JSON test definition with $schema, package, imports, testSuites
Matcher Mapping
The converter maps 30+ Vitest/Jest matchers to JSON assertion types:
| TypeScript Matcher | JSON Type | Example |
|---|---|---|
toBe() |
equals |
Strict equality |
toEqual() |
deepEquals |
Deep object equality |
toBeGreaterThan() |
greaterThan |
Numeric comparison |
toContain() |
contains |
String/array contains |
toThrow() |
throws |
Exception handling |
toBeVisible() |
toBeVisible |
DOM assertion |
toHaveClass() |
toHaveClass |
DOM assertion |
| ... and 24 more | ... | ... |
Package Name Mapping
Tests are placed in the appropriate package directory:
frontends/nextjs/*.test.ts→packages/nextjs_frontend/unit-tests/tests.jsonfrontends/cli/*.test.ts→packages/cli_frontend/unit-tests/tests.jsonfrontends/qt6/*.test.ts→packages/qt6_frontend/unit-tests/tests.json- Others →
packages/[extracted_name]/unit-tests/tests.json
JSON Test Format
Basic Structure
{
"$schema": "https://metabuilder.dev/schemas/tests.schema.json",
"schemaVersion": "2.0.0",
"package": "my_package",
"imports": [
{ "from": "@/lib/utils", "items": ["validateEmail"] }
],
"testSuites": [
{
"id": "suite_validate",
"name": "Email Validation",
"tests": [
{
"id": "test_valid_email",
"name": "should accept valid email",
"arrange": {
"fixtures": { "email": "user@example.com" }
},
"act": {
"type": "function_call",
"target": "validateEmail",
"input": "$arrange.fixtures.email"
},
"assert": {
"expectations": [
{
"type": "truthy",
"actual": "result",
"message": "Should return true for valid email"
}
]
}
}
]
}
]
}
Supported Actions (Act Phase)
function_call- Call an imported functionrender- Render React component (requires React Testing Library)click- Simulate click eventfill- Fill form inputselect- Select dropdown optionhover- Hover over elementfocus- Focus on elementblur- Blur from elementwaitFor- Wait for condition
Supported Assertions
Basic: equals, deepEquals, notEquals, truthy, falsy
Numeric: greaterThan, lessThan, greaterThanOrEqual, lessThanOrEqual
Type: null, notNull, undefined, notUndefined, instanceOf
Collection: contains, matches, hasProperty, hasLength
DOM: toBeVisible, toBeInTheDocument, toHaveTextContent, toHaveAttribute, toHaveClass, toBeDisabled, toBeEnabled, toHaveValue
Control: throws, notThrows, custom
Configuration
Converter Options
interface ConversionResult {
success: boolean;
jsonContent?: any; // The generated JSON
warnings: string[]; // Non-fatal issues
errors: string[]; // Fatal errors
}
Migrator Options
interface MigrationConfig {
dryRun?: boolean; // Preview only, don't write
verbose?: boolean; // Detailed logging
pattern?: string; // Glob pattern (default: 'frontends/**/*.test.ts')
targetDir?: string; // Output directory (default: 'packages')
}
CLI Flags
# Dry run (preview)
--dry-run
# Verbose logging
--verbose
# Custom glob pattern
--pattern 'src/**/*.test.ts'
# Custom target directory
--target-dir 'my-packages'
Limitations & Fallbacks
Known Limitations
- Complex Mocking - Tests with advanced mock setup (spies, call tracking) may not convert perfectly
- Custom Hooks - Tests with custom React hooks require manual adjustment
- Snapshots - Snapshot tests require manual conversion
- Dynamic Imports - Dynamic require() calls may not be captured
- Conditional Logic - Complex conditional test logic may be simplified
Handling Lossy Conversion
For tests that don't convert perfectly:
- Run converter with
--verboseto see warnings - Review warnings in output
- Manually adjust the generated JSON as needed
- Validate with validator tool
The 80/20 rule applies: ~80% of tests convert cleanly, ~20% need manual adjustment.
Workflow
Recommended Workflow
-
Backup - Commit current state before migration
git add . git commit -m "backup: before test migration" -
Dry Run - Preview what will happen
npx ts-node scripts/migrate-tests/migrator.ts --dry-run --verbose -
Migrate - Run actual migration
npx ts-node scripts/migrate-tests/migrator.ts --verbose -
Validate - Ensure JSON is valid
npx ts-node scripts/migrate-tests/validator.ts packages -
Test - Run unified test runner
npm run test:unified -
Commit - Save migration results
git add packages/*/unit-tests/ git commit -m "feat: migrate TypeScript tests to JSON format"
Examples
Example 1: Simple Function Test
TypeScript:
describe('Email Validation', () => {
it('should accept valid email', () => {
const result = validateEmail('user@example.com');
expect(result).toBe(true);
});
});
JSON (Converted):
{
"testSuites": [{
"name": "Email Validation",
"tests": [{
"name": "should accept valid email",
"act": {
"type": "function_call",
"target": "validateEmail",
"input": "user@example.com"
},
"assert": {
"expectations": [{
"type": "equals",
"expected": true
}]
}
}]
}]
}
Example 2: Test with Fixtures
TypeScript:
it('should validate email from fixture', () => {
const email = 'test@example.com';
const result = validateEmail(email);
expect(result).toBe(true);
});
JSON (Converted):
{
"arrange": {
"fixtures": { "email": "test@example.com" }
},
"act": {
"type": "function_call",
"target": "validateEmail",
"input": "$arrange.fixtures.email"
},
"assert": {
"expectations": [{
"type": "equals",
"expected": true
}]
}
}
Troubleshooting
Issue: "Schema not found"
Solution: Ensure schemas/package-schemas/tests_schema.json exists
Issue: "No test files found"
Solution: Check glob pattern matches your test files
# Verify pattern:
npx ts-node scripts/migrate-tests/migrator.ts --verbose
Issue: "Package directory not created"
Solution: Ensure output directory exists and is writable
mkdir -p packages/my_package/unit-tests
Issue: "Validation errors after conversion"
Solution: Review warnings and adjust JSON manually as needed
Files
converter.ts- Main conversion logic (350+ lines)migrator.ts- Batch migration orchestration (250+ lines)validator.ts- JSON validation against schema (300+ lines)index.ts- Export moduleREADME.md- This file
Integration with Unified Test Runner
After migration, tests are automatically discovered by the unified test runner:
import { UnifiedTestRunner } from '@/e2e/test-runner';
const runner = new UnifiedTestRunner();
const tests = await runner.discoverTests();
// Discovers: unit tests from packages/*/unit-tests/tests.json
Next Steps
- Run migration on existing TypeScript tests
- Validate all converted JSON
- Run unified test runner to execute tests
- Document any manual adjustments needed
- Update testing guidelines to use JSON format for new tests