diff --git a/static/css/main.css b/static/css/main.css index c10d6c8..7997da6 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -2687,6 +2687,18 @@ text-align: center; color: var(--accent-blue); } +/* PDF button in menu - White bg with red icon on hover */ +.menu-pdf-btn:hover, +.menu-pdf-btn.pdf-hover-sync { + background: white !important; + color: #e74c3c !important; +} + +.menu-pdf-btn:hover iconify-icon, +.menu-pdf-btn.pdf-hover-sync iconify-icon { + color: #e74c3c !important; +} + /* Print button in menu - White bg with green icon on hover */ .menu-print-btn:hover, .menu-print-btn.print-hover-sync { diff --git a/static/js/cv-functions.js b/static/js/cv-functions.js index d3275a2..9d8dcab 100644 --- a/static/js/cv-functions.js +++ b/static/js/cv-functions.js @@ -69,7 +69,8 @@ function toggleTheme(isClean) { * @param {boolean} show - true to add hover class, false to remove */ function syncPdfHover(show) { - const pdfButtons = document.querySelectorAll('.pdf-btn'); + // Select both action bar PDF button and menu PDF button + const pdfButtons = document.querySelectorAll('.pdf-btn, .menu-pdf-btn'); pdfButtons.forEach(button => { if (show) { @@ -85,7 +86,8 @@ function syncPdfHover(show) { * @param {boolean} show - true to add hover class, false to remove */ function syncPrintHover(show) { - const printButtons = document.querySelectorAll('.print-btn'); + // Select both action bar Print button and menu Print button + const printButtons = document.querySelectorAll('.print-btn, .menu-print-btn'); printButtons.forEach(button => { if (show) { diff --git a/templates/partials/navigation/hamburger-menu.html b/templates/partials/navigation/hamburger-menu.html index 877397b..2d1cb46 100644 --- a/templates/partials/navigation/hamburger-menu.html +++ b/templates/partials/navigation/hamburger-menu.html @@ -175,7 +175,10 @@ {{if eq .Lang "es"}}Acciones{{else}}Actions{{end}} - diff --git a/tests/mjs/8-hover-sync.test.mjs b/tests/mjs/8-hover-sync.test.mjs new file mode 100755 index 0000000..8920f0b --- /dev/null +++ b/tests/mjs/8-hover-sync.test.mjs @@ -0,0 +1,306 @@ +#!/usr/bin/env bun +/** + * HOVER SYNCHRONIZATION TEST + * =========================== + * Tests hover state synchronization between action bar and hamburger menu buttons + * + * CRITICAL BUG: PDF/Print button hover states don't sync between locations + * + * Expected behavior: + * - Hovering action bar PDF button should highlight menu PDF button + * - Hovering action bar Print button should highlight menu Print button + * - Hovering menu buttons should highlight action bar buttons + * - Classes added: .pdf-hover-sync and .print-hover-sync + */ + +import { chromium } from 'playwright'; + +const URL = "http://localhost:1999"; + +async function testHoverSync() { + console.log('🔄 HOVER SYNCHRONIZATION 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 = []; + + // Track errors + page.on('console', msg => { + if (msg.type() === 'error') { + errors.push(msg.text()); + console.log(`❌ ERROR: ${msg.text()}`); + } + }); + + await page.goto(URL); + await page.waitForTimeout(2000); + + // ======================================================================== + // TEST 1: PDF Button Hover Sync (Action Bar → Menu) + // ======================================================================== + console.log("\n1️⃣ Testing PDF Button Hover Sync (Action Bar → Menu)..."); + + // Open hamburger menu to make menu buttons visible + const hamburger = await page.$('.hamburger-btn'); + if (hamburger) { + await hamburger.click(); + await page.waitForTimeout(500); + } + + const pdfTest1 = await page.evaluate(() => { + // Find buttons + const actionBarPdf = document.querySelector('#action-bar-pdf-btn'); + const menuPdf = document.querySelector('.menu-action-btn'); // First menu-action-btn is PDF + + if (!actionBarPdf || !menuPdf) { + return { found: false, actionBarPdf: !!actionBarPdf, menuPdf: !!menuPdf }; + } + + // Simulate hover on action bar button + const mouseEnter = new MouseEvent('mouseenter', { bubbles: true }); + actionBarPdf.dispatchEvent(mouseEnter); + + // Check if both have hover class + const actionBarHasClass = actionBarPdf.classList.contains('pdf-hover-sync'); + const menuHasClass = menuPdf.classList.contains('pdf-hover-sync'); + + // Clean up + const mouseLeave = new MouseEvent('mouseleave', { bubbles: true }); + actionBarPdf.dispatchEvent(mouseLeave); + + return { + found: true, + actionBarHasClass, + menuHasClass, + actionBarClasses: Array.from(actionBarPdf.classList), + menuClasses: Array.from(menuPdf.classList) + }; + }); + + if (!pdfTest1.found) { + console.log(` ❌ FAIL - Buttons not found`); + console.log(` Action bar PDF: ${pdfTest1.actionBarPdf ? '✅' : '❌'}`); + console.log(` Menu PDF: ${pdfTest1.menuPdf ? '✅' : '❌'}`); + testResults.push({ test: 'PDF Hover Sync (Bar→Menu)', passed: false }); + } else { + console.log(` Action bar button adds class: ${pdfTest1.actionBarHasClass ? '✅' : '❌'}`); + console.log(` Menu button gets class: ${pdfTest1.menuHasClass ? '✅' : '❌'}`); + console.log(` Action bar classes: ${pdfTest1.actionBarClasses.join(', ')}`); + console.log(` Menu classes: ${pdfTest1.menuClasses.join(', ')}`); + + const passed = pdfTest1.actionBarHasClass && pdfTest1.menuHasClass; + console.log(` ${passed ? '✅ PASS' : '❌ FAIL'} - PDF hover sync (bar→menu)`); + testResults.push({ test: 'PDF Hover Sync (Bar→Menu)', passed }); + } + + // ======================================================================== + // TEST 2: Print Button Hover Sync (Action Bar → Menu) + // ======================================================================== + console.log("\n2️⃣ Testing Print Button Hover Sync (Action Bar → Menu)..."); + + const printTest1 = await page.evaluate(() => { + const actionBarPrint = document.querySelector('.action-bar-print-btn'); + const menuPrint = document.querySelector('.menu-print-btn'); + + if (!actionBarPrint || !menuPrint) { + return { found: false, actionBarPrint: !!actionBarPrint, menuPrint: !!menuPrint }; + } + + // Simulate hover on action bar button + const mouseEnter = new MouseEvent('mouseenter', { bubbles: true }); + actionBarPrint.dispatchEvent(mouseEnter); + + // Check if both have hover class + const actionBarHasClass = actionBarPrint.classList.contains('print-hover-sync'); + const menuHasClass = menuPrint.classList.contains('print-hover-sync'); + + // Clean up + const mouseLeave = new MouseEvent('mouseleave', { bubbles: true }); + actionBarPrint.dispatchEvent(mouseLeave); + + return { + found: true, + actionBarHasClass, + menuHasClass, + actionBarClasses: Array.from(actionBarPrint.classList), + menuClasses: Array.from(menuPrint.classList) + }; + }); + + if (!printTest1.found) { + console.log(` ❌ FAIL - Buttons not found`); + console.log(` Action bar Print: ${printTest1.actionBarPrint ? '✅' : '❌'}`); + console.log(` Menu Print: ${printTest1.menuPrint ? '✅' : '❌'}`); + testResults.push({ test: 'Print Hover Sync (Bar→Menu)', passed: false }); + } else { + console.log(` Action bar button adds class: ${printTest1.actionBarHasClass ? '✅' : '❌'}`); + console.log(` Menu button gets class: ${printTest1.menuHasClass ? '✅' : '❌'}`); + console.log(` Action bar classes: ${printTest1.actionBarClasses.join(', ')}`); + console.log(` Menu classes: ${printTest1.menuClasses.join(', ')}`); + + const passed = printTest1.actionBarHasClass && printTest1.menuHasClass; + console.log(` ${passed ? '✅ PASS' : '❌ FAIL'} - Print hover sync (bar→menu)`); + testResults.push({ test: 'Print Hover Sync (Bar→Menu)', passed }); + } + + // ======================================================================== + // TEST 3: PDF Button Hover Sync (Menu → Action Bar) + // ======================================================================== + console.log("\n3️⃣ Testing PDF Button Hover Sync (Menu → Action Bar)..."); + + const pdfTest2 = await page.evaluate(() => { + const actionBarPdf = document.querySelector('#action-bar-pdf-btn'); + const menuPdf = document.querySelector('.menu-action-btn'); + + if (!actionBarPdf || !menuPdf) { + return { found: false }; + } + + // Check if menu button has event handlers + const hasMouseEnter = menuPdf.getAttribute('_')?.includes('mouseenter'); + const hasMouseLeave = menuPdf.getAttribute('_')?.includes('mouseleave'); + + // Simulate hover on menu button (if it has handlers) + if (hasMouseEnter) { + const mouseEnter = new MouseEvent('mouseenter', { bubbles: true }); + menuPdf.dispatchEvent(mouseEnter); + } + + const actionBarHasClass = actionBarPdf.classList.contains('pdf-hover-sync'); + const menuHasClass = menuPdf.classList.contains('pdf-hover-sync'); + + // Clean up + if (hasMouseLeave) { + const mouseLeave = new MouseEvent('mouseleave', { bubbles: true }); + menuPdf.dispatchEvent(mouseLeave); + } + + return { + found: true, + hasMouseEnter, + hasMouseLeave, + actionBarHasClass, + menuHasClass + }; + }); + + if (!pdfTest2.found) { + console.log(` ❌ FAIL - Buttons not found`); + testResults.push({ test: 'PDF Hover Sync (Menu→Bar)', passed: false }); + } else { + console.log(` Menu button has mouseenter handler: ${pdfTest2.hasMouseEnter ? '✅' : '❌'}`); + console.log(` Menu button has mouseleave handler: ${pdfTest2.hasMouseLeave ? '✅' : '❌'}`); + console.log(` Action bar button gets class: ${pdfTest2.actionBarHasClass ? '✅' : '❌'}`); + console.log(` Menu button adds class: ${pdfTest2.menuHasClass ? '✅' : '❌'}`); + + const passed = pdfTest2.hasMouseEnter && pdfTest2.hasMouseLeave && + pdfTest2.actionBarHasClass && pdfTest2.menuHasClass; + console.log(` ${passed ? '✅ PASS' : '❌ FAIL'} - PDF hover sync (menu→bar)`); + testResults.push({ test: 'PDF Hover Sync (Menu→Bar)', passed }); + } + + // ======================================================================== + // TEST 4: Print Button Hover Sync (Menu → Action Bar) + // ======================================================================== + console.log("\n4️⃣ Testing Print Button Hover Sync (Menu → Action Bar)..."); + + const printTest2 = await page.evaluate(() => { + const actionBarPrint = document.querySelector('.action-bar-print-btn'); + const menuPrint = document.querySelector('.menu-print-btn'); + + if (!actionBarPrint || !menuPrint) { + return { found: false }; + } + + // Check if menu button has event handlers + const hasMouseEnter = menuPrint.getAttribute('_')?.includes('mouseenter'); + const hasMouseLeave = menuPrint.getAttribute('_')?.includes('mouseleave'); + + // Simulate hover on menu button + const mouseEnter = new MouseEvent('mouseenter', { bubbles: true }); + menuPrint.dispatchEvent(mouseEnter); + + const actionBarHasClass = actionBarPrint.classList.contains('print-hover-sync'); + const menuHasClass = menuPrint.classList.contains('print-hover-sync'); + + // Clean up + const mouseLeave = new MouseEvent('mouseleave', { bubbles: true }); + menuPrint.dispatchEvent(mouseLeave); + + return { + found: true, + hasMouseEnter, + hasMouseLeave, + actionBarHasClass, + menuHasClass + }; + }); + + if (!printTest2.found) { + console.log(` ❌ FAIL - Buttons not found`); + testResults.push({ test: 'Print Hover Sync (Menu→Bar)', passed: false }); + } else { + console.log(` Menu button has mouseenter handler: ${printTest2.hasMouseEnter ? '✅' : '❌'}`); + console.log(` Menu button has mouseleave handler: ${printTest2.hasMouseLeave ? '✅' : '❌'}`); + console.log(` Action bar button gets class: ${printTest2.actionBarHasClass ? '✅' : '❌'}`); + console.log(` Menu button adds class: ${printTest2.menuHasClass ? '✅' : '❌'}`); + + const passed = printTest2.hasMouseEnter && printTest2.hasMouseLeave && + printTest2.actionBarHasClass && printTest2.menuHasClass; + console.log(` ${passed ? '✅ PASS' : '❌ FAIL'} - Print hover sync (menu→bar)`); + testResults.push({ test: 'Print Hover Sync (Menu→Bar)', passed }); + } + + // ======================================================================== + // TEST 5: Visual hover test (manual verification) + // ======================================================================== + console.log("\n5️⃣ Manual Visual Test..."); + console.log(" Please hover over the following and verify visual sync:"); + console.log(" 1. Hover action bar PDF button → menu PDF should highlight"); + console.log(" 2. Hover action bar Print button → menu Print should highlight"); + console.log(" 3. Hover menu PDF button → action bar PDF should highlight"); + console.log(" 4. Hover menu Print button → action bar Print should highlight"); + + // ======================================================================== + // 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`); + } + + console.log("=".repeat(70) + "\n"); + + if (failedTests === 0) { + console.log("🎉 ALL HOVER SYNC TESTS PASSED!"); + } else { + console.log("⚠️ SOME TESTS FAILED - Hover synchronization broken"); + console.log("\nExpected bug locations:"); + console.log(" - templates/partials/navigation/hamburger-menu.html (missing mouseenter/leave on PDF button)"); + console.log(" - static/js/cv-functions.js (hover sync functions exist but not called properly)"); + } + + console.log("\nBrowser will stay open for manual verification."); + console.log("Press Ctrl+C when done.\n"); + + await new Promise(() => {}); // Keep browser open +} + +await testHoverSync();