Files
odysseus/tests/test_static_stylesheet_manifest.py
T
Léo 1863de33c2 test(css): pin computed styles against a committed baseline
static/style.css is 51,425 lines in one file. Hundreds of selectors are
declared more than once and !important is used throughout, so the rendered
result is a function of source order. Extracting a block into its own file
changes that order, and nothing in the suite would notice - which makes a
51k-line split unfalsifiable and "looks fine to me" the only available
evidence.

This moves no CSS. It captures getComputedStyle over a fixed inventory of
676 elements across three pages, four viewports, both themes and the three
density modes - 16,224 element snapshots - hashes them, and compares against
tests/css_snapshot/baseline.json. A capture takes about 21 seconds.

The bench page synthesises one element per selector from an evidence-driven
list: every selector declared more than once in style.css that can be
expressed as a static compound chain, plus a curated set per feature area.
Redeclared selectors are the ones a reorder can flip. The bench loads
whatever stylesheets index.html ships, so it keeps measuring the real set
once the file is split. tests/test_css_computed_style_snapshot.py also carries
a self-test that swaps two conflicting .attach-strip declarations and asserts
the digest moves, so the harness cannot silently stop watching.

The second half is the asset-manifest check specs/frontend.md asks for,
scoped to stylesheets: every stylesheet referenced by shipped HTML and by the
sw.js precache exists, and index.html and sw.js agree on the ?v= string. They
hardcode it independently today, so a split that updates one and not the
other ships an offline cache nobody notices until a plane.
2026-09-25 11:32:46 +02:00

103 lines
4.1 KiB
Python

"""Stylesheet asset manifest: shipped HTML and the service worker must agree.
``specs/frontend.md`` records that nothing validates the service-worker
precache against what ``index.html`` actually loads, and that a static asset
manifest regression should confirm referenced files exist. Both matter more
once ``static/style.css`` starts being split: every new stylesheet has to be
registered in ``static/index.html`` *and* in the ``static/sw.js`` precache,
with the same ``?v=`` cache-bust string in both. Today those two strings are
hardcoded independently, so a split that updates one and not the other ships an
offline cache that serves a stale or missing stylesheet - invisible until
someone loads the app without a network.
This test reads the shipped files rather than driving a browser: the artifact
under test *is* the URL string in two files, and a browser would only see one
of them at a time.
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
INDEX_HTML = ROOT / "static" / "index.html"
SERVICE_WORKER = ROOT / "static" / "sw.js"
_STYLESHEET_LINK = re.compile(
r"""<link\b[^>]*\brel=["']stylesheet["'][^>]*>""", re.IGNORECASE)
_HREF = re.compile(r"""\bhref=["']([^"']+)["']""", re.IGNORECASE)
def _stylesheet_hrefs(html_path):
"""Every href from a <link rel="stylesheet"> in one HTML file."""
html = html_path.read_text(encoding="utf-8")
hrefs = []
for tag in _STYLESHEET_LINK.findall(html):
match = _HREF.search(tag)
if match:
hrefs.append(match.group(1))
return hrefs
def _precache_entries(name):
"""The quoted URLs of one precache array in sw.js.
Entries built by spreading another list (``...KATEX_FONTS``) are skipped:
they carry no literal URL at this level, and the fonts they add are not
stylesheets.
"""
source = SERVICE_WORKER.read_text(encoding="utf-8")
match = re.search(rf"const {name} = \[(.*?)\n\];", source, re.DOTALL)
assert match, f"{name} array not found in static/sw.js"
return re.findall(r"""['"](/[^'"]+)['"]""", match.group(1))
def _local_path(url):
"""Map an app-absolute URL to the file it is served from."""
return ROOT / url.split("?", 1)[0].lstrip("/")
def test_every_stylesheet_referenced_by_shipped_html_exists():
checked = 0
for html_path in sorted((ROOT / "static").glob("*.html")):
for href in _stylesheet_hrefs(html_path):
if not href.startswith("/"):
continue # external or relative-to-page; not ours to resolve
assert _local_path(href).is_file(), (
f"{html_path.name} links {href}, which does not exist on disk"
)
checked += 1
assert checked, "no app stylesheet links found - the parser or the markup moved"
def test_every_precached_stylesheet_exists():
for name in ("PRECACHE", "PANEL_PRECACHE"):
for url in _precache_entries(name):
if not url.endswith(".css") and ".css?" not in url:
continue
assert _local_path(url).is_file(), (
f"sw.js {name} precaches {url}, which does not exist on disk"
)
def test_index_stylesheets_are_precached_with_the_same_cache_bust_string():
"""The precache must match the request the browser actually makes.
A service-worker entry only matches a fetch when the URL is identical,
query string included, so a stylesheet whose ``?v=`` differs between the
two files is precached under a URL nothing ever requests.
"""
precached = set(_precache_entries("PRECACHE"))
precached_paths = {url.split("?", 1)[0]: url for url in precached}
for href in _stylesheet_hrefs(INDEX_HTML):
if not href.startswith("/static/"):
continue
path = href.split("?", 1)[0]
assert path in precached_paths, (
f"index.html loads {href} but sw.js PRECACHE has no entry for {path}; "
"the app shell would not be available offline"
)
assert precached_paths[path] == href, (
f"cache-bust mismatch for {path}: index.html requests {href!r}, "
f"sw.js precaches {precached_paths[path]!r}"
)