#!/usr/bin/env bun /** * HYPERSCRIPT VALIDATION TEST * ============================ * Tests hyperscript integrity and parse error detection * - Verifies no parse errors on page load * - Validates max 3 def statements rule * - Checks keyboard event handlers * - Verifies operator precedence (parentheses) */ import { chromium } from 'playwright'; const URL = "http://localhost:1999"; async function testHyperscript() { console.log('šŸ“œ HYPERSCRIPT VALIDATION TEST\n'); console.log('='.repeat(70)); const browser = await chromium.launch({ headless: false }); const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } }); const errors = []; const testResults = []; page.on('console', msg => { const text = msg.text(); if (msg.type() === 'error') { errors.push(text); console.log(`āŒ ERROR: ${text}`); } }); page.on('pageerror', err => { errors.push(err.message); console.log(`āŒ PAGE ERROR: ${err.message}`); }); console.log("\n1ļøāƒ£ Loading page..."); await page.goto(URL); await page.waitForTimeout(2000); // ======================================================================== // TEST 1: No hyperscript parse errors // ======================================================================== console.log("\n2ļøāƒ£ Testing Parse Error Detection..."); const parseErrors = errors.filter(err => err.includes('hyperscript') || err.includes('_hyperscript') || err.includes('parse error') || err.includes('Unexpected token') ); const parseTest = parseErrors.length === 0; console.log(` Parse errors: ${parseErrors.length}`); if (parseErrors.length > 0) { parseErrors.forEach((err, i) => { console.log(` ${i + 1}. ${err}`); }); } console.log(` ${parseTest ? 'āœ… PASS' : 'āŒ FAIL'} - No parse errors`); testResults.push({ test: 'No Parse Errors', passed: parseTest }); // ======================================================================== // TEST 2: Hyperscript functions are defined // ======================================================================== console.log("\n3ļøāƒ£ Testing Function Definitions..."); const functionsTest = await page.evaluate(() => { const hasToggleCVLength = typeof toggleCVLength === 'function'; const hasToggleIcons = typeof toggleIcons === 'function'; const hasToggleTheme = typeof toggleTheme === 'function'; const hasKeyboardHandler = typeof handleKeyboardShortcut === 'function'; return { toggleCVLength: hasToggleCVLength, toggleIcons: hasToggleIcons, toggleTheme: hasToggleTheme, keyboardHandler: hasKeyboardHandler, allDefined: hasToggleCVLength && hasToggleIcons && hasToggleTheme && hasKeyboardHandler }; }); console.log(` toggleCVLength: ${functionsTest.toggleCVLength ? 'āœ…' : 'āŒ'}`); console.log(` toggleIcons: ${functionsTest.toggleIcons ? 'āœ…' : 'āŒ'}`); console.log(` toggleTheme: ${functionsTest.toggleTheme ? 'āœ…' : 'āŒ'}`); console.log(` handleKeyboardShortcut: ${functionsTest.keyboardHandler ? 'āœ…' : 'āŒ'}`); console.log(` ${functionsTest.allDefined ? 'āœ… PASS' : 'āŒ FAIL'} - All functions defined`); testResults.push({ test: 'Function Definitions', passed: functionsTest.allDefined }); // ======================================================================== // TEST 3: Keyboard event handlers work // ======================================================================== console.log("\n4ļøāƒ£ Testing Keyboard Event Handlers..."); const keyboardTest = await page.evaluate(async () => { const body = document.body; const initialTheme = body.classList.contains('theme-clean'); // Simulate 'v' key press const event = new KeyboardEvent('keydown', { key: 'v', bubbles: true }); document.body.dispatchEvent(event); await new Promise(r => setTimeout(r, 300)); const afterTheme = body.classList.contains('theme-clean'); return { initialTheme, afterTheme, toggled: initialTheme !== afterTheme }; }); console.log(` Before: ${keyboardTest.initialTheme ? 'clean' : 'default'}`); console.log(` After: ${keyboardTest.afterTheme ? 'clean' : 'default'}`); console.log(` ${keyboardTest.toggled ? 'āœ… PASS' : 'āŒ FAIL'} - Keyboard handler works`); testResults.push({ test: 'Keyboard Event Handlers', passed: keyboardTest.toggled }); // ======================================================================== // TEST 4: Check for def statements (should be 0 in HTML) // ======================================================================== console.log("\n5ļøāƒ£ Testing Def Statement Count..."); const defTest = await page.evaluate(() => { const scripts = Array.from(document.querySelectorAll('script[type="_hyperscript"]')); const defCount = scripts.reduce((count, script) => { const matches = script.textContent.match(/\bdef\b/g); return count + (matches ? matches.length : 0); }, 0); const inlineElements = Array.from(document.querySelectorAll('[_]')); const inlineDefCount = inlineElements.reduce((count, el) => { const matches = el.getAttribute('_').match(/\bdef\b/g); return count + (matches ? matches.length : 0); }, 0); return { scriptDefs: defCount, inlineDefs: inlineDefCount, total: defCount + inlineDefCount }; }); console.log(` Script tag defs: ${defTest.scriptDefs}`); console.log(` Inline defs: ${defTest.inlineDefs}`); console.log(` Total: ${defTest.total}`); console.log(` ${defTest.total <= 3 ? 'āœ… PASS' : 'āŒ FAIL'} - Within 3 def limit`); testResults.push({ test: 'Def Statement Count (≤3)', passed: defTest.total <= 3 }); // ======================================================================== // TEST 5: Operator precedence validation // ======================================================================== console.log("\n6ļøāƒ£ Testing Operator Precedence..."); const precedenceTest = await page.evaluate(() => { const elements = Array.from(document.querySelectorAll('[_]')); const problematicPatterns = []; elements.forEach(el => { const script = el.getAttribute('_'); // Check for unparenthesized 'or' and 'and' if (script.includes(' or ') || script.includes(' and ')) { // Look for patterns like: a or b and c (should be (a or b) and c) const hasProblematicPattern = /\w+\s+or\s+\w+\s+and\s+\w+/.test(script) || /\w+\s+and\s+\w+\s+or\s+\w+/.test(script); if (hasProblematicPattern) { problematicPatterns.push({ element: el.tagName, id: el.id || 'no-id', script: script.substring(0, 100) }); } } }); return { problematicCount: problematicPatterns.length, patterns: problematicPatterns }; }); console.log(` Problematic patterns: ${precedenceTest.problematicCount}`); if (precedenceTest.problematicCount > 0) { precedenceTest.patterns.forEach((p, i) => { console.log(` ${i + 1}. <${p.element}${p.id !== 'no-id' ? ` id="${p.id}"` : ''}>`); }); } console.log(` ${precedenceTest.problematicCount === 0 ? 'āœ… PASS' : 'āŒ FAIL'} - Proper operator precedence`); testResults.push({ test: 'Operator Precedence', passed: precedenceTest.problematicCount === 0 }); // ======================================================================== // FINAL SUMMARY // ======================================================================== console.log("\n" + "=".repeat(70)); console.log("šŸ“Š TEST SUMMARY\n"); const totalTests = testResults.length; const passedTests = testResults.filter(r => r.passed).length; const failedTests = totalTests - passedTests; testResults.forEach(result => { console.log(` ${result.passed ? 'āœ…' : 'āŒ'} ${result.test}`); }); console.log(`\n Total: ${passedTests}/${totalTests} tests passed`); if (errors.length === 0) { console.log("\nāœ… NO CONSOLE ERRORS"); } else { console.log(`\nāš ļø ${errors.length} CONSOLE ERRORS (see details above)`); } console.log("=".repeat(70) + "\n"); if (failedTests === 0 && parseErrors.length === 0) { console.log("šŸŽ‰ HYPERSCRIPT VALIDATION PASSED!"); } else { console.log("āš ļø SOME TESTS FAILED - See details above"); } console.log("\nBrowser will stay open for manual inspection."); console.log("Press Ctrl+C when done.\n"); await new Promise(() => {}); // Keep browser open } await testHyperscript();