UNPKG

@ordojs/core

Version:

Core compiler and runtime for OrdoJS framework

283 lines (282 loc) 9.53 kB
/** * @fileoverview Snapshot Testing Utilities */ import { existsSync } from 'fs'; import { mkdir, readFile, writeFile } from 'fs/promises'; import { dirname, join } from 'path'; /** * Utilities for snapshot testing generated code */ export class SnapshotTester { snapshotDir; updateSnapshots; constructor(snapshotDir = '__snapshots__', updateSnapshots = false) { this.snapshotDir = snapshotDir; this.updateSnapshots = updateSnapshots; } /** * Test generated code against snapshot */ async testSnapshot(testName, generatedCode, options) { const startTime = performance.now(); const errors = []; const warnings = []; try { // Normalize the generated code const normalizedCode = this.normalizeGeneratedCode(generatedCode, options); // Get snapshot file path const snapshotPath = this.getSnapshotPath(testName); // Check if snapshot exists const snapshotExists = existsSync(snapshotPath); if (!snapshotExists || options.updateSnapshots || this.updateSnapshots) { // Create or update snapshot await this.createSnapshot(snapshotPath, normalizedCode); if (!snapshotExists) { warnings.push(`Created new snapshot for test "${testName}"`); } else { warnings.push(`Updated snapshot for test "${testName}"`); } return { success: true, errors, warnings, duration: performance.now() - startTime }; } // Load existing snapshot const existingSnapshot = await this.loadSnapshot(snapshotPath); // Compare snapshots const comparison = this.compareSnapshots(normalizedCode, existingSnapshot, options); if (!comparison.matches) { errors.push(`Snapshot mismatch for test "${testName}"`); errors.push(...comparison.differences); } return { success: comparison.matches, errors, warnings, duration: performance.now() - startTime, metadata: { snapshotPath, differences: comparison.differences } }; } catch (error) { errors.push(error instanceof Error ? error.message : String(error)); return { success: false, errors, warnings, duration: performance.now() - startTime }; } } /** * Normalize generated code for consistent snapshots */ normalizeGeneratedCode(generatedCode, options) { let normalized = ''; // Add client code if (generatedCode.client) { normalized += '// CLIENT CODE\n'; normalized += this.normalizeCode(generatedCode.client, options); normalized += '\n\n'; } // Add server code if (generatedCode.server) { normalized += '// SERVER CODE\n'; normalized += this.normalizeCode(generatedCode.server, options); normalized += '\n\n'; } // Add HTML if (generatedCode.html) { normalized += '// HTML TEMPLATE\n'; normalized += this.normalizeHTML(generatedCode.html, options); normalized += '\n\n'; } // Add CSS if (generatedCode.css) { normalized += '// CSS STYLES\n'; normalized += this.normalizeCode(generatedCode.css, options); normalized += '\n\n'; } return normalized.trim(); } /** * Normalize code content */ normalizeCode(code, options) { let normalized = code; // Apply custom normalizer if provided if (options.customNormalizer) { normalized = options.customNormalizer(normalized); } // Remove excessive whitespace if configured if (options.ignoreWhitespace) { normalized = normalized.replace(/\s+/g, ' ').trim(); } // Normalize line endings normalized = normalized.replace(/\r\n/g, '\n'); return normalized; } /** * Normalize HTML content */ normalizeHTML(html, options) { let normalized = html; // Apply custom normalizer if provided if (options.customNormalizer) { normalized = options.customNormalizer(normalized); } // Normalize HTML if configured if (options.normalizeHTML) { // Remove extra whitespace between tags normalized = normalized.replace(/>\s+</g, '><'); // Normalize attribute order (simple approach) normalized = normalized.replace(/<(\w+)([^>]*)>/g, (match, tagName, attributes) => { if (!attributes.trim()) return match; // Sort attributes alphabetically const attrs = attributes.trim().split(/\s+/) .filter(attr => attr.includes('=')) .sort(); return `<${tagName} ${attrs.join(' ')}>`; }); } // Remove excessive whitespace if configured if (options.ignoreWhitespace) { normalized = normalized.replace(/\s+/g, ' ').trim(); } // Normalize line endings normalized = normalized.replace(/\r\n/g, '\n'); return normalized; } /** * Get snapshot file path */ getSnapshotPath(testName) { const sanitizedName = testName.replace(/[^a-zA-Z0-9-_]/g, '_'); return join(this.snapshotDir, `${sanitizedName}.snap`); } /** * Create snapshot file */ async createSnapshot(snapshotPath, content) { // Ensure directory exists const dir = dirname(snapshotPath); if (!existsSync(dir)) { await mkdir(dir, { recursive: true }); } // Write snapshot with metadata const snapshotContent = this.createSnapshotContent(content); await writeFile(snapshotPath, snapshotContent, 'utf8'); } /** * Create snapshot file content with metadata */ createSnapshotContent(content) { const timestamp = new Date().toISOString(); const header = `// OrdoJS Snapshot // Generated at: ${timestamp} // Do not edit this file manually `; return header + content; } /** * Load existing snapshot */ async loadSnapshot(snapshotPath) { const content = await readFile(snapshotPath, 'utf8'); // Remove header comments const lines = content.split('\n'); const contentStart = lines.findIndex(line => !line.startsWith('//') && line.trim() !== ''); return contentStart >= 0 ? lines.slice(contentStart).join('\n') : content; } /** * Compare two snapshots */ compareSnapshots(current, existing, options) { const differences = []; // Simple line-by-line comparison const currentLines = current.split('\n'); const existingLines = existing.split('\n'); const maxLines = Math.max(currentLines.length, existingLines.length); for (let i = 0; i < maxLines; i++) { const currentLine = currentLines[i] || ''; const existingLine = existingLines[i] || ''; if (currentLine !== existingLine) { differences.push(`Line ${i + 1}:`); differences.push(` Expected: ${existingLine}`); differences.push(` Received: ${currentLine}`); } } return { matches: differences.length === 0, differences }; } /** * Test multiple snapshots */ async testMultipleSnapshots(tests) { const results = []; for (const test of tests) { const options = { name: test.name, updateSnapshots: this.updateSnapshots, ...test.options }; const result = await this.testSnapshot(test.name, test.generatedCode, options); results.push(result); } return results; } /** * Clean up old snapshots */ async cleanupSnapshots(activeTestNames) { // This would implement cleanup of unused snapshot files // For now, just log what would be cleaned up console.log(`Would clean up snapshots not in: ${activeTestNames.join(', ')}`); } /** * Get snapshot statistics */ async getSnapshotStats() { // This would implement snapshot statistics // For now, return placeholder data return { total: 0, passed: 0, failed: 0, obsolete: 0 }; } /** * Set update snapshots mode */ setUpdateSnapshots(update) { this.updateSnapshots = update; } /** * Get update snapshots mode */ getUpdateSnapshots() { return this.updateSnapshots; } /** * Set snapshot directory */ setSnapshotDir(dir) { this.snapshotDir = dir; } /** * Get snapshot directory */ getSnapshotDir() { return this.snapshotDir; } } //# sourceMappingURL=snapshot-tester.js.map