2eafb78954
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
189 lines
6.4 KiB
JavaScript
Executable File
189 lines
6.4 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Test: Back-to-Top Button and Footer Text Fixes
|
|
*
|
|
* Verifies:
|
|
* 1. Back-to-top button shows full opacity (no transparency) when at page bottom
|
|
* 2. Footer text enlarges when footer is hovered/touched
|
|
*/
|
|
|
|
import { chromium } from 'playwright';
|
|
|
|
const TEST_URL = 'http://localhost:1999';
|
|
const VIEWPORT_WIDTH = 375; // Mobile width
|
|
const VIEWPORT_HEIGHT = 812; // iPhone X height
|
|
|
|
async function testBackToTopAndFooterFixes() {
|
|
console.log('🧪 Testing Back-to-Top Button Opacity & Footer Text Enlargement');
|
|
console.log('='.repeat(70));
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
const context = await browser.newContext({
|
|
viewport: { width: VIEWPORT_WIDTH, height: VIEWPORT_HEIGHT },
|
|
deviceScaleFactor: 2,
|
|
});
|
|
const page = await context.newPage();
|
|
|
|
// Disable cache
|
|
await page.route('**/*', (route) => {
|
|
route.continue({
|
|
headers: {
|
|
...route.request().headers(),
|
|
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
},
|
|
});
|
|
});
|
|
|
|
try {
|
|
await page.goto(TEST_URL, { waitUntil: 'networkidle' });
|
|
console.log(`✅ Navigated to ${TEST_URL}`);
|
|
await page.waitForTimeout(1500);
|
|
|
|
let allTestsPassed = true;
|
|
|
|
// TEST 1: Back-to-top button should have full opacity when at bottom
|
|
console.log('\n📊 TEST 1: Back-to-top button full opacity at page bottom');
|
|
console.log('-'.repeat(70));
|
|
|
|
// Scroll to bottom
|
|
await page.evaluate(() => {
|
|
window.scrollTo(0, document.body.scrollHeight);
|
|
});
|
|
await page.waitForTimeout(500);
|
|
|
|
const backToTopAtBottom = await page.evaluate(() => {
|
|
const btn = document.querySelector('.back-to-top');
|
|
if (!btn) return { exists: false };
|
|
|
|
const hasAtBottomClass = btn.classList.contains('at-bottom');
|
|
const styles = window.getComputedStyle(btn);
|
|
const opacity = parseFloat(styles.opacity);
|
|
const backgroundColor = styles.backgroundColor;
|
|
|
|
return {
|
|
exists: true,
|
|
hasAtBottomClass,
|
|
opacity,
|
|
backgroundColor
|
|
};
|
|
});
|
|
|
|
if (backToTopAtBottom.exists) {
|
|
// Should have opacity: 1 (full opacity, no transparency)
|
|
if (backToTopAtBottom.opacity >= 0.95) {
|
|
console.log(`✅ Back-to-top button has full opacity: ${backToTopAtBottom.opacity}`);
|
|
console.log(` Background: ${backToTopAtBottom.backgroundColor}`);
|
|
console.log(` Has .at-bottom class: ${backToTopAtBottom.hasAtBottomClass}`);
|
|
} else {
|
|
console.log(`❌ Back-to-top button still has transparency: opacity=${backToTopAtBottom.opacity} (expected >= 0.95)`);
|
|
allTestsPassed = false;
|
|
}
|
|
} else {
|
|
console.log('❌ Back-to-top button not found');
|
|
allTestsPassed = false;
|
|
}
|
|
|
|
// TEST 2: Footer text should enlarge when footer is hovered
|
|
console.log('\n📊 TEST 2: Footer text enlarges on hover');
|
|
console.log('-'.repeat(70));
|
|
|
|
// Get initial font size before hover
|
|
const initialFooterState = await page.evaluate(() => {
|
|
const footer = document.querySelector('footer.no-print');
|
|
const footerP = document.querySelector('footer.no-print p');
|
|
if (!footer || !footerP) return { exists: false };
|
|
|
|
const styles = window.getComputedStyle(footerP);
|
|
return {
|
|
exists: true,
|
|
fontSize: parseFloat(styles.fontSize),
|
|
footerHasClass: footer.classList.contains('footer-hovered')
|
|
};
|
|
});
|
|
|
|
if (!initialFooterState.exists) {
|
|
console.log('❌ Footer or footer paragraph not found');
|
|
allTestsPassed = false;
|
|
} else {
|
|
console.log(` Initial font size: ${initialFooterState.fontSize.toFixed(1)}px`);
|
|
console.log(` Footer has .footer-hovered class: ${initialFooterState.footerHasClass}`);
|
|
|
|
// Trigger footer hover by adding class via JavaScript
|
|
await page.evaluate(() => {
|
|
const footer = document.querySelector('footer.no-print');
|
|
if (footer) {
|
|
// Dispatch mouseenter event
|
|
const event = new MouseEvent('mouseenter', {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
view: window
|
|
});
|
|
footer.dispatchEvent(event);
|
|
}
|
|
});
|
|
|
|
// Wait for CSS transition
|
|
await page.waitForTimeout(500);
|
|
|
|
// Get font size after hover
|
|
const hoveredFooterState = await page.evaluate(() => {
|
|
const footer = document.querySelector('footer.no-print');
|
|
const footerP = document.querySelector('footer.no-print p');
|
|
if (!footer || !footerP) return { exists: false };
|
|
|
|
const styles = window.getComputedStyle(footerP);
|
|
return {
|
|
exists: true,
|
|
fontSize: parseFloat(styles.fontSize),
|
|
footerHasClass: footer.classList.contains('footer-hovered'),
|
|
computedFontSize: styles.fontSize
|
|
};
|
|
});
|
|
|
|
if (hoveredFooterState.exists) {
|
|
console.log(` After hover font size: ${hoveredFooterState.fontSize.toFixed(1)}px`);
|
|
console.log(` Footer has .footer-hovered class: ${hoveredFooterState.footerHasClass}`);
|
|
|
|
if (hoveredFooterState.footerHasClass) {
|
|
console.log('✅ Footer received .footer-hovered class');
|
|
} else {
|
|
console.log('❌ Footer did NOT receive .footer-hovered class');
|
|
allTestsPassed = false;
|
|
}
|
|
|
|
// Check if font size increased (should be 1.2x larger)
|
|
if (hoveredFooterState.fontSize > initialFooterState.fontSize) {
|
|
const increase = ((hoveredFooterState.fontSize - initialFooterState.fontSize) / initialFooterState.fontSize) * 100;
|
|
console.log(`✅ Footer text enlarged by ${increase.toFixed(1)}%`);
|
|
} else {
|
|
console.log(`❌ Footer text did NOT enlarge (${initialFooterState.fontSize.toFixed(1)}px → ${hoveredFooterState.fontSize.toFixed(1)}px)`);
|
|
allTestsPassed = false;
|
|
}
|
|
} else {
|
|
console.log('❌ Could not measure hovered footer state');
|
|
allTestsPassed = false;
|
|
}
|
|
}
|
|
|
|
console.log('-'.repeat(70));
|
|
|
|
if (allTestsPassed) {
|
|
console.log('\n✅ ALL TESTS PASSED!');
|
|
console.log(' • Back-to-top button has full opacity at page bottom');
|
|
console.log(' • Footer text enlarges when hovered');
|
|
} else {
|
|
console.log('\n❌ SOME TESTS FAILED - Check output above');
|
|
}
|
|
|
|
await browser.close();
|
|
process.exit(allTestsPassed ? 0 : 1);
|
|
|
|
} catch (error) {
|
|
console.error('\n❌ Test error:', error);
|
|
await browser.close();
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
testBackToTopAndFooterFixes();
|