#steering-toggle (the header 'Tuning' button) shipped visible in all six pages and was only removed after /api/whoami resolved, so anonymous visitors saw it flash for the whole round-trip (TODO.md L3). It now ships hidden on every page and initSharedHeader unhides it only for admin — the same ship-hidden / reveal-for-admin contract as the admin-only nav links; the anonymous end-state (removed from the DOM, phase-16 'absent, not hidden') is unchanged. Adds the story E2E suite (MutationObserver proves zero visible frames for anonymous on every page, admin reveal + panel + count badge, nav-contract regression) and the source-level unit pins. Also fixes test_steering.py's BASE_SCRIPT_COUNT (2 → 3: brand.js + markdown.js + app.js, since phase 39).
151 lines
6.1 KiB
Python
151 lines
6.1 KiB
Python
"""Unit: the tuning toggle ships hidden — the anonymous flash fix (phase 40).
|
|
|
|
The "Tuning" steering toggle (``#steering-toggle``) used to ship VISIBLE
|
|
in every page's shared header and was removed by ``assets/header.js``
|
|
only after ``/api/whoami`` resolved — so an anonymous user briefly saw
|
|
the button on every page load (``TODO.md`` L3). The fix mirrors the
|
|
admin-only NAV LINKS (phase 19/29/35 contract): the toggle now SHIPS
|
|
with the ``hidden`` attribute in all six pages and ``initSharedHeader``
|
|
unhides it only when whoami says admin. The browser behavior is
|
|
E2E-covered (``tests/e2e/test_tuning_toggle_flash.py``); here we pin
|
|
the source-level wiring — the ship-hidden markup on every page, the
|
|
admin unhide line inside ``initSharedHeader``, the intact anonymous
|
|
remove-from-DOM path (phase 16 "absent, not hidden"), and the
|
|
``#nav-tuning`` hidden contract this phase relies on — so a silent
|
|
regression is caught without a browser.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
FRONTEND = Path(__file__).resolve().parents[2] / "frontend"
|
|
ASSETS = FRONTEND / "assets"
|
|
|
|
HEADER_JS = ASSETS / "header.js"
|
|
|
|
#: All six pages carry the shared header block (phase 34's five pages +
|
|
#: phase 35's git-sources page).
|
|
PAGES = (
|
|
FRONTEND / "index.html",
|
|
FRONTEND / "sources.html",
|
|
FRONTEND / "document.html",
|
|
FRONTEND / "git-sources.html",
|
|
FRONTEND / "login.html",
|
|
FRONTEND / "tuning.html",
|
|
)
|
|
|
|
|
|
def _text(path: Path) -> str:
|
|
assert path.is_file(), f"missing frontend file: {path}"
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
def _toggle_tag(html: Path) -> str:
|
|
tag = re.search(r"<button[^>]*id=\"steering-toggle\"[^>]*>", _text(html))
|
|
assert tag, f"{html.name}: missing the #steering-toggle button"
|
|
return tag.group(0)
|
|
|
|
|
|
def _toggle_body(html: Path) -> str:
|
|
"""The full <button>…</button> block, for the icon/label/badge pins."""
|
|
text = _text(html)
|
|
start = text.find('id="steering-toggle"')
|
|
assert start != -1, f"{html.name}: missing the #steering-toggle button"
|
|
return text[start : text.find("</button>", start)]
|
|
|
|
|
|
def _init_body(js: str) -> str:
|
|
"""The source of initSharedHeader in header.js."""
|
|
fn = js.find("function initSharedHeader")
|
|
assert fn != -1, "initSharedHeader must be defined"
|
|
return js[fn : js.find("\n}", fn)]
|
|
|
|
|
|
# ---------- ship-hidden markup: zero flash for anonymous ----------
|
|
|
|
|
|
def test_steering_toggle_ships_hidden_on_all_six_pages() -> None:
|
|
"""#steering-toggle carries the ``hidden`` attribute in ALL SIX
|
|
pages — the exact ship-hidden contract the admin-only nav links
|
|
use, so an anonymous user never sees the "Tuning" button for a
|
|
single frame, on any page."""
|
|
for html in PAGES:
|
|
tag = _toggle_tag(html)
|
|
assert re.search(r"\bhidden\b", tag), (
|
|
f"{html.name}: #steering-toggle must ship hidden"
|
|
)
|
|
|
|
|
|
def test_steering_toggle_keeps_its_existing_markup() -> None:
|
|
"""Only the ``hidden`` attribute was added: type, class, the
|
|
aria-expanded / aria-controls wiring, the decorative icon, the
|
|
"Tuning" label, and the #steering-count badge stay byte-identical
|
|
on every page — the admin UX is unchanged."""
|
|
for html in PAGES:
|
|
tag = _toggle_tag(html)
|
|
assert 'type="button"' in tag
|
|
assert 'class="steering-toggle"' in tag
|
|
assert 'aria-expanded="false"' in tag
|
|
assert 'aria-controls="steering-panel"' in tag
|
|
body = _toggle_body(html)
|
|
assert '<svg aria-hidden="true"' in body, f"{html.name}: icon is gone"
|
|
assert '<span class="steering-label">Tuning</span>' in body, (
|
|
f"{html.name}: label markup changed"
|
|
)
|
|
assert '<span class="steering-count" id="steering-count">0</span>' in body, (
|
|
f"{html.name}: count badge markup changed"
|
|
)
|
|
|
|
|
|
def test_steering_panel_still_ships_hidden_on_all_six_pages() -> None:
|
|
"""The #steering-panel section already shipped hidden and stays
|
|
that way (this phase never touches the panel)."""
|
|
for html in PAGES:
|
|
tag = re.search(r'<section[^>]*id="steering-panel"[^>]*>', _text(html))
|
|
assert tag, f"{html.name}: missing the #steering-panel section"
|
|
assert re.search(r"\bhidden\b", tag.group(0)), "the panel ships hidden"
|
|
|
|
|
|
# ---------- header.js: reveal-for-admin, anonymous removal intact ----------
|
|
|
|
|
|
def test_header_js_unhides_the_toggle_for_admin() -> None:
|
|
"""Inside initSharedHeader, the admin branch unhides the toggle —
|
|
and that line sits BEFORE the refreshSteering() call (the count
|
|
badge is right before the panel is ever opened)."""
|
|
body = _init_body(_text(HEADER_JS))
|
|
assert "if (steeringToggle) steeringToggle.hidden = false;" in body, (
|
|
"the admin unhide is missing from initSharedHeader"
|
|
)
|
|
admin = body.find("if (admin)")
|
|
unhide = body.find("steeringToggle.hidden = false")
|
|
refresh = body.find("if (steeringPanel) refreshSteering();")
|
|
assert -1 < admin < unhide, "the unhide must live in the admin branch"
|
|
assert unhide < refresh, "the unhide must precede the refreshSteering() call"
|
|
|
|
|
|
def test_anonymous_removal_path_is_intact() -> None:
|
|
"""The phase-16 "absent, not hidden" contract is preserved: the
|
|
anonymous branch still REMOVES the toggle + panel from the DOM —
|
|
the new hidden attribute only closes the pre-whoami flash window,
|
|
the end state (absent) is unchanged."""
|
|
body = _init_body(_text(HEADER_JS))
|
|
assert "steeringToggle?.remove();" in body
|
|
assert "steeringPanel?.remove();" in body
|
|
|
|
|
|
# ---------- the nav contract this phase relies on ----------
|
|
|
|
|
|
def test_nav_tuning_still_ships_hidden_on_all_six_pages() -> None:
|
|
"""The admin-only Tuning NAV LINK (#nav-tuning) — the contract the
|
|
toggle now mirrors — still ships hidden on every page: one
|
|
ship-hidden / reveal-for-admin family, nav link and toggle alike."""
|
|
for html in PAGES:
|
|
tag = re.search(r'<a[^>]*id="nav-tuning"[^>]*>', _text(html))
|
|
assert tag, f"{html.name}: missing the #nav-tuning nav link"
|
|
assert re.search(r"\bhidden\b", tag.group(0)), (
|
|
f"{html.name}: #nav-tuning must ship hidden"
|
|
)
|