From 2f466e46bcc444306106a824419d0f0d3dd22329 Mon Sep 17 00:00:00 2001 From: juanatsap Date: Sun, 23 Nov 2025 08:37:29 +0000 Subject: [PATCH] feat: Default to light theme on mobile devices on first visit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements device-aware theme defaults: - Mobile devices (≤768px): Default to light theme - Desktop devices (>768px): Default to auto theme - Saved preferences: Always respected regardless of device Implementation: 1. FOUC Prevention Script (templates/index.html): - Detects device type using window.innerWidth/clientWidth/screen.width - Sets initial theme before page render to prevent flash - Mobile: 'light', Desktop: 'auto' 2. Theme Initialization (static/js/color-theme.js): - Modified initColorTheme() to respect FOUC-detected theme - Saves FOUC-detected theme to localStorage for persistence - Prevents overwriting mobile detection with 'auto' default Test Coverage: - Test 1: Mobile first visit → light theme ✅ - Test 2: Desktop first visit → auto theme ✅ - Test 3: Saved preference honored → dark theme ✅ Files Modified: - templates/index.html: Added mobile detection in FOUC prevention - static/js/color-theme.js: Respect FOUC-detected theme - tests/mjs/49-mobile-light-theme-default.mjs: Comprehensive test suite Screenshots: - tests/screenshots/mobile-light-theme-default.png - tests/screenshots/desktop-auto-theme-default.png --- static/js/color-theme.js | 16 ++- templates/index.html | 12 ++- tests/mjs/49-mobile-light-theme-default.mjs | 104 ++++++++++++++++++++ 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100755 tests/mjs/49-mobile-light-theme-default.mjs diff --git a/static/js/color-theme.js b/static/js/color-theme.js index ca3d94f..f6fb03b 100644 --- a/static/js/color-theme.js +++ b/static/js/color-theme.js @@ -44,9 +44,19 @@ function setColorTheme(mode) { // Initialize color theme function initColorTheme() { - // Get saved preference or default to 'auto' - const savedTheme = localStorage.getItem('color-theme-mode') || 'auto'; - setColorTheme(savedTheme); + // Check if theme was already set by FOUC prevention script + const existingTheme = document.documentElement.getAttribute('data-color-theme'); + + // If theme is already set (by FOUC script), save it to localStorage + if (existingTheme) { + // Save the FOUC-detected theme to localStorage + localStorage.setItem('color-theme-mode', existingTheme); + setColorTheme(existingTheme); + } else { + // Fallback: Get saved preference or default to 'auto' + const savedTheme = localStorage.getItem('color-theme-mode') || 'auto'; + setColorTheme(savedTheme); + } } // Setup button click handler diff --git a/templates/index.html b/templates/index.html index 14b2e33..fdc1b20 100644 --- a/templates/index.html +++ b/templates/index.html @@ -42,7 +42,17 @@ diff --git a/tests/mjs/49-mobile-light-theme-default.mjs b/tests/mjs/49-mobile-light-theme-default.mjs new file mode 100755 index 0000000..ba0a9c5 --- /dev/null +++ b/tests/mjs/49-mobile-light-theme-default.mjs @@ -0,0 +1,104 @@ +#!/usr/bin/env node + +import { chromium } from 'playwright'; + +const MOBILE_VIEWPORT = { width: 375, height: 667 }; +const DESKTOP_VIEWPORT = { width: 1920, height: 1080 }; + +(async () => { + const browser = await chromium.launch({ headless: true }); + + console.log('🧪 Testing Default Theme by Device Type\n'); + + // TEST 1: Mobile First Visit - Should Default to Light Theme + console.log('📱 TEST 1: Mobile First Visit (No localStorage)\n'); + const mobileContext = await browser.newContext({ viewport: MOBILE_VIEWPORT }); + const mobilePage = await mobileContext.newPage(); + + // Clear localStorage to simulate first visit + await mobilePage.goto('http://localhost:1999/?lang=en&view=extended'); + await mobilePage.evaluate(() => localStorage.clear()); + await mobilePage.reload(); + await mobilePage.waitForLoadState('networkidle'); + + // Check theme attribute + const mobileTheme = await mobilePage.evaluate(() => { + return document.documentElement.getAttribute('data-color-theme'); + }); + + console.log(`📊 Mobile Theme:`); + console.log(` • Theme attribute: ${mobileTheme}`); + console.log(` • Expected: light`); + console.log(` • Match: ${mobileTheme === 'light' ? '✅' : '❌'}\n`); + + await mobilePage.screenshot({ + path: 'tests/screenshots/mobile-light-theme-default.png', + fullPage: true + }); + console.log('📸 Screenshot: tests/screenshots/mobile-light-theme-default.png\n'); + + await mobileContext.close(); + + // TEST 2: Desktop First Visit - Should Default to Auto Theme + console.log('🖥️ TEST 2: Desktop First Visit (No localStorage)\n'); + const desktopContext = await browser.newContext({ viewport: DESKTOP_VIEWPORT }); + const desktopPage = await desktopContext.newPage(); + + // Clear localStorage to simulate first visit + await desktopPage.goto('http://localhost:1999/?lang=en&view=extended'); + await desktopPage.evaluate(() => localStorage.clear()); + await desktopPage.reload(); + await desktopPage.waitForLoadState('networkidle'); + + // Check theme attribute + const desktopTheme = await desktopPage.evaluate(() => { + return document.documentElement.getAttribute('data-color-theme'); + }); + + console.log(`📊 Desktop Theme:`); + console.log(` • Theme attribute: ${desktopTheme}`); + console.log(` • Expected: auto`); + console.log(` • Match: ${desktopTheme === 'auto' ? '✅' : '❌'}\n`); + + await desktopPage.screenshot({ + path: 'tests/screenshots/desktop-auto-theme-default.png', + fullPage: true + }); + console.log('📸 Screenshot: tests/screenshots/desktop-auto-theme-default.png\n'); + + await desktopContext.close(); + + // TEST 3: Mobile with Saved Preference - Should Respect localStorage + console.log('📱 TEST 3: Mobile with Saved Preference (Should Honor User Choice)\n'); + const mobileContext2 = await browser.newContext({ viewport: MOBILE_VIEWPORT }); + const mobilePage2 = await mobileContext2.newPage(); + + await mobilePage2.goto('http://localhost:1999/?lang=en&view=extended'); + await mobilePage2.evaluate(() => { + localStorage.setItem('color-theme-mode', 'dark'); + }); + await mobilePage2.reload(); + await mobilePage2.waitForLoadState('networkidle'); + + const savedTheme = await mobilePage2.evaluate(() => { + return document.documentElement.getAttribute('data-color-theme'); + }); + + console.log(`📊 Mobile with Saved Preference:`); + console.log(` • Theme attribute: ${savedTheme}`); + console.log(` • Expected: dark (from localStorage)`); + console.log(` • Match: ${savedTheme === 'dark' ? '✅' : '❌'}\n`); + + await mobileContext2.close(); + + // Final Results + const allPassed = mobileTheme === 'light' && + desktopTheme === 'auto' && + savedTheme === 'dark'; + + console.log(`${allPassed ? '✅' : '❌'} All tests ${allPassed ? 'PASSED' : 'FAILED'}\n`); + + await browser.close(); + + process.exit(allPassed ? 0 : 1); +})();