"""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. Source checks always run. Checks on the built site read website/dist and skip when no build is there. The hash functions and the table rule run under node and skip without it. Source checks always run. Checks on the built site read website/dist and skip when no build is there. The rules written in TypeScript or in a build plugin run under node and skip without it. Source checks always run. Checks on the built site read website/dist and skip when no build is there. The rules written in a build plugin run under node and skip without it. """ from __future__ import annotations import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache import json import re import shutil import subprocess import unittest from pathlib import Path import hashlib import struct import zlib import html import sys REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT / "scripts")) from common import list_release_pack_platforms # noqa: E402 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"", html, re.S) def _headers(table: str) -> list[str]: head = re.search(r"(.*?)", table, re.S) cells = 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"], "rows": 97, "action": False, "level": "setup"}, "reference": {"labels": ["A", "B", "C"], "rows": 5, "action": False, "level": "reference"}, # Four columns do not fit a phone column, on any page. "wide": {"labels": ["File", "Region", "Required", "Hash"], "rows": 105, "action": False, "level": "reference"}, # A hash never breaks: three columns with one overflow a phone (NetherSX2). "hashes": {"labels": ["File", "Required", "Hash"], "rows": 40, "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, "wide": "true", "hashes": "true", }) 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("UNSHOWN_FIELDS.has(tag.dataset.pagefindUiMeta ?? '')", 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", "Setups", "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'