Files
cv-site/tests/mjs/13-color-theme-switcher.test.mjs
T
juanatsap f3cce51fb3 feat: implement color theme switcher with dynamic button colors
Complete color theme system (light/dark/auto) with dynamic UI:

Features:
- Color theme switcher with auto/light/dark modes
- Dynamic button colors on hover (purple/yellow/blue per theme)
- localStorage persistence across sessions
- Proper button positioning (desktop and mobile)
- Mobile: 5-button layout with theme before info button

Fixes:
- CSP updated to allow jsDelivr CDN for iconify icons
- Button repositioning: Download PDF and Print Friendly at top
- Hover-only colors (not persistent)
- Mobile button order corrected

Files:
- static/css/color-theme.css - Theme system with CSS variables
- static/js/color-theme.js - Theme switching logic
- templates/partials/color-theme-switcher.html - Button component
- internal/middleware/security.go - CSP fix for jsDelivr
- tests/mjs/13-color-theme-switcher.test.mjs - Comprehensive test
- tests/TEST-SUMMARY.md - Updated test documentation
2025-11-18 15:49:30 +00:00

216 lines
7.2 KiB
JavaScript
Executable File

#!/usr/bin/env bun
/**
* COLOR THEME SWITCHER TEST
* ==========================
* Tests color theme switcher functionality with dynamic colors
* - Verifies button positioning (Download PDF and Print Friendly at top)
* - Tests theme cycling (auto → light → dark → auto)
* - Validates dynamic button colors per theme mode
* - Confirms icon changes per theme
* - Tests localStorage persistence
*/
import { chromium } from 'playwright';
const URL = "http://localhost:1999";
async function testColorThemeSwitcher() {
console.log('🧪 COLOR THEME SWITCHER TEST\n');
console.log('='.repeat(70));
const browser = await chromium.launch({ headless: false });
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
const errors = [];
const testResults = [];
page.on('console', msg => {
const text = msg.text();
if (msg.type() === 'error') {
errors.push(text);
console.log(`❌ ERROR: ${text}`);
}
});
page.on('pageerror', err => {
errors.push(err.message);
console.log(`❌ PAGE ERROR: ${err.message}`);
});
console.log("\n1️⃣ Loading page...");
await page.goto(URL);
await page.waitForTimeout(2000);
// ========================================================================
// TEST 1: Button Positioning
// ========================================================================
console.log("\n2️⃣ Testing button positioning...");
const themeBtn = page.locator('#color-theme-switcher');
const downloadBtn = page.locator('.download-btn');
const printBtn = page.locator('.print-friendly-btn');
const shortcutsBtn = page.locator('.shortcuts-btn');
const backToTopBtn = page.locator('.back-to-top');
const positions = {};
const buttons = [
{ name: 'Download PDF', locator: downloadBtn },
{ name: 'Print Friendly', locator: printBtn },
{ name: 'Theme Switcher', locator: themeBtn },
{ name: 'Shortcuts', locator: shortcutsBtn },
{ name: 'Back to Top', locator: backToTopBtn }
];
for (const btn of buttons) {
const box = await btn.locator.boundingBox();
if (box) {
positions[btn.name] = box.y;
}
}
// Verify order: smaller bottom value = higher on screen
const expectedOrder = ['Download PDF', 'Print Friendly', 'Theme Switcher', 'Shortcuts', 'Back to Top'];
let positionsCorrect = true;
for (let i = 0; i < expectedOrder.length - 1; i++) {
const current = positions[expectedOrder[i]];
const next = positions[expectedOrder[i + 1]];
if (current && next) {
const isCorrect = current < next;
if (!isCorrect) {
positionsCorrect = false;
console.log(`${expectedOrder[i]} should be above ${expectedOrder[i + 1]}`);
}
}
}
testResults.push({
test: 'Button Positioning',
passed: positionsCorrect,
message: positionsCorrect ? 'Buttons in correct order (top to bottom)' : 'Button order incorrect'
});
console.log(` ${positionsCorrect ? '✅' : '❌'} Button positioning: ${positionsCorrect ? 'CORRECT' : 'INCORRECT'}`);
// ========================================================================
// TEST 2: Theme Cycling and Dynamic Colors
// ========================================================================
console.log("\n3️⃣ Testing theme cycling and dynamic colors...");
const themes = [
{ name: 'auto', color: 'rgb(155, 89, 182)', icon: 'mdi:theme-light-dark' },
{ name: 'light', color: 'rgb(243, 156, 18)', icon: 'mdi:white-balance-sunny' },
{ name: 'dark', color: 'rgb(52, 152, 219)', icon: 'mdi:moon-waning-crescent' },
{ name: 'auto', color: 'rgb(155, 89, 182)', icon: 'mdi:theme-light-dark' } // Cycle back
];
let themeTestsPassed = true;
for (let i = 0; i < themes.length; i++) {
const theme = themes[i];
if (i > 0) {
await themeBtn.click();
await page.waitForTimeout(500);
}
// Get current theme mode from document
const currentMode = await page.evaluate(() =>
document.documentElement.getAttribute('data-color-theme')
);
// Get button background color
const bgColor = await themeBtn.evaluate(el =>
window.getComputedStyle(el).backgroundColor
);
// Get button data attribute
const dataMode = await themeBtn.getAttribute('data-theme-mode');
// Get icon
const iconName = await page.locator('#themeIcon').getAttribute('icon');
const modeMatch = currentMode === theme.name;
const colorMatch = bgColor === theme.color;
const dataMatch = dataMode === theme.name;
const iconMatch = iconName === theme.icon;
const allMatch = modeMatch && colorMatch && dataMatch && iconMatch;
console.log(` ${i + 1}. Theme: ${theme.name.toUpperCase()}`);
console.log(` Document mode: ${currentMode} ${modeMatch ? '✓' : '✗'}`);
console.log(` Button data: ${dataMode} ${dataMatch ? '✓' : '✗'}`);
console.log(` Icon: ${iconName} ${iconMatch ? '✓' : '✗'}`);
console.log(` Color: ${bgColor} ${colorMatch ? '✓' : '✗'}`);
console.log(` Status: ${allMatch ? '✅ PASS' : '❌ FAIL'}`);
if (!allMatch) themeTestsPassed = false;
}
testResults.push({
test: 'Theme Cycling',
passed: themeTestsPassed,
message: themeTestsPassed ? 'All theme modes cycle correctly' : 'Theme cycling failed'
});
testResults.push({
test: 'Dynamic Colors',
passed: themeTestsPassed,
message: themeTestsPassed ? 'Button colors change per theme mode' : 'Dynamic colors failed'
});
// ========================================================================
// TEST 3: localStorage Persistence
// ========================================================================
console.log("\n4️⃣ Testing localStorage persistence...");
// Set to light mode
await themeBtn.click();
await page.waitForTimeout(300);
const storedTheme = await page.evaluate(() =>
localStorage.getItem('color-theme-mode')
);
const persistenceWorks = storedTheme === 'light';
testResults.push({
test: 'localStorage Persistence',
passed: persistenceWorks,
message: persistenceWorks ? `Theme '${storedTheme}' saved to localStorage` : 'localStorage not saving theme'
});
console.log(` ${persistenceWorks ? '✅' : '❌'} localStorage: ${persistenceWorks ? `'${storedTheme}' saved correctly` : 'FAILED'}`);
// ========================================================================
// TEST SUMMARY
// ========================================================================
console.log('\n' + '='.repeat(70));
console.log('📊 TEST RESULTS');
console.log('='.repeat(70) + '\n');
const allPassed = testResults.every(r => r.passed);
testResults.forEach(r => {
console.log(` ${r.passed ? '✅' : '❌'} ${r.test}: ${r.message}`);
});
console.log('\n' + '='.repeat(70));
if (allPassed && errors.length === 0) {
console.log('\n🎉 ALL COLOR THEME TESTS PASSED!\n');
await browser.close();
process.exit(0);
} else {
console.log('\n❌ SOME TESTS FAILED\n');
if (errors.length > 0) {
console.log(`Browser errors: ${errors.length}`);
}
console.log("\nBrowser will stay open for manual inspection.");
console.log("Press Ctrl+C when done.\n");
await new Promise(() => {}); // Keep browser open
}
}
await testColorThemeSwitcher();