101 lines
4.1 KiB
Python
101 lines
4.1 KiB
Python
"""Every admin page links to every other admin page.
|
|
|
|
Two pages had drifted in opposite directions -- providers.html was missing
|
|
Proficiency and proficiency.html was missing Providers -- so you could not
|
|
navigate between them in either direction without editing the URL. Neither
|
|
gap was noticeable from the page you were on, which is why a test is worth
|
|
more here than care.
|
|
|
|
The page list is derived from the routes the router actually serves, so a
|
|
new admin page fails this until it is linked from the others, rather than
|
|
quietly launching unreachable.
|
|
|
|
The links render client-side now: navbar.js builds them from its
|
|
NAV_LINKS array into the empty <ul id="nav-links"> every page ships, so
|
|
statically parsing each page for anchors would report every link missing
|
|
on every page. The invariant the old parse guarded -- a complete list,
|
|
present everywhere -- moved with the list itself: it is defined exactly
|
|
once (in navbar.js, as complete as the route table) and every page carries
|
|
the mount that renders it. Hardcoded <li class="nav-item"> entries are
|
|
how the pages drifted originally, so their return fails this too. The
|
|
active-page highlighting lives in tests/test_admin_js_units.py, which
|
|
runs navbar.js's real activeNavHref through node.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
FRONTEND = Path(__file__).resolve().parent.parent / "admin" / "frontend"
|
|
NAVBAR_JS = FRONTEND / "navbar.js"
|
|
|
|
# href -> file that serves it. Kept explicit rather than globbed so that
|
|
# adding a page is a deliberate two-line edit here, not an accident.
|
|
PAGES = {
|
|
"/admin/": "index.html",
|
|
"/admin/models": "models.html",
|
|
"/admin/profiles": "profiles.html",
|
|
"/admin/proficiency": "proficiency.html",
|
|
"/admin/decisions": "decisions.html",
|
|
"/admin/quota": "quota.html",
|
|
"/admin/controls": "controls.html",
|
|
"/admin/providers": "providers.html",
|
|
}
|
|
|
|
# The nav renders every page except the landing page, which is reached by
|
|
# the logo -- a second door to the same place, in the row where width is
|
|
# scarcest. Order is the order the bar displays.
|
|
NAV_HREFS = list(PAGES)[1:]
|
|
|
|
NAV_LINKS_BEGIN = "/* NAV_LINKS:BEGIN */"
|
|
NAV_LINKS_END = "/* NAV_LINKS:END */"
|
|
|
|
NAV_RE = re.compile(r'<header class="navbar.*?</header>', re.DOTALL)
|
|
|
|
|
|
def _nav_hrefs() -> list[str]:
|
|
"""The hrefs navbar.js renders, in file order, read from NAV_LINKS."""
|
|
js = NAVBAR_JS.read_text(encoding="utf-8")
|
|
begin = js.find(NAV_LINKS_BEGIN)
|
|
end = js.find(NAV_LINKS_END)
|
|
assert begin != -1, "navbar.js: NAV_LINKS block not found"
|
|
assert end > begin, "navbar.js: NAV_LINKS:END absent or before begin"
|
|
return re.findall(r"href:\s*'([^']+)'", js[begin:end])
|
|
|
|
|
|
def test_navbar_js_owns_the_complete_link_list():
|
|
"""The one list is as complete as the route table, in bar order.
|
|
|
|
A page added to PAGES without a NAV_LINKS entry fails here while it
|
|
is still a two-line fix, instead of launching silently unreachable.
|
|
"""
|
|
assert _nav_hrefs() == NAV_HREFS, (
|
|
f"NAV_LINKS drifted from the served pages; expected {NAV_HREFS}"
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("filename", sorted(PAGES.values()))
|
|
def test_every_page_ships_the_mount_and_the_script(filename):
|
|
"""Each page carries the empty #nav-links ul and the script that fills it.
|
|
|
|
Old failure shape, new shape: instead of parsing anchors (they only
|
|
exist after navbar.js runs in the browser), completeness per page is
|
|
the mount plus the script tag -- and the absence of any hardcoded
|
|
nav-item <li>, because those are what drifted across eight copies.
|
|
"""
|
|
html = (FRONTEND / filename).read_text(encoding="utf-8")
|
|
nav = NAV_RE.search(html)
|
|
assert nav, f'{filename}: no <header class="navbar"> block found'
|
|
assert '<ul class="navbar-nav" id="nav-links"></ul>' in nav.group(0), (
|
|
f"{filename}: the empty #nav-links mount is missing from the navbar"
|
|
)
|
|
assert '<script src="navbar.js"></script>' in html, (
|
|
f"{filename}: navbar.js is not included"
|
|
)
|
|
assert '<li class="nav-item' not in nav.group(0), (
|
|
f"{filename}: hardcoded nav entries are back -- render them from navbar.js"
|
|
)
|