@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
373 lines • 13.8 kB
JavaScript
/**
* @fileoverview OrdoJS Test Framework - Main testing class
*/
import { JSDOM } from 'jsdom';
import { OrdoJSCodeGenerator, OrdoJSLexer, OrdoJSParser } from '../compiler/index.js';
import { ComponentTestUtils } from './component-utils.js';
import { ReactiveTestUtils } from './reactive-utils.js';
import { ServerMockUtils } from './server-mock.js';
import { SnapshotTester } from './snapshot-tester.js';
/**
* Main OrdoJS testing framework class
*/
export class OrdoJSTestFramework {
reactiveUtils;
serverMockUtils;
snapshotTester;
componentUtils;
currentEnvironment;
jsdomInstance;
constructor() {
this.reactiveUtils = new ReactiveTestUtils();
this.serverMockUtils = new ServerMockUtils();
this.snapshotTester = new SnapshotTester();
this.componentUtils = new ComponentTestUtils();
}
/**
* Set up test environment
*/
async setupEnvironment(environment) {
this.currentEnvironment = environment;
if (environment.jsdom) {
this.jsdomInstance = new JSDOM('<!DOCTYPE html><html><body></body></html>', {
url: 'http://localhost',
pretendToBeVisual: true,
resources: 'usable'
});
// Set up global DOM environment
global.window = this.jsdomInstance.window;
global.document = this.jsdomInstance.window.document;
global.navigator = this.jsdomInstance.window.navigator;
global.HTMLElement = this.jsdomInstance.window.HTMLElement;
global.Element = this.jsdomInstance.window.Element;
global.Node = this.jsdomInstance.window.Node;
}
// Set up global variables
if (environment.globals) {
Object.assign(global, environment.globals);
}
}
/**
* Clean up test environment
*/
async teardownEnvironment() {
if (this.jsdomInstance) {
this.jsdomInstance.window.close();
this.jsdomInstance = undefined;
}
// Clean up globals
if (this.currentEnvironment?.jsdom) {
delete global.window;
delete global.document;
delete global.navigator;
delete global.HTMLElement;
delete global.Element;
delete global.Node;
}
this.currentEnvironment = undefined;
}
/**
* Compile OrdoJS source code for testing
*/
async compileForTesting(source, options = {
target: 'es2022',
optimize: false,
sourceMaps: true,
minify: false
}) {
const startTime = performance.now();
try {
// Create lexer and tokenize
const lexer = new OrdoJSLexer();
const tokenStream = lexer.tokenize(source);
// Parse into AST
const parser = new OrdoJSParser(tokenStream);
const ast = parser.parse();
// Generate code
const generator = new OrdoJSCodeGenerator({
target: options.minify ? 'production' : 'development',
minify: options.minify,
sourceMaps: options.sourceMaps
});
const generatedCode = generator.generate(ast);
const duration = performance.now() - startTime;
return {
success: true,
errors: [],
warnings: [],
duration,
generatedCode,
componentInstance: null,
reactiveState: {},
domUpdates: []
};
}
catch (error) {
const duration = performance.now() - startTime;
return {
success: false,
errors: [error instanceof Error ? error.message : String(error)],
warnings: [],
duration,
componentInstance: null,
reactiveState: {},
domUpdates: []
};
}
}
/**
* Test component compilation and rendering
*/
async testComponent(source, options = {}) {
const startTime = performance.now();
try {
// Set up environment if needed
if (options.environment && !this.currentEnvironment) {
await this.setupEnvironment(options.environment);
}
// Mock server functions if provided
if (options.mockServerFunctions) {
for (const mockFn of options.mockServerFunctions) {
this.serverMockUtils.mockServerFunction(mockFn.name, mockFn.mock);
}
}
// Compile the component
const compilationResult = await this.compileForTesting(source);
if (!compilationResult.success || !compilationResult.generatedCode) {
return compilationResult;
}
// Create component instance
const componentInstance = await this.componentUtils.createComponentInstance(compilationResult.generatedCode, options.props || {}, options.initialState || {});
// Render HTML if not skipping hydration
let renderedHTML;
if (!options.skipHydration && compilationResult.generatedCode.html) {
renderedHTML = await this.componentUtils.renderComponent(componentInstance, options.props || {});
}
const duration = performance.now() - startTime;
return {
success: true,
errors: [],
warnings: [],
duration,
generatedCode: compilationResult.generatedCode,
renderedHTML,
componentInstance,
reactiveState: componentInstance?.getState?.() || {},
domUpdates: []
};
}
catch (error) {
const duration = performance.now() - startTime;
return {
success: false,
errors: [error instanceof Error ? error.message : String(error)],
warnings: [],
duration,
componentInstance: null,
reactiveState: {},
domUpdates: []
};
}
finally {
// Clean up mocks
if (options.mockServerFunctions) {
this.serverMockUtils.clearAllMocks();
}
}
}
/**
* Test reactive behavior
*/
async testReactivity(source, testCases) {
const results = [];
for (const testCase of testCases) {
const result = await this.testComponent(source, {
initialState: testCase.initialState,
environment: { jsdom: true }
});
if (result.success && result.componentInstance) {
// Execute actions
for (const action of testCase.actions) {
await this.reactiveUtils.executeAction(result.componentInstance, action);
}
// Verify final state
const finalState = result.componentInstance.getState?.() || {};
const stateMatches = this.reactiveUtils.compareStates(finalState, testCase.expectedState);
result.success = stateMatches;
if (!stateMatches) {
result.errors.push(`State mismatch in test "${testCase.name}". Expected: ${JSON.stringify(testCase.expectedState)}, Got: ${JSON.stringify(finalState)}`);
}
}
results.push(result);
}
return results;
}
/**
* Test compilation with multiple test cases
*/
async testCompilation(testCases) {
const results = [];
for (const testCase of testCases) {
try {
const result = await this.compileForTesting(testCase.source);
// Verify expectations
if (testCase.shouldFail) {
if (result.success) {
// Expected to fail but succeeded - mark as failure
result.success = false;
result.errors.push(`Expected compilation to fail for test "${testCase.name}"`);
}
else {
// Expected to fail and did fail - mark as success
const newResult = { ...result, success: true, errors: [] };
results.push(newResult);
continue;
}
}
if (testCase.expectedErrors) {
const hasExpectedErrors = testCase.expectedErrors.every(expectedError => result.errors.some(error => error.includes(expectedError)));
if (!hasExpectedErrors) {
result.success = false;
result.errors.push(`Expected errors not found in test "${testCase.name}". Expected: ${testCase.expectedErrors.join(', ')}`);
}
}
results.push(result);
}
catch (error) {
// If compilation throws an exception and we expected it to fail, that's a success
if (testCase.shouldFail) {
results.push({
success: true,
errors: [],
warnings: [],
duration: 0,
componentInstance: null,
reactiveState: {},
domUpdates: []
});
}
else {
// Otherwise it's a failure
results.push({
success: false,
errors: [error instanceof Error ? error.message : String(error)],
warnings: [],
duration: 0,
componentInstance: null,
reactiveState: {},
domUpdates: []
});
}
}
}
return results;
}
/**
* Test generated code snapshots
*/
async testSnapshots(testCases) {
const results = [];
for (const testCase of testCases) {
const compilationResult = await this.compileForTesting(testCase.source);
if (compilationResult.success && compilationResult.generatedCode) {
const snapshotResult = await this.snapshotTester.testSnapshot(testCase.name, compilationResult.generatedCode, {
name: testCase.name,
updateSnapshots: testCase.updateSnapshots
});
compilationResult.success = snapshotResult.success;
if (!snapshotResult.success) {
compilationResult.errors.push(...snapshotResult.errors);
}
}
results.push(compilationResult);
}
return results;
}
/**
* Measure performance metrics
*/
async measurePerformance(source, iterations = 10) {
const compilationTimes = [];
let bundleSize = 0;
for (let i = 0; i < iterations; i++) {
const startTime = performance.now();
const result = await this.compileForTesting(source);
const endTime = performance.now();
compilationTimes.push(endTime - startTime);
if (result.generatedCode) {
bundleSize = result.generatedCode.client.length + (result.generatedCode.html?.length || 0);
}
}
const avgCompilationTime = compilationTimes.reduce((a, b) => a + b, 0) / compilationTimes.length;
return {
compilationTime: avgCompilationTime,
bundleSize,
hydrationTime: 0, // TODO: Implement hydration timing
renderTime: 0, // TODO: Implement render timing
memoryUsage: 0 // TODO: Implement memory usage tracking
};
}
/**
* Run a complete test suite
*/
async runTestSuite(config, tests) {
console.log(`Running test suite: ${config.name}`);
try {
// Setup
if (config.environment) {
await this.setupEnvironment(config.environment);
}
if (config.setup) {
await config.setup();
}
// Run tests
if (config.parallel) {
await Promise.all(tests.map(test => test()));
}
else {
for (const test of tests) {
await test();
}
}
console.log(`✅ Test suite "${config.name}" completed successfully`);
}
catch (error) {
console.error(`❌ Test suite "${config.name}" failed:`, error);
throw error;
}
finally {
// Teardown
if (config.teardown) {
await config.teardown();
}
if (config.environment) {
await this.teardownEnvironment();
}
}
}
/**
* Get reactive testing utilities
*/
getReactiveUtils() {
return this.reactiveUtils;
}
/**
* Get server mocking utilities
*/
getServerMockUtils() {
return this.serverMockUtils;
}
/**
* Get snapshot testing utilities
*/
getSnapshotTester() {
return this.snapshotTester;
}
/**
* Get component testing utilities
*/
getComponentUtils() {
return this.componentUtils;
}
}
//# sourceMappingURL=test-framework.js.map