Files
Timmy-time-dashboard/tests/dashboard/test_mobile_scenarios.py
Timmy Time 7da434c85b
Some checks failed
Tests / lint (push) Successful in 3s
Tests / test (push) Has been cancelled
[loop-cycle-946] refactor: complete airllm removal (#486) (#545)
2026-03-19 20:46:20 -04:00

207 lines
7.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Mobile-first quality tests — automated validation of mobile UX requirements.
These tests verify the HTML, CSS, and HTMX attributes that make the dashboard
work correctly on phones. No browser / Playwright required: we parse the
static assets and server responses directly.
Categories:
M1xx Viewport & meta tags
M2xx Touch target sizing
M3xx iOS keyboard & zoom prevention
M4xx HTMX robustness (double-submit, sync)
M5xx Safe-area / notch support
"""
import re
from pathlib import Path
# ── helpers ───────────────────────────────────────────────────────────────────
def _css() -> str:
"""Read the main stylesheet."""
css_path = Path(__file__).parent.parent.parent / "static" / "style.css"
return css_path.read_text()
def _index_html(client) -> str:
return client.get("/").text
def _timmy_panel_html(client) -> str:
"""Fetch the Timmy chat panel (loaded dynamically from index via HTMX)."""
return client.get("/agents/default/panel").text
# ── M1xx — Viewport & meta tags ───────────────────────────────────────────────
def test_M101_viewport_meta_present(client):
"""viewport meta tag must exist for correct mobile scaling."""
html = _index_html(client)
assert 'name="viewport"' in html
def test_M102_viewport_includes_width_device_width(client):
html = _index_html(client)
assert "width=device-width" in html
def test_M103_viewport_includes_initial_scale_1(client):
html = _index_html(client)
assert "initial-scale=1" in html
def test_M104_viewport_includes_viewport_fit_cover(client):
"""viewport-fit=cover is required for iPhone notch / Dynamic Island support."""
html = _index_html(client)
assert "viewport-fit=cover" in html
def test_M105_apple_mobile_web_app_capable(client):
"""Enables full-screen / standalone mode when added to iPhone home screen."""
html = _index_html(client)
assert "apple-mobile-web-app-capable" in html
def test_M106_theme_color_meta_present(client):
"""theme-color sets the browser chrome colour on Android Chrome."""
html = _index_html(client)
assert 'name="theme-color"' in html
def test_M107_apple_status_bar_style_present(client):
html = _index_html(client)
assert "apple-mobile-web-app-status-bar-style" in html
def test_M108_lang_attribute_on_html(client):
"""lang attribute aids screen readers and mobile TTS."""
html = _index_html(client)
assert '<html lang="en"' in html
# ── M2xx — Touch target sizing ────────────────────────────────────────────────
def test_M201_send_button_min_height_44px():
"""SEND button must be at least 44 × 44 px — Apple HIG minimum."""
css = _css()
# Inside the mobile media query the send button must have min-height: 44px
assert "min-height: 44px" in css
def test_M203_send_button_min_width_64px():
"""Send button needs sufficient width so it isn't accidentally missed."""
css = _css()
assert "min-width: 64px" in css
def test_M204_touch_action_manipulation_on_buttons():
"""touch-action: manipulation removes 300ms tap delay on mobile browsers."""
css = _css()
assert "touch-action: manipulation" in css
# ── M3xx — iOS keyboard & zoom prevention ─────────────────────────────────────
def test_M301_input_font_size_16px_in_mobile_query():
"""iOS Safari zooms in when input font-size < 16px. Must be exactly 16px."""
css = _css()
# The mobile media-query block must override to 16px
mobile_block_match = re.search(r"@media\s*\(max-width:\s*768px\)(.*)", css, re.DOTALL)
assert mobile_block_match, "Mobile media query not found"
mobile_block = mobile_block_match.group(1)
assert "font-size: 16px" in mobile_block
def test_M302_input_autocapitalize_none(client):
"""autocapitalize=none prevents iOS from capitalising chat commands."""
html = _timmy_panel_html(client)
assert 'autocapitalize="none"' in html
def test_M303_input_autocorrect_off(client):
"""autocorrect=off prevents iOS from mangling technical / proper-noun input."""
html = _timmy_panel_html(client)
assert 'autocorrect="off"' in html
def test_M304_input_enterkeyhint_send(client):
"""enterkeyhint=send labels the iOS return key 'Send' for clearer UX."""
html = _timmy_panel_html(client)
assert 'enterkeyhint="send"' in html
def test_M305_input_spellcheck_false(client):
"""spellcheck=false prevents red squiggles on technical terms."""
html = _timmy_panel_html(client)
assert 'spellcheck="false"' in html
# ── M4xx — HTMX robustness ────────────────────────────────────────────────────
def test_M401_form_hx_sync_drop(client):
"""hx-sync=this:drop discards duplicate submissions (fast double-tap)."""
html = _timmy_panel_html(client)
assert 'hx-sync="this:drop"' in html
def test_M402_form_hx_disabled_elt(client):
"""hx-disabled-elt disables the SEND button while a request is in-flight."""
html = _timmy_panel_html(client)
assert "hx-disabled-elt" in html
def test_M403_form_hx_indicator(client):
"""hx-indicator wires up the loading spinner to the in-flight state."""
html = _timmy_panel_html(client)
assert "hx-indicator" in html
def test_M404_health_panel_auto_refreshes(client):
"""Health panel must poll via HTMX trigger — 'every 30s' confirms this."""
html = _index_html(client)
assert "every 30s" in html
def test_M405_chat_log_loads_history_on_boot(client):
"""Chat log fetches history via hx-trigger=load so it's populated on open."""
html = _index_html(client)
assert 'hx-trigger="load"' in html
# ── M5xx — Safe-area / notch support ─────────────────────────────────────────
def test_M501_safe_area_inset_top_in_header():
"""Header padding must accommodate the iPhone notch / status bar."""
css = _css()
assert "safe-area-inset-top" in css
def test_M502_safe_area_inset_bottom_in_footer():
"""Chat footer padding must clear the iPhone home indicator bar."""
css = _css()
assert "safe-area-inset-bottom" in css
def test_M503_overscroll_behavior_none():
"""overscroll-behavior: none prevents the jarring rubber-band effect."""
css = _css()
assert "overscroll-behavior: none" in css
def test_M504_webkit_overflow_scrolling_touch():
"""-webkit-overflow-scrolling: touch gives momentum scrolling on iOS."""
css = _css()
assert "-webkit-overflow-scrolling: touch" in css
def test_M505_dvh_units_used():
"""Dynamic viewport height (dvh) accounts for collapsing browser chrome."""
css = _css()
assert "dvh" in css