|
| 1 | +"""Regression test for highlight.js theme URL+hash drift between |
| 2 | +static/index.html (initial load) and static/js/app.js (runtime swap). |
| 3 | +
|
| 4 | +Both files carry the dark-theme stylesheet URL and its SRI hash. On a |
| 5 | +highlight.js version bump both must update together — if they drift, either |
| 6 | +the initial load breaks (HTML stale, mismatched hash) or the runtime theme |
| 7 | +swap breaks (JS stale). This test fails fast when they diverge so the |
| 8 | +"MUST also swap the integrity attribute" comments in both files become a |
| 9 | +checked invariant rather than a hope. |
| 10 | +""" |
| 11 | + |
| 12 | +from __future__ import annotations |
| 13 | + |
| 14 | +import re |
| 15 | +from pathlib import Path |
| 16 | + |
| 17 | +REPO_ROOT = Path(__file__).resolve().parent.parent |
| 18 | +INDEX_HTML = REPO_ROOT / "static" / "index.html" |
| 19 | +APP_JS = REPO_ROOT / "static" / "js" / "app.js" |
| 20 | + |
| 21 | + |
| 22 | +def _link_attr(html: str, link_id: str, attr: str) -> str: |
| 23 | + """Return the value of `attr` on the <link> tag with id=`link_id`.""" |
| 24 | + tag_re = re.compile( |
| 25 | + r'<link\b[^>]*\bid\s*=\s*"' + re.escape(link_id) + r'"[^>]*>', |
| 26 | + re.DOTALL, |
| 27 | + ) |
| 28 | + m = tag_re.search(html) |
| 29 | + assert m, f'No <link id="{link_id}"> found in index.html' |
| 30 | + attr_m = re.search(re.escape(attr) + r'\s*=\s*"([^"]*)"', m.group(0)) |
| 31 | + assert attr_m, f'<link id="{link_id}"> has no {attr!r} attribute' |
| 32 | + return attr_m.group(1) |
| 33 | + |
| 34 | + |
| 35 | +def _js_theme_entry(js: str, theme: str) -> dict: |
| 36 | + """Return {'href': ..., 'integrity': ...} from HLJS_THEME_SHEETS.<theme>.""" |
| 37 | + block = re.search(re.escape(theme) + r"\s*:\s*\{([^}]*)\}", js, re.DOTALL) |
| 38 | + assert block, f"HLJS_THEME_SHEETS.{theme} entry not found in app.js" |
| 39 | + body = block.group(1) |
| 40 | + out = {} |
| 41 | + for key in ("href", "integrity"): |
| 42 | + m = re.search(key + r"\s*:\s*['\"]([^'\"]+)['\"]", body) |
| 43 | + assert m, f"HLJS_THEME_SHEETS.{theme} has no {key!r} key" |
| 44 | + out[key] = m.group(1) |
| 45 | + return out |
| 46 | + |
| 47 | + |
| 48 | +def test_dark_theme_url_and_hash_match_between_html_and_js(): |
| 49 | + html = INDEX_HTML.read_text(encoding="utf-8") |
| 50 | + js = APP_JS.read_text(encoding="utf-8") |
| 51 | + |
| 52 | + html_href = _link_attr(html, "hljs-theme", "href") |
| 53 | + html_integrity = _link_attr(html, "hljs-theme", "integrity") |
| 54 | + js_dark = _js_theme_entry(js, "dark") |
| 55 | + |
| 56 | + assert html_href == js_dark["href"], ( |
| 57 | + "highlight.js theme URL drifted between index.html and app.js — " |
| 58 | + f"html={html_href!r}, app.js HLJS_THEME_SHEETS.dark={js_dark['href']!r}. " |
| 59 | + "On a version bump both must update together (issue #19)." |
| 60 | + ) |
| 61 | + assert html_integrity == js_dark["integrity"], ( |
| 62 | + "highlight.js theme SRI hash drifted between index.html and app.js — " |
| 63 | + f"html={html_integrity!r}, " |
| 64 | + f"app.js HLJS_THEME_SHEETS.dark={js_dark['integrity']!r}." |
| 65 | + ) |
0 commit comments