diff --git a/data/cv-en.json b/data/cv-en.json index 12c9514..23e4927 100644 --- a/data/cv-en.json +++ b/data/cv-en.json @@ -11,7 +11,7 @@ "linkedin": "https://www.linkedin.com/in/juan-andres-moreno-rubio", "github": "https://github.com/juanatsap", "domestika": "https://www.domestika.org/es/txeo/portfolio", - "website": "https://juan.andres.morenoyrubio.com", + "website": "https://juan.andres.morenorub.io", "photo": "/static/images/profile.jpg" }, "summary": "Full-stack developer specialized in high-availability systems. I've worked on Olympic Games platforms, airport authentication systems with millions of users, and built around 20 websites for diverse sectors (e-commerce, enterprise, institutional). Certified SAP Customer Data Cloud consultant, advising 35-40 international clients on digital identity solutions.", diff --git a/data/cv-es.json b/data/cv-es.json index bd17430..f0f8ac3 100644 --- a/data/cv-es.json +++ b/data/cv-es.json @@ -11,7 +11,7 @@ "linkedin": "https://www.linkedin.com/in/juan-andres-moreno-rubio", "github": "https://github.com/juanatsap", "domestika": "https://www.domestika.org/es/txeo/portfolio", - "website": "https://juan.andres.morenoyrubio.com", + "website": "https://juan.andres.morenorub.io", "photo": "/static/images/profile.jpg" }, "summary": "Desarrollador full-stack especializado en sistemas de alta disponibilidad. He participado en plataformas de Juegos Olímpicos, sistemas de autenticación aeroportuaria con millones de usuarios, y desarrollado unos 20 sitios web para diversos sectores (e-commerce, empresariales, institucionales). Consultor certificado de SAP Customer Data Cloud, asesorando a 35-40 clientes internacionales en soluciones de identidad digital.", diff --git a/static/robots.txt b/static/robots.txt index 69304fa..0bd4556 100644 --- a/static/robots.txt +++ b/static/robots.txt @@ -1,4 +1,4 @@ -# robots.txt for juan.andres.morenoyrubio.com +# robots.txt for juan.andres.morenorub.io # Allow all search engines User-agent: * @@ -11,7 +11,7 @@ Disallow: /.git/ Disallow: /.env # Sitemap location -Sitemap: https://juan.andres.morenoyrubio.com/static/sitemap.xml +Sitemap: https://juan.andres.morenorub.io/static/sitemap.xml # Crawl-delay (optional, helps prevent server overload) # Crawl-delay: 1 diff --git a/static/sitemap.xml b/static/sitemap.xml index dfcc330..52fddd3 100644 --- a/static/sitemap.xml +++ b/static/sitemap.xml @@ -4,27 +4,27 @@ - https://juan.andres.morenoyrubio.com/?lang=en + https://juan.andres.morenorub.io/?lang=en 2024-10-18 monthly 1.0 - - + + - https://juan.andres.morenoyrubio.com/?lang=es + https://juan.andres.morenorub.io/?lang=es 2024-10-18 monthly 1.0 - - + + - https://juan.andres.morenoyrubio.com/ + https://juan.andres.morenorub.io/ 2024-10-18 monthly 0.9 @@ -32,7 +32,7 @@ - https://juan.andres.morenoyrubio.com/health + https://juan.andres.morenorub.io/health 2024-10-18 daily 0.3 diff --git a/tests/SHORTCUTS-BUTTON-FIX-REPORT.md b/tests/SHORTCUTS-BUTTON-FIX-REPORT.md deleted file mode 100644 index da69c54..0000000 --- a/tests/SHORTCUTS-BUTTON-FIX-REPORT.md +++ /dev/null @@ -1,314 +0,0 @@ -# Shortcuts Button Visibility Fix - Test Report - -**Date:** 2025-11-15 -**Issue:** Shortcuts button exists with icon but appears nearly invisible -**Status:** ✅ **RESOLVED** - ---- - -## Problem Summary - -The keyboard shortcuts button (`#shortcuts-button`) was correctly implemented with: -- ✅ Proper HTML structure -- ✅ Iconify keyboard icon (`mdi:keyboard-outline`, 28x28px) -- ✅ Click functionality working -- ✅ ARIA labels and accessibility attributes - -However, the button appeared **nearly invisible** to users due to: -- ❌ Default opacity of `0.2` (80% transparent) -- ❌ Only became visible on hover or when scrolling to bottom -- ❌ Poor discoverability for new users - ---- - -## Root Cause Analysis - -### Original CSS Implementation - -```css -.shortcuts-btn { - /* ... other styles ... */ - opacity: 0.2; /* ❌ Too low - nearly invisible */ -} - -.shortcuts-btn:hover { - opacity: 1; /* Only visible on hover */ -} - -.shortcuts-btn.at-bottom { - opacity: 1; /* Only visible when at page bottom */ -} -``` - -### Why This Was Problematic - -1. **User Discovery**: Users couldn't find the button without hovering in the exact spot -2. **Test Automation**: Automated tests detected button as having no visible content -3. **UX Inconsistency**: Other fixed buttons (back-to-top) had better visibility -4. **Accessibility**: Low contrast made button hard to see for users with visual impairments - ---- - -## Solution Implemented - -### CSS Changes - -**File:** `/Users/txeo/Git/yo/cv/static/css/main.css` - -#### 1. Shortcuts Button (lines 3988-4006) - -```diff -.shortcuts-btn { - position: fixed; - bottom: 6rem; - left: 2rem; - width: 50px; - height: 50px; - background: var(--black-bar); - color: white; - /* ... */ -- opacity: 0.2; -+ opacity: 0.6; /* Increased from 0.2 for better discoverability */ -} -``` - -#### 2. Info Button (lines 2867-2885) - Consistency Update - -```diff -.info-button { - position: fixed; - bottom: 2rem; - left: 2rem; - /* ... */ -- opacity: 0.2; -+ opacity: 0.6; /* Increased from 0.2 for better discoverability */ -} -``` - -### Rationale for 0.6 Opacity - -- **Visible but Subtle**: Button is discoverable without being obtrusive -- **Still Enhances on Hover**: Hover state (opacity: 1) remains effective -- **Accessibility**: Meets minimum contrast requirements -- **UX Pattern**: Matches common fixed button opacity patterns (0.5-0.7) - ---- - -## Verification Tests - -### 1. Visual Test - -Created: `/Users/txeo/Git/yo/cv/tests/test-shortcuts-button-visibility.html` - -**Test Cases:** -- ✅ Compare old (0.2) vs new (0.6) opacity side-by-side -- ✅ Verify iconify-icon renders correctly -- ✅ Confirm hover state transitions smoothly -- ✅ Check button positioning and styling - -**Results:** -- ✅ Old opacity (0.2): Hard to see, poor discoverability -- ✅ New opacity (0.6): Clearly visible, good UX -- ✅ Hover state (1.0): Full visibility with blue background - -### 2. Live Site Test - -**URL:** `http://localhost:1999/?lang=en` - -**Verified:** -- ✅ Button renders with keyboard icon visible at opacity 0.6 -- ✅ Icon: `mdi:keyboard-outline` at 28x28px -- ✅ Button positioned: bottom-left, above info-button -- ✅ Click functionality: Opens shortcuts modal -- ✅ Hover effect: Opacity increases to 1.0, background turns blue -- ✅ Accessibility: `aria-label="Keyboard shortcuts"` present - -### 3. HTML Structure Verification - -```html - -``` - -**Status:** ✅ Perfect implementation - -### 4. CSS Verification - -```bash -$ grep -A10 "\.shortcuts-btn {" static/css/main.css -``` - -**Results:** -- ✅ Opacity: 0.6 (updated from 0.2) -- ✅ Position: Fixed bottom-left (6rem from bottom, 2rem from left) -- ✅ Size: 50x50px (45x45px on mobile) -- ✅ Hover: opacity: 1, transform: translateY(-3px), background: #3498db -- ✅ At-bottom state: opacity: 1, background: #3498db - ---- - -## Comparison: Before vs After - -| Aspect | Before (opacity: 0.2) | After (opacity: 0.6) | -|--------|----------------------|---------------------| -| **Visibility** | Nearly invisible | Clearly visible | -| **Discoverability** | Poor - hover required | Good - immediately visible | -| **User Experience** | Frustrating | Intuitive | -| **Accessibility** | Low contrast | Improved contrast | -| **Test Detection** | Appears as "no text" | Detectable as button | -| **Hover Effect** | Still valuable (5x increase) | Still valuable (1.67x increase) | - ---- - -## Related Files Modified - -1. **CSS:** `/Users/txeo/Git/yo/cv/static/css/main.css` - - Line 2884: `.info-button` opacity 0.2 → 0.6 - - Line 4005: `.shortcuts-btn` opacity 0.2 → 0.6 - -2. **Test Files Created:** - - `/Users/txeo/Git/yo/cv/tests/test-shortcuts-button-visibility.html` - - `/Users/txeo/Git/yo/cv/tests/SHORTCUTS-BUTTON-FIX-REPORT.md` - -3. **No Template Changes:** HTML already correct in: - - `/Users/txeo/Git/yo/cv/templates/partials/widgets/shortcuts-button.html` - ---- - -## Regression Testing - -### Tested Scenarios - -- ✅ Desktop viewport (>768px): Button visible at 50x50px -- ✅ Mobile viewport (<768px): Button visible at 45x45px -- ✅ Hover interaction: Smooth opacity transition to 1.0 -- ✅ Click interaction: Opens modal correctly -- ✅ Scroll to bottom: `.at-bottom` class applies correctly -- ✅ Print mode: `.no-print` class hides button -- ✅ Zoom control: Hyperscript zoom adjusts button correctly - -### Browser Testing - -- ✅ Chrome/Edge: Icon renders, opacity correct -- ✅ Firefox: Icon renders, opacity correct -- ✅ Safari: Icon renders, opacity correct - ---- - -## Performance Impact - -- **CSS File Size:** No change (single character diff: 0.2 → 0.6) -- **Render Performance:** No impact (same CSS properties) -- **Iconify Load:** No change (already loaded for other icons) -- **Bundle Size:** No change (CSS already included) - ---- - -## Accessibility Improvements - -### WCAG Compliance - -- ✅ **Contrast Ratio:** Improved from ~1.2:1 to ~2.8:1 (still enhances to ~4.5:1 on hover) -- ✅ **Discoverability:** Users can now see the button without trial-and-error -- ✅ **Focus Indicators:** Button remains focusable via keyboard -- ✅ **Screen Readers:** aria-label provides context - -### Keyboard Navigation - -- ✅ Tab order: Button is in logical sequence -- ✅ Enter/Space: Opens modal (native button behavior) -- ✅ Focus visible: Browser default focus ring applies - ---- - -## User Experience Improvements - -### Before Fix - -1. User lands on page -2. User doesn't see shortcuts button (opacity: 0.2) -3. User accidentally hovers over left side -4. Button appears! (opacity: 1) -5. User moves mouse away -6. Button disappears again (opacity: 0.2) -7. User confused about how to access it - -### After Fix - -1. User lands on page -2. User sees faint keyboard icon button (opacity: 0.6) -3. User recognizes it as interactive element -4. User hovers or clicks -5. Button highlights (opacity: 1, blue background) -6. User understands the pattern -7. Clear mental model established - ---- - -## Deployment Checklist - -- ✅ CSS changes applied to main.css -- ✅ Server rebuilt with `make build` -- ✅ Server restarted with updated CSS -- ✅ Visual testing completed -- ✅ Live site verification completed -- ✅ Test report documented -- ✅ No regressions detected - ---- - -## Recommendations for Future - -### Consider These Enhancements - -1. **First-Time User Hint:** Add a subtle pulse animation on first page load -2. **Tooltip on Load:** Show tooltip for 3 seconds on first visit -3. **Help Indicator:** Add "?" badge or "Press ?" hint -4. **Progressive Enhancement:** Store "has-seen-shortcuts" in localStorage - -### CSS Enhancement Example - -```css -/* Optional: Pulse animation for first-time discovery */ -@keyframes pulse-hint { - 0%, 100% { opacity: 0.6; } - 50% { opacity: 0.9; } -} - -.shortcuts-btn.first-visit { - animation: pulse-hint 2s ease-in-out 3; -} -``` - ---- - -## Conclusion - -### Problem -Shortcuts button icon was invisible due to 80% transparency (opacity: 0.2) - -### Solution -Increased default opacity to 0.6 (60% opacity / 40% transparency) - -### Result -✅ **Button is now clearly visible and discoverable** -✅ **Maintains subtle, non-obtrusive design** -✅ **Hover effect remains effective** -✅ **Accessibility improved** -✅ **User experience enhanced** - -### Status -**RESOLVED** - Ready for production deployment - ---- - -**Fix Verified By:** HTMX Frontend Specialist Agent -**Test Environment:** Local development server (localhost:1999) -**Build Status:** ✅ All tests passing -**Deployment Status:** ✅ Ready for commit diff --git a/tests/check-styling.html b/tests/check-styling.html deleted file mode 100644 index f81d478..0000000 --- a/tests/check-styling.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - Styling Comparison - - - -

CV Header Styling Comparison

-
-
-

Original Design

- Original -
-
Expected Styling:
-
• Font: Quicksand
-
• Size: ~0.85em (smaller than name)
-
• Weight: 400 (normal)
-
• Color: #666 (medium gray)
-
• Margin: minimal (4px top)
-
• Alignment: left with name
-
-
-
-

Current Implementation

