From 00c0b0f106213ad39c6be581e6e696375c525cdc Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 02:27:09 +0200 Subject: [PATCH] chore: add site component tests --- tests/test_site_r2_components.py | 229 +++++++++++++++++++++++++++++++ wiki/testing-guide.md | 2 +- 2 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 tests/test_site_r2_components.py diff --git a/tests/test_site_r2_components.py b/tests/test_site_r2_components.py new file mode 100644 index 00000000..bb9f21dd --- /dev/null +++ b/tests/test_site_r2_components.py @@ -0,0 +1,229 @@ +"""Round 2, package 2: the site's components and runtime. + +Source checks always run. Checks on the built site read website/dist and +skip when no build is there; the stacking rule runs the plugin under node +and skips without node or the site's node_modules. +""" + +from __future__ import annotations + +import json +import re +import shutil +import subprocess +import unittest +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +WEBSITE = REPO_ROOT / "website" +DIST = WEBSITE / "dist" +COMPONENTS = WEBSITE / "src" / "components" +SITE_TS = WEBSITE / "src" / "scripts" / "site.ts" +CONFIG = WEBSITE / "astro.config.mjs" +PLUGIN = WEBSITE / "src" / "plugins" / "rehype-stack-tables.mjs" + + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def _built(page: str) -> str: + path = DIST / page / "index.html" + if not path.is_file(): + raise unittest.SkipTest(f"no built site at {path}") + return _read(path) + + +def _tables(html: str) -> list[str]: + return re.findall(r"
| ]*>(.*?) | ", head.group(1), re.S) if head else [] + return [re.sub(r"<[^>]+>", "", cell).strip() for cell in cells] + + +class TablesStackWhenBuilt(unittest.TestCase): + """The setup tables of Get your files were 591, 503 and 511 px wide in a + 358 px phone column: site.ts stacked them only once they neared the + screen, so the ZIP column first painted off-screen.""" + + def test_the_plugin_is_registered_and_the_runtime_rule_is_gone(self): + config = _read(CONFIG) + self.assertIn("import stackTables from './src/plugins/rehype-stack-tables.mjs'", config) + self.assertIn("hastPlugins: [stackTables()]", config) + self.assertNotIn("stackActionTable", _read(SITE_TS)) + + def test_setup_tables_carry_the_layout_in_the_html(self): + html = _built("which-pack") + setups = [t for t in _tables(html) if _headers(t) == ["Setup", "Runs on", "BIOS folder", "ZIP"]] + self.assertGreaterEqual(len(setups), 3) + for table in setups: + with self.subTest(table=table[:80]): + self.assertIn('data-rb-stack="true"', table.split(">", 1)[0]) + cells = re.findall(r"]*>", table) + self.assertTrue(cells) + for cell in cells: + self.assertRegex(cell, r'data-label="(Setup|Runs on|BIOS folder|ZIP)"') + + def test_two_column_tables_are_label_and_value(self): + html = _built("platforms/retroarch") + pairs = [t for t in _tables(html) if len(_headers(t)) == 2] + self.assertTrue(pairs) + for table in pairs: + self.assertIn('data-rb-stack="kv"', table.split(">", 1)[0]) + + def test_the_rule_reads_the_table(self): + node = shutil.which("node") + if not node or not (WEBSITE / "node_modules").is_dir(): + self.skipTest("node or the site's node_modules is missing") + cases = { + # A setup added after the release: no ZIP link, the header still names one. + "zip-header": {"labels": ["Setup", "Runs on", "BIOS folder", "ZIP"], "rows": 5, "action": False}, + "action": {"labels": ["A", "B", "C"], "rows": 40, "action": True}, + "pair": {"labels": ["Where", "BIOS folder"], "rows": 3, "action": False}, + "short-start": {"labels": ["System", "Runs", "BIOS folder"], "rows": 8, "action": False, "level": "start"}, + "long-setup": {"labels": ["System", "Files", "Status", "Emulators"], "rows": 97, "action": False, "level": "setup"}, + "reference": {"labels": ["A", "B", "C"], "rows": 5, "action": False, "level": "reference"}, + } + script = ( + f"import {{ stackKind }} from {json.dumps(PLUGIN.as_uri())};" + f"const cases = {json.dumps(cases)};" + "console.log(JSON.stringify(Object.fromEntries(Object.entries(cases).map(([k, v]) => [k, stackKind(v)]))));" + ) + result = subprocess.run( + [node, "--input-type=module", "-e", script], + capture_output=True, text=True, check=True, cwd=WEBSITE, + ) + self.assertEqual(json.loads(result.stdout), { + "zip-header": "true", "action": "true", "pair": "kv", + "short-start": "true", "long-setup": None, "reference": None, + }) + + +class ScrollingTablesHaveTheirOwnName(unittest.TestCase): + """Every scrolling table was the region "Data table, scrollable table" + (axe landmark-unique on which-pack, retroarch, gaps).""" + + def test_no_bare_fallback_label(self): + source = _read(SITE_TS) + self.assertNotIn("'Data table'", source) + self.assertIn("`${tableName(table)}, scrollable table`", source) + self.assertIn("`${heading}, table ${count}`", source) + + +class CommandBlocksHaveNoWindowDots(unittest.TestCase): + def test_the_dots_are_off_in_the_theme(self): + self.assertIn("terminalTitlebarDotsOpacity: '0'", _read(CONFIG)) + css = DIST / "_astro" + if not css.is_dir(): + self.skipTest("no built site") + values = {m for f in css.glob("ec.*.css") for m in re.findall(r"--ec-frm-trmTtbDotsOpa:([^;]+)", _read(f))} + self.assertEqual(values, {"0"}) + + +class RedirectsComeFromTheGenerator(unittest.TestCase): + def test_config_reads_the_file_and_builds_without_it(self): + config = _read(CONFIG) + self.assertIn("./src/generated/redirects.json", config) + self.assertIn("if (error.code === 'ENOENT') return {};", config) + self.assertIn("redirects: readRedirects(),", config) + # Astro does not put the base before the new address. + self.assertIn("BASE + to.replace(/^\\//, '')", config) + + +class SearchAnswersAHashAndShowsNoFileDump(unittest.TestCase): + """Searching the scph5501 MD5 opened on Data model, results carried a + second "#_top" title, and a system page showed its 120 file names.""" + + def test_runtime(self): + source = _read(SITE_TS) + self.assertIn("Look up this hash", source) + self.assertIn("files/?hash=${value}", source) + self.assertIn("endsWith('#_top')", source) + self.assertIn("tag.dataset.pagefindUiMeta === 'files'", source) + # Pagefind's display rules beat the hidden attribute without this. + self.assertIn("#starlight__search [hidden] { display: none !important; }", _read(COMPONENTS / "Header.astro")) + + +class SectionTabsInTheHeader(unittest.TestCase): + def test_registered_and_built(self): + self.assertIn("Header: './src/components/Header.astro'", _read(CONFIG)) + html = _built("which-pack") + nav = re.search(r'', html, re.S) + self.assertIsNotNone(nav) + labels = [re.sub(r"<[^>]+>", "", a).strip() for a in re.findall(r"]*>.*?", nav.group(1), re.S)] + self.assertEqual(labels, ["Start here", "Your setup", "Explore", "Reference", "Contribute"]) + self.assertEqual(nav.group(1).count('aria-current="true"'), 1) + self.assertRegex(nav.group(1), r'aria-current="true"[^>]*>\s*Start here') + + +def _title_block(html: str) -> str: + """What PageTitle draws: from the main column to the h1.""" + end = html.index('id="_top"') + return html[html.rfind("', title) + + +class FooterSaysWhoAndWhen(unittest.TestCase): + def test_release_and_maintainer(self): + tag = json.loads(_read(REPO_ROOT / "release.json"))["tag"] + html = _built("which-pack") + footer = html[html.index('class="rb-footer'):] + self.assertIn("https://github.com/Abdess/retrobios/issues/new", footer) + self.assertIn(f"/releases/tag/{tag}", footer) + match = re.fullmatch(r"v(\d{4})\.(\d{2})\.(\d{2})", tag) + if match: + self.assertIn(f' |
|---|