Files
cv-site/tests/mjs/80-mobile-fab-overflow.test.mjs
T
juanatsap e06f98d1d8 fix: prevent FAB button overflow on very small screens (iPhone 13 mini)
- Add media query for screens ≤400px (iPhone 13 mini = 375px)
- Reduce button size to 34px and icon size to 16px
- Recalculate 6-button positions with 6px gaps (234px total width)
- Ensures buttons stay centered within narrow viewports
2025-12-06 10:01:12 +00:00

378 lines
13 KiB
JavaScript

#!/usr/bin/env bun
/**
* MOBILE FAB OVERFLOW TEST
* ========================
* Tests that floating action buttons don't overflow on very small screens
* like iPhone 13 mini (375px) and similar devices.
*
* The issue: FAB buttons were calculated for minimum 380px width,
* causing overflow on 375px screens.
*
* The fix: Added @media (max-width: 400px) with tighter button spacing.
*/
import { chromium } from 'playwright';
const URL = "http://localhost:1999";
async function testMobileFabOverflow() {
console.log('🎯 MOBILE FAB OVERFLOW TEST\n');
console.log('='.repeat(70));
const browser = await chromium.launch({ headless: true });
const testResults = [];
try {
// ========================================================================
// TEST 1: iPhone 13 mini (375px) - buttons don't overflow
// ========================================================================
console.log("\n1️⃣ Testing iPhone 13 mini (375x812) - no overflow...");
const context1 = await browser.newContext({
viewport: { width: 375, height: 812 },
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
});
const page1 = await context1.newPage();
// Add is-mobile-device class to simulate real mobile
await page1.goto(URL);
await page1.evaluate(() => document.body.classList.add('is-mobile-device'));
await page1.waitForTimeout(1500);
const iphone13Mini = await page1.evaluate(() => {
const viewportWidth = window.innerWidth;
const buttons = [
'.download-btn',
'.print-friendly-btn',
'.fixed-btn.contact-btn',
'.color-theme-switcher',
'.info-button',
'.back-to-top'
];
let leftMost = Infinity;
let rightMost = -Infinity;
let visibleCount = 0;
const buttonDetails = [];
buttons.forEach(selector => {
const btn = document.querySelector(selector);
if (btn) {
const style = window.getComputedStyle(btn);
const rect = btn.getBoundingClientRect();
const isVisible = style.display !== 'none' && style.visibility !== 'hidden';
if (isVisible) {
visibleCount++;
leftMost = Math.min(leftMost, rect.left);
rightMost = Math.max(rightMost, rect.right);
buttonDetails.push({
selector,
left: Math.round(rect.left),
right: Math.round(rect.right),
width: Math.round(rect.width)
});
}
}
});
// Check for horizontal overflow
const hasOverflow = rightMost > viewportWidth || leftMost < 0;
const overflowAmount = Math.max(0, rightMost - viewportWidth, -leftMost);
// Check document has no horizontal scroll
const hasHorizontalScroll = document.documentElement.scrollWidth > viewportWidth;
return {
viewportWidth,
leftMost: Math.round(leftMost),
rightMost: Math.round(rightMost),
visibleCount,
hasOverflow,
overflowAmount: Math.round(overflowAmount),
hasHorizontalScroll,
buttonDetails,
passed: !hasOverflow && visibleCount === 6
};
});
console.log(` Viewport width: ${iphone13Mini.viewportWidth}px`);
console.log(` Visible buttons: ${iphone13Mini.visibleCount}/6`);
console.log(` Button range: ${iphone13Mini.leftMost}px - ${iphone13Mini.rightMost}px`);
console.log(` Has overflow: ${iphone13Mini.hasOverflow ? '❌ YES (' + iphone13Mini.overflowAmount + 'px)' : '✅ NO'}`);
console.log(` Horizontal scroll: ${iphone13Mini.hasHorizontalScroll ? '❌ YES' : '✅ NO'}`);
const test1Passed = iphone13Mini.passed;
console.log(` ${test1Passed ? '✅ PASS' : '❌ FAIL'} - iPhone 13 mini no overflow`);
testResults.push({ test: 'iPhone 13 mini (375px) no overflow', passed: test1Passed });
await context1.close();
// ========================================================================
// TEST 2: iPhone SE (320px) - even smaller screen
// ========================================================================
console.log("\n2️⃣ Testing iPhone SE (320x568) - no overflow...");
const context2 = await browser.newContext({
viewport: { width: 320, height: 568 },
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
});
const page2 = await context2.newPage();
await page2.goto(URL);
await page2.evaluate(() => document.body.classList.add('is-mobile-device'));
await page2.waitForTimeout(1500);
const iphoneSE = await page2.evaluate(() => {
const viewportWidth = window.innerWidth;
const buttons = [
'.download-btn',
'.print-friendly-btn',
'.fixed-btn.contact-btn',
'.color-theme-switcher',
'.info-button',
'.back-to-top'
];
let leftMost = Infinity;
let rightMost = -Infinity;
let visibleCount = 0;
buttons.forEach(selector => {
const btn = document.querySelector(selector);
if (btn) {
const style = window.getComputedStyle(btn);
const rect = btn.getBoundingClientRect();
const isVisible = style.display !== 'none' && style.visibility !== 'hidden';
if (isVisible) {
visibleCount++;
leftMost = Math.min(leftMost, rect.left);
rightMost = Math.max(rightMost, rect.right);
}
}
});
const hasOverflow = rightMost > viewportWidth || leftMost < 0;
return {
viewportWidth,
leftMost: Math.round(leftMost),
rightMost: Math.round(rightMost),
visibleCount,
hasOverflow,
passed: !hasOverflow && visibleCount === 6
};
});
console.log(` Viewport width: ${iphoneSE.viewportWidth}px`);
console.log(` Visible buttons: ${iphoneSE.visibleCount}/6`);
console.log(` Button range: ${iphoneSE.leftMost}px - ${iphoneSE.rightMost}px`);
console.log(` Has overflow: ${iphoneSE.hasOverflow ? '❌ YES' : '✅ NO'}`);
const test2Passed = iphoneSE.passed;
console.log(` ${test2Passed ? '✅ PASS' : '❌ FAIL'} - iPhone SE no overflow`);
testResults.push({ test: 'iPhone SE (320px) no overflow', passed: test2Passed });
await context2.close();
// ========================================================================
// TEST 3: Buttons are centered on small screens
// ========================================================================
console.log("\n3️⃣ Testing buttons centered on small screens (375px)...");
const context3 = await browser.newContext({
viewport: { width: 375, height: 812 },
isMobile: true,
hasTouch: true,
});
const page3 = await context3.newPage();
await page3.goto(URL);
await page3.evaluate(() => document.body.classList.add('is-mobile-device'));
await page3.waitForTimeout(1500);
const centered = await page3.evaluate(() => {
const viewportWidth = window.innerWidth;
const buttons = [
'.download-btn',
'.print-friendly-btn',
'.fixed-btn.contact-btn',
'.color-theme-switcher',
'.info-button',
'.back-to-top'
];
let leftMost = Infinity;
let rightMost = -Infinity;
buttons.forEach(selector => {
const btn = document.querySelector(selector);
if (btn) {
const style = window.getComputedStyle(btn);
const rect = btn.getBoundingClientRect();
const isVisible = style.display !== 'none' && style.visibility !== 'hidden';
if (isVisible) {
leftMost = Math.min(leftMost, rect.left);
rightMost = Math.max(rightMost, rect.right);
}
}
});
const groupWidth = rightMost - leftMost;
const groupCenter = leftMost + (groupWidth / 2);
const viewportCenter = viewportWidth / 2;
const centerOffset = Math.abs(groupCenter - viewportCenter);
// Allow 20px tolerance for centering
const isCentered = centerOffset < 20;
return {
viewportWidth,
viewportCenter,
groupCenter: Math.round(groupCenter),
centerOffset: Math.round(centerOffset),
isCentered,
passed: isCentered
};
});
console.log(` Viewport center: ${centered.viewportCenter}px`);
console.log(` Button group center: ${centered.groupCenter}px`);
console.log(` Center offset: ${centered.centerOffset}px`);
console.log(` Is centered: ${centered.isCentered ? '✅ YES' : '❌ NO'}`);
const test3Passed = centered.passed;
console.log(` ${test3Passed ? '✅ PASS' : '❌ FAIL'} - Buttons centered`);
testResults.push({ test: 'Buttons centered on small screens', passed: test3Passed });
await context3.close();
// ========================================================================
// TEST 4: Button size reduced on very small screens
// ========================================================================
console.log("\n4️⃣ Testing button size reduction on small screens (375px)...");
const context4 = await browser.newContext({
viewport: { width: 375, height: 812 },
isMobile: true,
hasTouch: true,
});
const page4 = await context4.newPage();
await page4.goto(URL);
await page4.evaluate(() => document.body.classList.add('is-mobile-device'));
await page4.waitForTimeout(1500);
const buttonSize = await page4.evaluate(() => {
const btn = document.querySelector('.download-btn');
if (!btn) return { passed: false, reason: 'Button not found' };
const rect = btn.getBoundingClientRect();
const width = Math.round(rect.width);
const height = Math.round(rect.height);
// On screens <= 400px, buttons should be 34px
const expectedSize = 34;
const tolerance = 2; // Allow 2px tolerance
const correctSize = Math.abs(width - expectedSize) <= tolerance &&
Math.abs(height - expectedSize) <= tolerance;
return {
width,
height,
expectedSize,
correctSize,
passed: correctSize
};
});
console.log(` Button size: ${buttonSize.width}x${buttonSize.height}px`);
console.log(` Expected: ~${buttonSize.expectedSize}px`);
console.log(` Correct size: ${buttonSize.correctSize ? '✅ YES' : '❌ NO'}`);
const test4Passed = buttonSize.passed;
console.log(` ${test4Passed ? '✅ PASS' : '❌ FAIL'} - Button size reduced`);
testResults.push({ test: 'Button size reduced on small screens', passed: test4Passed });
await context4.close();
// ========================================================================
// TEST 5: Compare with larger screen (no size reduction)
// ========================================================================
console.log("\n5️⃣ Testing normal button size on larger mobile (500px)...");
const context5 = await browser.newContext({
viewport: { width: 500, height: 800 },
isMobile: true,
hasTouch: true,
});
const page5 = await context5.newPage();
await page5.goto(URL);
await page5.evaluate(() => document.body.classList.add('is-mobile-device'));
await page5.waitForTimeout(1500);
const largerScreen = await page5.evaluate(() => {
const btn = document.querySelector('.download-btn');
if (!btn) return { passed: false, reason: 'Button not found' };
const rect = btn.getBoundingClientRect();
const width = Math.round(rect.width);
// On screens > 400px, buttons should be larger (36-50px based on clamp)
const isLarger = width > 34;
return {
width,
isLarger,
passed: isLarger
};
});
console.log(` Button size: ${largerScreen.width}px`);
console.log(` Is larger than 34px: ${largerScreen.isLarger ? '✅ YES' : '❌ NO'}`);
const test5Passed = largerScreen.passed;
console.log(` ${test5Passed ? '✅ PASS' : '❌ FAIL'} - Normal size on larger screens`);
testResults.push({ test: 'Normal button size on larger screens (500px)', passed: test5Passed });
await context5.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`);
console.log("=".repeat(70) + "\n");
await browser.close();
if (failedTests === 0) {
console.log("🎉 ALL MOBILE FAB OVERFLOW TESTS PASSED!");
process.exit(0);
} else {
console.log("⚠️ SOME TESTS FAILED - See details above");
process.exit(1);
}
} catch (error) {
console.error('❌ Test failed:', error);
await browser.close();
process.exit(1);
}
}
await testMobileFabOverflow();