diff --git a/templates/index.html b/templates/index.html index 6614595..e05527e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -157,10 +157,10 @@
{{template "cv-content.html" .}}
- - {{template "page-footer" .}} + {{template "page-footer" .}} + {{template "error-toast" .}} {{template "back-to-top" .}} {{template "info-button" .}} diff --git a/tests/mjs/11-zoom-ui-exclusion.test.mjs b/tests/mjs/11-zoom-ui-exclusion.test.mjs new file mode 100644 index 0000000..0ad3f13 --- /dev/null +++ b/tests/mjs/11-zoom-ui-exclusion.test.mjs @@ -0,0 +1,243 @@ +#!/usr/bin/env bun +/** + * ZOOM UI EXCLUSION TEST + * ======================= + * Verifies that UI elements (footer, menu, buttons) are NOT affected by zoom + * + * Test Requirements: + * - Footer should NOT be inside #zoom-wrapper + * - Action bar should NOT be inside #zoom-wrapper + * - Hamburger menu should NOT be inside #zoom-wrapper + * - Fixed buttons should NOT be inside #zoom-wrapper + * - Only .cv-container content should be affected by zoom + * + * Test Flow: + * 1. Load page and show zoom control + * 2. Measure initial sizes of UI elements + * 3. Set zoom to 200% + * 4. Verify UI elements remain same size + * 5. Verify CV content has doubled in size + */ + +import { chromium } from "playwright"; + +const URL = "http://localhost:1999"; + +async function testZoomUIExclusion() { + console.log("๐Ÿงช ZOOM UI EXCLUSION TEST\n"); + console.log("=".repeat(70)); + + const browser = await chromium.launch({ headless: false }); + const page = await browser.newPage({ viewport: { width: 1400, 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}`); + }); + + // ======================================================================== + // TEST 1: Verify DOM Structure + // ======================================================================== + console.log("\n1๏ธโƒฃ Verifying DOM structure (UI outside zoom-wrapper)..."); + await page.goto(URL); + await page.waitForTimeout(2000); + + // Set zoom visible + await page.evaluate(() => localStorage.setItem('cv-zoom-visible', 'true')); + await page.reload(); + await page.waitForTimeout(2000); + + // Check that footer is NOT inside zoom-wrapper + const footerOutsideZoom = await page.evaluate(() => { + const footer = document.querySelector('.page-footer'); + const zoomWrapper = document.querySelector('#zoom-wrapper'); + if (!footer || !zoomWrapper) return false; + return !zoomWrapper.contains(footer); + }); + + // Check that action bar is NOT inside zoom-wrapper + const actionBarOutsideZoom = await page.evaluate(() => { + const actionBar = document.querySelector('.action-bar'); + const zoomWrapper = document.querySelector('#zoom-wrapper'); + if (!actionBar || !zoomWrapper) return false; + return !zoomWrapper.contains(actionBar); + }); + + // Check that hamburger menu is NOT inside zoom-wrapper + const menuOutsideZoom = await page.evaluate(() => { + const menu = document.querySelector('.navigation-menu'); + const zoomWrapper = document.querySelector('#zoom-wrapper'); + if (!menu || !zoomWrapper) return false; + return !zoomWrapper.contains(menu); + }); + + // Check that CV content IS inside zoom-wrapper + const cvInsideZoom = await page.evaluate(() => { + const cv = document.querySelector('.cv-container'); + const zoomWrapper = document.querySelector('#zoom-wrapper'); + if (!cv || !zoomWrapper) return false; + return zoomWrapper.contains(cv); + }); + + const test1Passed = footerOutsideZoom && actionBarOutsideZoom && menuOutsideZoom && cvInsideZoom; + + console.log(` Footer outside zoom-wrapper: ${footerOutsideZoom ? 'โœ… YES' : 'โŒ NO'}`); + console.log(` Action bar outside zoom-wrapper: ${actionBarOutsideZoom ? 'โœ… YES' : 'โŒ NO'}`); + console.log(` Menu outside zoom-wrapper: ${menuOutsideZoom ? 'โœ… YES' : 'โŒ NO'}`); + console.log(` CV content inside zoom-wrapper: ${cvInsideZoom ? 'โœ… YES' : 'โŒ NO'}`); + console.log(` ${test1Passed ? 'โœ… PASS' : 'โŒ FAIL'}`); + + testResults.push({ test: 'DOM Structure - UI Outside Zoom', passed: test1Passed }); + + // ======================================================================== + // TEST 2: Measure UI Elements at 100% Zoom + // ======================================================================== + console.log("\n2๏ธโƒฃ Measuring UI elements at 100% zoom..."); + + const initialSizes = await page.evaluate(() => { + const footer = document.querySelector('.page-footer'); + const actionBar = document.querySelector('.action-bar'); + const cvPaper = document.querySelector('.cv-paper'); + + return { + footerHeight: footer ? footer.offsetHeight : 0, + actionBarHeight: actionBar ? actionBar.offsetHeight : 0, + cvWidth: cvPaper ? cvPaper.offsetWidth : 0, + }; + }); + + console.log(` Footer height: ${initialSizes.footerHeight}px`); + console.log(` Action bar height: ${initialSizes.actionBarHeight}px`); + console.log(` CV width: ${initialSizes.cvWidth}px`); + + // ======================================================================== + // TEST 3: Set Zoom to 200% and Re-measure + // ======================================================================== + console.log("\n3๏ธโƒฃ Setting zoom to 200% and re-measuring..."); + + const slider = await page.$('#zoom-slider'); + if (slider) { + await slider.evaluate(el => { + el.value = '200'; + el.dispatchEvent(new Event('input', { bubbles: true })); + }); + await page.waitForTimeout(500); + } + + const zoomedSizes = await page.evaluate(() => { + const footer = document.querySelector('.page-footer'); + const actionBar = document.querySelector('.action-bar'); + const cvPaper = document.querySelector('.cv-paper'); + + return { + footerHeight: footer ? footer.offsetHeight : 0, + actionBarHeight: actionBar ? actionBar.offsetHeight : 0, + cvWidth: cvPaper ? cvPaper.offsetWidth : 0, + }; + }); + + console.log(` Footer height: ${zoomedSizes.footerHeight}px`); + console.log(` Action bar height: ${zoomedSizes.actionBarHeight}px`); + console.log(` CV width: ${zoomedSizes.cvWidth}px`); + + // ======================================================================== + // TEST 4: Verify UI Elements Unchanged + // ======================================================================== + console.log("\n4๏ธโƒฃ Verifying UI elements unchanged at 200% zoom..."); + + const footerUnchanged = Math.abs(initialSizes.footerHeight - zoomedSizes.footerHeight) < 5; + const actionBarUnchanged = Math.abs(initialSizes.actionBarHeight - zoomedSizes.actionBarHeight) < 5; + + // CV should be approximately doubled (with some tolerance) + const cvGrew = zoomedSizes.cvWidth > initialSizes.cvWidth * 1.8; + + const test4Passed = footerUnchanged && actionBarUnchanged && cvGrew; + + console.log(` Footer unchanged: ${footerUnchanged ? 'โœ… YES' : 'โŒ NO'} (${initialSizes.footerHeight}px โ†’ ${zoomedSizes.footerHeight}px)`); + console.log(` Action bar unchanged: ${actionBarUnchanged ? 'โœ… YES' : 'โŒ NO'} (${initialSizes.actionBarHeight}px โ†’ ${zoomedSizes.actionBarHeight}px)`); + console.log(` CV content grew: ${cvGrew ? 'โœ… YES' : 'โŒ NO'} (${initialSizes.cvWidth}px โ†’ ${zoomedSizes.cvWidth}px)`); + console.log(` ${test4Passed ? 'โœ… PASS' : 'โŒ FAIL'}`); + + testResults.push({ test: 'UI Elements Unchanged at 200%', passed: test4Passed }); + + // ======================================================================== + // TEST 5: Reset and Verify + // ======================================================================== + console.log("\n5๏ธโƒฃ Resetting zoom to 100%..."); + + const resetBtn = await page.$('#zoom-reset'); + if (resetBtn) { + await resetBtn.click(); + await page.waitForTimeout(500); + } + + const resetSizes = await page.evaluate(() => { + const footer = document.querySelector('.page-footer'); + const actionBar = document.querySelector('.action-bar'); + const cvPaper = document.querySelector('.cv-paper'); + + return { + footerHeight: footer ? footer.offsetHeight : 0, + actionBarHeight: actionBar ? actionBar.offsetHeight : 0, + cvWidth: cvPaper ? cvPaper.offsetWidth : 0, + }; + }); + + const resetPassed = Math.abs(resetSizes.cvWidth - initialSizes.cvWidth) < 10; + + console.log(` CV width restored: ${resetPassed ? 'โœ… YES' : 'โŒ NO'} (${resetSizes.cvWidth}px vs ${initialSizes.cvWidth}px initial)`); + console.log(` ${resetPassed ? 'โœ… PASS' : 'โŒ FAIL'}`); + + testResults.push({ test: 'Zoom Reset to 100%', passed: resetPassed }); + + // ======================================================================== + // 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! UI elements properly excluded from zoom."); + } 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 testZoomUIExclusion();