- Current -
-
Applied CSS:
-
font-family: 'Quicksand', sans-serif;
-
font-size: 0.85em;
-
font-weight: 400;
-
color: #666;
-
margin: 4px 0 0 0;
-
line-height: 1.4;
-
-
-
- - diff --git a/tests/compare-rendered.js b/tests/compare-rendered.js deleted file mode 100644 index e8c298e..0000000 --- a/tests/compare-rendered.js +++ /dev/null @@ -1,192 +0,0 @@ -const { chromium } = require('playwright'); -const fs = require('fs'); -const path = require('path'); - -async function compareRendered() { - const browser = await chromium.launch({ headless: true }); - - console.log('\n=== COMPARING RENDERED SITES ===\n'); - - // OLD SITE - const pageOld = await browser.newPage({ viewport: { width: 1920, height: 1080 } }); - console.log('Loading OLD site (React)...'); - await pageOld.goto('http://localhost:3000', { waitUntil: 'networkidle', timeout: 30000 }); - - // Wait for React to render - await pageOld.waitForTimeout(2000); - - // Take screenshot - await pageOld.screenshot({ - path: './tests/screenshots/old-full-rendered.png', - fullPage: true - }); - - // Get actual rendered content - const oldContent = await pageOld.evaluate(() => { - const app = document.getElementById('app') || document.body; - return { - innerHTML: app.innerHTML.substring(0, 2000), - hasContent: app.innerHTML.length > 100, - classes: Array.from(document.querySelectorAll('[class]')).map(el => el.className).filter(c => c).slice(0, 50) - }; - }); - - console.log('OLD site content loaded:', oldContent.hasContent); - console.log('OLD site classes found:', oldContent.classes.length); - if (oldContent.classes.length > 0) { - console.log('Sample classes:', oldContent.classes.slice(0, 10)); - } - - // NEW SITE - const pageNew = await browser.newPage({ viewport: { width: 1920, height: 1080 } }); - console.log('\nLoading NEW site (Go+HTMX)...'); - await pageNew.goto('http://localhost:1999', { waitUntil: 'networkidle' }); - - await pageNew.screenshot({ - path: './tests/screenshots/new-full-rendered.png', - fullPage: true - }); - - // SIDE-BY-SIDE COMPARISON - console.log('\n=== HEADER BADGES COMPARISON ===\n'); - - // Try multiple selectors for old site - const oldBadgeSelectors = [ - '[class*="badge"]', - '[class*="title"]', - 'div[class*="cv"]', - '.badge', - '.title-badge' - ]; - - let oldBadges = null; - for (const selector of oldBadgeSelectors) { - try { - const count = await pageOld.locator(selector).count(); - if (count > 0) { - console.log(`Found ${count} elements with selector: ${selector}`); - oldBadges = await pageOld.$$eval(selector, elements => - elements.slice(0, 5).map(el => { - const computed = window.getComputedStyle(el); - return { - tag: el.tagName, - class: el.className, - text: el.textContent?.substring(0, 50), - styles: { - fontSize: computed.fontSize, - fontWeight: computed.fontWeight, - color: computed.color, - backgroundColor: computed.backgroundColor, - padding: computed.padding, - height: computed.height - } - }; - }) - ); - break; - } - } catch (e) { - // Try next selector - } - } - - const newBadges = await pageNew.$$eval('.title-badge', elements => - elements.slice(0, 5).map(el => { - const computed = window.getComputedStyle(el); - return { - tag: el.tagName, - class: el.className, - text: el.textContent?.trim(), - styles: { - fontSize: computed.fontSize, - fontWeight: computed.fontWeight, - color: computed.color, - backgroundColor: computed.backgroundColor, - padding: computed.padding, - height: computed.height - } - }; - }) - ); - - console.log('\nOLD site badges:'); - console.log(JSON.stringify(oldBadges, null, 2)); - - console.log('\nNEW site badges:'); - console.log(JSON.stringify(newBadges, null, 2)); - - // VISUAL PIXEL COMPARISON - console.log('\n=== VISUAL COMPARISON ===\n'); - - // Get dimensions - const oldDimensions = await pageOld.evaluate(() => ({ - width: document.documentElement.scrollWidth, - height: document.documentElement.scrollHeight - })); - - const newDimensions = await pageNew.evaluate(() => ({ - width: document.documentElement.scrollWidth, - height: document.documentElement.scrollHeight - })); - - console.log('OLD site dimensions:', oldDimensions); - console.log('NEW site dimensions:', newDimensions); - - // Screenshot specific sections - try { - // Header comparison - const oldHeader = pageOld.locator('header, [class*="header"], div').first(); - const newHeader = pageNew.locator('.cv-title-badges-header').first(); - - if (await oldHeader.count() > 0) { - await oldHeader.screenshot({ path: './tests/screenshots/old-header-section.png' }); - } - await newHeader.screenshot({ path: './tests/screenshots/new-header-section.png' }); - - // Sidebar comparison - const oldSidebar = pageOld.locator('[class*="sidebar"], aside').first(); - const newSidebar = pageNew.locator('.cv-sidebar').first(); - - if (await oldSidebar.count() > 0) { - await oldSidebar.screenshot({ path: './tests/screenshots/old-sidebar-section.png' }); - } - await newSidebar.screenshot({ path: './tests/screenshots/new-sidebar-section.png' }); - } catch (e) { - console.log('Error capturing sections:', e.message); - } - - // CREATE COMPARISON REPORT - const report = { - timestamp: new Date().toISOString(), - oldSite: { - url: 'http://localhost:3000', - hasContent: oldContent.hasContent, - classesFound: oldContent.classes.length, - dimensions: oldDimensions, - badges: oldBadges - }, - newSite: { - url: 'http://localhost:1999', - dimensions: newDimensions, - badges: newBadges - }, - comparison: { - dimensionsMatch: Math.abs(oldDimensions.width - newDimensions.width) < 50 && - Math.abs(oldDimensions.height - newDimensions.height) < 50, - pixelPerfect: null // To be determined by visual inspection - } - }; - - fs.writeFileSync( - './tests/screenshots/comparison-report.json', - JSON.stringify(report, null, 2) - ); - - console.log('\n✓ Comparison complete!'); - console.log('✓ Screenshots saved to tests/screenshots/'); - console.log('✓ Report saved to comparison-report.json'); - - await browser.close(); -} - -compareRendered().catch(console.error); diff --git a/tests/comprehensive-features.spec.js b/tests/comprehensive-features.spec.js deleted file mode 100644 index a974e97..0000000 --- a/tests/comprehensive-features.spec.js +++ /dev/null @@ -1,618 +0,0 @@ -/** - * COMPREHENSIVE FEATURE TEST SUITE - * Tests all 5 features in the CV application - * - * Features: - * 001: Keyboard Shortcuts Help Modal - * 002: Skeleton Loader for Language Transitions - * 003: HTMX Loading Indicators - * 004: Theme Switcher - * 005: PDF Download Modal - */ - -import { test, expect } from '@playwright/test'; - -const BASE_URL = 'http://localhost:1999'; - -// Helper to wait for animations -const waitForAnimation = (ms = 700) => new Promise(resolve => setTimeout(resolve, ms)); - -test.describe('PHASE 1: DISCOVERY - Feature Detection', () => { - test('should load page and capture initial state', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - // Take screenshot of initial state - await page.screenshot({ path: 'test-results/01-initial-state.png', fullPage: true }); - - // Check for interactive elements - const shortcuts = await page.locator('button[data-action="show-shortcuts"], button:has-text("shortcuts"), button:has-text("atajos")').count(); - const langButtons = await page.locator('button[data-lang], [hx-get*="lang"]').count(); - const themeButton = await page.locator('button[data-theme], [data-action="toggle-theme"]').count(); - const pdfButton = await page.locator('button:has-text("PDF"), button:has-text("download")').count(); - const toggles = await page.locator('input[type="checkbox"][hx-get], input[type="checkbox"][hx-post]').count(); - - console.log('=== FEATURE DETECTION ==='); - console.log(`Shortcuts button found: ${shortcuts > 0}`); - console.log(`Language buttons found: ${langButtons}`); - console.log(`Theme button found: ${themeButton > 0}`); - console.log(`PDF button found: ${pdfButton > 0}`); - console.log(`Toggle controls found: ${toggles}`); - - expect(langButtons).toBeGreaterThan(0); - }); -}); - -test.describe('FEATURE 001: Keyboard Shortcuts Help Modal', () => { - test('should open shortcuts modal on button click', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - // Find shortcuts button (try multiple selectors) - const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or( - page.locator('button:has-text("shortcuts")').first() - ).or( - page.locator('button:has-text("?")').first() - ); - - const btnExists = await shortcutsBtn.count() > 0; - console.log(`Shortcuts button exists: ${btnExists}`); - - if (!btnExists) { - console.log('⚠️ Shortcuts button NOT FOUND - Feature may not be implemented'); - return; - } - - // Click button - await shortcutsBtn.click(); - await waitForAnimation(300); - - // Verify modal opened (check for dialog or modal element) - const dialog = page.locator('dialog[open], [role="dialog"]:visible, .modal:visible'); - const dialogVisible = await dialog.count() > 0; - - await page.screenshot({ path: 'test-results/01-shortcuts-modal-open.png', fullPage: true }); - - expect(dialogVisible).toBe(true); - console.log('✅ Shortcuts modal opens on button click'); - }); - - test('should close modal with ESC key', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or( - page.locator('button:has-text("shortcuts")').first() - ); - - if (await shortcutsBtn.count() === 0) return; - - await shortcutsBtn.click(); - await waitForAnimation(300); - - // Press ESC - await page.keyboard.press('Escape'); - await waitForAnimation(300); - - // Verify modal closed - const dialog = page.locator('dialog[open], [role="dialog"]:visible'); - const dialogClosed = await dialog.count() === 0; - - await page.screenshot({ path: 'test-results/01-shortcuts-modal-closed-esc.png', fullPage: true }); - - expect(dialogClosed).toBe(true); - console.log('✅ Modal closes with ESC key'); - }); - - test('should close modal on backdrop click', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or( - page.locator('button:has-text("shortcuts")').first() - ); - - if (await shortcutsBtn.count() === 0) return; - - await shortcutsBtn.click(); - await waitForAnimation(300); - - // Click backdrop (click dialog element itself, not content) - const dialog = page.locator('dialog[open]'); - if (await dialog.count() > 0) { - await dialog.click({ position: { x: 5, y: 5 } }); - await waitForAnimation(300); - - const dialogClosed = await page.locator('dialog[open]').count() === 0; - expect(dialogClosed).toBe(true); - console.log('✅ Modal closes on backdrop click'); - } - }); - - test('should show keyboard shortcuts content', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or( - page.locator('button:has-text("shortcuts")').first() - ); - - if (await shortcutsBtn.count() === 0) return; - - await shortcutsBtn.click(); - await waitForAnimation(300); - - // Check for keyboard shortcut content (look for kbd tags or shortcut listings) - const kbdElements = await page.locator('kbd').count(); - const hasShortcutContent = kbdElements > 0; - - console.log(`Keyboard shortcut elements found: ${kbdElements}`); - expect(hasShortcutContent).toBe(true); - console.log('✅ Modal displays keyboard shortcuts'); - }); - - test('should support bilingual content (EN/ES)', async ({ page }) => { - // Test English - await page.goto(`${BASE_URL}/?lang=en`); - const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').or( - page.locator('button:has-text("shortcuts")').first() - ); - - if (await shortcutsBtn.count() === 0) return; - - await shortcutsBtn.click(); - await waitForAnimation(300); - const enContent = await page.locator('dialog, [role="dialog"]').textContent(); - await page.keyboard.press('Escape'); - - // Test Spanish - await page.goto(`${BASE_URL}/?lang=es`); - const shortcutsBtnEs = page.locator('button[data-action="show-shortcuts"]').or( - page.locator('button:has-text("atajos")').first() - ); - - if (await shortcutsBtnEs.count() > 0) { - await shortcutsBtnEs.click(); - await waitForAnimation(300); - const esContent = await page.locator('dialog, [role="dialog"]').textContent(); - - const isDifferent = enContent !== esContent; - console.log(`Content differs between EN/ES: ${isDifferent}`); - expect(isDifferent).toBe(true); - console.log('✅ Modal supports bilingual content'); - } - }); -}); - -test.describe('FEATURE 002: Skeleton Loader for Language Transitions', () => { - test('should show skeleton loader during language switch', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - // Find language toggle button - const langButton = page.locator('button[data-lang="es"]').or( - page.locator('button:has-text("ES")').first() - ).or( - page.locator('[hx-get*="lang=es"]').first() - ); - - const btnExists = await langButton.count() > 0; - console.log(`Language button exists: ${btnExists}`); - - if (!btnExists) { - console.log('⚠️ Language button NOT FOUND'); - return; - } - - // Monitor for skeleton loader - let skeletonAppeared = false; - - // Set up observer before clicking - await page.evaluate(() => { - window.skeletonDetected = false; - const observer = new MutationObserver(() => { - const skeleton = document.querySelector('.skeleton, [data-skeleton], .skeleton-loader, .shimmer'); - if (skeleton && window.getComputedStyle(skeleton).opacity !== '0') { - window.skeletonDetected = true; - } - }); - observer.observe(document.body, { childList: true, subtree: true, attributes: true }); - }); - - // Click language button - await langButton.click(); - await waitForAnimation(100); - - // Check if skeleton appeared - skeletonAppeared = await page.evaluate(() => window.skeletonDetected); - - await waitForAnimation(600); - await page.screenshot({ path: 'test-results/02-skeleton-loader.png', fullPage: true }); - - console.log(`Skeleton loader appeared: ${skeletonAppeared}`); - expect(skeletonAppeared).toBe(true); - console.log('✅ Skeleton loader appears during language transition'); - }); - - test('should complete transition within 500-700ms', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const langButton = page.locator('button[data-lang="es"]').or( - page.locator('[hx-get*="lang=es"]').first() - ); - - if (await langButton.count() === 0) return; - - const startTime = Date.now(); - await langButton.click(); - - // Wait for HTMX to complete (htmx:afterSwap event) - await page.waitForFunction(() => { - return !document.body.classList.contains('htmx-swapping') && - !document.querySelector('.htmx-swapping'); - }, { timeout: 2000 }); - - const endTime = Date.now(); - const duration = endTime - startTime; - - console.log(`Transition duration: ${duration}ms`); - expect(duration).toBeGreaterThanOrEqual(400); - expect(duration).toBeLessThanOrEqual(1000); - console.log('✅ Transition completes within acceptable time range'); - }); - - test('should handle rapid language switching without breaking', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const enButton = page.locator('button[data-lang="en"]').or( - page.locator('[hx-get*="lang=en"]').first() - ); - const esButton = page.locator('button[data-lang="es"]').or( - page.locator('[hx-get*="lang=es"]').first() - ); - - if (await enButton.count() === 0 || await esButton.count() === 0) return; - - // Rapid clicking - await esButton.click(); - await waitForAnimation(100); - await enButton.click(); - await waitForAnimation(100); - await esButton.click(); - await waitForAnimation(800); - - // Check no errors in console - const errors = []; - page.on('console', msg => { - if (msg.type() === 'error') errors.push(msg.text()); - }); - - await page.screenshot({ path: 'test-results/02-rapid-switch.png', fullPage: true }); - - console.log(`Console errors during rapid switching: ${errors.length}`); - expect(errors.length).toBe(0); - console.log('✅ Handles rapid language switching without errors'); - }); -}); - -test.describe('FEATURE 003: HTMX Loading Indicators', () => { - test('should show loading indicator on language button click', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const langButton = page.locator('button[data-lang="es"]').or( - page.locator('[hx-get*="lang=es"]').first() - ); - - if (await langButton.count() === 0) return; - - // Look for loading indicator - let indicatorAppeared = false; - - await page.evaluate(() => { - window.indicatorDetected = false; - const observer = new MutationObserver(() => { - const indicator = document.querySelector('.htmx-indicator, .loading-indicator, .spinner, [data-loading]'); - if (indicator && window.getComputedStyle(indicator).opacity !== '0') { - window.indicatorDetected = true; - } - }); - observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] }); - }); - - await langButton.click(); - await waitForAnimation(50); - - indicatorAppeared = await page.evaluate(() => window.indicatorDetected); - - await waitForAnimation(600); - - console.log(`Loading indicator appeared: ${indicatorAppeared}`); - expect(indicatorAppeared).toBe(true); - console.log('✅ Loading indicator appears on language button click'); - }); - - test('should show loading indicators on toggle controls', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const toggles = page.locator('input[type="checkbox"][hx-get], input[type="checkbox"][hx-post]'); - const toggleCount = await toggles.count(); - - console.log(`Toggle controls found: ${toggleCount}`); - - if (toggleCount === 0) { - console.log('⚠️ No toggle controls found'); - return; - } - - // Test first toggle - const firstToggle = toggles.first(); - - await page.evaluate(() => { - window.toggleIndicatorDetected = false; - const observer = new MutationObserver(() => { - const indicator = document.querySelector('.htmx-indicator, .loading-indicator, .spinner'); - if (indicator && window.getComputedStyle(indicator).opacity !== '0') { - window.toggleIndicatorDetected = true; - } - }); - observer.observe(document.body, { childList: true, subtree: true, attributes: true }); - }); - - await firstToggle.click(); - await waitForAnimation(50); - - const indicatorAppeared = await page.evaluate(() => window.toggleIndicatorDetected); - - await waitForAnimation(500); - await page.screenshot({ path: 'test-results/03-toggle-indicator.png', fullPage: true }); - - console.log(`Toggle loading indicator appeared: ${indicatorAppeared}`); - console.log('✅ Loading indicators work on toggle controls'); - }); - - test('should hide indicators after request completes', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const langButton = page.locator('button[data-lang="es"]').or( - page.locator('[hx-get*="lang=es"]').first() - ); - - if (await langButton.count() === 0) return; - - await langButton.click(); - await waitForAnimation(800); - - // Check that all indicators are hidden - const visibleIndicators = await page.locator('.htmx-indicator:visible, .loading-indicator:visible, .spinner:visible').count(); - - console.log(`Visible indicators after completion: ${visibleIndicators}`); - expect(visibleIndicators).toBe(0); - console.log('✅ Indicators hide after request completion'); - }); -}); - -test.describe('FEATURE 004: Theme Switcher', () => { - test('should detect theme switcher button', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const themeButton = page.locator('button[data-theme], button[data-action="toggle-theme"], button:has-text("theme")').first(); - const exists = await themeButton.count() > 0; - - console.log(`Theme switcher button exists: ${exists}`); - - if (!exists) { - console.log('⚠️ Theme switcher NOT IMPLEMENTED'); - return; - } - - await page.screenshot({ path: 'test-results/04-theme-button.png', fullPage: true }); - expect(exists).toBe(true); - }); - - test('should expand to show theme options', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const themeButton = page.locator('button[data-theme], button[data-action="toggle-theme"]').first(); - - if (await themeButton.count() === 0) { - console.log('⚠️ Theme switcher NOT FOUND'); - return; - } - - await themeButton.click(); - await waitForAnimation(300); - - // Look for theme options (Light, Dark, Auto) - const lightOption = await page.locator('button:has-text("Light"), [data-theme="light"]').count(); - const darkOption = await page.locator('button:has-text("Dark"), [data-theme="dark"]').count(); - const autoOption = await page.locator('button:has-text("Auto"), [data-theme="auto"]').count(); - - console.log(`Light option: ${lightOption}, Dark option: ${darkOption}, Auto option: ${autoOption}`); - - await page.screenshot({ path: 'test-results/04-theme-options.png', fullPage: true }); - - const hasOptions = lightOption > 0 || darkOption > 0 || autoOption > 0; - expect(hasOptions).toBe(true); - console.log('✅ Theme switcher shows options'); - }); - - test('should persist theme selection in localStorage', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const themeButton = page.locator('button[data-theme], button[data-action="toggle-theme"]').first(); - - if (await themeButton.count() === 0) return; - - await themeButton.click(); - await waitForAnimation(300); - - const darkOption = page.locator('button:has-text("Dark"), [data-theme="dark"]').first(); - - if (await darkOption.count() > 0) { - await darkOption.click(); - await waitForAnimation(300); - - // Check localStorage - const storedTheme = await page.evaluate(() => localStorage.getItem('theme')); - console.log(`Stored theme: ${storedTheme}`); - - // Reload and verify persistence - await page.reload(); - await waitForAnimation(300); - - const themeAfterReload = await page.evaluate(() => localStorage.getItem('theme')); - expect(themeAfterReload).toBe(storedTheme); - console.log('✅ Theme selection persists in localStorage'); - } - }); -}); - -test.describe('FEATURE 005: PDF Download Modal', () => { - test('should detect PDF modal trigger button', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const pdfButton = page.locator('button:has-text("PDF"), button:has-text("download"), [data-action="show-pdf"]').first(); - const exists = await pdfButton.count() > 0; - - console.log(`PDF modal button exists: ${exists}`); - - if (!exists) { - console.log('⚠️ PDF MODAL NOT IMPLEMENTED'); - return; - } - - await page.screenshot({ path: 'test-results/05-pdf-button.png', fullPage: true }); - expect(exists).toBe(true); - }); - - test('should show three thumbnail cards', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const pdfButton = page.locator('button:has-text("PDF"), button:has-text("download")').first(); - - if (await pdfButton.count() === 0) return; - - await pdfButton.click(); - await waitForAnimation(300); - - // Look for thumbnail cards - const thumbnails = await page.locator('.thumbnail, .pdf-card, [data-pdf-type]').count(); - console.log(`Thumbnail cards found: ${thumbnails}`); - - await page.screenshot({ path: 'test-results/05-pdf-modal-open.png', fullPage: true }); - - expect(thumbnails).toBeGreaterThanOrEqual(2); - console.log('✅ PDF modal shows thumbnail cards'); - }); - - test('should enable download button after selection', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const pdfButton = page.locator('button:has-text("PDF"), button:has-text("download")').first(); - - if (await pdfButton.count() === 0) return; - - await pdfButton.click(); - await waitForAnimation(300); - - // Find download button (should be disabled initially) - const downloadBtn = page.locator('button:has-text("Download"), button[data-action="download"]').first(); - - if (await downloadBtn.count() > 0) { - const initiallyDisabled = await downloadBtn.isDisabled(); - console.log(`Download button initially disabled: ${initiallyDisabled}`); - - // Click first thumbnail - const thumbnail = page.locator('.thumbnail, .pdf-card, [data-pdf-type]').first(); - if (await thumbnail.count() > 0) { - await thumbnail.click(); - await waitForAnimation(200); - - const enabledAfterSelection = !(await downloadBtn.isDisabled()); - console.log(`Download button enabled after selection: ${enabledAfterSelection}`); - - expect(enabledAfterSelection).toBe(true); - console.log('✅ Download button enables after selection'); - } - } - }); -}); - -test.describe('INTEGRATION TESTS: Cross-Feature Interactions', () => { - test('should handle language switch while modal is open', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - // Open shortcuts modal if exists - const shortcutsBtn = page.locator('button[data-action="show-shortcuts"]').first(); - - if (await shortcutsBtn.count() > 0) { - await shortcutsBtn.click(); - await waitForAnimation(300); - - // Switch language - const langButton = page.locator('button[data-lang="es"]').first(); - if (await langButton.count() > 0) { - await langButton.click(); - await waitForAnimation(800); - - await page.screenshot({ path: 'test-results/int-modal-lang-switch.png', fullPage: true }); - - console.log('✅ Language switch works with modal open'); - } - } - }); - - test('should handle multiple rapid feature interactions', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - - const errors = []; - page.on('console', msg => { - if (msg.type() === 'error') errors.push(msg.text()); - }); - - // Rapid interactions - const langButton = page.locator('button[data-lang="es"]').first(); - const toggle = page.locator('input[type="checkbox"]').first(); - - if (await langButton.count() > 0) await langButton.click(); - await waitForAnimation(100); - if (await toggle.count() > 0) await toggle.click(); - await waitForAnimation(100); - if (await langButton.count() > 0) await langButton.click(); - await waitForAnimation(800); - - console.log(`Errors during rapid interactions: ${errors.length}`); - expect(errors.length).toBe(0); - console.log('✅ Handles rapid feature interactions without errors'); - }); -}); - -test.describe('PERFORMANCE & ACCESSIBILITY', () => { - test('should have no console errors on page load', async ({ page }) => { - const errors = []; - page.on('console', msg => { - if (msg.type() === 'error') errors.push(msg.text()); - }); - - await page.goto(`${BASE_URL}/?lang=en`); - await waitForAnimation(1000); - - console.log('Console errors on load:', errors); - expect(errors.length).toBe(0); - console.log('✅ No console errors on page load'); - }); - - test('should measure Core Web Vitals', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - await waitForAnimation(1000); - - const metrics = await page.evaluate(() => { - const paint = performance.getEntriesByType('paint'); - const navigation = performance.getEntriesByType('navigation')[0]; - - return { - fcp: paint.find(p => p.name === 'first-contentful-paint')?.startTime, - domContentLoaded: navigation?.domContentLoadedEventEnd - navigation?.domContentLoadedEventStart, - loadComplete: navigation?.loadEventEnd - navigation?.loadEventStart - }; - }); - - console.log('Performance metrics:', metrics); - expect(metrics.fcp).toBeLessThan(3000); - console.log('✅ Performance metrics within acceptable range'); - }); -}); diff --git a/tests/inspect-structure.js b/tests/inspect-structure.js deleted file mode 100644 index ba7203e..0000000 --- a/tests/inspect-structure.js +++ /dev/null @@ -1,98 +0,0 @@ -const { chromium } = require('playwright'); - -async function inspectStructure() { - const browser = await chromium.launch(); - const page = await browser.newPage(); - - console.log('\n=== INSPECTING OLD SITE (localhost:3000) ===\n'); - await page.goto('http://localhost:3000', { waitUntil: 'networkidle' }); - - // Get all class names - const classes = await page.evaluate(() => { - const allElements = document.querySelectorAll('*'); - const classSet = new Set(); - allElements.forEach(el => { - if (el.className && typeof el.className === 'string') { - el.className.split(' ').forEach(cls => { - if (cls.trim()) classSet.add(cls.trim()); - }); - } - }); - return Array.from(classSet).sort(); - }); - - console.log('All classes found:'); - console.log(classes.filter(c => c.includes('badge') || c.includes('title') || c.includes('cv') || c.includes('sidebar')).join('\n')); - - // Get main structure - const structure = await page.evaluate(() => { - const getStructure = (el, depth = 0) => { - if (depth > 3) return null; - const tag = el.tagName.toLowerCase(); - const classes = el.className || ''; - const id = el.id || ''; - return { - tag, - classes, - id, - children: Array.from(el.children).map(child => getStructure(child, depth + 1)).filter(Boolean) - }; - }; - return getStructure(document.body); - }); - - console.log('\n\nMain structure:'); - console.log(JSON.stringify(structure, null, 2).substring(0, 5000)); - - // Find elements with "badge" or "title" in their classes - const badgeElements = await page.$$eval('[class*="badge"], [class*="title"]', elements => - elements.slice(0, 20).map(el => ({ - tag: el.tagName, - class: el.className, - text: el.textContent?.substring(0, 100), - computedStyles: (() => { - const computed = window.getComputedStyle(el); - return { - fontSize: computed.fontSize, - fontWeight: computed.fontWeight, - color: computed.color, - backgroundColor: computed.backgroundColor, - padding: computed.padding, - height: computed.height - }; - })() - })) - ); - - console.log('\n\nBadge/Title elements:'); - console.log(JSON.stringify(badgeElements, null, 2)); - - console.log('\n\n=== INSPECTING NEW SITE (localhost:1999) ===\n'); - await page.goto('http://localhost:1999', { waitUntil: 'networkidle' }); - - const newBadgeElements = await page.$$eval('[class*="badge"], [class*="title"]', elements => - elements.slice(0, 20).map(el => ({ - tag: el.tagName, - class: el.className, - text: el.textContent?.substring(0, 100), - computedStyles: (() => { - const computed = window.getComputedStyle(el); - return { - fontSize: computed.fontSize, - fontWeight: computed.fontWeight, - color: computed.color, - backgroundColor: computed.backgroundColor, - padding: computed.padding, - height: computed.height - }; - })() - })) - ); - - console.log('Badge/Title elements:'); - console.log(JSON.stringify(newBadgeElements, null, 2)); - - await browser.close(); -} - -inspectStructure().catch(console.error); diff --git a/tests/manual-inspection.spec.js b/tests/manual-inspection.spec.js deleted file mode 100644 index 9b2323d..0000000 --- a/tests/manual-inspection.spec.js +++ /dev/null @@ -1,408 +0,0 @@ -/** - * MANUAL INSPECTION - Deep Dive into Features - * Investigates specific issues found in comprehensive tests - */ - -import { test, expect } from '@playwright/test'; - -const BASE_URL = 'http://localhost:1999'; -const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)); - -test.describe('MANUAL INSPECTION: Feature Deep Dive', () => { - test('Inspect page structure and all interactive elements', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - await wait(1000); - - console.log('\n=== PAGE STRUCTURE INSPECTION ===\n'); - - // Find all buttons - const allButtons = await page.$$('button'); - console.log(`Total buttons: ${allButtons.length}`); - - for (let i = 0; i < allButtons.length; i++) { - const btn = allButtons[i]; - const text = await btn.textContent(); - const id = await btn.getAttribute('id'); - const dataAction = await btn.getAttribute('data-action'); - const classes = await btn.getAttribute('class'); - - console.log(`Button ${i + 1}: text="${text?.trim()}" id="${id}" data-action="${dataAction}" class="${classes}"`); - } - - // Find all toggles - console.log('\n=== TOGGLE CONTROLS ===\n'); - const toggles = await page.$$('input[type="checkbox"]'); - console.log(`Total checkboxes: ${toggles.length}`); - - for (let i = 0; i < toggles.length; i++) { - const toggle = toggles[i]; - const id = await toggle.getAttribute('id'); - const hxGet = await toggle.getAttribute('hx-get'); - const hxPost = await toggle.getAttribute('hx-post'); - const hxIndicator = await toggle.getAttribute('hx-indicator'); - - console.log(`Toggle ${i + 1}: id="${id}" hx-get="${hxGet}" hx-post="${hxPost}" hx-indicator="${hxIndicator}"`); - } - - // Find modals/dialogs - console.log('\n=== MODALS/DIALOGS ===\n'); - const dialogs = await page.$$('dialog'); - console.log(`Native dialogs: ${dialogs.length}`); - - for (let i = 0; i < dialogs.length; i++) { - const dialog = dialogs[i]; - const id = await dialog.getAttribute('id'); - const classes = await dialog.getAttribute('class'); - const textPreview = (await dialog.textContent())?.substring(0, 50); - - console.log(`Dialog ${i + 1}: id="${id}" class="${classes}" preview="${textPreview}..."`); - } - - // Find HTMX indicators - console.log('\n=== HTMX INDICATORS ===\n'); - const indicators = await page.$$('.htmx-indicator, [class*="indicator"], [class*="loading"], [class*="spinner"]'); - console.log(`Indicator elements: ${indicators.length}`); - - for (let i = 0; i < indicators.length; i++) { - const indicator = indicators[i]; - const classes = await indicator.getAttribute('class'); - const id = await indicator.getAttribute('id'); - - console.log(`Indicator ${i + 1}: id="${id}" class="${classes}"`); - } - - // Find skeleton loaders - console.log('\n=== SKELETON LOADERS ===\n'); - const skeletons = await page.$$('[class*="skeleton"], [class*="shimmer"]'); - console.log(`Skeleton elements: ${skeletons.length}`); - - for (let i = 0; i < skeletons.length; i++) { - const skeleton = skeletons[i]; - const classes = await skeleton.getAttribute('class'); - const id = await skeleton.getAttribute('id'); - - console.log(`Skeleton ${i + 1}: id="${id}" class="${classes}"`); - } - - await page.screenshot({ path: 'test-results/inspect-full-page.png', fullPage: true }); - }); - - test('Test language switch with detailed timing', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - await wait(1000); - - console.log('\n=== LANGUAGE SWITCH DETAILED TIMING ===\n'); - - // Find ES button - const esButton = await page.locator('button').filter({ hasText: 'ES' }).first(); - - // Monitor all DOM changes during switch - await page.evaluate(() => { - window.transitionLog = []; - window.startTime = Date.now(); - - // Monitor skeleton - const observer = new MutationObserver(() => { - const skeleton = document.querySelector('[class*="skeleton"]'); - if (skeleton) { - const opacity = window.getComputedStyle(skeleton).opacity; - const display = window.getComputedStyle(skeleton).display; - window.transitionLog.push({ - time: Date.now() - window.startTime, - event: 'skeleton', - opacity, - display - }); - } - }); - observer.observe(document.body, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['class', 'style'] - }); - - // Monitor HTMX events - document.body.addEventListener('htmx:beforeSwap', () => { - window.transitionLog.push({ time: Date.now() - window.startTime, event: 'beforeSwap' }); - }); - document.body.addEventListener('htmx:afterSwap', () => { - window.transitionLog.push({ time: Date.now() - window.startTime, event: 'afterSwap' }); - }); - document.body.addEventListener('htmx:afterSettle', () => { - window.transitionLog.push({ time: Date.now() - window.startTime, event: 'afterSettle' }); - }); - }); - - // Click ES button - const clickTime = Date.now(); - await esButton.click(); - - // Wait and capture screenshots at different stages - await wait(100); - await page.screenshot({ path: 'test-results/lang-switch-100ms.png', fullPage: true }); - - await wait(200); - await page.screenshot({ path: 'test-results/lang-switch-300ms.png', fullPage: true }); - - await wait(300); - await page.screenshot({ path: 'test-results/lang-switch-600ms.png', fullPage: true }); - - await wait(200); - const endTime = Date.now(); - - // Get transition log - const log = await page.evaluate(() => window.transitionLog); - console.log('Transition timeline:'); - log.forEach(entry => { - console.log(` ${entry.time}ms: ${entry.event}${entry.opacity ? ` (opacity: ${entry.opacity})` : ''}`); - }); - - console.log(`\nTotal measured time: ${endTime - clickTime}ms`); - }); - - test('Inspect HTMX loading indicators in detail', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - await wait(1000); - - console.log('\n=== HTMX INDICATOR INSPECTION ===\n'); - - // Find language button with hx attributes - const langButtons = await page.$$('button[hx-get], button[data-lang]'); - console.log(`Buttons with HTMX attributes: ${langButtons.length}`); - - for (let i = 0; i < langButtons.length; i++) { - const btn = langButtons[i]; - const hxIndicator = await btn.getAttribute('hx-indicator'); - const text = await btn.textContent(); - - console.log(`Button "${text?.trim()}": hx-indicator="${hxIndicator}"`); - - if (hxIndicator) { - const indicatorExists = await page.locator(hxIndicator).count(); - console.log(` → Indicator "${hxIndicator}" exists: ${indicatorExists > 0}`); - - if (indicatorExists > 0) { - const classes = await page.locator(hxIndicator).getAttribute('class'); - const styles = await page.locator(hxIndicator).evaluate(el => ({ - display: window.getComputedStyle(el).display, - opacity: window.getComputedStyle(el).opacity, - visibility: window.getComputedStyle(el).visibility - })); - - console.log(` → Classes: "${classes}"`); - console.log(` → Computed styles: ${JSON.stringify(styles)}`); - } - } - } - - // Test clicking and monitoring - const esButton = page.locator('button').filter({ hasText: 'ES' }).first(); - - await page.evaluate(() => { - window.indicatorStates = []; - - const observer = new MutationObserver(() => { - const indicators = document.querySelectorAll('.htmx-indicator, [class*="loading"]'); - indicators.forEach((ind, idx) => { - const styles = window.getComputedStyle(ind); - window.indicatorStates.push({ - time: Date.now(), - indicator: idx, - opacity: styles.opacity, - display: styles.display, - classes: ind.className - }); - }); - }); - - observer.observe(document.body, { - attributes: true, - childList: true, - subtree: true, - attributeFilter: ['class', 'style'] - }); - }); - - await esButton.click(); - await wait(50); - await page.screenshot({ path: 'test-results/indicator-active-50ms.png', fullPage: true }); - await wait(700); - - const states = await page.evaluate(() => window.indicatorStates); - console.log('\nIndicator state changes:'); - states.forEach(state => { - console.log(` ${state.time}: Indicator ${state.indicator} - opacity=${state.opacity}, display=${state.display}`); - }); - }); - - test('Test PDF modal structure', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - await wait(1000); - - console.log('\n=== PDF MODAL INSPECTION ===\n'); - - // Find PDF button - const pdfButtons = await page.$$('button'); - let pdfButton = null; - - for (const btn of pdfButtons) { - const text = (await btn.textContent())?.toLowerCase() || ''; - if (text.includes('pdf') || text.includes('download')) { - pdfButton = btn; - console.log(`Found PDF button: "${await btn.textContent()}"`); - break; - } - } - - if (!pdfButton) { - console.log('❌ No PDF button found'); - return; - } - - // Click to open modal - await pdfButton.click(); - await wait(500); - - await page.screenshot({ path: 'test-results/pdf-modal-detailed.png', fullPage: true }); - - // Inspect modal structure - const modalContent = await page.evaluate(() => { - const dialog = document.querySelector('dialog[open]'); - if (!dialog) return { found: false }; - - const allElements = dialog.querySelectorAll('*'); - const structure = { - found: true, - totalElements: allElements.length, - images: dialog.querySelectorAll('img').length, - cards: dialog.querySelectorAll('[class*="card"], [class*="thumbnail"], [data-pdf]').length, - buttons: dialog.querySelectorAll('button').length, - textContent: dialog.textContent?.substring(0, 200) - }; - - return structure; - }); - - console.log('Modal structure:', JSON.stringify(modalContent, null, 2)); - - // Look for specific PDF-related elements - const pdfElements = await page.$$('[data-pdf-type], [class*="pdf"], .thumbnail, .card'); - console.log(`\nPDF-related elements found: ${pdfElements.length}`); - - for (let i = 0; i < pdfElements.length; i++) { - const el = pdfElements[i]; - const classes = await el.getAttribute('class'); - const dataPdf = await el.getAttribute('data-pdf-type'); - const tagName = await el.evaluate(node => node.tagName); - - console.log(` Element ${i + 1}: <${tagName}> class="${classes}" data-pdf-type="${dataPdf}"`); - } - }); - - test('Search for shortcuts button systematically', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - await wait(1000); - - console.log('\n=== SHORTCUTS BUTTON SEARCH ===\n'); - - // Try all possible button texts - const searchTerms = ['shortcuts', 'shortcut', 'keyboard', 'help', '?', 'atajos', 'ayuda']; - - for (const term of searchTerms) { - const count = await page.locator(`button:has-text("${term}")`).count(); - console.log(`Buttons containing "${term}": ${count}`); - } - - // Try data attributes - const dataActions = await page.$$('[data-action]'); - console.log(`\nElements with data-action: ${dataActions.length}`); - - for (const el of dataActions) { - const action = await el.getAttribute('data-action'); - const tagName = await el.evaluate(node => node.tagName); - const text = (await el.textContent())?.trim(); - - console.log(` <${tagName}> data-action="${action}" text="${text}"`); - } - - // Look for info icon or help icon - const icons = await page.$$('[class*="icon"], i, svg'); - console.log(`\nIcon elements: ${icons.length}`); - - for (let i = 0; i < Math.min(icons.length, 20); i++) { - const icon = icons[i]; - const classes = await icon.getAttribute('class'); - const parent = await icon.evaluateHandle(node => node.parentElement); - const parentTag = await parent.evaluate(node => node?.tagName); - - if (classes?.includes('info') || classes?.includes('help') || classes?.includes('question')) { - console.log(` Icon ${i + 1}: class="${classes}" parent=<${parentTag}>`); - } - } - }); - - test('Test theme switcher detection', async ({ page }) => { - await page.goto(`${BASE_URL}/?lang=en`); - await wait(1000); - - console.log('\n=== THEME SWITCHER SEARCH ===\n'); - - // Search for theme-related elements - const themeElements = await page.$$('[data-theme], [class*="theme"], button:has-text("theme")'); - console.log(`Theme-related elements: ${themeElements.length}`); - - for (const el of themeElements) { - const tagName = await el.evaluate(node => node.tagName); - const classes = await el.getAttribute('class'); - const dataTheme = await el.getAttribute('data-theme'); - const text = (await el.textContent())?.substring(0, 30); - - console.log(` <${tagName}> class="${classes}" data-theme="${dataTheme}" text="${text}"`); - } - - // Check localStorage - const themeInStorage = await page.evaluate(() => localStorage.getItem('theme')); - console.log(`\nTheme in localStorage: "${themeInStorage}"`); - - // Check for moon/sun icons (common theme toggle icons) - const moonSun = await page.$$('[class*="moon"], [class*="sun"], [class*="dark"], [class*="light"]'); - console.log(`Moon/sun/dark/light elements: ${moonSun.length}`); - }); - - test('Console error monitoring', async ({ page }) => { - const errors = []; - const warnings = []; - - page.on('console', msg => { - if (msg.type() === 'error') errors.push({ text: msg.text(), location: msg.location() }); - if (msg.type() === 'warning') warnings.push(msg.text()); - }); - - page.on('pageerror', error => { - errors.push({ text: error.message, stack: error.stack }); - }); - - await page.goto(`${BASE_URL}/?lang=en`); - await wait(2000); - - // Interact with features - const esButton = page.locator('button').filter({ hasText: 'ES' }).first(); - if (await esButton.count() > 0) { - await esButton.click(); - await wait(1000); - } - - console.log('\n=== CONSOLE MONITORING ===\n'); - console.log(`Errors: ${errors.length}`); - errors.forEach((err, i) => { - console.log(` Error ${i + 1}: ${err.text}`); - if (err.stack) console.log(` Stack: ${err.stack.substring(0, 100)}...`); - }); - - console.log(`\nWarnings: ${warnings.length}`); - warnings.forEach((warn, i) => { - console.log(` Warning ${i + 1}: ${warn}`); - }); - }); -}); diff --git a/tests/mjs/7-mobile-responsive.test.mjs b/tests/mjs/7-mobile-responsive.test.mjs new file mode 100755 index 0000000..7532c54 --- /dev/null +++ b/tests/mjs/7-mobile-responsive.test.mjs @@ -0,0 +1,283 @@ +#!/usr/bin/env bun +/** + * MOBILE RESPONSIVE TEST + * ======================= + * Tests mobile viewport rendering and interactions + * - Mobile viewport sizing (375px, 768px, 1024px) + * - Touch interactions + * - Mobile menu functionality + * - Responsive layout breakpoints + * - Text readability at small sizes + */ + +import { chromium } from 'playwright'; + +const URL = "http://localhost:1999"; + +// Common mobile viewports +const VIEWPORTS = { + mobile: { width: 375, height: 667 }, // iPhone SE + tablet: { width: 768, height: 1024 }, // iPad + desktop: { width: 1920, height: 1080 } // Desktop baseline +}; + +async function testMobileResponsive() { + console.log('📱 MOBILE RESPONSIVE TEST\n'); + console.log('='.repeat(70)); + + const browser = await chromium.launch({ headless: false }); + const errors = []; + const testResults = []; + + // ======================================================================== + // TEST 1: Mobile viewport (375px) + // ======================================================================== + console.log("\n1️⃣ Testing Mobile Viewport (375px)..."); + const mobilePage = await browser.newPage({ viewport: VIEWPORTS.mobile }); + + mobilePage.on('console', msg => { + if (msg.type() === 'error') { + errors.push(msg.text()); + console.log(`❌ ERROR: ${msg.text()}`); + } + }); + + await mobilePage.goto(URL); + await mobilePage.waitForTimeout(2000); + + const mobileTest = await mobilePage.evaluate(() => { + const paper = document.querySelector('.cv-paper'); + const body = document.body; + const hamburger = document.querySelector('.hamburger-btn'); + + // Check for horizontal overflow + const hasHorizontalScroll = document.documentElement.scrollWidth > window.innerWidth; + + // Check if text is readable (not too small) + const paragraphs = Array.from(document.querySelectorAll('p, li')); + const fontSizes = paragraphs.map(p => { + const size = parseFloat(window.getComputedStyle(p).fontSize); + return size; + }); + const minFontSize = Math.min(...fontSizes); + + // Check if hamburger menu exists (mobile navigation) + const hasHamburger = !!hamburger; + const hamburgerVisible = hasHamburger ? + window.getComputedStyle(hamburger).display !== 'none' : false; + + return { + width: window.innerWidth, + height: window.innerHeight, + hasHorizontalScroll, + minFontSize, + hasHamburger, + hamburgerVisible, + paperWidth: paper ? paper.offsetWidth : 0 + }; + }); + + console.log(` Viewport: ${mobileTest.width}x${mobileTest.height}`); + console.log(` Horizontal scroll: ${mobileTest.hasHorizontalScroll ? '❌ YES (BAD)' : '✅ NO (GOOD)'}`); + console.log(` Min font size: ${mobileTest.minFontSize.toFixed(1)}px ${mobileTest.minFontSize >= 14 ? '✅' : '⚠️'}`); + console.log(` Hamburger menu: ${mobileTest.hasHamburger ? '✅ Present' : '⚠️ Not found'}`); + console.log(` Hamburger visible: ${mobileTest.hamburgerVisible ? '✅ YES' : '⚠️ NO'}`); + + const mobileViewportPassed = !mobileTest.hasHorizontalScroll && mobileTest.minFontSize >= 14; + console.log(` ${mobileViewportPassed ? '✅ PASS' : '❌ FAIL'} - Mobile viewport`); + testResults.push({ test: 'Mobile Viewport (375px)', passed: mobileViewportPassed }); + + // ======================================================================== + // TEST 2: Touch interactions (hamburger menu) + // ======================================================================== + console.log("\n2️⃣ Testing Touch Interactions..."); + + const hamburger = await mobilePage.$('.hamburger-btn'); + if (hamburger) { + // Tap hamburger to open menu + await hamburger.tap(); + await mobilePage.waitForTimeout(500); + + const menuTest = await mobilePage.evaluate(() => { + const menu = document.querySelector('.navigation-menu'); + if (!menu) return { found: false }; + + const isOpen = menu.classList.contains('menu-open') || + window.getComputedStyle(menu).display !== 'none'; + + return { + found: true, + isOpen, + isVisible: menu.offsetHeight > 0 + }; + }); + + console.log(` Menu found: ${menuTest.found ? '✅' : '❌'}`); + console.log(` Menu opens on tap: ${menuTest.isOpen ? '✅' : '❌'}`); + console.log(` ${menuTest.found && menuTest.isOpen ? '✅ PASS' : '❌ FAIL'} - Touch interactions`); + testResults.push({ test: 'Touch Interactions', passed: menuTest.found && menuTest.isOpen }); + + // Close menu + if (menuTest.isOpen) { + await hamburger.tap(); + await mobilePage.waitForTimeout(300); + } + } else { + console.log(` ⚠️ SKIP - Hamburger menu not found`); + testResults.push({ test: 'Touch Interactions', passed: true }); + } + + // ======================================================================== + // TEST 3: Tablet viewport (768px) + // ======================================================================== + console.log("\n3️⃣ Testing Tablet Viewport (768px)..."); + const tabletPage = await browser.newPage({ viewport: VIEWPORTS.tablet }); + await tabletPage.goto(URL); + await tabletPage.waitForTimeout(2000); + + const tabletTest = await tabletPage.evaluate(() => { + const hasHorizontalScroll = document.documentElement.scrollWidth > window.innerWidth; + const paper = document.querySelector('.cv-paper'); + const actionBar = document.querySelector('.action-bar, .cv-controls'); + + return { + width: window.innerWidth, + hasHorizontalScroll, + paperWidth: paper ? paper.offsetWidth : 0, + hasActionBar: !!actionBar, + actionBarVisible: actionBar ? window.getComputedStyle(actionBar).display !== 'none' : false + }; + }); + + console.log(` Viewport: ${tabletTest.width}px`); + console.log(` Horizontal scroll: ${tabletTest.hasHorizontalScroll ? '❌ YES' : '✅ NO'}`); + console.log(` Action bar: ${tabletTest.hasActionBar ? '✅ Present' : '⚠️ Not found'}`); + + const tabletViewportPassed = !tabletTest.hasHorizontalScroll; + console.log(` ${tabletViewportPassed ? '✅ PASS' : '❌ FAIL'} - Tablet viewport`); + testResults.push({ test: 'Tablet Viewport (768px)', passed: tabletViewportPassed }); + + await tabletPage.close(); + + // ======================================================================== + // TEST 4: Responsive breakpoints + // ======================================================================== + console.log("\n4️⃣ Testing Responsive Breakpoints..."); + + const breakpoints = [ + { name: 'Small Mobile', width: 320 }, + { name: 'Mobile', width: 375 }, + { name: 'Large Mobile', width: 414 }, + { name: 'Small Tablet', width: 600 }, + { name: 'Tablet', width: 768 }, + { name: 'Large Tablet', width: 1024 }, + { name: 'Desktop', width: 1920 } + ]; + + const breakpointResults = []; + + for (const bp of breakpoints) { + await mobilePage.setViewportSize({ width: bp.width, height: 800 }); + await mobilePage.waitForTimeout(200); + + const result = await mobilePage.evaluate(() => { + return { + hasHorizontalScroll: document.documentElement.scrollWidth > window.innerWidth, + bodyWidth: document.body.offsetWidth + }; + }); + + const passed = !result.hasHorizontalScroll; + breakpointResults.push({ name: bp.name, width: bp.width, passed }); + console.log(` ${bp.name} (${bp.width}px): ${passed ? '✅' : '❌'}`); + } + + const allBreakpointsPassed = breakpointResults.every(r => r.passed); + console.log(` ${allBreakpointsPassed ? '✅ PASS' : '❌ FAIL'} - All breakpoints`); + testResults.push({ test: 'Responsive Breakpoints', passed: allBreakpointsPassed }); + + // ======================================================================== + // TEST 5: Mobile-specific features + // ======================================================================== + console.log("\n5️⃣ Testing Mobile-Specific Features..."); + + await mobilePage.setViewportSize(VIEWPORTS.mobile); + await mobilePage.waitForTimeout(500); + + const mobileFeatures = await mobilePage.evaluate(() => { + // Check viewport meta tag + const viewportMeta = document.querySelector('meta[name="viewport"]'); + const hasViewportMeta = !!viewportMeta; + const viewportContent = viewportMeta?.getAttribute('content') || ''; + + // Check for touch-friendly button sizes (minimum 44x44px) + const buttons = Array.from(document.querySelectorAll('button, a[role="button"], .btn, input[type="checkbox"]')); + const buttonSizes = buttons.map(btn => { + const rect = btn.getBoundingClientRect(); + return { width: rect.width, height: rect.height }; + }); + const tooSmallButtons = buttonSizes.filter(s => s.width < 44 || s.height < 44).length; + + // Check for text overflow + const hasTextOverflow = Array.from(document.querySelectorAll('*')).some(el => { + return el.scrollWidth > el.clientWidth && window.getComputedStyle(el).overflow === 'visible'; + }); + + return { + hasViewportMeta, + viewportContent, + totalButtons: buttons.length, + tooSmallButtons, + hasTextOverflow + }; + }); + + console.log(` Viewport meta tag: ${mobileFeatures.hasViewportMeta ? '✅' : '❌'}`); + console.log(` Content: "${mobileFeatures.viewportContent}"`); + console.log(` Touch-friendly buttons: ${mobileFeatures.totalButtons - mobileFeatures.tooSmallButtons}/${mobileFeatures.totalButtons}`); + console.log(` Too small (<44px): ${mobileFeatures.tooSmallButtons} ${mobileFeatures.tooSmallButtons === 0 ? '✅' : '⚠️'}`); + console.log(` Text overflow: ${mobileFeatures.hasTextOverflow ? '⚠️ YES' : '✅ NO'}`); + + const mobileFeaturesPass = mobileFeatures.hasViewportMeta && !mobileFeatures.hasTextOverflow; + console.log(` ${mobileFeaturesPass ? '✅ PASS' : '❌ FAIL'} - Mobile features`); + testResults.push({ test: 'Mobile Features', passed: mobileFeaturesPass }); + + await mobilePage.close(); + + // ======================================================================== + // 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("🎉 MOBILE RESPONSIVE VALIDATED!"); + } 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 testMobileResponsive(); diff --git a/tests/test-all-features.js b/tests/test-all-features.js deleted file mode 100644 index e640ad5..0000000 --- a/tests/test-all-features.js +++ /dev/null @@ -1,105 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 1200 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🎬 Starting comprehensive feature test...\n'); - - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - - console.log('✅ Step 1: Initial URL check'); - let url = page.url(); - console.log(` URL: ${url}`); - console.log(` Clean: ${!url.includes('#') ? '✓' : '✗'}\n`); - - console.log('🔄 Step 2: Testing language switch (atomic OOB swaps)'); - await page.click('button[aria-label="Español"]'); - await page.waitForTimeout(1000); - url = page.url(); - const contentES = await page.locator('.sidebar-accordion-header span').first().textContent(); - console.log(` URL: ${url}`); - console.log(` Content: "${contentES}"`); - console.log(` Success: ${contentES === 'Competencias Técnicas' && url.includes('lang=es') ? '✓' : '✗'}\n`); - - console.log('🎨 Step 3: Testing theme toggle (atomic OOB swaps)'); - await page.click('#themeToggle'); - await page.waitForTimeout(800); - const hasCleanTheme = await page.evaluate(() => document.body.classList.contains('theme-clean')); - url = page.url(); - console.log(` Theme: ${hasCleanTheme ? 'clean' : 'default'}`); - console.log(` URL still clean: ${!url.includes('#') ? '✓' : '✗'}\n`); - - console.log('📏 Step 4: Testing length toggle (atomic OOB swaps)'); - await page.click('#lengthToggle'); - await page.waitForTimeout(800); - const isLong = await page.locator('.cv-paper').evaluate(el => el.classList.contains('cv-long')); - url = page.url(); - console.log(` Length: ${isLong ? 'long' : 'short'}`); - console.log(` URL still clean: ${!url.includes('#') ? '✓' : '✗'}\n`); - - console.log('🖼️ Step 5: Testing logo toggle (atomic OOB swaps)'); - await page.click('#logoToggle'); - await page.waitForTimeout(800); - const showIcons = await page.locator('.cv-paper').evaluate(el => el.classList.contains('show-icons')); - url = page.url(); - console.log(` Icons: ${showIcons ? 'visible' : 'hidden'}`); - console.log(` URL still clean: ${!url.includes('#') ? '✓' : '✗'}\n`); - - console.log('⬆️ Step 6: Testing back-to-top (URL cleanliness)'); - await page.evaluate(() => window.scrollTo(0, 500)); - await page.waitForTimeout(500); - const backToTopBtn = await page.locator('.back-to-top').isVisible(); - console.log(` Back-to-top visible after scroll: ${backToTopBtn ? '✓' : '✗'}`); - - await page.click('.back-to-top'); - await page.waitForTimeout(1000); - url = page.url(); - const scrollPos = await page.evaluate(() => window.pageYOffset); - console.log(` URL after click: ${url}`); - console.log(` No #top anchor: ${!url.includes('#top') ? '✓' : '✗'}`); - console.log(` Scrolled to top: ${scrollPos < 50 ? '✓' : '✗'}\n`); - - console.log('🔄 Step 7: Switch back to English (verify everything persists)'); - await page.click('button[aria-label="English"]'); - await page.waitForTimeout(1000); - url = page.url(); - const contentEN = await page.locator('.sidebar-accordion-header span').first().textContent(); - const stillClean = await page.evaluate(() => document.body.classList.contains('theme-clean')); - const stillLong = await page.locator('.cv-paper').evaluate(el => el.classList.contains('cv-long')); - const stillShowIcons = await page.locator('.cv-paper').evaluate(el => el.classList.contains('show-icons')); - - console.log(` Language: ${contentEN === 'Technical Skills' ? 'English ✓' : 'Failed ✗'}`); - console.log(` Theme persisted: ${stillClean ? 'clean ✓' : 'default ✗'}`); - console.log(` Length persisted: ${stillLong ? 'long ✓' : 'short ✗'}`); - console.log(` Icons persisted: ${stillShowIcons ? 'visible ✓' : 'hidden ✗'}`); - console.log(` URL: ${url}`); - console.log(` Clean URL: ${!url.includes('#') ? '✓' : '✗'}\n`); - - const allPassed = - contentES === 'Competencias Técnicas' && - contentEN === 'Technical Skills' && - hasCleanTheme && stillClean && - isLong && stillLong && - showIcons && stillShowIcons && - !url.includes('#'); - - console.log(`\n${allPassed ? '✅ ALL FEATURES WORKING PERFECTLY!' : '❌ SOME TESTS FAILED'}`); - console.log('\n📊 IMPLEMENTATION SUMMARY:'); - console.log(' ✅ Language switching - Atomic OOB swaps'); - console.log(' ✅ Theme toggle - Atomic OOB swaps'); - console.log(' ✅ Length toggle - Atomic OOB swaps'); - console.log(' ✅ Logo toggle - Atomic OOB swaps'); - console.log(' ✅ URL cleanliness - No anchor pollution'); - console.log(' ✅ State persistence - All preferences maintained'); - console.log(' ✅ Smooth scrolling - Hyperscript powered'); - console.log(' ✅ Minimal payloads - <5KB per toggle'); - console.log(' ✅ Zero JavaScript bloat - Pure HTMX + Hyperscript!'); - - await page.waitForTimeout(3000); - await browser.close(); -})(); diff --git a/tests/test-all-toggles.js b/tests/test-all-toggles.js deleted file mode 100644 index 9e658b4..0000000 --- a/tests/test-all-toggles.js +++ /dev/null @@ -1,116 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 500 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - // Listen for console errors - page.on('console', msg => { - const text = msg.text(); - const type = msg.type(); - if (type === 'error' || text.toLowerCase().includes('error')) { - console.log('❌ CONSOLE ERROR:', text); - } - }); - page.on('pageerror', error => console.log('❌ PAGE EXCEPTION:', error.message)); - - console.log('📄 Loading page...'); - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - - // Scroll down to test scroll preservation - console.log('\n📜 Scrolling down 500px...'); - await page.evaluate(() => window.scrollTo(0, 500)); - await page.waitForTimeout(500); - const scrollBefore = await page.evaluate(() => window.pageYOffset); - console.log(` Current scroll position: ${scrollBefore}px`); - - // Test Theme Toggle (pure hyperscript - should not affect scroll) - console.log('\n🎨 TEST 1: Theme toggle (hyperscript)...'); - await page.locator('.selector-group').filter({ hasText: 'View' }).locator('label.icon-toggle').click(); - await page.waitForTimeout(1000); - const scrollAfterTheme = await page.evaluate(() => window.pageYOffset); - console.log(` Scroll after theme toggle: ${scrollAfterTheme}px`); - if (scrollAfterTheme === scrollBefore) { - console.log(' ✅ Scroll preserved!'); - } else { - console.log(` ❌ Scroll changed! (${scrollBefore}px → ${scrollAfterTheme}px)`); - } - - // Test Length Toggle (HTMX - should preserve scroll) - console.log('\n📄 TEST 2: Length toggle (HTMX)...'); - const scrollBeforeLength = await page.evaluate(() => window.pageYOffset); - await page.locator('.selector-group').filter({ hasText: 'Length' }).locator('label.icon-toggle').click(); - await page.waitForTimeout(2000); // Wait for HTMX swap - const scrollAfterLength = await page.evaluate(() => window.pageYOffset); - console.log(` Scroll before: ${scrollBeforeLength}px, after: ${scrollAfterLength}px`); - if (scrollAfterLength === scrollBeforeLength) { - console.log(' ✅ Scroll preserved!'); - } else { - console.log(` ❌ Scroll changed! (${scrollBeforeLength}px → ${scrollAfterLength}px)`); - } - - // Test Logo Toggle (HTMX - should preserve scroll) - console.log('\n🖼️ TEST 3: Logo toggle (HTMX)...'); - const scrollBeforeLogo = await page.evaluate(() => window.pageYOffset); - await page.locator('.selector-group').filter({ hasText: 'Icons' }).locator('label.icon-toggle').click(); - await page.waitForTimeout(2000); // Wait for HTMX swap - const scrollAfterLogo = await page.evaluate(() => window.pageYOffset); - console.log(` Scroll before: ${scrollBeforeLogo}px, after: ${scrollAfterLogo}px`); - if (scrollAfterLogo === scrollBeforeLogo) { - console.log(' ✅ Scroll preserved!'); - } else { - console.log(` ❌ Scroll changed! (${scrollBeforeLogo}px → ${scrollAfterLogo}px)`); - } - - // Test Toggle Sync on Mobile - console.log('\n📱 TEST 4: Toggle sync on mobile...'); - await page.setViewportSize({ width: 600, height: 800 }); - await page.waitForTimeout(500); - - console.log(' 🍔 Opening hamburger menu...'); - await page.click('.hamburger-btn'); - await page.waitForTimeout(500); - - // Check if toggles are synced - const desktopTheme = await page.locator('#themeToggle').isChecked(); - const mobileTheme = await page.locator('#themeToggleMenu').isChecked(); - console.log(` Desktop theme: ${desktopTheme}, Mobile theme: ${mobileTheme}`); - - const desktopLength = await page.locator('#lengthToggle').isChecked(); - const mobileLength = await page.locator('#lengthToggleMenu').isChecked(); - console.log(` Desktop length: ${desktopLength}, Mobile length: ${mobileLength}`); - - const desktopLogo = await page.locator('#logoToggle').isChecked(); - const mobileLogo = await page.locator('#logoToggleMenu').isChecked(); - console.log(` Desktop logo: ${desktopLogo}, Mobile logo: ${mobileLogo}`); - - if (desktopTheme === mobileTheme && desktopLength === mobileLength && desktopLogo === mobileLogo) { - console.log(' ✅ All toggles are SYNCED!'); - } else { - console.log(' ❌ Toggles are OUT OF SYNC!'); - } - - // Test mobile logo toggle to verify sync works - console.log('\n📱 TEST 5: Toggle logo from mobile menu...'); - await page.locator('#logoToggleMenu').click(); - await page.waitForTimeout(2000); - - const desktopLogoAfter = await page.locator('#logoToggle').isChecked(); - const mobileLogoAfter = await page.locator('#logoToggleMenu').isChecked(); - console.log(` Desktop logo after: ${desktopLogoAfter}, Mobile logo after: ${mobileLogoAfter}`); - - if (desktopLogoAfter === mobileLogoAfter) { - console.log(' ✅ Logo toggle synced correctly!'); - } else { - console.log(' ❌ Logo toggle NOT synced!'); - } - - console.log('\n✅ All tests complete - Check results above'); - await page.waitForTimeout(1000); - await browser.close(); -})(); diff --git a/tests/test-atomic-lang-switch.js b/tests/test-atomic-lang-switch.js deleted file mode 100644 index 4190039..0000000 --- a/tests/test-atomic-lang-switch.js +++ /dev/null @@ -1,61 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 1000 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('📄 Loading English page...\n'); - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - - console.log('✅ Initial state check:'); - const enActive1 = await page.locator('button[aria-label="English"]').evaluate(el => el.classList.contains('active')); - const esActive1 = await page.locator('button[aria-label="Español"]').evaluate(el => el.classList.contains('active')); - const content1 = await page.locator('.sidebar-accordion-header span').first().textContent(); - console.log(` EN button active: ${enActive1} (expected: true)`); - console.log(` ES button active: ${esActive1} (expected: false)`); - console.log(` Content language: "${content1}" (expected: "Technical Skills")\n`); - - console.log('🌍 Clicking Spanish button...'); - await page.click('button[aria-label="Español"]'); - await page.waitForTimeout(1500); // Wait for 200ms fade + extra time - - console.log('✅ After Spanish click:'); - const enActive2 = await page.locator('button[aria-label="English"]').evaluate(el => el.classList.contains('active')); - const esActive2 = await page.locator('button[aria-label="Español"]').evaluate(el => el.classList.contains('active')); - const content2 = await page.locator('.sidebar-accordion-header span').first().textContent(); - console.log(` EN button active: ${enActive2} (expected: false)`); - console.log(` ES button active: ${esActive2} (expected: true)`); - console.log(` Content language: "${content2}" (expected: "Competencias Técnicas")\n`); - - console.log('🌍 Clicking English button...'); - await page.click('button[aria-label="English"]'); - await page.waitForTimeout(1500); - - console.log('✅ After English click:'); - const enActive3 = await page.locator('button[aria-label="English"]').evaluate(el => el.classList.contains('active')); - const esActive3 = await page.locator('button[aria-label="Español"]').evaluate(el => el.classList.contains('active')); - const content3 = await page.locator('.sidebar-accordion-header span').first().textContent(); - console.log(` EN button active: ${enActive3} (expected: true)`); - console.log(` ES button active: ${esActive3} (expected: false)`); - console.log(` Content language: "${content3}" (expected: "Technical Skills")\n`); - - const buttonsCorrect = enActive1 && !esActive1 && !enActive2 && esActive2 && enActive3 && !esActive3; - const contentCorrect = content1 === 'Technical Skills' && content2 === 'Competencias Técnicas' && content3 === 'Technical Skills'; - - console.log(`\n${buttonsCorrect && contentCorrect ? '✅ ALL TESTS PASSED!' : '❌ SOME TESTS FAILED'}`); - console.log('\n📊 ATOMIC UPDATE IMPLEMENTATION:'); - console.log(' ✅ Single endpoint: /switch-language?lang=XX'); - console.log(' ✅ Out-of-band swaps: 3 components updated atomically'); - console.log(' ✅ Language buttons swap with active state'); - console.log(' ✅ Page 1 content swaps with 200ms fade'); - console.log(' ✅ Page 2 content swaps with 200ms fade'); - console.log(' ✅ URL updates to /?lang=XX'); - console.log(' ✅ Zero custom JavaScript - pure HTMX!'); - - await page.waitForTimeout(2000); - await browser.close(); -})(); diff --git a/tests/test-bottom-buttons.html b/tests/test-bottom-buttons.html deleted file mode 100644 index 19ff575..0000000 --- a/tests/test-bottom-buttons.html +++ /dev/null @@ -1,216 +0,0 @@ - - - - - - Bottom Buttons Test - - - -
-

🧪 Bottom Buttons Test

-

Scroll to the bottom to see the buttons turn green!

-
- Not at bottom -
-
- Current Scroll: 0px
- Distance from Bottom: 0px -
-
- -
-

Top of Page

-

Start scrolling down to test the bottom detection...

-
- -
-

Middle Section

-

Keep scrolling...

-
- -
-

Almost There...

-

Just a bit more...

-
- -
-

🎯 Bottom of Page

-

You should see the buttons turn green now!

-

The info button (ℹ️ bottom-left) and back-to-top button (↑ bottom-right) should both be GREEN when you're at the bottom.

-
- - - - - - - - diff --git a/tests/test-button-sizes.mjs b/tests/test-button-sizes.mjs deleted file mode 100644 index 8592815..0000000 --- a/tests/test-button-sizes.mjs +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env node - -/** - * Test fixed button sizes at different zoom levels - */ - -import { chromium } from 'playwright'; - -async function testButtonSizes() { - console.log('🧪 Testing Fixed Button Sizes at Different Zoom Levels\n'); - - const browser = await chromium.launch({ headless: false }); - const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const page = await context.newPage(); - - try { - await page.goto('http://localhost:1999/?lang=en', { waitUntil: 'networkidle' }); - await page.waitForTimeout(2000); - - const zoomSlider = page.locator('#zoom-slider'); - await zoomSlider.waitFor({ state: 'visible' }); - - // Test at different zoom levels - const zoomLevels = [25, 100, 175]; - const results = {}; - - for (const zoomLevel of zoomLevels) { - console.log(`\n📏 Testing at ${zoomLevel}% zoom...`); - - // Set zoom level - await zoomSlider.fill(zoomLevel.toString()); - await page.waitForTimeout(500); - - // Measure button sizes - const backToTop = await page.locator('#back-to-top').boundingBox(); - const infoButton = await page.locator('#info-button').boundingBox(); - const shortcutsButton = await page.locator('#shortcuts-button').boundingBox(); - - results[zoomLevel] = { - backToTop: backToTop ? Math.round(backToTop.width) : 0, - infoButton: infoButton ? Math.round(infoButton.width) : 0, - shortcutsButton: shortcutsButton ? Math.round(shortcutsButton.width) : 0 - }; - - console.log(` Back-to-top: ${results[zoomLevel].backToTop}px`); - console.log(` Info button: ${results[zoomLevel].infoButton}px`); - console.log(` Shortcuts button: ${results[zoomLevel].shortcutsButton}px`); - } - - // Check if sizes are consistent - console.log('\n' + '='.repeat(60)); - console.log('📊 BUTTON SIZE CONSISTENCY CHECK'); - console.log('='.repeat(60)); - - const baseSize = results[100]; - let allConsistent = true; - - for (const [zoomLevel, sizes] of Object.entries(results)) { - if (zoomLevel === '100') continue; - - const backTopDiff = Math.abs(sizes.backToTop - baseSize.backToTop); - const infoDiff = Math.abs(sizes.infoButton - baseSize.infoButton); - const shortcutsDiff = Math.abs(sizes.shortcutsButton - baseSize.shortcutsButton); - - const maxDiff = Math.max(backTopDiff, infoDiff, shortcutsDiff); - - if (maxDiff <= 2) { - console.log(`✅ ${zoomLevel}% zoom: Buttons stay consistent (max diff: ${maxDiff}px)`); - } else { - console.log(`❌ ${zoomLevel}% zoom: Buttons changed size (max diff: ${maxDiff}px)`); - allConsistent = false; - } - } - - console.log('='.repeat(60)); - - if (allConsistent) { - console.log('✅ SUCCESS: Fixed buttons maintain consistent size at all zoom levels!'); - } else { - console.log('❌ FAIL: Fixed buttons change size with zoom level'); - } - - console.log('\n⏸️ Browser will stay open for 5 seconds for manual verification...'); - await page.waitForTimeout(5000); - - } catch (error) { - console.error('\n❌ Test failed:', error.message); - } finally { - await browser.close(); - console.log('\n✅ Test completed\n'); - } -} - -testButtonSizes(); diff --git a/tests/test-clear-storage.mjs b/tests/test-clear-storage.mjs deleted file mode 100644 index df69a51..0000000 --- a/tests/test-clear-storage.mjs +++ /dev/null @@ -1,18 +0,0 @@ -import { chromium } from '@playwright/test'; - -(async () => { - const browser = await chromium.launch({ headless: true }); - const context = await browser.newContext(); - const page = await context.newPage(); - - await page.goto('http://localhost:1999'); - - // Clear all localStorage - await page.evaluate(() => { - localStorage.clear(); - }); - - console.log('✅ localStorage cleared'); - - await browser.close(); -})(); diff --git a/tests/test-double-toggle.js b/tests/test-double-toggle.js deleted file mode 100644 index 65d0956..0000000 --- a/tests/test-double-toggle.js +++ /dev/null @@ -1,92 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 500 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🧪 Testing Double Toggle Bug Fix (Using Hamburger Menu)\n'); - - // Listen for HTMX errors - this is the critical test - let hasError = false; - let errorMessages = []; - page.on('console', msg => { - const text = msg.text(); - if (msg.type() === 'error' || text.includes('htmx:swapError') || text.includes('insertBefore')) { - console.log(`❌ Browser error: ${text}`); - hasError = true; - errorMessages.push(text); - } - }); - - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - - console.log('📜 Scrolling slightly to reveal hamburger menu...'); - await page.evaluate(() => window.scrollTo(0, 300)); - await page.waitForTimeout(1000); - - // Hover over hamburger to open menu - console.log('🍔 Opening hamburger menu...'); - await page.hover('.hamburger-btn'); - await page.waitForTimeout(1000); - - // Wait for menu toggle to be visible - await page.waitForSelector('#lengthToggleMenu', { state: 'visible', timeout: 5000 }); - - console.log('\n🔄 Testing Double Toggle (This should NOT error):\n'); - - // First toggle - console.log(' 1️⃣ First toggle...'); - await page.click('#lengthToggleMenu'); - await page.waitForTimeout(800); - - // Check state after first toggle - const isCheckedAfter1 = await page.locator('#lengthToggleMenu').isChecked(); - console.log(` ✅ After first toggle: ${isCheckedAfter1 ? 'Long' : 'Short'}`); - - // Second toggle (THIS IS WHERE THE BUG OCCURRED) - console.log(' 2️⃣ Second toggle (CRITICAL - this caused the error before)...'); - await page.click('#lengthToggleMenu'); - await page.waitForTimeout(800); - - // Check state after second toggle - const isCheckedAfter2 = await page.locator('#lengthToggleMenu').isChecked(); - console.log(` ✅ After second toggle: ${isCheckedAfter2 ? 'Long' : 'Short'}`); - - // Third toggle to be thorough - console.log(' 3️⃣ Third toggle (extra verification)...'); - await page.click('#lengthToggleMenu'); - await page.waitForTimeout(800); - - const isCheckedAfter3 = await page.locator('#lengthToggleMenu').isChecked(); - console.log(` ✅ After third toggle: ${isCheckedAfter3 ? 'Long' : 'Short'}`); - - // Fourth toggle - be really sure - console.log(' 4️⃣ Fourth toggle (thorough test)...'); - await page.click('#lengthToggleMenu'); - await page.waitForTimeout(800); - - const isCheckedAfter4 = await page.locator('#lengthToggleMenu').isChecked(); - console.log(` ✅ After fourth toggle: ${isCheckedAfter4 ? 'Long' : 'Short'}`); - - console.log('\n📊 TEST RESULTS:'); - if (hasError) { - console.log('❌ FAILED: HTMX errors detected!'); - console.log('❌ Error messages:'); - errorMessages.forEach(msg => console.log(` - ${msg}`)); - console.log('\n⚠️ The bug is NOT fixed!'); - } else { - console.log('✅ SUCCESS: No htmx:swapError detected!'); - console.log('✅ No insertBefore errors!'); - console.log('✅ Toggle survived 4 consecutive clicks'); - console.log('✅ DOM element preserved (not destroyed/recreated)'); - console.log('✅ Smooth CSS transitions maintained'); - console.log('\n🎉 THE BUG IS FIXED!'); - } - - await page.waitForTimeout(2000); - await browser.close(); -})(); diff --git a/tests/test-final-toggle.js b/tests/test-final-toggle.js deleted file mode 100644 index 215a608..0000000 --- a/tests/test-final-toggle.js +++ /dev/null @@ -1,81 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 400 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🎬 Final Toggle Test - HTMX Out-of-Band Swaps\n'); - - // Monitor network requests - page.on('response', async (response) => { - if (response.url().includes('/toggle/')) { - console.log(` 📡 Server response from: ${response.url()}`); - const text = await response.text(); - const hasOOB = text.includes('hx-swap-oob="true"'); - console.log(` ${hasOOB ? '✅' : '❌'} Out-of-band swap: ${hasOOB}`); - } - }); - - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - await page.evaluate(() => window.scrollTo(0, 0)); - await page.waitForTimeout(500); - - // TEST 1: Desktop toggle - console.log('TEST 1: Click Desktop Toggle\n'); - const desktopLabel = page.locator('#desktop-length-toggle .icon-toggle'); - await desktopLabel.click(); - await page.waitForTimeout(1500); - - let desktopChecked = await page.locator('#lengthToggle').isChecked(); - let mobileChecked = await page.locator('#lengthToggleMenu').isChecked(); - - console.log(`\n Desktop: ${desktopChecked}, Mobile: ${mobileChecked}`); - console.log(` Sync: ${desktopChecked === mobileChecked ? '✅ YES' : '❌ NO'}\n`); - - // TEST 2: Mobile toggle - but DON'T close menu - console.log('TEST 2: Open Menu & Click Mobile Toggle (keep menu open)\n'); - const hamburger = page.locator('.hamburger-btn'); - await hamburger.click(); - await page.waitForTimeout(800); - - console.log(' Menu is now open, clicking mobile toggle...'); - const mobileLabel = page.locator('#mobile-length-toggle .icon-toggle'); - await mobileLabel.click(); - - // Wait for HTMX to complete - await page.waitForTimeout(2000); - - // Check states AFTER the swap completes - desktopChecked = await page.locator('#lengthToggle').isChecked(); - mobileChecked = await page.locator('#lengthToggleMenu').isChecked(); - - console.log(`\n Desktop: ${desktopChecked}, Mobile: ${mobileChecked}`); - console.log(` Sync: ${desktopChecked === mobileChecked ? '✅ YES' : '❌ NO'}\n`); - - // TEST 3: Check if elements exist - const desktopExists = await page.locator('#desktop-length-toggle').count(); - const mobileExists = await page.locator('#mobile-length-toggle').count(); - - console.log('📊 ELEMENT CHECK:'); - console.log(` Desktop toggle exists: ${desktopExists > 0 ? '✅' : '❌'}`); - console.log(` Mobile toggle exists: ${mobileExists > 0 ? '✅' : '❌'}`); - - console.log('\n🔍 DIAGNOSIS:'); - if (desktopChecked === mobileChecked) { - console.log(' ✅ PERFECT! Both toggles are in sync!'); - console.log(' ✅ HTMX out-of-band swaps working correctly!'); - console.log(' ✅ Desktop/mobile sync via server response!'); - } else { - console.log(' ❌ Out of sync - possible issues:'); - console.log(' 1. OOB swap timing problem'); - console.log(' 2. Element not found during swap'); - console.log(' 3. Hyperscript executing before swap'); - } - - await page.waitForTimeout(3000); - await browser.close(); -})(); diff --git a/tests/test-htmx-atomic-updates.sh b/tests/test-htmx-atomic-updates.sh deleted file mode 100755 index 0b77c97..0000000 --- a/tests/test-htmx-atomic-updates.sh +++ /dev/null @@ -1,188 +0,0 @@ -#!/bin/bash -# Test HTMX Atomic Updates Implementation -# Tests URL cleanliness, toggles, and language switching - -set -e - -BASE_URL="http://localhost:1999" -COOKIES="/tmp/test-cookies.txt" -RESULTS="/tmp/test-results.txt" - -echo "🧪 Testing HTMX Atomic Updates Implementation" -echo "==============================================" -echo "" - -# Clean up previous test data -rm -f $COOKIES $RESULTS - -# Test 1: Server Health -echo "✓ Test 1: Server Health Check" -HEALTH=$(curl -s "$BASE_URL/health" | jq -r .status) -if [ "$HEALTH" = "ok" ]; then - echo " ✓ Server is running" -else - echo " ✗ Server health check failed" - exit 1 -fi -echo "" - -# Test 2: Theme Toggle -echo "✓ Test 2: Theme Toggle (Atomic Out-of-Band Swaps)" -THEME_RESPONSE=$(curl -s -X POST -c $COOKIES "$BASE_URL/toggle/theme?lang=en") -OOB_COUNT=$(echo "$THEME_RESPONSE" | grep -c "hx-swap-oob" || true) -if [ "$OOB_COUNT" -eq 1 ]; then - echo " ✓ Theme toggle returns 1 out-of-band swap (mobile toggle)" -else - echo " ✗ Expected 1 OOB swap, got $OOB_COUNT" -fi - -if echo "$THEME_RESPONSE" | grep -q "desktop-theme-toggle"; then - echo " ✓ Desktop toggle present" -else - echo " ✗ Desktop toggle missing" -fi - -if echo "$THEME_RESPONSE" | grep -q "mobile-theme-toggle"; then - echo " ✓ Mobile toggle present" -else - echo " ✗ Mobile toggle missing" -fi - -if grep -q "cv-theme" $COOKIES; then - THEME_VALUE=$(grep "cv-theme" $COOKIES | awk '{print $7}') - echo " ✓ Theme cookie set: $THEME_VALUE" -else - echo " ✗ Theme cookie not set" -fi -echo "" - -# Test 3: Length Toggle -echo "✓ Test 3: Length Toggle (Atomic Out-of-Band Swaps)" -LENGTH_RESPONSE=$(curl -s -X POST -c $COOKIES "$BASE_URL/toggle/length?lang=en") -OOB_COUNT=$(echo "$LENGTH_RESPONSE" | grep -c "hx-swap-oob" || true) -if [ "$OOB_COUNT" -eq 1 ]; then - echo " ✓ Length toggle returns 1 out-of-band swap" -else - echo " ✗ Expected 1 OOB swap, got $OOB_COUNT" -fi - -if echo "$LENGTH_RESPONSE" | grep -q "desktop-length-toggle"; then - echo " ✓ Desktop toggle present" -else - echo " ✗ Desktop toggle missing" -fi -echo "" - -# Test 4: Logo Toggle -echo "✓ Test 4: Logo Toggle (Atomic Out-of-Band Swaps)" -LOGO_RESPONSE=$(curl -s -X POST -c $COOKIES "$BASE_URL/toggle/logos?lang=en") -OOB_COUNT=$(echo "$LOGO_RESPONSE" | grep -c "hx-swap-oob" || true) -if [ "$OOB_COUNT" -eq 1 ]; then - echo " ✓ Logo toggle returns 1 out-of-band swap" -else - echo " ✗ Expected 1 OOB swap, got $OOB_COUNT" -fi -echo "" - -# Test 5: Language Switch -echo "✓ Test 5: Language Switching (Out-of-Band Swaps)" -LANG_RESPONSE=$(curl -s "$BASE_URL/switch-language?lang=es") -OOB_COUNT=$(echo "$LANG_RESPONSE" | grep -c "hx-swap-oob" || true) -if [ "$OOB_COUNT" -eq 2 ]; then - echo " ✓ Language switch returns 2 out-of-band swaps (page 1 + page 2)" -else - echo " ✗ Expected 2 OOB swaps, got $OOB_COUNT" -fi - -if echo "$LANG_RESPONSE" | grep -q "Competencias Técnicas"; then - echo " ✓ Spanish content present" -else - echo " ✗ Spanish content missing" -fi -echo "" - -# Test 6: URL Cleanliness (check homepage doesn't have anchors) -echo "✓ Test 6: URL Cleanliness (No Anchor Pollution)" -HOME_PAGE=$(curl -s "$BASE_URL/?lang=en") - -if echo "$HOME_PAGE" | grep -q 'scrollIntoView'; then - echo " ✓ Hyperscript smooth scroll implemented" -else - echo " ✗ Hyperscript smooth scroll missing" -fi - -if echo "$HOME_PAGE" | grep -q 'id="back-to-top"'; then - echo " ✓ Back-to-top button present" -else - echo " ✗ Back-to-top button missing" -fi - -# Check that back-to-top uses hyperscript, not href="#top" -if echo "$HOME_PAGE" | grep -q 'window.scrollTo({top: 0, behavior'; then - echo " ✓ Back-to-top uses hyperscript (no URL pollution)" -else - echo " ✗ Back-to-top might use anchors" -fi -echo "" - -# Test 7: Cookie Persistence -echo "✓ Test 7: Cookie Persistence Across Requests" -if grep -q "cv-theme" $COOKIES && grep -q "cv-length" $COOKIES && grep -q "cv-logos" $COOKIES; then - echo " ✓ All preference cookies persisted:" - grep "cv-" $COOKIES | awk '{print " - " $6 ": " $7}' -else - echo " ✗ Some cookies missing" -fi -echo "" - -# Test 8: Hyperscript Integration -echo "✓ Test 8: Hyperscript + HTMX Integration" -if echo "$THEME_RESPONSE" | grep -q 'on htmx:afterRequest'; then - echo " ✓ Hyperscript event handlers present" -else - echo " ✗ Hyperscript event handlers missing" -fi - -if echo "$THEME_RESPONSE" | grep -q 'localStorage'; then - echo " ✓ LocalStorage integration present" -else - echo " ✗ LocalStorage integration missing" -fi -echo "" - -# Test 9: Response Size (toggles should be small) -echo "✓ Test 9: Response Payload Size (Should be minimal)" -THEME_SIZE=$(echo "$THEME_RESPONSE" | wc -c) -LENGTH_SIZE=$(echo "$LENGTH_RESPONSE" | wc -c) -LOGO_SIZE=$(echo "$LOGO_RESPONSE" | wc -c) - -echo " - Theme toggle: $THEME_SIZE bytes" -echo " - Length toggle: $LENGTH_SIZE bytes" -echo " - Logo toggle: $LOGO_SIZE bytes" - -if [ "$THEME_SIZE" -lt 5000 ]; then - echo " ✓ Toggle payloads are minimal (<5KB)" -else - echo " ✗ Toggle payloads are too large" -fi -echo "" - -# Summary -echo "==============================================" -echo "✅ All HTMX Atomic Updates Tests Passed!" -echo "" -echo "Summary:" -echo "- ✅ Theme toggle: Atomic OOB swaps working" -echo "- ✅ Length toggle: Atomic OOB swaps working" -echo "- ✅ Logo toggle: Atomic OOB swaps working" -echo "- ✅ Language switch: Atomic OOB swaps working" -echo "- ✅ URL cleanliness: No anchor pollution" -echo "- ✅ Cookie persistence: All cookies saved" -echo "- ✅ Hyperscript integration: Working" -echo "- ✅ Minimal payloads: <5KB per toggle" -echo "" - -# Clean up -rm -f $COOKIES $RESULTS - -echo "🎉 Testing complete!" diff --git a/tests/test-htmx-debug.js b/tests/test-htmx-debug.js deleted file mode 100644 index 1ed853e..0000000 --- a/tests/test-htmx-debug.js +++ /dev/null @@ -1,37 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 800 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - // Capture console logs - page.on('console', msg => console.log('BROWSER:', msg.text())); - - // Capture network requests - page.on('response', response => { - if (response.url().includes('switch-language')) { - console.log(`\n📡 NETWORK: ${response.url()}`); - console.log(` Status: ${response.status()}`); - } - }); - - console.log('📄 Loading page...\n'); - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - - console.log('\n🌍 Clicking Spanish button...\n'); - await page.click('button[aria-label="Español"]'); - await page.waitForTimeout(2000); - - // Check what actually happened - const htmlAfter = await page.content(); - console.log('\n📊 Checking results:'); - console.log(` Spanish button has 'active': ${htmlAfter.includes('selector-btn active')}`); - console.log(` Content has Spanish text: ${htmlAfter.includes('Competencias Técnicas')}`); - - await page.waitForTimeout(2000); - await browser.close(); -})(); diff --git a/tests/test-htmx-indicators.html b/tests/test-htmx-indicators.html deleted file mode 100644 index b8a920b..0000000 --- a/tests/test-htmx-indicators.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - HTMX Indicator Test - - - - - - -

HTMX Loading Indicators Test

- -
-

Test 1: Button with Child Indicator (Default Pattern)

-

Click button - spinner should appear INSIDE button during request

- -
Result will appear here
-
- -
-

Test 2: Language Selector (Actual Component)

-

This mirrors the actual language selector from the CV

-
- - -
-
Result will appear here
-
- -
-

Test 3: CSS Verification

-

Manually verify CSS rules are applied:

-
-
1. Open DevTools
-
2. Click a button above
-
3. Watch Network tab for request
-
4. Check Elements tab - button should have class "htmx-request"
-
5. Check Computed styles - iconify-icon.htmx-indicator should have opacity: 1
-
-
- -
-

Debug: CSS Rules Status

-
-
- - - - diff --git a/tests/test-htmx-timing.js b/tests/test-htmx-timing.js deleted file mode 100644 index df0f820..0000000 --- a/tests/test-htmx-timing.js +++ /dev/null @@ -1,73 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 800 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🔍 HTMX Timing & State Analysis\n'); - - // Intercept and log ALL console messages - page.on('console', msg => { - const text = msg.text(); - if (text.includes('HTMX') || text.includes('swap') || text.includes('Toggle')) { - console.log(` 🔧 ${text}`); - } - }); - - // Add detailed event logging to the page - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - await page.evaluate(() => window.scrollTo(0, 0)); - await page.waitForTimeout(500); - - // Inject logging into the page - await page.evaluate(() => { - const desktop = document.querySelector('#lengthToggle'); - const mobile = document.querySelector('#lengthToggleMenu'); - - // Log all HTMX events - document.body.addEventListener('htmx:beforeRequest', (e) => { - const target = e.detail.elt; - const id = target.id; - const checked = target.checked; - console.log(`HTMX beforeRequest: #${id} checked=${checked}`); - }); - - document.body.addEventListener('htmx:afterSwap', (e) => { - console.log(`HTMX afterSwap completed`); - console.log(` Desktop: #lengthToggle checked=${document.querySelector('#lengthToggle')?.checked}`); - console.log(` Mobile: #lengthToggleMenu checked=${document.querySelector('#lengthToggleMenu')?.checked}`); - }); - - document.body.addEventListener('htmx:oobAfterSwap', (e) => { - console.log(`HTMX oobAfterSwap: ${e.detail.target?.id}`); - }); - }); - - // TEST SEQUENCE - console.log('▶ Step 1: Click Desktop Toggle\n'); - await page.locator('#desktop-length-toggle .icon-toggle').click(); - await page.waitForTimeout(2000); - - let desktopState = await page.evaluate(() => document.querySelector('#lengthToggle').checked); - let mobileState = await page.evaluate(() => document.querySelector('#lengthToggleMenu').checked); - console.log(`\nResult: Desktop=${desktopState}, Mobile=${mobileState}, Sync=${desktopState === mobileState ? '✅' : '❌'}\n`); - - console.log('▶ Step 2: Open Menu\n'); - await page.locator('.hamburger-btn').click(); - await page.waitForTimeout(1000); - - console.log('▶ Step 3: Click Mobile Toggle\n'); - await page.locator('#mobile-length-toggle .icon-toggle').click(); - await page.waitForTimeout(2500); - - desktopState = await page.evaluate(() => document.querySelector('#lengthToggle').checked); - mobileState = await page.evaluate(() => document.querySelector('#lengthToggleMenu').checked); - console.log(`\nResult: Desktop=${desktopState}, Mobile=${mobileState}, Sync=${desktopState === mobileState ? '✅' : '❌'}\n`); - - await page.waitForTimeout(2000); - await browser.close(); -})(); diff --git a/tests/test-inline-loading.html b/tests/test-inline-loading.html deleted file mode 100644 index 7088a49..0000000 --- a/tests/test-inline-loading.html +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - Test Inline Loading - No Blocking Overlay - - - - -
✓ No Blocking Overlay
- -

Inline Loading States Test

- -
-

What to Observe:

-
    -
  • NO full-page overlay appears when switching languages
  • -
  • Language button shows inline spinner during request
  • -
  • CV content fades/blurs slightly during swap (inline effect)
  • -
  • Everything else remains accessible (no blocking)
  • -
  • Smooth transition without page blocking
  • -
-
- -
-

Language Selector (With Inline Indicators)

-
- - -
-
-
- -
-

CV Content (With Inline Loading States)

-
-
-

CV Content Page 1

-

This content will fade and blur slightly during language transitions.

-

Observe: No blocking overlay appears - just a subtle inline effect!

-

You can still scroll and interact with other parts of the page during the transition.

-
-
-
- -
-

Additional Scrollable Content

-

This section demonstrates that the page remains functional during language transitions.

-

Try scrolling, clicking around, or interacting with other elements while switching languages.

-
-

Key Improvement:

-
    -
  • ✓ Before: Full-page overlay blocked everything
  • -
  • ✓ After: Inline loading states, no blocking
  • -
  • ✓ Language buttons show inline spinners
  • -
  • ✓ Content areas show subtle blur/fade
  • -
  • ✓ Rest of UI remains accessible
  • -
-
-
- - - - diff --git a/tests/test-keyboard-shortcuts.md b/tests/test-keyboard-shortcuts.md deleted file mode 100644 index da24a82..0000000 --- a/tests/test-keyboard-shortcuts.md +++ /dev/null @@ -1,225 +0,0 @@ -# Keyboard Shortcuts Feature - Test Results - -## Test Date: 2025-11-15 - -## ✅ Implementation Verification - -### Files Created -1. ✅ `/templates/partials/modals/shortcuts-modal.html` - Modal dialog with all shortcuts -2. ✅ `/templates/partials/widgets/shortcuts-button.html` - Fixed button widget -3. ✅ `/static/hyperscript/functions._hs` - Updated with `initKeyboardShortcuts()` function -4. ✅ `/static/css/main.css` - Added CSS for shortcuts button and modal -5. ✅ `/data/ui-en.json` - English translations -6. ✅ `/data/ui-es.json` - Spanish translations -7. ✅ `/internal/models/cv.go` - Go struct definitions for ShortcutsModal - -### Integration Points -1. ✅ `templates/index.html` - Modal and button templates included -2. ✅ `templates/index.html` - Hyperscript initialization updated to call `initKeyboardShortcuts()` -3. ✅ No backend changes required (frontend-only feature) - -## ✅ Functionality Tests - -### Test 1: Button Visibility -- ✅ Button renders on page (id: `shortcuts-button`) -- ✅ Button positioned bottom-right (CSS: `position: fixed; bottom: 2rem; right: 2rem`) -- ✅ Keyboard icon visible (`mdi:keyboard-outline`) -- ✅ Proper ARIA labels present -- ✅ Opacity 0.2 by default, 1.0 on hover (matches info-button pattern) - -### Test 2: Modal Structure -- ✅ Native `` element used (id: `shortcuts-modal`) -- ✅ Opens on button click via `onclick="document.getElementById('shortcuts-modal').showModal()"` -- ✅ Closes on ESC key (native dialog behavior) -- ✅ Closes on backdrop click (hyperscript handler) -- ✅ Closes on X button click - -### Test 3: Keyboard Shortcut `?` -- ✅ `initKeyboardShortcuts()` function defined in `functions._hs` -- ✅ Listens for `?` key press (Shift + /) -- ✅ Excludes modifier keys (Ctrl, Cmd, Alt) -- ✅ Prevents triggering in input/textarea fields -- ✅ Opens shortcuts modal when pressed - -### Test 4: Shortcuts Content -**Total Shortcuts: 14** - -#### Zoom Control (3 shortcuts) -- ✅ Ctrl/Cmd + Plus: Zoom in (+10%) -- ✅ Ctrl/Cmd + Minus: Zoom out (-10%) -- ✅ Ctrl/Cmd + 0: Reset zoom to 100% - -#### View Controls (3 shortcuts) -- ✅ Tab to Length: Toggle CV length (Short/Long) -- ✅ Tab to Logos: Show/hide company logos -- ✅ Tab to View: Switch theme (Default/Clean) - -#### Navigation (3 shortcuts) -- ✅ Menu → Expand All: Expand all CV sections -- ✅ Menu → Collapse All: Collapse all CV sections -- ✅ Click ↑ Button: Scroll back to top - -#### Actions (3 shortcuts) -- ✅ Ctrl/Cmd + P: Print or save as PDF -- ✅ ESC: Close any open modal -- ✅ ?: Show this shortcuts help - -#### Browser Defaults (2 shortcuts) -- ✅ Tab: Navigate between controls -- ✅ Enter/Space: Activate focused control - -### Test 5: Bilingual Support -**English (lang=en)** -- ✅ Title: "Keyboard Shortcuts" -- ✅ Button aria-label: "Keyboard shortcuts" -- ✅ All section titles in English -- ✅ All descriptions in English - -**Spanish (lang=es)** -- ✅ Title: "Atajos de Teclado" -- ✅ Button aria-label: "Atajos de teclado" -- ✅ All section titles in Spanish (e.g., "Control de Zoom") -- ✅ All descriptions in Spanish (e.g., "Aumentar zoom (+10%)") - -### Test 6: Styling -- ✅ Modal uses existing `info-modal` class (consistency) -- ✅ Button uses `shortcuts-btn` class with matching style to `info-button` -- ✅ Keyboard keys styled as `` elements with professional appearance -- ✅ Sections organized with icons (iconify-icon) -- ✅ Responsive design (mobile adjustments present) -- ✅ Hover effects working (blue highlight on hover) - -### Test 7: Accessibility -- ✅ ARIA labels on button ("Keyboard shortcuts") -- ✅ Native `` element (built-in focus trap) -- ✅ ESC key support (native) -- ✅ Semantic HTML (`` for shortcuts, `

` for sections) -- ✅ Keyboard navigation support (Tab through controls) - -### Test 8: Performance -- ✅ No JavaScript bloat (uses native dialog, hyperscript) -- ✅ CSS loaded inline in main.css (no extra HTTP request) -- ✅ Templates automatically loaded by Go template engine -- ✅ No server-side processing needed (static content) - -## ✅ Server Response Tests - -### HTTP Responses -```bash -# Health check -curl http://localhost:1999/health -# Response: {"status":"ok","timestamp":"...","version":"1.1.0"} ✅ - -# English page -curl http://localhost:1999/?lang=en | grep "shortcuts-button" -# Found: -
- HARD TO SEE
- Requires hover to discover -
- - -
-

✅ NEW: Opacity 0.6

- -
- VISIBLE
- Easy to discover -
-
- -
-

✨ Hover State

- -
- FULL OPACITY
- Background changes to blue -
-
- - -
-

✅ Verification Checklist

-
    -
  • Icon renders correctly - mdi:keyboard-outline displays at 28x28px
  • -
  • Iconify library loaded - Script from code.iconify.design works
  • -
  • Button structure correct - Circular button with flex centering
  • -
  • Improved visibility - Opacity increased from 0.2 to 0.6
  • -
  • Hover effect works - Full opacity (1.0) and blue background on hover
  • -
  • Consistent with info-button - Both buttons use same opacity pattern
  • -
  • Accessibility maintained - aria-label and title attributes present
  • -
-
- -
-

✅ ISSUE RESOLVED

-

- The shortcuts button now has visible keyboard icon with improved discoverability. - Default opacity increased from 0.2 to 0.6 while maintaining smooth hover transitions. -

-
- - - - - diff --git a/tests/test-skeleton-fix.html b/tests/test-skeleton-fix.html deleted file mode 100644 index 135a57d..0000000 --- a/tests/test-skeleton-fix.html +++ /dev/null @@ -1,287 +0,0 @@ - - - - Skeleton Loader Fix - Manual Test - - - - - -

🔬 Skeleton Loader Fix - Manual Verification

- -
-

📋 Test Instructions:

-
    -
  1. Click the "Switch to Spanish" button below
  2. -
  3. Watch for the dark overlay to appear briefly
  4. -
  5. The overlay should disappear after the content loads
  6. -
  7. Check the console log below for event tracking
  8. -
  9. Try switching back and forth multiple times
  10. -
-

✅ PASS: Overlay appears and disappears smoothly

-

❌ FAIL: Overlay stays visible permanently

-
- -
-

Language Selector (HTMX + Hyperscript):

- - -
-
🔄 LOADING... (This should disappear!)
-
- - -
- - -
- - -
-
-
- -
-

Status Monitor:

-
- Skeleton Loader: Hidden (opacity: 0) -
-
- Last Event: None -
-
- -
-

Console Log:

-
-
- - - - diff --git a/tests/test-smooth-toggles.js b/tests/test-smooth-toggles.js deleted file mode 100644 index 2e0ac85..0000000 --- a/tests/test-smooth-toggles.js +++ /dev/null @@ -1,62 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 800 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🎬 Testing Smooth Toggle Animations\n'); - - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - - // Scroll down to make header visible - console.log('📜 Scrolling to reveal header...'); - await page.evaluate(() => window.scrollTo(0, 500)); - await page.waitForTimeout(1500); - - console.log('\n🔄 Testing Length Toggle (Desktop)'); - console.log(' Watch for SMOOTH slide animation...\n'); - - await page.click('#lengthToggle'); - await page.waitForTimeout(2000); - - console.log('✅ Animation completed!'); - console.log(' Did you see a smooth 300ms slide? (analogical)'); - console.log(' Or did it snap instantly? (digital)\n'); - - console.log('🔄 Clicking again (toggling back)...\n'); - await page.click('#lengthToggle'); - await page.waitForTimeout(2000); - - console.log('🍔 Opening hamburger menu to test mobile toggle...'); - await page.hover('.hamburger-btn'); - await page.waitForTimeout(1000); - - console.log('\n🔄 Testing Length Toggle (Mobile)'); - console.log(' This should also be smooth...\n'); - - const menuToggle = await page.locator('#lengthToggleMenu').isVisible(); - if (menuToggle) { - await page.click('#lengthToggleMenu'); - await page.waitForTimeout(2000); - - console.log('✅ Mobile toggle animation completed!'); - console.log(' Both toggles should be in sync now.\n'); - } else { - console.log('⚠️ Mobile toggle not visible\n'); - } - - console.log('📊 SMOOTH ANIMATION TEST SUMMARY:'); - console.log(' ✅ Toggles use CSS transitions (0.3s ease)'); - console.log(' ✅ No DOM replacement (hx-swap="none")'); - console.log(' ✅ Element stays in DOM during animation'); - console.log(' ✅ Desktop/mobile sync via hyperscript'); - console.log('\n🎯 Expected: Smooth "analogical" slide'); - console.log('🎯 You should see the slider move smoothly over 300ms'); - - await page.waitForTimeout(3000); - await browser.close(); -})(); diff --git a/tests/test-theme-toggle.js b/tests/test-theme-toggle.js deleted file mode 100644 index dea3d82..0000000 --- a/tests/test-theme-toggle.js +++ /dev/null @@ -1,57 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 400 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } // Desktop - controls visible > 900px - }); - const page = await context.newPage(); - - // Listen for console errors - page.on('console', msg => { - const text = msg.text(); - const type = msg.type(); - if (type === 'error' || text.toLowerCase().includes('error')) { - console.log('❌ CONSOLE ERROR:', text); - } - }); - page.on('pageerror', error => console.log('❌ PAGE EXCEPTION:', error.message)); - - console.log('📄 Loading page (desktop 1920px - controls visible)...'); - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - - console.log('\n🖱️ TEST 1: Click desktop theme toggle...'); - await page.locator('.selector-group').filter({ hasText: 'View' }).locator('label.icon-toggle').click(); - await page.waitForTimeout(2000); - console.log('✅ Desktop toggle complete - No errors!'); - - console.log('\n🖱️ TEST 2: Click desktop theme toggle again (toggle back)...'); - await page.locator('.selector-group').filter({ hasText: 'View' }).locator('label.icon-toggle').click(); - await page.waitForTimeout(2000); - console.log('✅ Desktop toggle back complete - No errors!'); - - console.log('\n📱 TEST 3: Resize to mobile and test menu sync...'); - await page.setViewportSize({ width: 600, height: 800 }); - await page.waitForTimeout(500); - - console.log('🍔 Opening hamburger menu...'); - await page.click('.hamburger-btn'); - await page.waitForTimeout(500); - - console.log('🔍 Checking mobile toggle state...'); - const mobileChecked = await page.locator('#themeToggleMenu').isChecked(); - const expectedState = false; // Should be default (not clean) - console.log(` Mobile toggle checked: ${mobileChecked} (expected: ${expectedState})`); - - if (mobileChecked === expectedState) { - console.log(' ✅ Toggles are SYNCED!'); - } else { - console.log(' ❌ Toggles are OUT OF SYNC!'); - } - - console.log('\n✅ All tests complete - Check for errors above'); - await page.waitForTimeout(1000); - await browser.close(); -})(); diff --git a/tests/test-toggle-complete.js b/tests/test-toggle-complete.js deleted file mode 100644 index a238113..0000000 --- a/tests/test-toggle-complete.js +++ /dev/null @@ -1,119 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 400 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('✨ COMPLETE TOGGLE SYSTEM VALIDATION ✨\n'); - console.log('═══════════════════════════════════════════════════\n'); - - let errors = []; - page.on('console', msg => { - if (msg.type() === 'error' && msg.text().includes('hyperscript')) { - errors.push(msg.text()); - } - }); - - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - await page.evaluate(() => window.scrollTo(0, 0)); - await page.waitForTimeout(500); - - // TEST 1: Desktop toggles - console.log('TEST 1: Desktop Toggles\n'); - - console.log(' Length toggle...'); - await page.locator('#desktop-length-toggle .icon-toggle').click(); - await page.waitForTimeout(1000); - let dLen = await page.locator('#lengthToggle').isChecked(); - let mLen = await page.locator('#lengthToggleMenu').isChecked(); - console.log(` Sync: ${dLen === mLen ? '✅ PASS' : '❌ FAIL'} (Desktop=${dLen}, Mobile=${mLen})`); - - console.log(' Logo toggle...'); - await page.locator('#desktop-logo-toggle .icon-toggle').click(); - await page.waitForTimeout(1000); - let dLogo = await page.locator('#logoToggle').isChecked(); - let mLogo = await page.locator('#logoToggleMenu').isChecked(); - console.log(` Sync: ${dLogo === mLogo ? '✅ PASS' : '❌ FAIL'} (Desktop=${dLogo}, Mobile=${mLogo})`); - - console.log(' Theme toggle...'); - await page.locator('#desktop-theme-toggle .icon-toggle').click(); - await page.waitForTimeout(1000); - let dTheme = await page.locator('#themeToggle').isChecked(); - let mTheme = await page.locator('#themeToggleMenu').isChecked(); - console.log(` Sync: ${dTheme === mTheme ? '✅ PASS' : '❌ FAIL'} (Desktop=${dTheme}, Mobile=${mTheme})\n`); - - // TEST 2: Mobile toggles - console.log('TEST 2: Mobile Menu Toggles\n'); - - console.log(' Opening menu...'); - await page.locator('.hamburger-btn').click(); - await page.waitForTimeout(800); - - console.log(' Length toggle (mobile)...'); - await page.locator('#mobile-length-toggle .icon-toggle').click(); - await page.waitForTimeout(1000); - dLen = await page.locator('#lengthToggle').isChecked(); - mLen = await page.locator('#lengthToggleMenu').isChecked(); - console.log(` Sync: ${dLen === mLen ? '✅ PASS' : '❌ FAIL'} (Desktop=${dLen}, Mobile=${mLen})`); - - console.log(' Logo toggle (mobile)...'); - await page.locator('#mobile-logo-toggle .icon-toggle').click(); - await page.waitForTimeout(1000); - dLogo = await page.locator('#logoToggle').isChecked(); - mLogo = await page.locator('#logoToggleMenu').isChecked(); - console.log(` Sync: ${dLogo === mLogo ? '✅ PASS' : '❌ FAIL'} (Desktop=${dLogo}, Mobile=${mLogo})`); - - console.log(' Theme toggle (mobile)...'); - await page.locator('#mobile-theme-toggle .icon-toggle').click(); - await page.waitForTimeout(1000); - dTheme = await page.locator('#themeToggle').isChecked(); - mTheme = await page.locator('#themeToggleMenu').isChecked(); - console.log(` Sync: ${dTheme === mTheme ? '✅ PASS' : '❌ FAIL'} (Desktop=${dTheme}, Mobile=${mTheme})\n`); - - // TEST 3: Animations - console.log('TEST 3: Smooth Animations\n'); - console.log(' Clicking toggle and observing animation...'); - await page.locator('#desktop-length-toggle .icon-toggle').click(); - await page.waitForTimeout(1500); - console.log(' ✅ Visual check: Did the toggle slide smoothly? (300ms CSS transition)\n'); - - // TEST 4: Persistence - console.log('TEST 4: LocalStorage Persistence\n'); - const storage = await page.evaluate(() => { - return { - length: localStorage.getItem('cv-length'), - icons: localStorage.getItem('cv-icons'), - theme: localStorage.getItem('cv-theme') - }; - }); - console.log(` Length: ${storage.length || 'not set'}`); - console.log(` Logos: ${storage.icons || 'not set'}`); - console.log(` Theme: ${storage.theme || 'not set'}\n`); - - // Final summary - console.log('═══════════════════════════════════════════════════'); - console.log('📊 FINAL VALIDATION SUMMARY:\n'); - - if (errors.length === 0) { - console.log('✅ No hyperscript syntax errors'); - } else { - console.log(`❌ Found ${errors.length} hyperscript errors`); - } - - console.log('✅ Desktop/mobile sync working perfectly'); - console.log('✅ All 3 toggle types functional (length, icons, theme)'); - console.log('✅ Smooth CSS animations (300ms transitions)'); - console.log('✅ State persistence via localStorage'); - console.log('✅ HTMX integration with hx-swap="none"'); - console.log('✅ Server-side cookie storage\n'); - - console.log('🎉 TOGGLE SYSTEM FULLY OPERATIONAL! 🎉'); - console.log('═══════════════════════════════════════════════════\n'); - - await page.waitForTimeout(2000); - await browser.close(); -})(); diff --git a/tests/test-toggle-debug.js b/tests/test-toggle-debug.js deleted file mode 100644 index cdaa0dd..0000000 --- a/tests/test-toggle-debug.js +++ /dev/null @@ -1,77 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 300 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🔍 Debugging Toggle Sync Issue\n'); - - // Capture ALL console messages - page.on('console', msg => { - const type = msg.type(); - const text = msg.text(); - if (type === 'log') { - console.log(' 📝', text); - } else if (type === 'error') { - console.log(' ❌', text); - } - }); - - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - await page.evaluate(() => window.scrollTo(0, 0)); - await page.waitForTimeout(500); - - // Add debug logging to the page - await page.evaluate(() => { - console.log('🔧 Setting up debug listeners...'); - - const desktopToggle = document.querySelector('#lengthToggle'); - const mobileToggle = document.querySelector('#lengthToggleMenu'); - - desktopToggle.addEventListener('change', (e) => { - console.log(`Desktop changed: ${e.target.checked}`); - }, true); - - mobileToggle.addEventListener('change', (e) => { - console.log(`Mobile changed: ${e.target.checked}`); - }, true); - }); - - console.log('\n📱 Step 1: Click Desktop Toggle'); - const desktopLabel = page.locator('#desktop-length-toggle .icon-toggle'); - await desktopLabel.click(); - await page.waitForTimeout(1500); - - let desktopState = await page.locator('#lengthToggle').isChecked(); - let mobileState = await page.locator('#lengthToggleMenu').isChecked(); - console.log(`Result: Desktop=${desktopState}, Mobile=${mobileState}`); - - console.log('\n🍔 Step 2: Open Menu'); - const hamburger = page.locator('.hamburger-btn'); - await hamburger.click(); - await page.waitForTimeout(500); - - console.log('\n📱 Step 3: Click Mobile Toggle'); - const mobileLabel = page.locator('#mobile-length-toggle .icon-toggle'); - await mobileLabel.click(); - await page.waitForTimeout(1500); - - desktopState = await page.locator('#lengthToggle').isChecked(); - mobileState = await page.locator('#lengthToggleMenu').isChecked(); - console.log(`Result: Desktop=${desktopState}, Mobile=${mobileState}`); - - // Final check - console.log('\n📊 Final State Check:'); - const finalDesktop = await page.evaluate(() => document.querySelector('#lengthToggle').checked); - const finalMobile = await page.evaluate(() => document.querySelector('#lengthToggleMenu').checked); - console.log(` Desktop (via evaluate): ${finalDesktop}`); - console.log(` Mobile (via evaluate): ${finalMobile}`); - console.log(` Match: ${finalDesktop === finalMobile ? '✅' : '❌'}`); - - await page.waitForTimeout(3000); - await browser.close(); -})(); diff --git a/tests/test-toggle-fix.js b/tests/test-toggle-fix.js deleted file mode 100644 index da69f02..0000000 --- a/tests/test-toggle-fix.js +++ /dev/null @@ -1,96 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 500 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🎬 Testing Toggle Fix - Syntax Errors & Animations\n'); - - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - - // Ensure header is visible (scroll to top) - console.log('📜 Scrolling to top to ensure header is visible...'); - await page.evaluate(() => window.scrollTo(0, 0)); - await page.waitForTimeout(1000); - - // Check for hyperscript errors in console - const errors = []; - page.on('console', msg => { - if (msg.type() === 'error') { - errors.push(msg.text()); - console.log('❌ Console Error:', msg.text()); - } - }); - - console.log('\n✅ Testing Desktop Length Toggle'); - console.log(' Expected: Smooth 300ms slide animation\n'); - - // Click the toggle - const lengthToggle = page.locator('#lengthToggle'); - await lengthToggle.waitFor({ state: 'visible' }); - - console.log(' Clicking toggle...'); - await lengthToggle.click(); - await page.waitForTimeout(1500); - - // Check if both toggles are synced - const desktopChecked = await page.locator('#lengthToggle').isChecked(); - const mobileChecked = await page.locator('#lengthToggleMenu').isChecked(); - - console.log(` Desktop toggle checked: ${desktopChecked}`); - console.log(` Mobile toggle checked: ${mobileChecked}`); - console.log(` Sync status: ${desktopChecked === mobileChecked ? '✅ SYNCED' : '❌ OUT OF SYNC'}`); - - console.log('\n✅ Testing Logo Toggle'); - const logoToggle = page.locator('#logoToggle'); - await logoToggle.click(); - await page.waitForTimeout(1500); - - console.log('\n✅ Testing Theme Toggle'); - const themeToggle = page.locator('#themeToggle'); - await themeToggle.click(); - await page.waitForTimeout(1500); - - console.log('\n🍔 Testing Mobile Menu Toggles'); - console.log(' Opening hamburger menu...'); - const hamburger = page.locator('.hamburger-btn'); - await hamburger.click(); - await page.waitForTimeout(1000); - - console.log(' Testing mobile length toggle...'); - const mobileToggle = page.locator('#lengthToggleMenu'); - await mobileToggle.click(); - await page.waitForTimeout(1500); - - // Final sync check - const finalDesktopChecked = await page.locator('#lengthToggle').isChecked(); - const finalMobileChecked = await page.locator('#lengthToggleMenu').isChecked(); - - console.log(`\n📊 Final Sync Status:`); - console.log(` Desktop: ${finalDesktopChecked}`); - console.log(` Mobile: ${finalMobileChecked}`); - console.log(` Synced: ${finalDesktopChecked === finalMobileChecked ? '✅' : '❌'}`); - - // Check for hyperscript errors - console.log(`\n🔍 Hyperscript Errors:`); - if (errors.length === 0) { - console.log(' ✅ No hyperscript syntax errors!'); - } else { - console.log(` ❌ Found ${errors.length} errors`); - errors.forEach(err => console.log(` - ${err}`)); - } - - console.log('\n✅ TEST COMPLETE'); - console.log(' Key fixes:'); - console.log(' 1. Fixed hyperscript syntax (removed selector)'); - console.log(' 2. Using direct ID references (#lengthToggle, #lengthToggleMenu)'); - console.log(' 3. Maintained hx-swap="none" for smooth animations'); - console.log(' 4. Desktop/mobile sync working via hyperscript'); - - await page.waitForTimeout(3000); - await browser.close(); -})(); diff --git a/tests/test-toggle-visual.js b/tests/test-toggle-visual.js deleted file mode 100644 index 370b553..0000000 --- a/tests/test-toggle-visual.js +++ /dev/null @@ -1,64 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 1500 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🧪 Testing Toggle Visual State\n'); - - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - - // Take screenshot of toggles - console.log('📸 Taking screenshot of toggle controls...'); - - // Scroll to top to see header controls - await page.evaluate(() => window.scrollTo(0, 0)); - await page.waitForTimeout(500); - - // Check if toggles are visible - const lengthToggleVisible = await page.locator('#lengthToggle').isVisible().catch(() => false); - const themeToggleVisible = await page.locator('#themeToggle').isVisible().catch(() => false); - const logoToggleVisible = await page.locator('#logoToggle').isVisible().catch(() => false); - - console.log(`\n📊 Toggle Visibility:`); - console.log(` Length toggle: ${lengthToggleVisible ? '✅ Visible' : '❌ Hidden'}`); - console.log(` Theme toggle: ${themeToggleVisible ? '✅ Visible' : '❌ Hidden'}`); - console.log(` Logo toggle: ${logoToggleVisible ? '✅ Visible' : '❌ Hidden'}`); - - if (!lengthToggleVisible) { - console.log(`\n⚠️ Desktop toggles are hidden (might be in hamburger menu)`); - - // Try to find hamburger menu - const hamburgerExists = await page.locator('.hamburger-btn').count(); - console.log(` Hamburger button exists: ${hamburgerExists > 0 ? '✅ Yes' : '❌ No'}`); - - if (hamburgerExists > 0) { - console.log(`\n🍔 Opening hamburger menu...`); - await page.hover('.hamburger-btn'); - await page.waitForTimeout(1000); - - const menuLengthToggle = await page.locator('#lengthToggleMenu').isVisible().catch(() => false); - console.log(` Mobile length toggle visible: ${menuLengthToggle ? '✅ Yes' : '❌ No'}`); - - if (menuLengthToggle) { - console.log(`\n🔄 Testing mobile toggle...`); - const isChecked = await page.locator('#lengthToggleMenu').isChecked(); - console.log(` Current state: ${isChecked ? 'Long' : 'Short'}`); - - await page.click('#lengthToggleMenu'); - await page.waitForTimeout(1500); - - const isCheckedAfter = await page.locator('#lengthToggleMenu').isChecked(); - console.log(` After click: ${isCheckedAfter ? 'Long' : 'Short'}`); - console.log(` Toggle changed: ${isChecked !== isCheckedAfter ? '✅ Yes' : '❌ No'}`); - } - } - } - - await page.waitForTimeout(3000); - await browser.close(); -})(); diff --git a/tests/test-toggle-working.js b/tests/test-toggle-working.js deleted file mode 100644 index c38cfd2..0000000 --- a/tests/test-toggle-working.js +++ /dev/null @@ -1,110 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 500 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🎬 Testing Toggle Fix - Complete Validation\n'); - - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - - // Ensure header is visible - console.log('📜 Scrolling to top...'); - await page.evaluate(() => window.scrollTo(0, 0)); - await page.waitForTimeout(500); - - // Track console errors - const errors = []; - page.on('console', msg => { - if (msg.type() === 'error') { - const text = msg.text(); - if (text.includes('hyperscript') || text.includes('Expected')) { - errors.push(text); - console.log('❌ Hyperscript Error:', text); - } - } - }); - - console.log('\n✅ TEST 1: Desktop Length Toggle (click on toggle label)'); - - // Click on the label element (visible toggle UI) - const lengthLabel = page.locator('#desktop-length-toggle .icon-toggle'); - await lengthLabel.waitFor({ state: 'visible' }); - - console.log(' Clicking toggle...'); - await lengthLabel.click(); - await page.waitForTimeout(1000); - - // Check both toggles' state - const desktopChecked = await page.locator('#lengthToggle').isChecked(); - const mobileChecked = await page.locator('#lengthToggleMenu').isChecked(); - - console.log(` Desktop toggle: ${desktopChecked}`); - console.log(` Mobile toggle: ${mobileChecked}`); - console.log(` Sync: ${desktopChecked === mobileChecked ? '✅ SYNCED' : '❌ OUT OF SYNC'}`); - - console.log('\n✅ TEST 2: Logo Toggle'); - const logoLabel = page.locator('#desktop-logo-toggle .icon-toggle'); - await logoLabel.click(); - await page.waitForTimeout(1000); - - const logoDesktop = await page.locator('#logoToggle').isChecked(); - const logoMobile = await page.locator('#logoToggleMenu').isChecked(); - console.log(` Sync: ${logoDesktop === logoMobile ? '✅ SYNCED' : '❌ OUT OF SYNC'}`); - - console.log('\n✅ TEST 3: Theme Toggle'); - const themeLabel = page.locator('#desktop-theme-toggle .icon-toggle'); - await themeLabel.click(); - await page.waitForTimeout(1000); - - const themeDesktop = await page.locator('#themeToggle').isChecked(); - const themeMobile = await page.locator('#themeToggleMenu').isChecked(); - console.log(` Sync: ${themeDesktop === themeMobile ? '✅ SYNCED' : '❌ OUT OF SYNC'}`); - - console.log('\n✅ TEST 4: Mobile Menu Toggles'); - console.log(' Opening hamburger menu...'); - const hamburger = page.locator('.hamburger-btn'); - await hamburger.click(); - await page.waitForTimeout(500); - - console.log(' Clicking mobile length toggle...'); - const mobileLengthLabel = page.locator('#mobile-length-toggle .icon-toggle'); - await mobileLengthLabel.click(); - await page.waitForTimeout(1000); - - const finalDesktop = await page.locator('#lengthToggle').isChecked(); - const finalMobile = await page.locator('#lengthToggleMenu').isChecked(); - - console.log(` Desktop: ${finalDesktop}`); - console.log(` Mobile: ${finalMobile}`); - console.log(` Sync: ${finalDesktop === finalMobile ? '✅ SYNCED' : '❌ OUT OF SYNC'}`); - - // Final results - console.log('\n📊 FINAL RESULTS:'); - console.log('═══════════════════════════════════════'); - - if (errors.length === 0) { - console.log('✅ No hyperscript syntax errors detected!'); - } else { - console.log(`❌ Found ${errors.length} hyperscript errors:`); - errors.forEach(err => console.log(` ${err}`)); - } - - console.log('\n🎯 KEY FIXES IMPLEMENTED:'); - console.log(' 1. ✅ Fixed hyperscript syntax errors'); - console.log(' - Removed broken selector syntax'); - console.log(' - Using direct ID references: #lengthToggle, #lengthToggleMenu'); - console.log(' 2. ✅ Maintained smooth animations'); - console.log(' - hx-swap="none" keeps elements in DOM'); - console.log(' - CSS transitions work perfectly (300ms)'); - console.log(' 3. ✅ Desktop/mobile sync working'); - console.log(' - Both toggles update simultaneously'); - console.log(' - State stored in localStorage'); - - await page.waitForTimeout(2000); - await browser.close(); -})(); diff --git a/tests/test-url-cleanliness.js b/tests/test-url-cleanliness.js deleted file mode 100644 index 673714f..0000000 --- a/tests/test-url-cleanliness.js +++ /dev/null @@ -1,75 +0,0 @@ -const { chromium } = require('playwright'); - -(async () => { - const browser = await chromium.launch({ headless: false, slowMo: 1000 }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🧪 Testing URL Cleanliness and Language Switching\n'); - - console.log('📄 Step 1: Load English page'); - await page.goto('http://localhost:1999/?lang=en'); - await page.waitForLoadState('networkidle'); - let url = page.url(); - console.log(` URL: ${url}`); - console.log(` Clean (no anchors): ${!url.includes('#') ? '✅' : '❌'}\n`); - - console.log('🌍 Step 2: Switch to Spanish'); - await page.click('button[aria-label="Español"]'); - await page.waitForTimeout(1000); - url = page.url(); - const contentES = await page.locator('.sidebar-accordion-header span').first().textContent(); - console.log(` URL: ${url}`); - console.log(` Content: "${contentES}"`); - console.log(` Success: ${contentES.includes('Competencias') && url.includes('lang=es') ? '✅' : '❌'}`); - console.log(` Clean (no anchors): ${!url.includes('#') ? '✅' : '❌'}\n`); - - console.log('📜 Step 3: Scroll down page'); - await page.evaluate(() => window.scrollTo(0, 800)); - await page.waitForTimeout(1000); - url = page.url(); - console.log(` URL after scroll: ${url}`); - console.log(` Still clean: ${!url.includes('#') ? '✅' : '❌'}\n`); - - console.log('⬆️ Step 4: Click back-to-top button'); - await page.waitForSelector('.back-to-top', { state: 'visible' }); - await page.click('.back-to-top'); - await page.waitForTimeout(1500); - - url = page.url(); - const scrollPos = await page.evaluate(() => window.pageYOffset); - - console.log(` URL after back-to-top: ${url}`); - console.log(` No #top anchor: ${!url.includes('#top') ? '✅' : '❌'}`); - console.log(` No # at all: ${!url.includes('#') ? '✅' : '❌'}`); - console.log(` Scrolled to top (< 50px): ${scrollPos < 50 ? '✅' : '❌'}`); - console.log(` Current scroll position: ${scrollPos}px\n`); - - console.log('🌍 Step 5: Switch back to English'); - await page.click('button[aria-label="English"]'); - await page.waitForTimeout(1000); - url = page.url(); - const contentEN = await page.locator('.sidebar-accordion-header span').first().textContent(); - console.log(` URL: ${url}`); - console.log(` Content: "${contentEN}"`); - console.log(` Success: ${contentEN.includes('Technical') && url.includes('lang=en') ? '✅' : '❌'}`); - console.log(` Still clean: ${!url.includes('#') ? '✅' : '❌'}\n`); - - const allClean = !page.url().includes('#'); - const bothLangsWork = contentES.includes('Competencias') && contentEN.includes('Technical'); - const scrollWorked = scrollPos < 50; - - console.log(`\n${allClean && bothLangsWork && scrollWorked ? '✅ ALL URL CLEANLINESS TESTS PASSED!' : '❌ SOME TESTS FAILED'}`); - console.log('\n📊 KEY ACHIEVEMENTS:'); - console.log(` ${allClean ? '✅' : '❌'} URLs stay clean - no anchor pollution`); - console.log(` ${bothLangsWork ? '✅' : '❌'} Language switching works atomically`); - console.log(` ${scrollWorked ? '✅' : '❌'} Back-to-top scrolls without URL changes`); - console.log(' ✅ Smooth scrolling via hyperscript'); - console.log(' ✅ Out-of-band swaps for atomic updates'); - console.log(' ✅ Clean URL: Only ?lang=XX parameter'); - - await page.waitForTimeout(2000); - await browser.close(); -})(); diff --git a/tests/test-verification.mjs b/tests/test-verification.mjs deleted file mode 100755 index 6adfafd..0000000 --- a/tests/test-verification.mjs +++ /dev/null @@ -1,578 +0,0 @@ -#!/usr/bin/env node -/** - * COMPREHENSIVE VERIFICATION TEST SUITE - * Tests both HTMX indicators and shortcuts button visibility fixes - */ - -import { chromium } from 'playwright'; - -const BASE_URL = 'http://localhost:1999'; -const RESULTS = { - passed: [], - failed: [], - warnings: [] -}; - -function log(status, message) { - const timestamp = new Date().toLocaleTimeString(); - const icons = { pass: '✅', fail: '❌', warn: '⚠️', info: 'ℹ️' }; - console.log(`[${timestamp}] ${icons[status] || icons.info} ${message}`); - - if (status === 'pass') RESULTS.passed.push(message); - if (status === 'fail') RESULTS.failed.push(message); - if (status === 'warn') RESULTS.warnings.push(message); -} - -function measureTime(start) { - return `${Date.now() - start}ms`; -} - -async function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - -async function test1_HTMXLoadingIndicators(page) { - log('info', '═══════════════════════════════════════════════════════'); - log('info', 'TEST 1: HTMX Loading Indicators (Feature 003)'); - log('info', '═══════════════════════════════════════════════════════'); - - try { - // Navigate to page - await page.goto(BASE_URL); - await page.waitForLoadState('networkidle'); - log('pass', 'Page loaded successfully'); - - // Test 1.1: Verify indicator elements exist - log('info', 'Test 1.1: Checking indicator elements exist...'); - const enIndicator = page.locator('#lang-indicator-en'); - const esIndicator = page.locator('#lang-indicator-es'); - - await enIndicator.waitFor({ state: 'attached', timeout: 5000 }); - await esIndicator.waitFor({ state: 'attached', timeout: 5000 }); - log('pass', 'Both language indicators found in DOM'); - - // Test 1.2: Verify initial opacity is 0 (hidden) - log('info', 'Test 1.2: Checking initial indicator opacity...'); - const enInitialOpacity = await enIndicator.evaluate(el => - window.getComputedStyle(el).opacity - ); - const esInitialOpacity = await esIndicator.evaluate(el => - window.getComputedStyle(el).opacity - ); - - if (enInitialOpacity === '0' && esInitialOpacity === '0') { - log('pass', `Indicators hidden initially (opacity: ${enInitialOpacity})`); - } else { - log('fail', `Indicators should be hidden (EN: ${enInitialOpacity}, ES: ${esInitialOpacity})`); - } - - // Test 1.3: Click EN button and verify indicator appears - log('info', 'Test 1.3: Testing EN button loading indicator...'); - - // Get the ES button (since we're on EN by default) - const esButton = page.locator('button.selector-btn[data-short="ES"]'); - await esButton.waitFor({ state: 'visible' }); - - // Set up monitoring for opacity changes - const opacityPromise = page.evaluate(() => { - return new Promise(resolve => { - const indicator = document.querySelector('#lang-indicator-es'); - let maxOpacity = 0; - let opacityChanges = []; - - const observer = new MutationObserver(() => { - const currentOpacity = parseFloat(window.getComputedStyle(indicator).opacity); - opacityChanges.push(currentOpacity); - maxOpacity = Math.max(maxOpacity, currentOpacity); - }); - - observer.observe(indicator.parentElement, { - attributes: true, - attributeFilter: ['class'], - subtree: true - }); - - // Check opacity every 10ms for 2 seconds - let checks = 0; - const interval = setInterval(() => { - const currentOpacity = parseFloat(window.getComputedStyle(indicator).opacity); - opacityChanges.push(currentOpacity); - maxOpacity = Math.max(maxOpacity, currentOpacity); - checks++; - - if (checks > 200) { // 2 seconds - clearInterval(interval); - observer.disconnect(); - resolve({ maxOpacity, opacityChanges: opacityChanges.filter(o => o > 0) }); - } - }, 10); - }); - }); - - // Click the button - const clickTime = Date.now(); - await esButton.click(); - - // Wait for the opacity monitoring to complete - const opacityData = await opacityPromise; - const responseTime = measureTime(clickTime); - - log('info', `Request completed in ${responseTime}`); - log('info', `Max indicator opacity: ${opacityData.maxOpacity}`); - log('info', `Opacity changes detected: ${opacityData.opacityChanges.length}`); - - if (opacityData.maxOpacity >= 0.9) { - log('pass', `Indicator became visible (max opacity: ${opacityData.maxOpacity})`); - } else if (opacityData.maxOpacity > 0) { - log('warn', `Indicator partially visible but not fully (max: ${opacityData.maxOpacity})`); - } else { - log('warn', 'Indicator not visible on fast request (expected on localhost - will verify with throttled test)'); - } - - // Test 1.4: Verify indicator faded out after request - await sleep(500); - const finalOpacity = await esIndicator.evaluate(el => - window.getComputedStyle(el).opacity - ); - - if (finalOpacity === '0') { - log('pass', `Indicator hidden after request (opacity: ${finalOpacity})`); - } else { - log('warn', `Indicator may not have faded out (opacity: ${finalOpacity})`); - } - - // Test 1.5: Take screenshot during loading - log('info', 'Test 1.5: Capturing screenshot during loading...'); - - // Click back to EN to trigger another loading state - await sleep(500); - const enButton = page.locator('button.selector-btn[data-short="EN"]'); - - // Start click and immediately capture - const screenshotPromise = page.screenshot({ - path: '/Users/txeo/Git/yo/cv/test-screenshots/htmx-indicator-loading.png', - fullPage: false - }); - - await enButton.click(); - await screenshotPromise; - - log('pass', 'Screenshot captured: test-screenshots/htmx-indicator-loading.png'); - - // Test 1.6: Network throttling test - log('info', 'Test 1.6: Testing with slow 3G network...'); - - // Slow 3G preset - only delay the specific endpoint - let requestIntercepted = false; - await page.route('**/switch-language**', async route => { - if (!requestIntercepted) { - requestIntercepted = true; - await sleep(800); // Simulate 800ms delay - } - await route.continue(); - }); - - await sleep(500); - const slowClickTime = Date.now(); - - // Click and monitor - const slowOpacityPromise = page.evaluate(() => { - return new Promise(resolve => { - const indicator = document.querySelector('#lang-indicator-en'); - let maxOpacity = 0; - const interval = setInterval(() => { - const opacity = parseFloat(window.getComputedStyle(indicator).opacity); - maxOpacity = Math.max(maxOpacity, opacity); - }, 10); - - setTimeout(() => { - clearInterval(interval); - resolve(maxOpacity); - }, 1000); - }); - }); - - await enButton.click(); - const slowOpacity = await slowOpacityPromise; - - await page.waitForLoadState('networkidle'); - const slowResponseTime = measureTime(slowClickTime); - - log('info', `Slow request completed in ${slowResponseTime}`); - log('info', `Mid-request opacity: ${slowOpacity}`); - - if (slowOpacity >= 0.9) { - log('pass', `Indicator visible during slow request (opacity: ${slowOpacity})`); - } else { - log('fail', `Indicator not visible during slow request (opacity: ${slowOpacity})`); - } - - // Unroute to restore normal speed - await page.unroute('**/switch-language**'); - - } catch (error) { - log('fail', `Test 1 error: ${error.message}`); - console.error(error); - } -} - -async function test2_ShortcutsButtonVisibility(page) { - log('info', '═══════════════════════════════════════════════════════'); - log('info', 'TEST 2: Shortcuts Button Visibility (Feature 001)'); - log('info', '═══════════════════════════════════════════════════════'); - - try { - // Ensure we're on the page - await page.goto(BASE_URL); - await page.waitForLoadState('networkidle'); - - // Test 2.1: Verify button exists and is visible - log('info', 'Test 2.1: Checking shortcuts button exists...'); - const shortcutsBtn = page.locator('.shortcuts-btn'); - - await shortcutsBtn.waitFor({ state: 'visible', timeout: 5000 }); - log('pass', 'Shortcuts button found and visible'); - - // Test 2.2: Measure initial opacity - log('info', 'Test 2.2: Measuring button opacity...'); - const opacity = await shortcutsBtn.evaluate(el => - window.getComputedStyle(el).opacity - ); - - const opacityNum = parseFloat(opacity); - log('info', `Button opacity: ${opacity}`); - - if (opacityNum === 0.6) { - log('pass', `Button opacity is exactly 0.6 as expected`); - } else if (opacityNum >= 0.5 && opacityNum <= 0.7) { - log('warn', `Button opacity close to target (${opacity} vs 0.6)`); - } else { - log('fail', `Button opacity incorrect (${opacity}, expected 0.6)`); - } - - // Test 2.3: Verify button is actually visible to users - log('info', 'Test 2.3: Verifying visual discoverability...'); - const boundingBox = await shortcutsBtn.boundingBox(); - - if (boundingBox) { - log('pass', `Button has dimensions: ${boundingBox.width}x${boundingBox.height}px`); - log('info', `Position: (${boundingBox.x}, ${boundingBox.y})`); - } else { - log('fail', 'Button has no bounding box (may not be rendered)'); - } - - // Test 2.4: Test hover state - log('info', 'Test 2.4: Testing hover state...'); - await shortcutsBtn.hover(); - await sleep(500); // Wait for transition - - const hoverOpacity = await shortcutsBtn.evaluate(el => - window.getComputedStyle(el).opacity - ); - - if (parseFloat(hoverOpacity) === 1.0) { - log('pass', `Hover opacity is 1.0 (full visibility)`); - } else { - log('warn', `Hover opacity: ${hoverOpacity} (expected 1.0)`); - } - - // Test 2.5: Take screenshot - log('info', 'Test 2.5: Capturing button screenshot...'); - await page.screenshot({ - path: '/Users/txeo/Git/yo/cv/test-screenshots/shortcuts-button-visible.png', - fullPage: false - }); - log('pass', 'Screenshot captured: test-screenshots/shortcuts-button-visible.png'); - - // Test 2.6: Verify functionality - log('info', 'Test 2.6: Testing button functionality...'); - await shortcutsBtn.click(); - await sleep(300); - - // Check if modal opened - const modal = page.locator('.shortcuts-modal, [id*="shortcut"], [class*="modal"]'); - const modalVisible = await modal.isVisible().catch(() => false); - - if (modalVisible) { - log('pass', 'Shortcuts modal opened successfully'); - - // Test ESC to close - await page.keyboard.press('Escape'); - await sleep(300); - - const modalClosed = await modal.isVisible().catch(() => false); - if (!modalClosed) { - log('pass', 'Modal closes with ESC key'); - } else { - log('warn', 'Modal may not close with ESC'); - } - } else { - log('fail', 'Modal did not open on button click'); - } - - // Test 2.7: Check info button consistency - log('info', 'Test 2.7: Verifying info button has same opacity...'); - const infoBtn = page.locator('.info-button'); - const infoBtnExists = await infoBtn.count(); - - if (infoBtnExists > 0) { - const infoOpacity = await infoBtn.evaluate(el => - window.getComputedStyle(el).opacity - ); - - if (parseFloat(infoOpacity) === 0.6) { - log('pass', `Info button also has opacity 0.6 (consistency maintained)`); - } else { - log('warn', `Info button opacity: ${infoOpacity} (expected 0.6)`); - } - } else { - log('info', 'Info button not found (may not be on this page)'); - } - - } catch (error) { - log('fail', `Test 2 error: ${error.message}`); - console.error(error); - } -} - -async function test3_RegressionTests(page) { - log('info', '═══════════════════════════════════════════════════════'); - log('info', 'TEST 3: Regression Testing (Ensure Nothing Broke)'); - log('info', '═══════════════════════════════════════════════════════'); - - try { - await page.goto(BASE_URL); - await page.waitForLoadState('networkidle'); - - // Test 3.1: Skeleton loader still works - log('info', 'Test 3.1: Verifying skeleton loader animation...'); - - const skeletonExists = await page.locator('#skeleton-loader').count(); - if (skeletonExists > 0) { - log('pass', 'Skeleton loader element found'); - - // Trigger language switch - const esButton = page.locator('button.selector-btn[data-short="ES"]'); - - const skeletonActivated = await page.evaluate(() => { - return new Promise(resolve => { - const skeleton = document.querySelector('#skeleton-loader'); - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.target.classList.contains('active')) { - observer.disconnect(); - resolve(true); - } - } - }); - - observer.observe(skeleton, { attributes: true, attributeFilter: ['class'] }); - - setTimeout(() => { - observer.disconnect(); - resolve(false); - }, 2000); - }); - }); - - await esButton.click(); - await page.waitForLoadState('networkidle'); - - if (skeletonActivated) { - log('pass', 'Skeleton loader activated during language switch'); - } else { - log('warn', 'Skeleton loader may not be activating'); - } - } else { - log('info', 'Skeleton loader not found (may not be used)'); - } - - // Test 3.2: No console errors - log('info', 'Test 3.2: Checking for console errors...'); - const errors = []; - page.on('console', msg => { - if (msg.type() === 'error') { - errors.push(msg.text()); - } - }); - - await page.reload(); - await sleep(1000); - - if (errors.length === 0) { - log('pass', 'No console errors detected'); - } else { - log('fail', `Console errors found: ${errors.join(', ')}`); - } - - // Test 3.3: No layout shifts - log('info', 'Test 3.3: Measuring Cumulative Layout Shift...'); - - const cls = await page.evaluate(() => { - return new Promise(resolve => { - let clsValue = 0; - - try { - const observer = new PerformanceObserver((list) => { - for (const entry of list.getEntries()) { - if (!entry.hadRecentInput) { - clsValue += entry.value; - } - } - }); - - observer.observe({ type: 'layout-shift', buffered: true }); - - setTimeout(() => { - observer.disconnect(); - resolve(clsValue); - }, 2000); - } catch (e) { - resolve(0); - } - }); - }); - - log('info', `CLS Score: ${cls.toFixed(3)}`); - - if (cls < 0.1) { - log('pass', 'Excellent CLS score (< 0.1)'); - } else if (cls < 0.25) { - log('warn', `CLS needs improvement (${cls.toFixed(3)})`); - } else { - log('fail', `Poor CLS score (${cls.toFixed(3)})`); - } - - // Test 3.4: Page load performance - log('info', 'Test 3.4: Measuring page load performance...'); - - const perfMetrics = await page.evaluate(() => { - const perf = performance.getEntriesByType('navigation')[0]; - return { - loadTime: perf.loadEventEnd - perf.fetchStart, - domContentLoaded: perf.domContentLoadedEventEnd - perf.fetchStart, - firstPaint: performance.getEntriesByType('paint')[0]?.startTime || 0 - }; - }); - - log('info', `Load time: ${perfMetrics.loadTime.toFixed(0)}ms`); - log('info', `DOMContentLoaded: ${perfMetrics.domContentLoaded.toFixed(0)}ms`); - log('info', `First Paint: ${perfMetrics.firstPaint.toFixed(0)}ms`); - - if (perfMetrics.loadTime < 3000) { - log('pass', 'Page loads in under 3 seconds'); - } else { - log('warn', `Page load time: ${perfMetrics.loadTime.toFixed(0)}ms`); - } - - } catch (error) { - log('fail', `Test 3 error: ${error.message}`); - console.error(error); - } -} - -async function generateReport() { - log('info', '═══════════════════════════════════════════════════════'); - log('info', 'FINAL TEST REPORT'); - log('info', '═══════════════════════════════════════════════════════'); - - console.log('\n📊 SUMMARY:'); - console.log(` ✅ Passed: ${RESULTS.passed.length}`); - console.log(` ❌ Failed: ${RESULTS.failed.length}`); - console.log(` ⚠️ Warnings: ${RESULTS.warnings.length}`); - - if (RESULTS.failed.length > 0) { - console.log('\n❌ FAILURES:'); - RESULTS.failed.forEach((msg, i) => console.log(` ${i + 1}. ${msg}`)); - } - - if (RESULTS.warnings.length > 0) { - console.log('\n⚠️ WARNINGS:'); - RESULTS.warnings.forEach((msg, i) => console.log(` ${i + 1}. ${msg}`)); - } - - console.log('\n📈 FEATURE GRADES:'); - - // Feature 003: HTMX Indicators - const indicatorTests = RESULTS.passed.filter(m => - m.includes('indicator') || m.includes('Indicator') - ).length; - const indicatorFails = RESULTS.failed.filter(m => - m.includes('indicator') || m.includes('Indicator') - ).length; - - let feature003Grade = 'F'; - if (indicatorFails === 0 && indicatorTests >= 5) feature003Grade = 'A'; - else if (indicatorFails === 0 && indicatorTests >= 3) feature003Grade = 'B'; - else if (indicatorFails <= 1) feature003Grade = 'C'; - else if (indicatorFails <= 2) feature003Grade = 'D'; - - console.log(` Feature 003 (HTMX Indicators): ${feature003Grade} (${indicatorTests} tests passed, ${indicatorFails} failed)`); - - // Feature 001: Shortcuts Button - const buttonTests = RESULTS.passed.filter(m => - m.includes('Button') || m.includes('button') || m.includes('opacity') - ).length; - const buttonFails = RESULTS.failed.filter(m => - m.includes('Button') || m.includes('button') || m.includes('opacity') - ).length; - - let feature001Grade = 'A-'; - if (buttonFails === 0 && buttonTests >= 6) feature001Grade = 'A'; - else if (buttonFails === 0 && buttonTests >= 4) feature001Grade = 'A-'; - else if (buttonFails <= 1) feature001Grade = 'B+'; - else if (buttonFails <= 2) feature001Grade = 'B'; - - console.log(` Feature 001 (Shortcuts Button): ${feature001Grade} (${buttonTests} tests passed, ${buttonFails} failed)`); - - console.log('\n📸 SCREENSHOTS:'); - console.log(' - test-screenshots/htmx-indicator-loading.png'); - console.log(' - test-screenshots/shortcuts-button-visible.png'); - - const overallSuccess = RESULTS.failed.length === 0; - console.log(`\n${overallSuccess ? '✅ ALL TESTS PASSED' : '❌ SOME TESTS FAILED'}\n`); - - return overallSuccess; -} - -async function main() { - console.log('🧪 COMPREHENSIVE VERIFICATION TEST SUITE'); - console.log('Testing HTMX Indicators + Shortcuts Button Fixes\n'); - - // Create screenshots directory - const { mkdir } = await import('fs/promises'); - await mkdir('/Users/txeo/Git/yo/cv/test-screenshots', { recursive: true }); - - const browser = await chromium.launch({ - headless: true, - args: ['--disable-dev-shm-usage'] - }); - - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 }, - userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' - }); - - const page = await context.newPage(); - - try { - // Run all test suites - await test1_HTMXLoadingIndicators(page); - await test2_ShortcutsButtonVisibility(page); - await test3_RegressionTests(page); - - // Generate report - const success = await generateReport(); - - await browser.close(); - - process.exit(success ? 0 : 1); - - } catch (error) { - console.error('Fatal error:', error); - await browser.close(); - process.exit(1); - } -} - -main(); diff --git a/tests/test-visual-states.mjs b/tests/test-visual-states.mjs deleted file mode 100644 index 6fb4cf4..0000000 --- a/tests/test-visual-states.mjs +++ /dev/null @@ -1,122 +0,0 @@ -import { chromium } from '@playwright/test'; -import { fileURLToPath } from 'url'; -import { dirname, join } from 'path'; -import fs from 'fs'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const screenshotsDir = join(__dirname, 'test-screenshots'); - -// Ensure screenshots directory exists -if (!fs.existsSync(screenshotsDir)) { - fs.mkdirSync(screenshotsDir); -} - -(async () => { - const browser = await chromium.launch({ headless: false }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🎨 Testing Zoom Toggle Button Visual States...\n'); - - await page.goto('http://localhost:1999'); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - - // Clear localStorage for fresh start - await page.evaluate(() => localStorage.clear()); - await page.reload(); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(500); - - const toggleBtn = page.locator('#zoom-toggle-button'); - - // State 1: Inactive (default) - console.log('📸 State 1: Inactive (default)'); - const inactiveStyles = await toggleBtn.evaluate(el => { - const styles = window.getComputedStyle(el); - return { - background: styles.backgroundColor, - color: styles.color, - opacity: styles.opacity - }; - }); - console.log(' Background:', inactiveStyles.background); - console.log(' Icon color:', inactiveStyles.color); - console.log(' Opacity:', inactiveStyles.opacity); - await page.screenshot({ path: join(screenshotsDir, '1-inactive.png') }); - - // State 2: Inactive hover - console.log('\n📸 State 2: Inactive hover'); - await toggleBtn.hover(); - await page.waitForTimeout(300); - const inactiveHoverStyles = await toggleBtn.evaluate(el => { - const styles = window.getComputedStyle(el); - return { - background: styles.backgroundColor, - color: styles.color, - opacity: styles.opacity, - transform: styles.transform - }; - }); - console.log(' Background:', inactiveHoverStyles.background); - console.log(' Icon color:', inactiveHoverStyles.color); - console.log(' Opacity:', inactiveHoverStyles.opacity); - console.log(' Transform:', inactiveHoverStyles.transform); - await page.screenshot({ path: join(screenshotsDir, '2-inactive-hover.png') }); - - // Move mouse away - await page.mouse.move(500, 500); - await page.waitForTimeout(300); - - // State 3: Active (zoom opened) - console.log('\n📸 State 3: Active (zoom opened)'); - await toggleBtn.click(); - await page.waitForTimeout(500); - const activeStyles = await toggleBtn.evaluate(el => { - const styles = window.getComputedStyle(el); - return { - background: styles.backgroundColor, - color: styles.color, - opacity: styles.opacity - }; - }); - console.log(' Background:', activeStyles.background); - console.log(' Icon color:', activeStyles.color); - console.log(' Opacity:', activeStyles.opacity); - await page.screenshot({ path: join(screenshotsDir, '3-active.png') }); - - // State 4: Active hover - console.log('\n📸 State 4: Active hover'); - await toggleBtn.hover(); - await page.waitForTimeout(300); - const activeHoverStyles = await toggleBtn.evaluate(el => { - const styles = window.getComputedStyle(el); - return { - background: styles.backgroundColor, - color: styles.color, - opacity: styles.opacity, - transform: styles.transform, - boxShadow: styles.boxShadow - }; - }); - console.log(' Background:', activeHoverStyles.background); - console.log(' Icon color:', activeHoverStyles.color); - console.log(' Opacity:', activeHoverStyles.opacity); - console.log(' Transform:', activeHoverStyles.transform); - console.log(' Box shadow:', activeHoverStyles.boxShadow); - await page.screenshot({ path: join(screenshotsDir, '4-active-hover.png') }); - - console.log('\n✅ Visual state tests complete!'); - console.log(`📁 Screenshots saved to: ${screenshotsDir}`); - console.log('\n🎯 Summary:'); - console.log(' - Inactive: Gray icon (#888), 60% opacity'); - console.log(' - Inactive hover: Lighter gray bg, brighter icon (#aaa), 80% opacity'); - console.log(' - Active: Blue bg, white icon, 100% opacity'); - console.log(' - Active hover: Darker blue, blue glow, slight scale'); - - await page.waitForTimeout(5000); - await browser.close(); -})(); diff --git a/tests/test-zoom-debug.html b/tests/test-zoom-debug.html deleted file mode 100644 index 7e383ea..0000000 --- a/tests/test-zoom-debug.html +++ /dev/null @@ -1,401 +0,0 @@ - - - - - - Zoom Control Debug Test - - - - - - - - - - -
-

Zoom Control Debug Test

-

Testing the zoom control component to identify issues with:

-
    -
  1. X button not working - Click the X button (top-right corner of zoom control)
  2. -
  3. Drag not working - Try to drag the zoom control by clicking and holding
  4. -
- -

Instructions:

-
    -
  • The zoom control should appear at the bottom center of the screen
  • -
  • Click the X button to hide it - a "Show Zoom" button should appear
  • -
  • Try dragging the zoom control around the screen
  • -
  • Check the console log (right side) for debugging info
  • -
- -
-

Expected Behavior:

-
    -
  • ✅ X button should hide the zoom control and show "Show Zoom" button
  • -
  • ✅ Dragging should move the zoom control around the screen
  • -
  • ✅ Position should be saved to localStorage
  • -
  • ✅ Dragging should NOT trigger when clicking slider or buttons
  • -
-
-
- - -
-
Debug console initialized
-
- - - - - -
- - - - - - - - - - -
- - - - diff --git a/tests/test-zoom-final.mjs b/tests/test-zoom-final.mjs deleted file mode 100644 index 989e8b4..0000000 --- a/tests/test-zoom-final.mjs +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env node - -/** - * Final Zoom Control Test - X Button and Drag - */ - -import { chromium } from 'playwright'; - -async function testZoomControl() { - console.log('🧪 Final Zoom Control Test - X Button & Drag\n'); - - const browser = await chromium.launch({ headless: false }); - const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const page = await context.newPage(); - - try { - // Navigate - console.log('📄 Loading http://localhost:1999/?lang=en'); - await page.goto('http://localhost:1999/?lang=en', { waitUntil: 'networkidle' }); - await page.waitForTimeout(2000); - - const zoomControl = page.locator('#zoom-control'); - await zoomControl.waitFor({ state: 'visible', timeout: 5000 }); - console.log('✅ Zoom control loaded\n'); - - // TEST 1: X Button - console.log('🧪 TEST 1: X Button Click'); - const closeButton = page.locator('#zoom-close'); - await closeButton.click({ force: true }); - await page.waitForTimeout(500); - - const isHidden = await zoomControl.evaluate(el => - window.getComputedStyle(el).display === 'none' - ); - - if (isHidden) { - console.log('✅ SUCCESS: X button works! Zoom control is hidden\n'); - } else { - console.log('❌ FAIL: X button did not hide zoom control\n'); - throw new Error('X button test failed'); - } - - // Show it again for drag test - await page.evaluate(() => { - document.getElementById('zoom-control').style.display = ''; - }); - await page.waitForTimeout(500); - - // TEST 2: Drag Functionality - console.log('🧪 TEST 2: Drag Functionality'); - - const initialBox = await zoomControl.boundingBox(); - console.log(` Initial position: x=${Math.round(initialBox.x)}, y=${Math.round(initialBox.y)}`); - - // Click on grey background area (not on buttons) - const dragX = initialBox.x + 60; // Left side, away from buttons - const dragY = initialBox.y + initialBox.height / 2; - - await page.mouse.move(dragX, dragY); - await page.mouse.down(); - - // Drag to new location - const targetX = dragX + 300; - const targetY = dragY - 150; - console.log(` Dragging to: x=${Math.round(targetX)}, y=${Math.round(targetY)}`); - - await page.mouse.move(targetX, targetY, { steps: 20 }); - await page.mouse.up(); - await page.waitForTimeout(500); - - const finalBox = await zoomControl.boundingBox(); - console.log(` Final position: x=${Math.round(finalBox.x)}, y=${Math.round(finalBox.y)}`); - - const deltaX = Math.abs(finalBox.x - initialBox.x); - const deltaY = Math.abs(finalBox.y - initialBox.y); - - if (deltaX > 100 || deltaY > 50) { - console.log(`✅ SUCCESS: Drag works! Moved ${Math.round(deltaX)}px horizontally, ${Math.round(deltaY)}px vertically\n`); - } else { - console.log(`❌ FAIL: Drag did not work properly. Only moved ${Math.round(deltaX)}px, ${Math.round(deltaY)}px\n`); - throw new Error('Drag test failed'); - } - - // Summary - console.log('='.repeat(60)); - console.log('✅ ALL TESTS PASSED!'); - console.log('='.repeat(60)); - console.log('✅ X button hides zoom control'); - console.log('✅ Drag functionality works correctly'); - console.log('='.repeat(60)); - - console.log('\n🎉 Zoom control is fully functional!'); - console.log('\n⏸️ Browser will stay open for 5 seconds...'); - await page.waitForTimeout(5000); - - } catch (error) { - console.error('\n❌ Test failed:', error.message); - } finally { - await browser.close(); - console.log('\n✅ Test completed\n'); - } -} - -testZoomControl(); diff --git a/tests/test-zoom-fixed.html b/tests/test-zoom-fixed.html deleted file mode 100644 index fbda28b..0000000 --- a/tests/test-zoom-fixed.html +++ /dev/null @@ -1,389 +0,0 @@ - - - - - - Zoom Control - FIXED VERSION - - - - - - - - - - -
-

🔧 Zoom Control - FIXED VERSION

- -
-

✅ Fixes Applied:

-
    -
  1. X Button Fix: Added pointer-events: none to the iconify-icon element to prevent it from capturing clicks
  2. -
  3. X Button Fix: Added halt the event to prevent event propagation
  4. -
  5. Drag Fix: Improved the mousedown handler to properly check for interactive elements using both classList.contains() and closest()
  6. -
-
- -
-

🧪 Test Steps:

-
    -
  1. Test X Button: -
      -
    • Click the X button (top-right corner of zoom control)
    • -
    • ✅ Expected: Zoom control should disappear and "Show Zoom Control" button should appear at bottom
    • -
    -
  2. -
  3. Test Show Button: -
      -
    • Click the "Show Zoom Control" button
    • -
    • ✅ Expected: Zoom control should reappear and button should disappear
    • -
    -
  4. -
  5. Test Dragging: -
      -
    • Click and hold on the grey background of the zoom control (NOT on slider/buttons)
    • -
    • Move your mouse while holding
    • -
    • ✅ Expected: Zoom control should move with your mouse
    • -
    • Release the mouse
    • -
    • ✅ Expected: Position should be saved (reload page to verify)
    • -
    -
  6. -
  7. Test Slider: -
      -
    • Click and drag the slider
    • -
    • ✅ Expected: Slider should move, NOT trigger drag mode
    • -
    -
  8. -
-
- -

Key Changes Made:

-
-1. Close Button (zoom-control.html:76-81)
-   - Added: halt the event
-   - Added: style="pointer-events: none;" to iconify-icon
-
-2. Drag Handler (zoom-control.html:26-42)
-   - Changed from: event.target.closest('.zoom-slider, .zoom-close-btn, .zoom-reset-btn')
-   - Changed to: Multiple specific checks:
-     * if target.classList.contains('zoom-slider') exit end
-     * if target.classList.contains('zoom-close-btn') exit end
-     * if target.classList.contains('zoom-reset-btn') exit end
-     * if target.closest('.zoom-close-btn') exit end
-     * if target.closest('.zoom-reset-btn') exit end
-
- -
-

📝 Testing Checklist:

-

Mark off as you test:

-
    -
  • ☐ X button hides zoom control
  • -
  • ☐ Show button reveals zoom control
  • -
  • ☐ Dragging works (click on grey background)
  • -
  • ☐ Slider doesn't trigger drag mode
  • -
  • ☐ Reset button doesn't trigger drag mode
  • -
  • ☐ Position persists after page reload
  • -
-
-
- - - - - -
- - - - - - - - - - - -
- - - - diff --git a/tests/test-zoom-functionality.mjs b/tests/test-zoom-functionality.mjs deleted file mode 100755 index 4a100ef..0000000 --- a/tests/test-zoom-functionality.mjs +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env node - -/** - * Zoom Control Functionality Test - * Tests the X button and drag functionality - */ - -import { chromium } from 'playwright'; - -async function testZoomControl() { - console.log('🧪 Starting Zoom Control Test...\n'); - - const browser = await chromium.launch({ headless: false }); - const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const page = await context.newPage(); - - try { - // Navigate to the CV page - console.log('📄 Navigating to http://localhost:1999/?lang=en'); - await page.goto('http://localhost:1999/?lang=en', { waitUntil: 'networkidle' }); - await page.waitForTimeout(2000); - - // Wait for zoom control to be visible - console.log('⏳ Waiting for zoom control to load...'); - const zoomControl = page.locator('#zoom-control'); - await zoomControl.waitFor({ state: 'visible', timeout: 5000 }); - console.log('✅ Zoom control is visible\n'); - - // Test 1: X Button Click - console.log('🧪 TEST 1: X Button Click'); - console.log(' Locating X button...'); - const closeButton = page.locator('#zoom-close'); - await closeButton.waitFor({ state: 'visible' }); - - const closeButtonBox = await closeButton.boundingBox(); - console.log(` X button position: x=${closeButtonBox.x}, y=${closeButtonBox.y}`); - - console.log(' Clicking X button...'); - await closeButton.click({ force: true }); - await page.waitForTimeout(500); - - // Check if zoom control is hidden - const isHidden = await zoomControl.evaluate(el => { - const style = window.getComputedStyle(el); - return style.display === 'none'; - }); - - if (isHidden) { - console.log('✅ X button works! Zoom control is hidden'); - } else { - console.log('❌ X button failed! Zoom control is still visible'); - } - - // Check if show button appeared - const showButton = page.locator('#show-zoom-menu-btn'); - const showButtonVisible = await showButton.evaluate(el => { - const style = window.getComputedStyle(el); - return style.display !== 'none'; - }); - - if (showButtonVisible) { - console.log('✅ Show zoom button is visible'); - } else { - console.log('❌ Show zoom button did not appear'); - } - - // Test 2: Show Button Click - console.log('\n🧪 TEST 2: Show Button Click'); - console.log(' Clicking show zoom button...'); - await showButton.click(); - await page.waitForTimeout(500); - - const isVisibleAgain = await zoomControl.evaluate(el => { - const style = window.getComputedStyle(el); - return style.display !== 'none'; - }); - - if (isVisibleAgain) { - console.log('✅ Show button works! Zoom control is visible again'); - } else { - console.log('❌ Show button failed! Zoom control is still hidden'); - } - - // Test 3: Drag Functionality - console.log('\n🧪 TEST 3: Drag Functionality'); - - // Get initial position - const initialPosition = await zoomControl.boundingBox(); - console.log(` Initial position: x=${Math.round(initialPosition.x)}, y=${Math.round(initialPosition.y)}`); - - // Calculate drag target (click on the grey background, not on buttons) - const dragStartX = initialPosition.x + 100; // Middle of control - const dragStartY = initialPosition.y + initialPosition.height / 2; - - console.log(' Starting drag operation...'); - await page.mouse.move(dragStartX, dragStartY); - await page.mouse.down(); - - // Drag to new position - const newX = dragStartX + 200; - const newY = dragStartY - 100; - console.log(` Dragging to: x=${Math.round(newX)}, y=${Math.round(newY)}`); - - await page.mouse.move(newX, newY, { steps: 10 }); - await page.mouse.up(); - await page.waitForTimeout(500); - - // Get final position - const finalPosition = await zoomControl.boundingBox(); - console.log(` Final position: x=${Math.round(finalPosition.x)}, y=${Math.round(finalPosition.y)}`); - - const movedX = Math.abs(finalPosition.x - initialPosition.x); - const movedY = Math.abs(finalPosition.y - initialPosition.y); - - if (movedX > 50 || movedY > 50) { - console.log(`✅ Drag works! Moved ${Math.round(movedX)}px horizontally, ${Math.round(movedY)}px vertically`); - } else { - console.log(`❌ Drag failed! Only moved ${Math.round(movedX)}px horizontally, ${Math.round(movedY)}px vertically`); - } - - // Test 4: Slider Doesn't Trigger Drag - console.log('\n🧪 TEST 4: Slider Click Doesn\'t Trigger Drag'); - const slider = page.locator('#zoom-slider'); - const sliderBox = await slider.boundingBox(); - - console.log(' Clicking on slider...'); - await slider.click({ force: true }); - await page.waitForTimeout(500); - - const positionAfterSlider = await zoomControl.boundingBox(); - const sliderMovedX = Math.abs(positionAfterSlider.x - finalPosition.x); - const sliderMovedY = Math.abs(positionAfterSlider.y - finalPosition.y); - - if (sliderMovedX < 5 && sliderMovedY < 5) { - console.log('✅ Slider click doesn\'t trigger drag mode'); - } else { - console.log(`❌ Slider click triggered drag! Moved ${Math.round(sliderMovedX)}px, ${Math.round(sliderMovedY)}px`); - } - - // Summary - console.log('\n' + '='.repeat(60)); - console.log('📊 TEST SUMMARY'); - console.log('='.repeat(60)); - console.log(`${isHidden ? '✅' : '❌'} X button hides zoom control`); - console.log(`${showButtonVisible ? '✅' : '❌'} Show button appears when hidden`); - console.log(`${isVisibleAgain ? '✅' : '❌'} Show button reveals zoom control`); - console.log(`${movedX > 50 || movedY > 50 ? '✅' : '❌'} Drag functionality works`); - console.log(`${sliderMovedX < 5 && sliderMovedY < 5 ? '✅' : '❌'} Slider doesn't trigger drag`); - console.log('='.repeat(60)); - - console.log('\n⏸️ Browser will stay open for 10 seconds for manual inspection...'); - await page.waitForTimeout(10000); - - } catch (error) { - console.error('❌ Test failed with error:', error.message); - console.error(error.stack); - } finally { - await browser.close(); - console.log('\n✅ Test completed'); - } -} - -testZoomControl(); diff --git a/tests/test-zoom-persistence.html b/tests/test-zoom-persistence.html deleted file mode 100644 index 6eabb51..0000000 --- a/tests/test-zoom-persistence.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - Test Zoom Persistence - - -

Testing localStorage for zoom visibility

- - - - - - -
- - - - diff --git a/tests/test-zoom-toggle.mjs b/tests/test-zoom-toggle.mjs deleted file mode 100644 index 4901c67..0000000 --- a/tests/test-zoom-toggle.mjs +++ /dev/null @@ -1,171 +0,0 @@ -import { chromium } from '@playwright/test'; - -(async () => { - const browser = await chromium.launch({ headless: false }); - const context = await browser.newContext({ - viewport: { width: 1920, height: 1080 } - }); - const page = await context.newPage(); - - console.log('🧪 Testing Zoom Toggle Button Implementation...\n'); - - // Navigate to the page - await page.goto('http://localhost:1999'); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(1000); - - console.log('✅ Page loaded'); - - // Test 1: Check for blinking - zoom control should be hidden from start - console.log('\n📋 Test 1: No blinking on load (zoom should start hidden)'); - - const zoomControl = page.locator('#zoom-control'); - const isHidden = await zoomControl.evaluate(el => el.classList.contains('zoom-hidden')); - - if (isHidden) { - console.log('✅ PASS: Zoom control starts hidden (no blinking)'); - } else { - console.log('❌ FAIL: Zoom control is visible on load (will blink)'); - } - - // Test 2: Verify toggle button exists and is positioned correctly - console.log('\n📋 Test 2: Toggle button exists and positioned'); - - const toggleBtn = page.locator('#zoom-toggle-button'); - await toggleBtn.waitFor({ state: 'visible', timeout: 5000 }); - - const toggleBox = await toggleBtn.boundingBox(); - console.log(`✅ Toggle button found at position: left=${toggleBox.x}px, top=${toggleBox.y}px`); - - // Test 3: Check initial state (should be dimmed) - console.log('\n📋 Test 3: Toggle button initial state (should be dimmed)'); - - const hasActiveClass = await toggleBtn.evaluate(el => el.classList.contains('zoom-active')); - const opacity = await toggleBtn.evaluate(el => window.getComputedStyle(el).opacity); - - if (!hasActiveClass && parseFloat(opacity) === 0.5) { - console.log('✅ PASS: Toggle button is dimmed (opacity 0.5, no zoom-active class)'); - } else { - console.log(`❌ FAIL: Toggle button state incorrect (zoom-active: ${hasActiveClass}, opacity: ${opacity})`); - } - - // Test 4: Click toggle button to show zoom - console.log('\n📋 Test 4: Click toggle button to show zoom'); - - await toggleBtn.click(); - await page.waitForTimeout(500); - - const zoomVisible = await zoomControl.evaluate(el => !el.classList.contains('zoom-hidden')); - const toggleActive = await toggleBtn.evaluate(el => el.classList.contains('zoom-active')); - const newOpacity = await toggleBtn.evaluate(el => window.getComputedStyle(el).opacity); - - if (zoomVisible && toggleActive && parseFloat(newOpacity) === 1) { - console.log('✅ PASS: Zoom control shown, toggle button active (opacity 1, blue background)'); - } else { - console.log(`❌ FAIL: Zoom toggle failed (visible: ${zoomVisible}, active: ${toggleActive}, opacity: ${newOpacity})`); - } - - // Test 5: Click toggle button again to hide zoom - console.log('\n📋 Test 5: Click toggle button to hide zoom'); - - await toggleBtn.click(); - await page.waitForTimeout(500); - - const zoomHidden = await zoomControl.evaluate(el => el.classList.contains('zoom-hidden')); - const toggleInactive = await toggleBtn.evaluate(el => !el.classList.contains('zoom-active')); - const dimOpacity = await toggleBtn.evaluate(el => window.getComputedStyle(el).opacity); - - if (zoomHidden && toggleInactive && parseFloat(dimOpacity) === 0.5) { - console.log('✅ PASS: Zoom control hidden, toggle button dimmed again'); - } else { - console.log(`❌ FAIL: Zoom toggle hide failed (hidden: ${zoomHidden}, inactive: ${toggleInactive}, opacity: ${dimOpacity})`); - } - - // Test 6: Verify localStorage persistence - console.log('\n📋 Test 6: Verify localStorage persistence'); - - const storageValue = await page.evaluate(() => localStorage.getItem('cv-zoom-visible')); - console.log(` localStorage cv-zoom-visible: "${storageValue}"`); - - if (storageValue === 'false') { - console.log('✅ PASS: localStorage correctly set to "false"'); - } else { - console.log(`❌ FAIL: localStorage incorrect (expected "false", got "${storageValue}")`); - } - - // Test 7: Refresh page and verify zoom stays hidden - console.log('\n📋 Test 7: Refresh page and verify no blinking + stays hidden'); - - await page.reload(); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(500); // Wait a bit to catch any blinking - - const stillHidden = await zoomControl.evaluate(el => el.classList.contains('zoom-hidden')); - const toggleStillDimmed = await toggleBtn.evaluate(el => !el.classList.contains('zoom-active')); - - if (stillHidden && toggleStillDimmed) { - console.log('✅ PASS: After refresh, zoom stays hidden and toggle stays dimmed'); - } else { - console.log(`❌ FAIL: After refresh, state incorrect (hidden: ${stillHidden}, dimmed: ${toggleStillDimmed})`); - } - - // Test 8: Show zoom again and refresh to test persistence - console.log('\n📋 Test 8: Show zoom, refresh, verify it stays visible'); - - await toggleBtn.click(); - await page.waitForTimeout(500); - - await page.reload(); - await page.waitForLoadState('networkidle'); - await page.waitForTimeout(500); - - const staysVisible = await zoomControl.evaluate(el => !el.classList.contains('zoom-hidden')); - const toggleStaysActive = await toggleBtn.evaluate(el => el.classList.contains('zoom-active')); - - if (staysVisible && toggleStaysActive) { - console.log('✅ PASS: After refresh, zoom stays visible and toggle stays active'); - } else { - console.log(`❌ FAIL: Persistence failed (visible: ${staysVisible}, active: ${toggleStaysActive})`); - } - - // Test 9: Test X button still works - console.log('\n📋 Test 9: Test X button close functionality'); - - const closeBtn = page.locator('#zoom-close'); - await closeBtn.click(); - await page.waitForTimeout(500); - - const closedByX = await zoomControl.evaluate(el => el.classList.contains('zoom-hidden')); - const toggleDimmedAgain = await toggleBtn.evaluate(el => !el.classList.contains('zoom-active')); - - if (closedByX && toggleDimmedAgain) { - console.log('✅ PASS: X button closes zoom and syncs toggle button state'); - } else { - console.log(`❌ FAIL: X button failed (hidden: ${closedByX}, toggle dimmed: ${toggleDimmedAgain})`); - } - - // Test 10: Verify button positioning relative to shortcuts button - console.log('\n📋 Test 10: Verify button positioning'); - - const shortcutsBtn = page.locator('#shortcuts-button'); - const shortcutsBox = await shortcutsBtn.boundingBox(); - - console.log(` Shortcuts button: bottom=${1080 - shortcutsBox.y}px (should be ~6rem = ~96px)`); - console.log(` Toggle button: bottom=${1080 - toggleBox.y}px (should be ~10rem = ~160px)`); - - const verticalGap = (1080 - toggleBox.y - toggleBox.height) - (1080 - shortcutsBox.y); - console.log(` Vertical gap between buttons: ${Math.abs(verticalGap)}px`); - - if (Math.abs(verticalGap) > 50) { - console.log('✅ PASS: Toggle button is clearly above shortcuts button'); - } else { - console.log('⚠️ WARNING: Buttons might be too close together'); - } - - console.log('\n🎯 All tests completed! Browser will stay open for 10 seconds for visual inspection...\n'); - - await page.waitForTimeout(10000); - - await browser.close(); - console.log('✅ Test suite finished'); -})(); diff --git a/tests/testdata/cv-test-en.json b/tests/testdata/cv-test-en.json deleted file mode 100644 index 0eb3284..0000000 --- a/tests/testdata/cv-test-en.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "personal": { - "name": "Test User", - "email": "test@example.com", - "phone": "+1234567890", - "location": "Test City, USA", - "website": "https://test.example.com", - "github": "https://github.com/testuser", - "linkedin": "https://linkedin.com/in/testuser", - "title": "Test Engineer", - "summary": "Test summary for unit testing purposes. This is a minimal valid CV structure.", - "image": "https://example.com/test-profile.jpg" - }, - "experience": [ - { - "company": "Test Company Inc.", - "position": "Senior Test Engineer", - "start_date": "2020-01-01", - "end_date": "", - "description": "Test description of responsibilities and achievements.", - "location": "Test Location, USA", - "technologies": ["Go", "HTMX", "Testing"] - }, - { - "company": "Previous Test Corp", - "position": "Junior Test Engineer", - "start_date": "2018-06-01", - "end_date": "2019-12-31", - "description": "Earlier role description for testing.", - "location": "Test City, USA", - "technologies": ["JavaScript", "React"] - } - ], - "education": [ - { - "institution": "Test University", - "degree": "Bachelor of Science", - "field": "Computer Science", - "start_date": "2015-09-01", - "end_date": "2019-06-01", - "location": "Test University, USA", - "gpa": "3.8" - } - ], - "skills": { - "technical": ["Go", "HTMX", "Testing", "CI/CD", "Docker"], - "languages": ["English", "Spanish"], - "frameworks": ["Hono-style routing", "net/http"], - "tools": ["Git", "Make", "VSCode"] - }, - "projects": [ - { - "name": "Test Project", - "description": "Sample project for testing purposes", - "technologies": ["Go", "HTMX"], - "url": "https://github.com/testuser/test-project", - "start_date": "2023-01-01", - "end_date": "" - } - ], - "certifications": [ - { - "name": "Test Certification", - "issuer": "Test Organization", - "date": "2022-03-15", - "url": "https://example.com/cert" - } - ] -} diff --git a/tests/testdata/cv-test-es.json b/tests/testdata/cv-test-es.json deleted file mode 100644 index 6ef3e79..0000000 --- a/tests/testdata/cv-test-es.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "personal": { - "name": "Usuario de Prueba", - "email": "prueba@ejemplo.com", - "phone": "+34123456789", - "location": "Ciudad de Prueba, España", - "website": "https://prueba.ejemplo.com", - "github": "https://github.com/usuarioprueba", - "linkedin": "https://linkedin.com/in/usuarioprueba", - "title": "Ingeniero de Pruebas", - "summary": "Resumen de prueba para propósitos de testing. Esta es una estructura mínima válida de CV.", - "image": "https://ejemplo.com/perfil-prueba.jpg" - }, - "experience": [ - { - "company": "Empresa de Prueba S.L.", - "position": "Ingeniero Senior de Pruebas", - "start_date": "2020-01-01", - "end_date": "", - "description": "Descripción de prueba de responsabilidades y logros.", - "location": "Madrid, España", - "technologies": ["Go", "HTMX", "Testing"] - } - ], - "education": [ - { - "institution": "Universidad de Prueba", - "degree": "Grado en Informática", - "field": "Ciencias de la Computación", - "start_date": "2015-09-01", - "end_date": "2019-06-01", - "location": "Madrid, España", - "gpa": "8.5" - } - ], - "skills": { - "technical": ["Go", "HTMX", "Testing", "CI/CD", "Docker"], - "languages": ["Español", "Inglés"], - "frameworks": ["Enrutamiento estilo Hono", "net/http"], - "tools": ["Git", "Make", "VSCode"] - }, - "projects": [ - { - "name": "Proyecto de Prueba", - "description": "Proyecto de ejemplo para propósitos de testing", - "technologies": ["Go", "HTMX"], - "url": "https://github.com/usuarioprueba/proyecto-prueba", - "start_date": "2023-01-01", - "end_date": "" - } - ], - "certifications": [] -} diff --git a/tests/testdata/ui-test-en.json b/tests/testdata/ui-test-en.json deleted file mode 100644 index 76d9029..0000000 --- a/tests/testdata/ui-test-en.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "language_name": "English", - "language_code": "en", - "sections": { - "experience": "Experience", - "education": "Education", - "skills": "Skills", - "projects": "Projects", - "certifications": "Certifications", - "about": "About" - }, - "labels": { - "present": "Present", - "location": "Location", - "download_pdf": "Download PDF", - "view_on_github": "View on GitHub", - "email": "Email", - "phone": "Phone", - "website": "Website", - "technologies": "Technologies", - "tools": "Tools", - "languages": "Languages" - }, - "actions": { - "print": "Print", - "download": "Download", - "share": "Share", - "close": "Close" - }, - "messages": { - "loading": "Loading...", - "error": "Error loading data", - "not_found": "Page not found" - } -} diff --git a/tests/testdata/ui-test-es.json b/tests/testdata/ui-test-es.json deleted file mode 100644 index c564767..0000000 --- a/tests/testdata/ui-test-es.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "language_name": "Español", - "language_code": "es", - "sections": { - "experience": "Experiencia", - "education": "Educación", - "skills": "Habilidades", - "projects": "Proyectos", - "certifications": "Certificaciones", - "about": "Acerca de" - }, - "labels": { - "present": "Presente", - "location": "Ubicación", - "download_pdf": "Descargar PDF", - "view_on_github": "Ver en GitHub", - "email": "Correo", - "phone": "Teléfono", - "website": "Sitio web", - "technologies": "Tecnologías", - "tools": "Herramientas", - "languages": "Idiomas" - }, - "actions": { - "print": "Imprimir", - "download": "Descargar", - "share": "Compartir", - "close": "Cerrar" - }, - "messages": { - "loading": "Cargando...", - "error": "Error al cargar datos", - "not_found": "Página no encontrada" - } -} diff --git a/tests/verify-years-styling.sh b/tests/verify-years-styling.sh deleted file mode 100755 index 0ae2843..0000000 --- a/tests/verify-years-styling.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/bash - -# Test script to verify the "years of experience" styling matches the original design - -echo "🧪 Testing Years of Experience Styling" -echo "========================================" - -# Colors for output -GREEN='\033[0;32m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# Test counter -PASSED=0 -FAILED=0 - -# Test 1: Check CSS file contains correct styles -echo -n "1. CSS contains correct font-size (0.85em)... " -if grep -q "font-size: 0.85em;" /Users/txeo/Git/yo/cv/static/css/main.css; then - echo -e "${GREEN}✓ PASS${NC}" - ((PASSED++)) -else - echo -e "${RED}✗ FAIL${NC}" - ((FAILED++)) -fi - -# Test 2: Check font-weight -echo -n "2. CSS contains correct font-weight (400)... " -if grep -A 6 ".years-experience" /Users/txeo/Git/yo/cv/static/css/main.css | grep -q "font-weight: 400;"; then - echo -e "${GREEN}✓ PASS${NC}" - ((PASSED++)) -else - echo -e "${RED}✗ FAIL${NC}" - ((FAILED++)) -fi - -# Test 3: Check color -echo -n "3. CSS contains correct color (#666)... " -if grep -A 6 ".years-experience" /Users/txeo/Git/yo/cv/static/css/main.css | grep -q "color: #666;"; then - echo -e "${GREEN}✓ PASS${NC}" - ((PASSED++)) -else - echo -e "${RED}✗ FAIL${NC}" - ((FAILED++)) -fi - -# Test 4: Check margin -echo -n "4. CSS contains minimal top margin (4px 0 0 0)... " -if grep -A 6 ".years-experience" /Users/txeo/Git/yo/cv/static/css/main.css | grep -q "margin: 4px 0 0 0;"; then - echo -e "${GREEN}✓ PASS${NC}" - ((PASSED++)) -else - echo -e "${RED}✗ FAIL${NC}" - ((FAILED++)) -fi - -# Test 5: Check English version renders -echo -n "5. English version renders correctly... " -if curl -s "http://localhost:1999/?lang=en" | grep -q "20 years of experience"; then - echo -e "${GREEN}✓ PASS${NC}" - ((PASSED++)) -else - echo -e "${RED}✗ FAIL${NC}" - ((FAILED++)) -fi - -# Test 6: Check Spanish version renders -echo -n "6. Spanish version renders correctly... " -if curl -s "http://localhost:1999/?lang=es" | grep -q "20 años de experiencia"; then - echo -e "${GREEN}✓ PASS${NC}" - ((PASSED++)) -else - echo -e "${RED}✗ FAIL${NC}" - ((FAILED++)) -fi - -# Test 7: Check short version -echo -n "7. Short CV version includes years text... " -if curl -s "http://localhost:1999/?lang=en&version=short" | grep -q "years-experience"; then - echo -e "${GREEN}✓ PASS${NC}" - ((PASSED++)) -else - echo -e "${RED}✗ FAIL${NC}" - ((FAILED++)) -fi - -# Test 8: Check long version -echo -n "8. Long CV version includes years text... " -if curl -s "http://localhost:1999/?lang=en&version=long" | grep -q "years-experience"; then - echo -e "${GREEN}✓ PASS${NC}" - ((PASSED++)) -else - echo -e "${RED}✗ FAIL${NC}" - ((FAILED++)) -fi - -# Summary -echo "" -echo "========================================" -echo "Test Results: ${PASSED} passed, ${FAILED} failed" -if [ $FAILED -eq 0 ]; then - echo -e "${GREEN}✅ All tests passed!${NC}" - exit 0 -else - echo -e "${RED}❌ Some tests failed${NC}" - exit 1 -fi diff --git a/tests/visual-comparison.spec.js b/tests/visual-comparison.spec.js deleted file mode 100644 index 0300a61..0000000 --- a/tests/visual-comparison.spec.js +++ /dev/null @@ -1,353 +0,0 @@ -/** - * Visual Comparison Test Suite - * Compares new Go + HTMX CV (localhost:1999) vs old React CV (localhost:3000) - */ - -const { test, expect } = require('@playwright/test'); -const fs = require('fs'); -const path = require('path'); - -const OLD_SITE = 'http://localhost:3000'; -const NEW_SITE = 'http://localhost:1999'; -const SCREENSHOTS_DIR = path.join(__dirname, 'screenshots'); - -// Ensure screenshots directory exists -if (!fs.existsSync(SCREENSHOTS_DIR)) { - fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true }); -} - -test.describe('Visual Comparison: New vs Old CV', () => { - - test('Full page screenshots', async ({ browser }) => { - const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - - const pageOld = await contextOld.newPage(); - const pageNew = await contextNew.newPage(); - - // Load both sites - await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); - await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); - - // Take full page screenshots - await pageOld.screenshot({ - path: path.join(SCREENSHOTS_DIR, 'old-fullpage.png'), - fullPage: true - }); - await pageNew.screenshot({ - path: path.join(SCREENSHOTS_DIR, 'new-fullpage.png'), - fullPage: true - }); - - console.log('✓ Full page screenshots saved'); - - await contextOld.close(); - await contextNew.close(); - }); - - test('Header section comparison', async ({ browser }) => { - const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - - const pageOld = await contextOld.newPage(); - const pageNew = await contextNew.newPage(); - - await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); - await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); - - // Screenshot header sections - const headerOld = await pageOld.locator('.cv-title-badges-header, [class*="header"]').first(); - const headerNew = await pageNew.locator('.cv-title-badges-header').first(); - - if (await headerOld.count() > 0) { - await headerOld.screenshot({ path: path.join(SCREENSHOTS_DIR, 'old-header.png') }); - } - if (await headerNew.count() > 0) { - await headerNew.screenshot({ path: path.join(SCREENSHOTS_DIR, 'new-header.png') }); - } - - console.log('✓ Header screenshots saved'); - - await contextOld.close(); - await contextNew.close(); - }); - - test('Badge measurements comparison', async ({ browser }) => { - const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - - const pageOld = await contextOld.newPage(); - const pageNew = await contextNew.newPage(); - - await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); - await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); - - // Measure badge elements - const measurements = { - old: {}, - new: {} - }; - - // New site badge measurements - const badgeNew = pageNew.locator('.title-badge').first(); - if (await badgeNew.count() > 0) { - const box = await badgeNew.boundingBox(); - const styles = await badgeNew.evaluate(el => { - const computed = window.getComputedStyle(el); - return { - height: computed.height, - padding: computed.padding, - fontSize: computed.fontSize, - fontWeight: computed.fontWeight, - color: computed.color, - backgroundColor: computed.backgroundColor, - borderRadius: computed.borderRadius, - display: computed.display, - alignItems: computed.alignItems - }; - }); - measurements.new.badge = { box, styles }; - } - - // Old site badge measurements - const badgeOld = pageOld.locator('.title-badge, [class*="badge"]').first(); - if (await badgeOld.count() > 0) { - const box = await badgeOld.boundingBox(); - const styles = await badgeOld.evaluate(el => { - const computed = window.getComputedStyle(el); - return { - height: computed.height, - padding: computed.padding, - fontSize: computed.fontSize, - fontWeight: computed.fontWeight, - color: computed.color, - backgroundColor: computed.backgroundColor, - borderRadius: computed.borderRadius, - display: computed.display, - alignItems: computed.alignItems - }; - }); - measurements.old.badge = { box, styles }; - } - - // Save measurements - fs.writeFileSync( - path.join(SCREENSHOTS_DIR, 'badge-measurements.json'), - JSON.stringify(measurements, null, 2) - ); - - console.log('✓ Badge measurements saved'); - console.log('\nBadge Comparison:'); - console.log('OLD:', JSON.stringify(measurements.old.badge?.styles, null, 2)); - console.log('NEW:', JSON.stringify(measurements.new.badge?.styles, null, 2)); - - await contextOld.close(); - await contextNew.close(); - }); - - test('Typography comparison', async ({ browser }) => { - const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - - const pageOld = await contextOld.newPage(); - const pageNew = await contextNew.newPage(); - - await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); - await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); - - const typography = { - old: {}, - new: {} - }; - - // Selectors to compare - const selectors = { - name: '.cv-name', - sidebarTitle: '.sidebar-title', - sectionTitle: '.section-title', - badge: '.title-badge' - }; - - // Measure new site typography - for (const [key, selector] of Object.entries(selectors)) { - const element = pageNew.locator(selector).first(); - if (await element.count() > 0) { - typography.new[key] = await element.evaluate(el => { - const computed = window.getComputedStyle(el); - return { - fontFamily: computed.fontFamily, - fontSize: computed.fontSize, - fontWeight: computed.fontWeight, - lineHeight: computed.lineHeight, - color: computed.color, - letterSpacing: computed.letterSpacing - }; - }); - } - } - - // Measure old site typography - for (const [key, selector] of Object.entries(selectors)) { - const element = pageOld.locator(selector).first(); - if (await element.count() > 0) { - typography.old[key] = await element.evaluate(el => { - const computed = window.getComputedStyle(el); - return { - fontFamily: computed.fontFamily, - fontSize: computed.fontSize, - fontWeight: computed.fontWeight, - lineHeight: computed.lineHeight, - color: computed.color, - letterSpacing: computed.letterSpacing - }; - }); - } - } - - // Save typography comparison - fs.writeFileSync( - path.join(SCREENSHOTS_DIR, 'typography-comparison.json'), - JSON.stringify(typography, null, 2) - ); - - console.log('✓ Typography comparison saved'); - console.log('\nTypography Comparison:'); - console.log('OLD:', JSON.stringify(typography.old, null, 2)); - console.log('NEW:', JSON.stringify(typography.new, null, 2)); - - await contextOld.close(); - await contextNew.close(); - }); - - test('Sidebar comparison', async ({ browser }) => { - const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - - const pageOld = await contextOld.newPage(); - const pageNew = await contextNew.newPage(); - - await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); - await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); - - // Screenshot sidebars - const sidebarOld = pageOld.locator('.cv-sidebar, [class*="sidebar"]').first(); - const sidebarNew = pageNew.locator('.cv-sidebar').first(); - - if (await sidebarOld.count() > 0) { - await sidebarOld.screenshot({ path: path.join(SCREENSHOTS_DIR, 'old-sidebar.png') }); - } - if (await sidebarNew.count() > 0) { - await sidebarNew.screenshot({ path: path.join(SCREENSHOTS_DIR, 'new-sidebar.png') }); - } - - // Measure sidebar styles - const sidebarComparison = { - old: {}, - new: {} - }; - - if (await sidebarNew.count() > 0) { - sidebarComparison.new = await sidebarNew.evaluate(el => { - const computed = window.getComputedStyle(el); - return { - backgroundColor: computed.backgroundColor, - padding: computed.padding, - width: computed.width, - minWidth: computed.minWidth - }; - }); - } - - if (await sidebarOld.count() > 0) { - sidebarComparison.old = await sidebarOld.evaluate(el => { - const computed = window.getComputedStyle(el); - return { - backgroundColor: computed.backgroundColor, - padding: computed.padding, - width: computed.width, - minWidth: computed.minWidth - }; - }); - } - - fs.writeFileSync( - path.join(SCREENSHOTS_DIR, 'sidebar-comparison.json'), - JSON.stringify(sidebarComparison, null, 2) - ); - - console.log('✓ Sidebar comparison saved'); - console.log('\nSidebar Comparison:'); - console.log('OLD:', JSON.stringify(sidebarComparison.old, null, 2)); - console.log('NEW:', JSON.stringify(sidebarComparison.new, null, 2)); - - await contextOld.close(); - await contextNew.close(); - }); - - test('Critical elements style extraction', async ({ browser }) => { - const contextOld = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - const contextNew = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); - - const pageOld = await contextOld.newPage(); - const pageNew = await contextNew.newPage(); - - await pageOld.goto(OLD_SITE, { waitUntil: 'networkidle' }); - await pageNew.goto(NEW_SITE, { waitUntil: 'networkidle' }); - - const criticalElements = [ - '.cv-title-badges-header', - '.title-badge', - '.badge-separator', - '.sidebar-title', - '.section-title', - '.cv-name' - ]; - - const styleComparison = { - old: {}, - new: {} - }; - - // Extract from new site - for (const selector of criticalElements) { - const element = pageNew.locator(selector).first(); - if (await element.count() > 0) { - styleComparison.new[selector] = await element.evaluate(el => { - const computed = window.getComputedStyle(el); - const styles = {}; - for (let i = 0; i < computed.length; i++) { - const prop = computed[i]; - styles[prop] = computed.getPropertyValue(prop); - } - return styles; - }); - } - } - - // Extract from old site - for (const selector of criticalElements) { - const element = pageOld.locator(selector).first(); - if (await element.count() > 0) { - styleComparison.old[selector] = await element.evaluate(el => { - const computed = window.getComputedStyle(el); - const styles = {}; - for (let i = 0; i < computed.length; i++) { - const prop = computed[i]; - styles[prop] = computed.getPropertyValue(prop); - } - return styles; - }); - } - } - - fs.writeFileSync( - path.join(SCREENSHOTS_DIR, 'critical-elements-full-styles.json'), - JSON.stringify(styleComparison, null, 2) - ); - - console.log('✓ Critical elements styles extracted'); - - await contextOld.close(); - await contextNew.close(); - }); -});