3f77fedeaf
CRITICAL FIX: Icon toggle now works without page refresh - Changed class name from 'show-logos' to 'show-icons' (CSS mismatch bug) - Updated localStorage key from 'cv-logos' to 'cv-icons' - Fixed toggleIcons() function in cv-functions.js HYPERSCRIPT ARCHITECTURE: - Moved 6 toggle functions from hyperscript to JavaScript (cv-functions.js) - Solves hyperscript 0.9.14 parser limitation (max 3 def statements total) - Upgraded hyperscript from 0.9.12 to 0.9.14 - Fixed operator precedence in keyboard shortcuts - Cleaned view-controls.html templates (inline → function calls) NEW FILES: - static/js/cv-functions.js - Global toggle functions (6 functions) - HYPERSCRIPT-RULES.md - Permanent architecture documentation - tests/mjs/0-zoom.test.mjs - Zoom functionality test - tests/mjs/1-toggles.test.mjs - Comprehensive toggle test with real-time verification - tests/TEST-SUMMARY.md - Test suite documentation TESTS: - Real-time DOM update verification (no refresh required) - Screenshot capture for visual regression - localStorage persistence validation - Toggle synchronization between action bar and menu BREAKING CHANGE: localStorage key changed from 'cv-logos' to 'cv-icons' Users may need to re-toggle icons preference on first load after update.
303 lines
11 KiB
JavaScript
Executable File
303 lines
11 KiB
JavaScript
Executable File
#!/usr/bin/env bun
|
|
/**
|
|
* COMPREHENSIVE TOGGLE TEST
|
|
* ==========================
|
|
* Tests ALL toggles work with REAL-TIME visual verification
|
|
* - Checks that toggles update DOM immediately (no refresh needed)
|
|
* - Verifies localStorage persistence
|
|
* - Tests synchronization between action bar and menu toggles
|
|
* - Validates visual rendering changes
|
|
*/
|
|
|
|
import { chromium } from "playwright";
|
|
|
|
const URL = "http://localhost:1999";
|
|
|
|
async function testAllToggles() {
|
|
console.log("🧪 COMPREHENSIVE TOGGLE 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: Length Toggle (Action Bar)
|
|
// ========================================================================
|
|
console.log("\n2️⃣ Testing Length Toggle (Action Bar)...");
|
|
const lengthToggle = await page.$('#lengthToggle');
|
|
if (lengthToggle) {
|
|
const paper = await page.$('.cv-paper');
|
|
|
|
// Get initial state
|
|
const before = await paper.evaluate(el => ({
|
|
className: el.className,
|
|
isLong: el.classList.contains('cv-long'),
|
|
isShort: el.classList.contains('cv-short')
|
|
}));
|
|
|
|
// Click toggle
|
|
await lengthToggle.click();
|
|
await page.waitForTimeout(300); // Wait for DOM update
|
|
|
|
// Get state after click
|
|
const after = await paper.evaluate(el => ({
|
|
className: el.className,
|
|
isLong: el.classList.contains('cv-long'),
|
|
isShort: el.classList.contains('cv-short')
|
|
}));
|
|
|
|
// Verify localStorage
|
|
const localStorage = await page.evaluate(() => localStorage.getItem('cv-length'));
|
|
|
|
const changed = before.isLong !== after.isLong;
|
|
const testPassed = changed && (after.isLong ? localStorage === 'long' : localStorage === 'short');
|
|
|
|
console.log(` Before: ${before.isLong ? 'long' : 'short'}`);
|
|
console.log(` After: ${after.isLong ? 'long' : 'short'}`);
|
|
console.log(` localStorage: ${localStorage}`);
|
|
console.log(` Visual change: ${changed ? '✅ YES' : '❌ NO'}`);
|
|
console.log(` ${testPassed ? '✅ PASS' : '❌ FAIL'}`);
|
|
|
|
testResults.push({ test: 'Length Toggle (Action Bar)', passed: testPassed });
|
|
} else {
|
|
console.log(` ❌ Toggle not found`);
|
|
testResults.push({ test: 'Length Toggle (Action Bar)', passed: false });
|
|
}
|
|
|
|
// ========================================================================
|
|
// TEST 2: Icon Toggle (Action Bar)
|
|
// ========================================================================
|
|
console.log("\n3️⃣ Testing Icon/Logo Toggle (Action Bar)...");
|
|
const iconToggle = await page.$('#iconToggle');
|
|
if (iconToggle) {
|
|
const paper = await page.$('.cv-paper');
|
|
|
|
// Take screenshot BEFORE toggle
|
|
await page.screenshot({ path: 'tests/screenshots/before-icon-toggle.png', fullPage: false });
|
|
|
|
const before = await paper.evaluate(el => ({
|
|
className: el.className,
|
|
showIcons: el.classList.contains('show-icons')
|
|
}));
|
|
|
|
// Click toggle
|
|
await iconToggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
// Take screenshot AFTER toggle
|
|
await page.screenshot({ path: 'tests/screenshots/after-icon-toggle.png', fullPage: false });
|
|
|
|
const after = await paper.evaluate(el => ({
|
|
className: el.className,
|
|
showIcons: el.classList.contains('show-icons')
|
|
}));
|
|
|
|
const localStorage = await page.evaluate(() => localStorage.getItem('cv-icons'));
|
|
|
|
const changed = before.showIcons !== after.showIcons;
|
|
const testPassed = changed && (after.showIcons ? localStorage === 'true' : localStorage === 'false');
|
|
|
|
console.log(` Before: ${before.showIcons ? 'icons shown' : 'icons hidden'}`);
|
|
console.log(` After: ${after.showIcons ? 'icons shown' : 'icons hidden'}`);
|
|
console.log(` localStorage: ${localStorage}`);
|
|
console.log(` Visual change: ${changed ? '✅ YES (no refresh needed)' : '❌ NO (requires refresh - BUG!)'}`);
|
|
console.log(` Screenshots saved: before-icon-toggle.png, after-icon-toggle.png`);
|
|
console.log(` ${testPassed ? '✅ PASS' : '❌ FAIL'}`);
|
|
|
|
testResults.push({ test: 'Icon Toggle (Action Bar)', passed: testPassed });
|
|
} else {
|
|
console.log(` ❌ Toggle not found`);
|
|
testResults.push({ test: 'Icon Toggle (Action Bar)', passed: false });
|
|
}
|
|
|
|
// ========================================================================
|
|
// TEST 3: Theme Toggle (Action Bar)
|
|
// ========================================================================
|
|
console.log("\n4️⃣ Testing Theme Toggle (Action Bar)...");
|
|
const themeToggle = await page.$('#themeToggle');
|
|
if (themeToggle) {
|
|
const body = await page.$('body');
|
|
|
|
const before = await body.evaluate(el => ({
|
|
className: el.className,
|
|
isClean: el.classList.contains('theme-clean')
|
|
}));
|
|
|
|
await themeToggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
const after = await body.evaluate(el => ({
|
|
className: el.className,
|
|
isClean: el.classList.contains('theme-clean')
|
|
}));
|
|
|
|
const localStorage = await page.evaluate(() => localStorage.getItem('cv-theme'));
|
|
|
|
const changed = before.isClean !== after.isClean;
|
|
const testPassed = changed && (after.isClean ? localStorage === 'clean' : localStorage === 'default');
|
|
|
|
console.log(` Before: ${before.isClean ? 'clean' : 'default'}`);
|
|
console.log(` After: ${after.isClean ? 'clean' : 'default'}`);
|
|
console.log(` localStorage: ${localStorage}`);
|
|
console.log(` Visual change: ${changed ? '✅ YES' : '❌ NO'}`);
|
|
console.log(` ${testPassed ? '✅ PASS' : '❌ FAIL'}`);
|
|
|
|
testResults.push({ test: 'Theme Toggle (Action Bar)', passed: testPassed });
|
|
} else {
|
|
console.log(` ❌ Toggle not found`);
|
|
testResults.push({ test: 'Theme Toggle (Action Bar)', passed: false });
|
|
}
|
|
|
|
// ========================================================================
|
|
// TEST 4: Hamburger Menu Toggles + Synchronization
|
|
// ========================================================================
|
|
console.log("\n5️⃣ Testing Hamburger Menu + Toggle Synchronization...");
|
|
const hamburger = await page.$('.hamburger-btn');
|
|
if (hamburger) {
|
|
await hamburger.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const menu = await page.$('.navigation-menu');
|
|
const isOpen = await menu.evaluate(el => el.classList.contains('menu-open'));
|
|
console.log(` ${isOpen ? '✅ Menu opened' : '❌ Menu failed to open'}`);
|
|
|
|
if (isOpen) {
|
|
// Test Menu Length Toggle
|
|
console.log("\n6️⃣ Testing Length Toggle (Menu)...");
|
|
const menuLengthToggle = await page.$('#lengthToggleMenu');
|
|
if (menuLengthToggle) {
|
|
const paper = await page.$('.cv-paper');
|
|
const before = await paper.evaluate(el => el.classList.contains('cv-long'));
|
|
|
|
await menuLengthToggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
const after = await paper.evaluate(el => el.classList.contains('cv-long'));
|
|
|
|
// Check if action bar toggle synchronized
|
|
const actionBarSynced = await page.$eval('#lengthToggle', el => el.checked);
|
|
const menuChecked = await page.$eval('#lengthToggleMenu', el => el.checked);
|
|
|
|
const changed = before !== after;
|
|
const synced = actionBarSynced === menuChecked;
|
|
|
|
console.log(` Visual change: ${changed ? '✅ YES' : '❌ NO'}`);
|
|
console.log(` Synchronization: ${synced ? '✅ YES' : '❌ NO'}`);
|
|
console.log(` ${changed && synced ? '✅ PASS' : '❌ FAIL'}`);
|
|
|
|
testResults.push({ test: 'Length Toggle (Menu + Sync)', passed: changed && synced });
|
|
}
|
|
|
|
// Test Menu Icon Toggle
|
|
console.log("\n7️⃣ Testing Icon Toggle (Menu)...");
|
|
const menuIconToggle = await page.$('#iconToggleMenu');
|
|
if (menuIconToggle) {
|
|
const paper = await page.$('.cv-paper');
|
|
const before = await paper.evaluate(el => el.classList.contains('show-icons'));
|
|
|
|
await menuIconToggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
const after = await paper.evaluate(el => el.classList.contains('show-icons'));
|
|
|
|
const actionBarSynced = await page.$eval('#iconToggle', el => el.checked);
|
|
const menuChecked = await page.$eval('#iconToggleMenu', el => el.checked);
|
|
|
|
const changed = before !== after;
|
|
const synced = actionBarSynced === menuChecked;
|
|
|
|
console.log(` Visual change: ${changed ? '✅ YES (no refresh!)' : '❌ NO (BUG!)'}`);
|
|
console.log(` Synchronization: ${synced ? '✅ YES' : '❌ NO'}`);
|
|
console.log(` ${changed && synced ? '✅ PASS' : '❌ FAIL'}`);
|
|
|
|
testResults.push({ test: 'Icon Toggle (Menu + Sync)', passed: changed && synced });
|
|
}
|
|
|
|
// Test Menu Theme Toggle
|
|
console.log("\n8️⃣ Testing Theme Toggle (Menu)...");
|
|
const menuThemeToggle = await page.$('#themeToggleMenu');
|
|
if (menuThemeToggle) {
|
|
const body = await page.$('body');
|
|
const before = await body.evaluate(el => el.classList.contains('theme-clean'));
|
|
|
|
await menuThemeToggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
const after = await body.evaluate(el => el.classList.contains('theme-clean'));
|
|
|
|
const actionBarSynced = await page.$eval('#themeToggle', el => el.checked);
|
|
const menuChecked = await page.$eval('#themeToggleMenu', el => el.checked);
|
|
|
|
const changed = before !== after;
|
|
const synced = actionBarSynced === menuChecked;
|
|
|
|
console.log(` Visual change: ${changed ? '✅ YES' : '❌ NO'}`);
|
|
console.log(` Synchronization: ${synced ? '✅ YES' : '❌ NO'}`);
|
|
console.log(` ${changed && synced ? '✅ PASS' : '❌ FAIL'}`);
|
|
|
|
testResults.push({ test: 'Theme Toggle (Menu + Sync)', passed: changed && synced });
|
|
}
|
|
}
|
|
}
|
|
|
|
// ========================================================================
|
|
// 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 FOUND:\n`);
|
|
errors.forEach((err, i) => {
|
|
console.log(`${i + 1}. ${err}`);
|
|
});
|
|
}
|
|
|
|
console.log("=".repeat(70) + "\n");
|
|
|
|
if (failedTests === 0 && errors.length === 0) {
|
|
console.log("🎉 ALL TESTS PASSED! All toggles work with real-time rendering.");
|
|
} 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 testAllToggles();
|