fb313d8dc6
Removed 'open' attribute from accordion <details> elements to ensure sidebars start collapsed on mobile view, providing a cleaner initial state. Changes: - templates/partials/cv/sidebar.html: Removed open attribute - templates/cv-content.html: Removed open attributes (2 occurrences) - templates/language-switch.html: Removed open attributes (2 occurrences) - tests/mjs/43-mobile-accordion-and-modal-test.mjs: Updated test expectations Test results: ✅ Accordion initially closed ✅ Content initially hidden ✅ Toggle functionality working perfectly ✅ Modal centering maintained (0px offset)
179 lines
8.1 KiB
JavaScript
Executable File
179 lines
8.1 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
import { chromium } from 'playwright';
|
|
|
|
const MOBILE_VIEWPORT = { width: 375, height: 667 }; // iPhone SE size
|
|
|
|
(async () => {
|
|
const browser = await chromium.launch({ headless: true });
|
|
const context = await browser.newContext({ viewport: MOBILE_VIEWPORT });
|
|
const page = await context.newPage();
|
|
|
|
try {
|
|
console.log('🧪 Testing Mobile Accordion and Modal Centering\n');
|
|
console.log(`📱 Mobile Viewport: ${MOBILE_VIEWPORT.width}x${MOBILE_VIEWPORT.height}`);
|
|
|
|
// Navigate to extended view
|
|
await page.goto('http://localhost:1999/?lang=en&view=extended');
|
|
await page.waitForLoadState('networkidle');
|
|
console.log('✅ Extended view loaded\n');
|
|
|
|
// ===== TEST 1: SIDEBAR ACCORDION =====
|
|
console.log('🔍 TEST 1: Sidebar Accordion Structure\n');
|
|
|
|
// Check for accordion elements
|
|
const accordionDetails = await page.locator('.sidebar-accordion').first();
|
|
const accordionSummary = await page.locator('.sidebar-accordion-header').first();
|
|
const accordionContent = await page.locator('.sidebar-accordion-content').first();
|
|
|
|
const detailsCount = await page.locator('.sidebar-accordion').count();
|
|
const summaryCount = await page.locator('.sidebar-accordion-header').count();
|
|
const contentCount = await page.locator('.sidebar-accordion-content').count();
|
|
|
|
console.log(`📊 Accordion Structure:`);
|
|
console.log(` • Details elements: ${detailsCount}`);
|
|
console.log(` • Summary elements: ${summaryCount}`);
|
|
console.log(` • Content elements: ${contentCount}`);
|
|
|
|
// Check if accordion is initially closed (should be closed on mobile)
|
|
const isOpen = await accordionDetails.evaluate(el => el.hasAttribute('open'));
|
|
console.log(` • Initially closed: ${!isOpen ? '✅ YES' : '❌ NO (should be closed)'}`);
|
|
console.log(` • Initially open: ${isOpen ? '❌ YES (should be closed)' : '✅ NO'}`);
|
|
|
|
// Check if summary is visible on mobile
|
|
const summaryDisplay = await accordionSummary.evaluate(el => {
|
|
const style = window.getComputedStyle(el);
|
|
return style.display;
|
|
});
|
|
console.log(` • Summary display: ${summaryDisplay}`);
|
|
console.log(` • Summary visible: ${summaryDisplay !== 'none' ? '✅ YES' : '❌ NO'}`);
|
|
|
|
// Check if content is initially hidden (since accordion starts closed)
|
|
const initialContentHidden = await accordionContent.evaluate(el => {
|
|
const style = window.getComputedStyle(el);
|
|
return style.maxHeight === '0px' || style.opacity === '0';
|
|
});
|
|
console.log(` • Content initially hidden: ${initialContentHidden ? '✅ YES' : '❌ NO'}`);
|
|
|
|
// Test accordion toggle
|
|
if (detailsCount > 0) {
|
|
console.log('\n🖱️ Testing Accordion Toggle...');
|
|
|
|
// Click to OPEN (since it starts closed)
|
|
await accordionSummary.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const isOpenedAfterClick = await accordionDetails.evaluate(el => el.hasAttribute('open'));
|
|
console.log(` • Opened after click: ${isOpenedAfterClick ? '✅ YES' : '❌ NO'}`);
|
|
|
|
// Check if content is now visible
|
|
const contentVisible = await accordionContent.evaluate(el => {
|
|
const style = window.getComputedStyle(el);
|
|
return style.opacity === '1';
|
|
});
|
|
console.log(` • Content visible: ${contentVisible ? '✅ YES' : '❌ NO'}`);
|
|
|
|
// Click to close again
|
|
await accordionSummary.click();
|
|
await page.waitForTimeout(500);
|
|
|
|
const isClosedAgain = await accordionDetails.evaluate(el => !el.hasAttribute('open'));
|
|
console.log(` • Closed after second click: ${isClosedAgain ? '✅ YES' : '❌ NO'}`);
|
|
|
|
// Verify content is hidden again
|
|
const contentHiddenAgain = await accordionContent.evaluate(el => {
|
|
const style = window.getComputedStyle(el);
|
|
return style.maxHeight === '0px' || style.opacity === '0';
|
|
});
|
|
console.log(` • Content hidden again: ${contentHiddenAgain ? '✅ YES' : '❌ NO'}`);
|
|
}
|
|
|
|
// ===== TEST 2: MODAL CENTERING =====
|
|
console.log('\n🔍 TEST 2: Modal Centering\n');
|
|
|
|
// Open the PDF modal
|
|
await page.click('#download-button');
|
|
await page.waitForTimeout(500);
|
|
|
|
const modal = await page.locator('#pdf-modal');
|
|
const isModalOpen = await modal.evaluate(el => el.hasAttribute('open'));
|
|
console.log(`📦 Modal Status:`);
|
|
console.log(` • Modal open: ${isModalOpen ? '✅ YES' : '❌ NO'}`);
|
|
|
|
if (isModalOpen) {
|
|
// Get modal bounding box
|
|
const modalBox = await modal.boundingBox();
|
|
|
|
console.log(`\n📐 Modal Position:`);
|
|
console.log(` • X position: ${modalBox.x.toFixed(2)}px`);
|
|
console.log(` • Y position: ${modalBox.y.toFixed(2)}px`);
|
|
console.log(` • Width: ${modalBox.width.toFixed(2)}px`);
|
|
console.log(` • Height: ${modalBox.height.toFixed(2)}px`);
|
|
|
|
// Calculate centers
|
|
const modalCenterX = modalBox.x + modalBox.width / 2;
|
|
const modalCenterY = modalBox.y + modalBox.height / 2;
|
|
const viewportCenterX = MOBILE_VIEWPORT.width / 2;
|
|
const viewportCenterY = MOBILE_VIEWPORT.height / 2;
|
|
|
|
console.log(`\n🎯 Centering Analysis:`);
|
|
console.log(` • Modal center X: ${modalCenterX.toFixed(2)}px`);
|
|
console.log(` • Viewport center X: ${viewportCenterX.toFixed(2)}px`);
|
|
console.log(` • Modal center Y: ${modalCenterY.toFixed(2)}px`);
|
|
console.log(` • Viewport center Y: ${viewportCenterY.toFixed(2)}px`);
|
|
|
|
// Check if centered (10px tolerance)
|
|
const horizontalOffset = Math.abs(modalCenterX - viewportCenterX);
|
|
const verticalOffset = Math.abs(modalCenterY - viewportCenterY);
|
|
const TOLERANCE = 10;
|
|
|
|
console.log(`\n📏 Offset Analysis:`);
|
|
console.log(` • Horizontal offset: ${horizontalOffset.toFixed(2)}px`);
|
|
console.log(` • Vertical offset: ${verticalOffset.toFixed(2)}px`);
|
|
console.log(` • Tolerance: ${TOLERANCE}px`);
|
|
|
|
const isHorizontallyCentered = horizontalOffset <= TOLERANCE;
|
|
const isVerticallyCentered = verticalOffset <= TOLERANCE;
|
|
|
|
console.log(`\n✅ Centering Results:`);
|
|
console.log(` • Horizontally centered: ${isHorizontallyCentered ? '✅ YES' : '❌ NO'}`);
|
|
console.log(` • Vertically centered: ${isVerticallyCentered ? '✅ YES' : '❌ NO'}`);
|
|
|
|
// Get CSS properties
|
|
const cssProps = await modal.evaluate(el => {
|
|
const style = window.getComputedStyle(el);
|
|
return {
|
|
position: style.position,
|
|
top: style.top,
|
|
left: style.left,
|
|
transform: style.transform
|
|
};
|
|
});
|
|
|
|
console.log(`\n🎨 CSS Properties:`);
|
|
console.log(` • position: ${cssProps.position}`);
|
|
console.log(` • top: ${cssProps.top}`);
|
|
console.log(` • left: ${cssProps.left}`);
|
|
console.log(` • transform: ${cssProps.transform}`);
|
|
|
|
// Overall test results
|
|
console.log(`\n📊 OVERALL RESULTS:`);
|
|
if (isHorizontallyCentered && isVerticallyCentered) {
|
|
console.log('✅ Modal is PROPERLY CENTERED on mobile!');
|
|
} else {
|
|
console.log('❌ Modal is NOT centered on mobile');
|
|
console.log(` Needs adjustment: ${!isHorizontallyCentered ? 'horizontal' : ''} ${!isVerticallyCentered ? 'vertical' : ''}`);
|
|
}
|
|
}
|
|
|
|
console.log('\n✅ Test completed successfully!\n');
|
|
|
|
} catch (error) {
|
|
console.error('❌ Test failed:', error.message);
|
|
console.error(error.stack);
|
|
process.exit(1);
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
})();
|