Initial commit
This commit is contained in:
102
tests/test_mobile_nav.py
Normal file
102
tests/test_mobile_nav.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""Tests that the mobile navigation bar is correctly structured and positioned."""
|
||||
import re
|
||||
import pytest
|
||||
from httpx import AsyncClient, ASGITransport
|
||||
|
||||
from tests.registry import app
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def client():
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as c:
|
||||
yield c
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mob_nav_exists(client):
|
||||
"""mob-nav element exists in page output."""
|
||||
resp = await client.get("/")
|
||||
assert resp.status_code == 200
|
||||
assert 'class="mob-nav"' in resp.text, "mob-nav not found in HTML"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mob_nav_is_direct_body_child(client):
|
||||
"""mob-nav must be a direct child of body, not nested in any container."""
|
||||
resp = await client.get("/")
|
||||
html = resp.text
|
||||
mob_idx = html.find('id="mobNav"')
|
||||
body_close = html.find('</body>')
|
||||
assert mob_idx != -1, "mobNav not found"
|
||||
assert body_close != -1, "</body> not found"
|
||||
between = html[mob_idx:body_close]
|
||||
assert between.count('</main>') == 0, "mob-nav appears to be inside <main>"
|
||||
assert between.count('</div></div></div>') == 0, "mob-nav appears deeply nested"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mob_nav_has_five_items(client):
|
||||
"""Bottom bar must have exactly 5 navigation items (4 links + 1 button)."""
|
||||
resp = await client.get("/")
|
||||
html = resp.text
|
||||
start = html.find('id="mobNav"')
|
||||
assert start != -1
|
||||
# Scope to just the mob-nav element (ends at first </div> after it)
|
||||
end = html.find('</div>', start)
|
||||
chunk = html[start:end]
|
||||
links = len(re.findall(r'<a\b', chunk))
|
||||
buttons = len(re.findall(r'<button\b', chunk))
|
||||
assert links == 4, f"Expected 4 link items, found {links}"
|
||||
assert buttons == 1, f"Expected 1 button item, found {buttons}"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mob_nav_has_inline_fixed_position(client):
|
||||
"""mob-nav must have position:fixed as an inline style for maximum reliability."""
|
||||
resp = await client.get("/")
|
||||
assert 'id="mobNav" style="position:fixed' in resp.text, \
|
||||
"mob-nav missing inline position:fixed style"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mob_nav_css_has_fixed_position(client):
|
||||
"""CSS must include position:fixed for mob-nav."""
|
||||
css_resp = await client.get("/static/style.css")
|
||||
css = css_resp.text
|
||||
assert "position: fixed" in css or "position:fixed" in css, \
|
||||
"No position:fixed found in CSS"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mob_nav_inline_style_in_head(client):
|
||||
"""Critical mob-nav styles must be inlined in <head> as a fallback."""
|
||||
resp = await client.get("/")
|
||||
html = resp.text
|
||||
head_end = html.find('</head>')
|
||||
head = html[:head_end]
|
||||
assert '.mob-nav' in head, "No inline mob-nav styles found in <head>"
|
||||
assert 'position:fixed' in head, "No position:fixed in inline <head> styles"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mob_nav_not_inside_transformed_parent(client):
|
||||
"""No ancestor of mob-nav should have transform that breaks position:fixed."""
|
||||
resp = await client.get("/")
|
||||
html = resp.text
|
||||
mob_idx = html.find('id="mobNav"')
|
||||
body_start = html.find('<body')
|
||||
prefix = html[body_start:mob_idx]
|
||||
opens = len(re.findall(r'<div\b[^>]*>', prefix))
|
||||
closes = prefix.count('</div>')
|
||||
nesting = opens - closes
|
||||
assert nesting <= 1, \
|
||||
f"mob-nav is nested {nesting} divs deep - must be 0 or 1 (direct body child)"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mob_nav_present_on_all_pages(client):
|
||||
"""mob-nav should appear on every page, not just dashboard."""
|
||||
for path in ["/", "/tasks/", "/focus/", "/capture/", "/contacts/"]:
|
||||
resp = await client.get(path)
|
||||
assert 'id="mobNav"' in resp.text, f"mob-nav missing on {path}"
|
||||
Reference in New Issue
Block a user