style(design): migrate to DESIGN.md system
Full visual migration: Space Grotesk + DM Sans + JetBrains Mono fonts, warm amber two-accent system (amber=state, blue=action), dark mode with CSS vars + localStorage + FOUC prevention, mobile bottom nav (5 tabs), full-width tables, error/skipped dot glow, ~13 hardcoded hex replaced with CSS vars in 4 JS files, 5 new E2E tests. Files: style.css (full rewrite), base.html (fonts, theme script, dark toggle, bottom nav), settings.html (dark toggle card), dashboard.js, logs.js, mappings.js, settings.js (color vars), 5 templates (bnav active blocks), test_design_system_e2e.py (NEW). Cache-bust: style.css?v=18, shared.js?v=14, dashboard.js?v=27, logs.js?v=13, mappings.js?v=12, settings.js?v=8 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
105
api/tests/e2e/test_design_system_e2e.py
Normal file
105
api/tests/e2e/test_design_system_e2e.py
Normal file
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
E2E tests for DESIGN.md migration (Commit 0.5).
|
||||
Tests: dark toggle, FOUC prevention, bottom nav, active tab amber, dark contrast.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
pytestmark = [pytest.mark.e2e]
|
||||
|
||||
|
||||
def test_dark_mode_toggle(page, app_url):
|
||||
"""Dark toggle switches theme and persists in localStorage."""
|
||||
page.goto(f"{app_url}/settings")
|
||||
page.wait_for_load_state("networkidle")
|
||||
|
||||
# Settings page has the dark mode toggle
|
||||
toggle = page.locator("#settDarkMode")
|
||||
assert toggle.is_visible()
|
||||
|
||||
# Start in light mode
|
||||
theme = page.evaluate("document.documentElement.getAttribute('data-theme')")
|
||||
if theme == "dark":
|
||||
toggle.click()
|
||||
page.wait_for_timeout(200)
|
||||
|
||||
# Toggle to dark
|
||||
toggle.click()
|
||||
page.wait_for_timeout(200)
|
||||
assert page.evaluate("document.documentElement.getAttribute('data-theme')") == "dark"
|
||||
assert page.evaluate("localStorage.getItem('theme')") == "dark"
|
||||
|
||||
# Toggle back to light
|
||||
toggle.click()
|
||||
page.wait_for_timeout(200)
|
||||
assert page.evaluate("document.documentElement.getAttribute('data-theme')") != "dark"
|
||||
assert page.evaluate("localStorage.getItem('theme')") == "light"
|
||||
|
||||
|
||||
def test_fouc_prevention(page, app_url):
|
||||
"""Theme is applied before CSS loads (inline script in <head>)."""
|
||||
# Set dark theme in localStorage before navigation
|
||||
page.goto(f"{app_url}/")
|
||||
page.evaluate("localStorage.setItem('theme', 'dark')")
|
||||
|
||||
# Navigate fresh — the inline script should apply dark before paint
|
||||
page.goto(f"{app_url}/")
|
||||
# Check immediately (before networkidle) that data-theme is set
|
||||
theme = page.evaluate("document.documentElement.getAttribute('data-theme')")
|
||||
assert theme == "dark", "FOUC: dark theme not applied before first paint"
|
||||
|
||||
# Cleanup
|
||||
page.evaluate("localStorage.removeItem('theme')")
|
||||
|
||||
|
||||
def test_bottom_nav_visible_on_mobile(page, app_url):
|
||||
"""Bottom nav is visible on mobile viewport, top navbar is hidden."""
|
||||
page.set_viewport_size({"width": 375, "height": 812})
|
||||
page.goto(f"{app_url}/")
|
||||
page.wait_for_load_state("networkidle")
|
||||
|
||||
bottom_nav = page.locator(".bottom-nav")
|
||||
top_navbar = page.locator(".top-navbar")
|
||||
|
||||
assert bottom_nav.is_visible(), "Bottom nav should be visible on mobile"
|
||||
assert not top_navbar.is_visible(), "Top navbar should be hidden on mobile"
|
||||
|
||||
# Check 5 tabs exist
|
||||
tabs = page.locator(".bottom-nav-item")
|
||||
assert tabs.count() == 5
|
||||
|
||||
|
||||
def test_active_tab_amber_accent(page, app_url):
|
||||
"""Active nav tab uses amber accent color, not blue."""
|
||||
page.goto(f"{app_url}/")
|
||||
page.wait_for_load_state("networkidle")
|
||||
|
||||
active_tab = page.locator(".nav-tab.active")
|
||||
assert active_tab.count() >= 1
|
||||
|
||||
# Get computed color of active tab
|
||||
color = page.evaluate("""
|
||||
() => getComputedStyle(document.querySelector('.nav-tab.active')).color
|
||||
""")
|
||||
# Amber #D97706 = rgb(217, 119, 6)
|
||||
assert "217" in color and "119" in color, f"Active tab color should be amber, got: {color}"
|
||||
|
||||
|
||||
def test_dark_mode_contrast(page, app_url):
|
||||
"""Dark mode has proper contrast — bg is dark, text is light."""
|
||||
page.goto(f"{app_url}/")
|
||||
page.wait_for_load_state("networkidle")
|
||||
|
||||
# Enable dark mode
|
||||
page.evaluate("document.documentElement.setAttribute('data-theme', 'dark')")
|
||||
page.wait_for_timeout(100)
|
||||
|
||||
bg = page.evaluate("getComputedStyle(document.body).backgroundColor")
|
||||
color = page.evaluate("getComputedStyle(document.body).color")
|
||||
|
||||
# bg should be dark (#121212 = rgb(18, 18, 18))
|
||||
assert "18" in bg, f"Dark mode bg should be dark, got: {bg}"
|
||||
# text should be light (#E8E4DD = rgb(232, 228, 221))
|
||||
assert "232" in color or "228" in color, f"Dark mode text should be light, got: {color}"
|
||||
|
||||
# Cleanup
|
||||
page.evaluate("document.documentElement.removeAttribute('data-theme')")
|
||||
@@ -51,5 +51,5 @@ def test_dashboard_navigates_to_logs(page: Page, app_url: str):
|
||||
page.goto(f"{app_url}/")
|
||||
page.wait_for_load_state("networkidle")
|
||||
|
||||
logs_link = page.locator("a[href='/logs']")
|
||||
expect(logs_link).to_be_visible()
|
||||
logs_link = page.locator(".top-navbar a[href='/logs'], .bottom-nav a[href='/logs']")
|
||||
expect(logs_link.first).to_be_visible()
|
||||
|
||||
Reference in New Issue
Block a user