#!/usr/bin/env node /** * TDD Test Runner for JavaScript Refactoring * * Drives component extraction and testing during architecture refactoring. * Ensures all functionality remains stable while achieving separation of concerns. */ class RefactorTestRunner { constructor() { this.tests = []; this.passed = 0; this.failed = 0; this.currentSuite = null; this.setupDOM(); } setupDOM() { // Set up minimal DOM environment for testing if (typeof document === 'undefined') { const { JSDOM } = require('jsdom'); const dom = new JSDOM('
', { url: 'http://localhost', pretendToBeVisual: true, resources: 'usable' }); global.window = dom.window; global.document = dom.window.document; global.HTMLElement = dom.window.HTMLElement; global.Event = dom.window.Event; global.CustomEvent = dom.window.CustomEvent; // Only set navigator if it doesn't exist if (typeof global.navigator === 'undefined') { global.navigator = dom.window.navigator; } } } describe(suiteName, fn) { console.log(`\n๐ ${suiteName}`); this.currentSuite = suiteName; fn(); this.currentSuite = null; } it(testName, fn) { const fullName = this.currentSuite ? `${this.currentSuite}: ${testName}` : testName; try { fn(); console.log(` โ ${testName}`); this.passed++; } catch (error) { console.log(` โ ${testName}`); console.log(` Error: ${error.message}`); if (error.stack) { console.log(` Stack: ${error.stack.split('\n')[1]?.trim()}`); } this.failed++; } } expect(actual) { return { toBe: (expected) => { if (actual !== expected) { throw new Error(`Expected ${expected}, got ${actual}`); } }, toBeTruthy: () => { if (!actual) { throw new Error(`Expected truthy value, got ${actual}`); } }, toBeFalsy: () => { if (actual) { throw new Error(`Expected falsy value, got ${actual}`); } }, toEqual: (expected) => { if (JSON.stringify(actual) !== JSON.stringify(expected)) { throw new Error(`Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); } }, toContain: (expected) => { if (!actual.includes(expected)) { throw new Error(`Expected ${actual} to contain ${expected}`); } }, toHaveProperty: (property) => { if (!(property in actual)) { throw new Error(`Expected object to have property ${property}`); } }, toBeInstanceOf: (expectedClass) => { if (!(actual instanceof expectedClass)) { throw new Error(`Expected instance of ${expectedClass.name}, got ${actual.constructor.name}`); } } }; } /** * Test that a component can be extracted from the monolith without breaking functionality */ testComponentExtraction(componentName, extractFn, originalTests) { this.describe(`Component Extraction: ${componentName}`, () => { this.it('should extract without syntax errors', () => { try { const component = extractFn(); this.expect(component).toBeTruthy(); } catch (error) { throw new Error(`Component extraction failed: ${error.message}`); } }); this.it('should maintain original API', () => { const component = extractFn(); originalTests.forEach(test => { try { test(component); } catch (error) { throw new Error(`API compatibility test failed: ${error.message}`); } }); }); }); } /** * Test component integration after extraction */ testComponentIntegration(components, integrationTests) { this.describe('Component Integration', () => { integrationTests.forEach((test, index) => { this.it(`integration test ${index + 1}`, () => { test(components); }); }); }); } /** * Setup test environment with mock dependencies */ setupTestEnvironment() { // Create test container const container = document.createElement('div'); container.id = 'test-container'; container.innerHTML = ''; document.body.appendChild(container); // Mock any global dependencies global.mockSectionManager = { sections: new Map(), createSectionsFromMarkdown: () => [], startEditing: () => true, stopEditing: () => true, getAllSections: () => [] }; return { container }; } /** * Cleanup test environment */ cleanupTestEnvironment() { const container = document.getElementById('test-container'); if (container) { container.remove(); } // Clear any global mocks delete global.mockSectionManager; } async run() { console.log('๐งช TDD Refactoring Test Runner Starting...\n'); const startTime = Date.now(); // Run all collected tests // Tests will be added by importing component test files const endTime = Date.now(); const duration = endTime - startTime; console.log(`\n๐ Test Results:`); console.log(` โ Passed: ${this.passed}`); console.log(` โ Failed: ${this.failed}`); console.log(` โฑ๏ธ Duration: ${duration}ms`); if (this.failed > 0) { console.log(`\nโ ${this.failed} test(s) failed. Refactoring should not proceed.`); process.exit(1); } else { console.log(`\nโ All tests passed! Refactoring is safe to continue.`); } } } // Export for use in component tests if (typeof module !== 'undefined' && module.exports) { module.exports = { RefactorTestRunner }; } // Export for browser use if (typeof window !== 'undefined') { window.RefactorTestRunner = RefactorTestRunner; } module.exports = RefactorTestRunner;