fix: Mobile hamburger menu and iPad sidebar visibility

Mobile fixes:
- Add click toggle handler for hamburger menu (was hover-only)
- Menu now opens/closes on tap and closes when clicking outside
- Keep hover support for desktop

iPad fixes:
- Sidebar content now visible on touch devices (901-1280px)
- Added (hover: hover) media query to prevent hide-on-hover on tablets

Security improvements:
- Replace exec.CommandContext with go-git library for git operations
- Add path traversal and command injection prevention
- Fix race condition in template hot reload
- Add environment-based cookie Secure flag

Code quality:
- Add constants.go for magic numbers
- Remove unused code (ParsePreferenceToggleRequest, DomainError)
- Add FOUC prevention with inline critical CSS
- Add Makefile dev/run/clean targets
- Fix README git clone URL
- Add doc/DECISIONS.md for architectural decisions

Tests:
- Add hamburger menu click toggle tests
- Add iPad sidebar visibility tests
- Update security tests for go-git implementation
- Add cookie Secure flag tests
This commit is contained in:
juanatsap
2025-11-30 09:29:35 +00:00
parent 60c1b5ac2b
commit eb92f64e93
18 changed files with 874 additions and 183 deletions
+57 -25
View File
@@ -25,7 +25,7 @@ async function testMobileResponsive() {
console.log('📱 MOBILE RESPONSIVE TEST\n');
console.log('='.repeat(70));
const browser = await chromium.launch({ headless: false });
const browser = await chromium.launch({ headless: true });
const errors = [];
const testResults = [];
@@ -88,43 +88,77 @@ async function testMobileResponsive() {
testResults.push({ test: 'Mobile Viewport (375px)', passed: mobileViewportPassed });
// ========================================================================
// TEST 2: Touch interactions (hamburger menu)
// TEST 2: Hamburger Menu Click Toggle (Mobile)
// ========================================================================
console.log("\n2️⃣ Testing Touch Interactions...");
console.log("\n2️⃣ Testing Hamburger Menu Click Toggle...");
const hamburger = await mobilePage.$('.hamburger-btn');
if (hamburger) {
// Tap hamburger to open menu
await hamburger.tap();
await mobilePage.waitForTimeout(500);
// Click hamburger to open menu (simulates mobile tap)
await hamburger.click();
await mobilePage.waitForTimeout(300);
const menuTest = await mobilePage.evaluate(() => {
const menuOpenTest = 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';
const hasMenuOpen = menu.classList.contains('menu-open');
const computedStyle = window.getComputedStyle(menu);
const isVisible = computedStyle.opacity === '1' && computedStyle.maxHeight !== '0px';
return {
found: true,
isOpen,
isVisible: menu.offsetHeight > 0
hasMenuOpen,
isVisible,
maxHeight: computedStyle.maxHeight
};
});
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 });
console.log(` Menu found: ${menuOpenTest.found ? '✅' : '❌'}`);
console.log(` Has menu-open class: ${menuOpenTest.hasMenuOpen ? '✅' : '❌'}`);
console.log(` Menu visible: ${menuOpenTest.isVisible ? '✅' : '❌'}`);
const openPassed = menuOpenTest.found && menuOpenTest.hasMenuOpen;
console.log(` ${openPassed ? '✅ PASS' : '❌ FAIL'} - Menu opens on click`);
testResults.push({ test: 'Menu Opens on Click', passed: openPassed });
// TEST 2B: Click again to close
await hamburger.click();
await mobilePage.waitForTimeout(300);
const menuCloseTest = await mobilePage.evaluate(() => {
const menu = document.querySelector('.navigation-menu');
return {
hasMenuOpen: menu?.classList.contains('menu-open') ?? false
};
});
const closePassed = !menuCloseTest.hasMenuOpen;
console.log(` Menu closes on second click: ${closePassed ? '✅' : '❌'}`);
testResults.push({ test: 'Menu Closes on Click', passed: closePassed });
// TEST 2C: Click outside to close
await hamburger.click(); // Open again
await mobilePage.waitForTimeout(300);
await mobilePage.click('body', { position: { x: 300, y: 400 } }); // Click outside
await mobilePage.waitForTimeout(300);
const outsideCloseTest = await mobilePage.evaluate(() => {
const menu = document.querySelector('.navigation-menu');
return {
hasMenuOpen: menu?.classList.contains('menu-open') ?? false
};
});
const outsidePassed = !outsideCloseTest.hasMenuOpen;
console.log(` Menu closes on outside click: ${outsidePassed ? '✅' : '❌'}`);
testResults.push({ test: 'Menu Closes on Outside Click', passed: outsidePassed });
// 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 });
testResults.push({ test: 'Menu Opens on Click', passed: false });
testResults.push({ test: 'Menu Closes on Click', passed: false });
testResults.push({ test: 'Menu Closes on Outside Click', passed: false });
}
// ========================================================================
@@ -274,10 +308,8 @@ async function testMobileResponsive() {
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 browser.close();
process.exit(failedTests === 0 ? 0 : 1);
}
await testMobileResponsive();