UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

373 lines 13.8 kB
/** * @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