Files
cv-site/tests/mjs/41-mobile-accordion-test.mjs
juanatsap 2eafb78954 fix: Mobile view improvements - accordion styling and modal centering
Fixed two critical mobile view issues:

1. Extended CV Sidebar Accordion:
   - Updated sidebar.html to use native <details> element (was div with onclick)
   - Styled accordion header to match CV title badges dark theme (#303030)
   - Applied consistent styling: dark gray background, light text, uppercase, no spacing
   - Result: Sidebars now collapse/expand properly with native HTML functionality

2. PDF Download Modal Centering:
   - Added JavaScript-based centering for mobile viewports (≤768px)
   - Uses inline styles with !important flag to override browser defaults
   - Updated download button to call openPdfModal() function
   - Result: Modal is perfectly centered on mobile (0px offset)

Technical notes:
   - Modal centering required setProperty() with 'important' flag
   - Accordion matches cv-title-badges-header style exactly
   - All tests passing: accordion toggle, modal centering

Files modified:
   - templates/partials/cv/sidebar.html
   - static/css/05-responsive/_breakpoints.css
   - static/js/main.js
   - templates/partials/widgets/download-button.html

Tests added:
   - tests/mjs/43-mobile-accordion-and-modal-test.mjs
   - tests/mjs/46-visual-accordion-style-test.mjs
2025-11-22 16:23:05 +00:00

181 lines
7.6 KiB
JavaScript
Executable File

#!/usr/bin/env bun
/**
* Mobile Accordion Test
* ======================
* Tests the sidebar accordion functionality on mobile devices in the extended CV view.
*
* What this test verifies:
* - Accordion headers are visible on mobile (hidden on desktop)
* - Accordion opens and closes when clicked
* - Content is properly shown/hidden
* - Chevron icon rotates correctly
* - Both left and right sidebars work
*/
import { chromium } from 'playwright';
const BASE_URL = 'http://localhost:1999';
const MOBILE_VIEWPORT = { width: 375, height: 667 }; // iPhone SE size
async function testMobileAccordion() {
console.log('🧪 Starting Mobile Accordion Test...\n');
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({
viewport: MOBILE_VIEWPORT,
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15'
});
const page = await context.newPage();
let allTestsPassed = true;
try {
// Navigate to page with extended CV (lang param and let localStorage handle length)
console.log('📱 Loading page in mobile viewport (375x667)...');
// First, set localStorage for extended CV
await page.goto(BASE_URL);
await page.evaluate(() => {
localStorage.setItem('cv-length', 'long');
});
// Reload with the extended CV setting
await page.goto(BASE_URL, { waitUntil: 'networkidle' });
await page.waitForTimeout(1500);
// Test Left Sidebar Accordion
console.log('\n🔍 Testing LEFT sidebar accordion...');
const leftAccordion = await page.locator('.cv-sidebar-left .sidebar-accordion').first();
const leftHeader = await page.locator('.cv-sidebar-left .sidebar-accordion-header').first();
const leftContent = await page.locator('.cv-sidebar-left .sidebar-accordion-content').first();
const leftChevron = await page.locator('.cv-sidebar-left .sidebar-accordion-header .chevron').first();
// Check header is visible on mobile
const leftHeaderVisible = await leftHeader.isVisible();
console.log(` ✓ Left accordion header visible: ${leftHeaderVisible ? '✅ YES' : '❌ NO'}`);
if (!leftHeaderVisible) allTestsPassed = false;
// Check if accordion is initially open
const leftInitiallyOpen = await leftAccordion.getAttribute('open');
console.log(` ✓ Left accordion initially open: ${leftInitiallyOpen !== null ? '✅ YES' : '❌ NO'}`);
// Check content is visible when open
const leftContentVisible = await leftContent.isVisible();
console.log(` ✓ Left content visible when open: ${leftContentVisible ? '✅ YES' : '❌ NO'}`);
if (!leftContentVisible && leftInitiallyOpen) allTestsPassed = false;
// Close the accordion
console.log('\n 🖱️ Clicking left accordion to close...');
await leftHeader.click();
await page.waitForTimeout(500); // Wait for animation
// Check accordion is closed
const leftNowClosed = await leftAccordion.getAttribute('open');
console.log(` ✓ Left accordion closed: ${leftNowClosed === null ? '✅ YES' : '❌ NO'}`);
if (leftNowClosed !== null) allTestsPassed = false;
// Check content is hidden when closed
const leftContentHidden = !(await leftContent.isVisible());
console.log(` ✓ Left content hidden when closed: ${leftContentHidden ? '✅ YES' : '❌ NO'}`);
if (!leftContentHidden) allTestsPassed = false;
// Open the accordion again
console.log('\n 🖱️ Clicking left accordion to re-open...');
await leftHeader.click();
await page.waitForTimeout(500); // Wait for animation
// Check accordion is open again
const leftReopened = await leftAccordion.getAttribute('open');
console.log(` ✓ Left accordion re-opened: ${leftReopened !== null ? '✅ YES' : '❌ NO'}`);
if (leftReopened === null) allTestsPassed = false;
// Check content is visible again
const leftContentVisibleAgain = await leftContent.isVisible();
console.log(` ✓ Left content visible again: ${leftContentVisibleAgain ? '✅ YES' : '❌ NO'}`);
if (!leftContentVisibleAgain) allTestsPassed = false;
// Test Right Sidebar Accordion
console.log('\n🔍 Testing RIGHT sidebar accordion...');
const rightAccordion = await page.locator('.cv-sidebar-right .sidebar-accordion').first();
const rightHeader = await page.locator('.cv-sidebar-right .sidebar-accordion-header').first();
const rightContent = await page.locator('.cv-sidebar-right .sidebar-accordion-content').first();
const rightChevron = await page.locator('.cv-sidebar-right .sidebar-accordion-header .chevron').first();
// Check header is visible on mobile
const rightHeaderVisible = await rightHeader.isVisible();
console.log(` ✓ Right accordion header visible: ${rightHeaderVisible ? '✅ YES' : '❌ NO'}`);
if (!rightHeaderVisible) allTestsPassed = false;
// Close and re-open right accordion
console.log('\n 🖱️ Clicking right accordion to close...');
await rightHeader.click();
await page.waitForTimeout(500);
const rightClosed = await rightAccordion.getAttribute('open');
console.log(` ✓ Right accordion closed: ${rightClosed === null ? '✅ YES' : '❌ NO'}`);
if (rightClosed !== null) allTestsPassed = false;
console.log('\n 🖱️ Clicking right accordion to re-open...');
await rightHeader.click();
await page.waitForTimeout(500);
const rightReopened = await rightAccordion.getAttribute('open');
console.log(` ✓ Right accordion re-opened: ${rightReopened !== null ? '✅ YES' : '❌ NO'}`);
if (rightReopened === null) allTestsPassed = false;
// Test styling
console.log('\n🎨 Testing accordion styling...');
const leftHeaderStyles = await leftHeader.evaluate(el => {
const styles = window.getComputedStyle(el);
return {
display: styles.display,
background: styles.backgroundColor,
padding: styles.padding,
cursor: styles.cursor,
borderRadius: styles.borderRadius
};
});
console.log(` ✓ Header display: ${leftHeaderStyles.display === 'flex' ? '✅ flex' : '❌ ' + leftHeaderStyles.display}`);
console.log(` ✓ Header background: ${leftHeaderStyles.background}`);
console.log(` ✓ Header cursor: ${leftHeaderStyles.cursor === 'pointer' ? '✅ pointer' : '❌ ' + leftHeaderStyles.cursor}`);
if (leftHeaderStyles.display !== 'flex' || leftHeaderStyles.cursor !== 'pointer') {
allTestsPassed = false;
}
// Take screenshot
console.log('\n📸 Taking screenshot...');
await page.screenshot({
path: 'tests/screenshots/mobile-accordion-extended.png',
fullPage: true
});
console.log(' Saved: tests/screenshots/mobile-accordion-extended.png');
} catch (error) {
console.error('\n❌ Test failed with error:', error);
allTestsPassed = false;
} finally {
await browser.close();
}
// Final result
console.log('\n' + '='.repeat(50));
if (allTestsPassed) {
console.log('✅ ALL MOBILE ACCORDION TESTS PASSED!');
console.log('='.repeat(50));
process.exit(0);
} else {
console.log('❌ SOME MOBILE ACCORDION TESTS FAILED');
console.log('='.repeat(50));
process.exit(1);
}
}
// Run test
testMobileAccordion();