various bug fixes Sunday 20260301 5:15pm

This commit is contained in:
2026-03-01 23:44:47 +00:00
parent bcb0007217
commit 9dedf6dbf2
9 changed files with 2078 additions and 254 deletions

View File

@@ -328,3 +328,11 @@ def seed_note(all_seeds):
@pytest.fixture(scope="session")
def seed_meeting(all_seeds):
return {"id": all_seeds["meeting"], "title": "Test Meeting"}
@pytest.fixture(scope="session")
def seed_list(all_seeds):
return {"id": all_seeds["list"], "name": "Test List"}
@pytest.fixture(scope="session")
def seed_appointment(all_seeds):
return {"id": all_seeds["appointment"], "title": "Test Appointment"}

File diff suppressed because it is too large Load Diff

View File

@@ -33,10 +33,13 @@ from tests.form_factory import build_form_data, build_edit_data
_CREATE_ROUTES = [r for r in ALL_ROUTES if r.kind == RouteKind.CREATE and not r.has_file_upload]
_EDIT_ROUTES = [r for r in ALL_ROUTES if r.kind == RouteKind.EDIT and not r.has_file_upload]
_DELETE_ROUTES = [r for r in ALL_ROUTES if r.kind == RouteKind.DELETE]
_ACTION_ROUTES = [r for r in ALL_ROUTES if r.kind in (RouteKind.ACTION, RouteKind.TOGGLE)]
# Destructive actions that wipe data other tests depend on
_DESTRUCTIVE_ACTIONS = {"/admin/trash/empty", "/admin/trash/{table}/{item_id}/permanent-delete"}
# Admin trash actions are excluded here — covered by TestAdminTrashLifecycle in test_business_logic.py
_ADMIN_TRASH_PATHS = {
"/admin/trash/empty",
"/admin/trash/{table}/{item_id}/permanent-delete",
"/admin/trash/{table}/{item_id}/restore",
}
_ACTION_ROUTES = [r for r in ALL_ROUTES if r.kind in (RouteKind.ACTION, RouteKind.TOGGLE) and r.path not in _ADMIN_TRASH_PATHS]
# ---------------------------------------------------------------------------
@@ -97,10 +100,6 @@ async def test_edit_redirects(client: AsyncClient, all_seeds: dict, route):
)
async def test_action_does_not_crash(client: AsyncClient, all_seeds: dict, route):
"""POST action routes should not return 500."""
# Skip destructive actions that would wipe seed data
if route.path in _DESTRUCTIVE_ACTIONS:
pytest.skip(f"Skipping destructive action {route.path}")
resolved = resolve_path(route.path, all_seeds)
if "{" in resolved:
pytest.skip(f"No seed data mapping for {route.path}")

102
tests/test_mobile_nav.py Normal file
View 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}"