"""Entry pages, get/ pages and guides. The home page said the command "writes only in your BIOS folder" while install.py writes the entries a manifest files under `root: save` into the frontend's save folder. It ended on a tinted "Next" box, a raw ISO stamp and a setup count the diagrams gave one lower. Get your files offered the same choice three times, a Batocera page sent a beginner to "a terminal on the device" with no way to open one, and command blocks had no Windows form or no title. A handheld owner found no tile for Android, ArkOS, muOS, Onion or spruceOS: the systems sat in a closed note with a path seen from inside the device. The setup version a pack fits was named only under Reference, the setup page offered the full pack only, Get your files listed every setup twice, the Your setup index ended on a table in project words, and the home page said a Dreamcast needs its BIOS while Flycast boots without one. Six handheld setups have no ZIP, and three of them offered SSH as root as the only route while their page already named a network folder. Six rows of the Linux handhelds table said "The command finds it on the device" for devices with no terminal, while the installer held each folder with its source. The offline line sat on six setup pages, the home page carried a stray "Reference:" label, the Your setup index left out the handheld tiles, and an iPhone owner found one FAQ line. The get/ page of a setup with no ZIP printed one command for three systems, with no --dest, so it failed on the computer most readers open it on. "All good when you see 0 wrong hash" was also what an empty folder prints. Setups with no ZIP lost every route that works without a network. The get/ pages were titled "Download KNULLI", /get/ answered 404, and two Linux handheld rows sent the reader to a menu. """ from __future__ import annotations import tests # noqa: F401 offline, scratch under tmp/, the suite's own cache import json import re import sys import unittest from datetime import datetime, timezone from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT / "scripts")) sys.path.insert(0, str(REPO_ROOT)) import generate_readme # noqa: E402 import generate_site # noqa: E402 import release_record # noqa: E402 import siteguides # noqa: E402 import sitemarkup # noqa: E402 from common import load_platform_config, load_platform_registry # noqa: E402 from tests import sitepages # noqa: E402 REGISTRY = load_platform_registry(str(REPO_ROOT / "platforms")) GUIDES_YAML = (REPO_ROOT / "platforms" / "_guides.yml").read_text(encoding="utf-8") UNTITLED_FENCE = re.compile(r"^\s*```(?:sh|powershell)\s*$", re.M) # A path a reader types: a network share, a home or root path, a drive letter. ABSOLUTE_PATH = re.compile( r"(?])(?:\\\\[^\s,)]+|smb://[^\s,)]+|~/[^\s,)]*|/[A-Za-z][^\s,)]*|[A-Z]:\\[^\s,)]*)" ) def _page(name: str) -> str: sitepages.require() return sitepages.page(name).read_text(encoding="utf-8") def _body(text: str) -> str: """A page without its front matter.""" return text.split("\n---\n", 1)[1] if text.startswith("---\n") else text def _guide_part(text: str) -> str: match = re.search(r"^## Reference\b", text, re.M) return text[: match.start()] if match else text class TrustItemsSayWhereItWrites(unittest.TestCase): def test_no_only_in_the_bios_folder_while_a_manifest_writes_saves(self): saves = [ path.name for path in sorted((REPO_ROOT / "install").glob("*.json")) if any(entry.get("root") == "save" for entry in json.loads(path.read_text(encoding="utf-8")).get("files", [])) ] self.assertTrue(saves, "no manifest files an entry under root: save") text = " ".join(siteguides.TRUST_ITEMS).lower() self.assertNotIn("only in your bios folder", text) self.assertIn("save folder", text) self.assertIn("--restore", text) def test_home_shows_the_same_items(self): # A term's first use on the page carries its gloss. home = re.sub(r"]*>", "", _page("index.md")) for item in siteguides.TRUST_ITEMS: self.assertIn(f"- {item}", home) self.assertNotIn("only in your BIOS folder", home) class NextStepsIsAPlainList(unittest.TestCase): def test_the_helpers(self): block = "\n".join(generate_site._next_block(["[A](a.md)", "[B](b.md)"])) self.assertEqual(block, "## Next steps\n\n- [A](a.md)\n- [B](b.md)\n") self.assertIn("## Next steps", generate_site.reference_next()) self.assertNotIn(":::", generate_site.reference_next()) def test_entry_pages(self): for name in ("which-pack.md", "contributing.md", "platforms/batocera.md", "platforms/retroarch.md"): with self.subTest(page=name): text = _page(name) self.assertNotIn(":::tip[Next]", text) self.assertIn("## Next steps", text) def test_home_has_no_next_box_and_no_stamp(self): home = _page("index.md") self.assertNotIn("Next steps", home) self.assertNotIn(":::tip", home) self.assertNotIn("Generated on", home) self.assertNotRegex(home, r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z") class DatesInWords(unittest.TestCase): def test_data_as_of(self): moment = datetime(2026, 10, 22, 22, 43, 8, tzinfo=timezone.utc) self.assertEqual( generate_site.data_as_of(moment), '', ) def test_get_your_files_carries_no_date(self): self.assertNotIn("Generated on", _page("which-pack.md")) class OneSetupCount(unittest.TestCase): def test_every_entry_page_states_the_registry_count(self): count = len(REGISTRY) self.assertEqual(siteguides.setup_count(), count) self.assertIn(f"and {count - 3} more.", _page("index.md")) self.assertIn(f'{count}', _page("explore.md")) class SetupTiles(unittest.TestCase): def test_one_tile_per_setup_on_home_and_get_your_files(self): for name in ("index.md", "which-pack.md"): text = _page(name) with self.subTest(page=name): extra = sum(len(tiles) for tiles in siteguides.GROUP_TILES.values()) self.assertEqual(text.count('class="rb-card rb-tile"'), len(REGISTRY) + extra) for key in REGISTRY: self.assertIn(f'href="/retrobios/platforms/{key}/"', text) def test_get_your_files_offers_the_choice_once(self): text = _page("which-pack.md") self.assertEqual(text.count('class="rb-card"'), 0) self.assertEqual(text.count("