From c42caf3c0e616daadbe2ee9157d5764b78320814 Mon Sep 17 00:00:00 2001 From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:06:10 +0200 Subject: [PATCH] chore: test the round 4 components --- tests/test_site_r4_components.py | 266 +++++++++++++++++++++++++++++++ wiki/testing-guide.md | 2 +- 2 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 tests/test_site_r4_components.py diff --git a/tests/test_site_r4_components.py b/tests/test_site_r4_components.py new file mode 100644 index 00000000..40a6fac5 --- /dev/null +++ b/tests/test_site_r4_components.py @@ -0,0 +1,266 @@ +"""Round 4, package 2: components and runtime. + +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. +""" + +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" +SHORT_CODE = WEBSITE / "src" / "plugins" / "rehype-short-code.mjs" +LOGOS = REPO_ROOT / "docs_assets" / "logos" + +# The four hashes of one dump, bios/Sony/PlayStation/scph5501.bin. +SCPH5501 = { + "md5": "490f666e1afb15b7362b406ed1cea246", + "sha1": "0555c6fae8906f3f09baf5988f00e55f88e9f30b", + "crc32": "8d8cb7e4", + "sha256": "11052b6499e466bbf0a709b1f9cb6834a9418e66680387912451e971cf8a1fef", +} +ANDROID_UA = ("Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/130.0.0.0 Mobile Safari/537.36") + + +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 _node(script: str) -> str: + node = shutil.which("node") + if not node: + raise unittest.SkipTest("node is missing") + result = subprocess.run( + [node, "--input-type=module-typescript", "-e", script], + capture_output=True, text=True, check=True, cwd=WEBSITE, + ) + return result.stdout + + +def _short_unmarked(html: str) -> list[str]: + """Inline code of 30 characters or fewer without rb-short, outside pre.""" + body = re.sub(r"
", "", html, flags=re.S)
+ found = []
+ for attributes, text in re.findall(r"]*)?>([^<]*)", body):
+ plain = re.sub(r"&(?:#\d+|#x[0-9a-f]+|[a-z]+);", "_", text, flags=re.I)
+ if 0 < len(plain) <= 30 and "rb-short" not in (attributes or ""):
+ found.append(text)
+ return found
+
+
+class OneVerdictWhateverHash(unittest.TestCase):
+ """2.1: the SHA1 of scph5501.bin found 4 setups, its MD5 12, its SHA256
+ none (V1, J4). A setup's list writes one hash of a file."""
+
+ def test_the_other_hashes_are_read(self):
+ source = _read(SITE_TS)
+ self.assertIn("const heldHashes = (file: Held) =>", source)
+ self.assertIn("await lookupValues(others)", source)
+ self.assertIn("item.sha1 ?? files.find((file) => file && heldHashes(file).includes(item.value))?.sha1", source)
+ self.assertIn("(fileOf(declared) ?? item.sha1) === item.sha1", source)
+
+ def test_the_verdict_names_the_other_name(self):
+ source = _read(SITE_TS)
+ self.assertIn("'another name' : 'other names'}: `", source)
+ self.assertNotIn("under the ${otherNames.length === 1 ? 'name' : 'names'}", source)
+
+ def test_entries_match_on_any_hash_of_the_file(self):
+ source = _read(SITE_TS)
+ self.assertIn("const HASH_FIELDS = ['sha1', 'md5', 'crc32', 'sha256'];", source)
+ self.assertIn("declaredEntry(item, valuesOf(item))", source)
+ self.assertIn("readerEntry(reader.get(item.owner ?? ''), item, valuesOf(item))", source)
+
+ def test_what_the_renderer_reads_is_built(self):
+ """Each hash finds the collection record with its SHA1, and the
+ manufacturer file gives the other three, so every paste reads the
+ same four shards."""
+ api = DIST / "api" / "v1"
+ if not (api / "lookup").is_dir():
+ self.skipTest("no built site")
+ for value in SCPH5501.values():
+ with self.subTest(value=value):
+ shard = json.loads(_read(api / "lookup" / f"{value[:2]}.json"))["items"]
+ held = [i for i in shard if i["value"] == value and i["layer"] == "collection"]
+ self.assertEqual([i["sha1"] for i in held], [SCPH5501["sha1"]])
+ self.assertIn("/systems/sony-playstation/", held[0]["url"])
+ consoles = json.loads(_read(api / "systems" / "sony.json"))["items"][0]["consoles"]
+ card = next(f for files in consoles.values() for f in files if f["sha1"] == SCPH5501["sha1"])
+ self.assertEqual({k: card[k] for k in SCPH5501}, SCPH5501)
+
+
+class TheVisitorsTab(unittest.TestCase):
+ """2.2: on an Android phone the tabs opened on Windows (V4, T-F1)."""
+
+ def _rule(self) -> str:
+ source = _read(SITE_TS)
+ return source[source.index("const OS_TAB_RULES"):source.index("// End of the OS tab rule.")]
+
+ def test_labels_from_the_user_agent(self):
+ cases = [
+ [ANDROID_UA, ""],
+ ["Mozilla/5.0 (X11; Linux x86_64) Valve Steam GameOverlay/1729", "Linux"],
+ ["Mozilla/5.0 (X11; Linux x86_64; SteamOS) Firefox/130.0", ""],
+ ["Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) Safari/605.1.15", ""],
+ ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/130.0", "Windows"],
+ ["Mozilla/5.0 (X11; Linux x86_64) Chrome/130.0", "Linux"],
+ ["Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) Safari/604.1", ""],
+ ["Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) Chrome/130.0", "Chrome OS"],
+ ]
+ out = _node(self._rule() + f"console.log(JSON.stringify({json.dumps(cases)}.map(([ua, p]) => osTabLabel(ua, p))));")
+ self.assertEqual(json.loads(out), [
+ "Android (Termux)", "Steam Deck", "Steam Deck", "macOS", "Windows", "Linux", "", "",
+ ])
+
+ def test_labels_are_tab_labels(self):
+ guides = _read(REPO_ROOT / "platforms" / "_guides.yml")
+ for label in re.findall(r"\[/[^/]+/i, '([^']+)'\]", self._rule()):
+ with self.subTest(label=label):
+ self.assertIn(f"label: {label}\n", guides)
+
+ def test_a_saved_choice_wins_and_the_guess_is_not_saved(self):
+ source = _read(SITE_TS)
+ block = source[source.index("const osTabs = () =>"):source.index("// The sidebar lists")]
+ self.assertIn("savedTab(OS_SYNC_KEY)", block)
+ self.assertIn("set.switchTab(set.tabs[index], index, false)", block)
+ self.assertNotIn("localStorage.setItem", source)
+ self.assertNotIn("keepTabInView", source)
+
+
+class TheSidebarShowsTheCurrentPage(unittest.TestCase):
+ """2.3: the current link sat at y 1364 in a 900 px window (V5)."""
+
+ def test_the_pane_scrolls_never_the_page(self):
+ source = _read(SITE_TS)
+ block = source[source.index("const showCurrentPage = () =>"):source.index("// A glossary term")]
+ self.assertIn("pane.scrollTop +=", block)
+ self.assertIn("pane.clientHeight / 3", block)
+ self.assertIn("addEventListener('toggle'", block)
+ self.assertNotIn("scrollIntoView", source)
+ self.assertIn("showCurrentPage();", source)
+
+
+class ChromeOutOfTheIndex(unittest.TestCase):
+ """2.4: excerpts opened on the title and closed on the licence line, and
+ "bios" matched every page through the footer (N2, V6)."""
+
+ def test_components(self):
+ title = _read(COMPONENTS / "PageTitle.astro")
+ self.assertIn('