"""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 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): home = _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("MM", tile) tile = "".join(sitemarkup.platform_tiles([("RomM", "x.md", "logo.png", "A server")])) self.assertIn('{display[members[0]]}", home) def test_hardware_is_named_and_cited(self): self.assertIn("Raspberry Pi", siteguides.GUIDES["batocera"].summary) self.assertIn("Raspberry Pi", siteguides.DEVICE_GROUPS["gaming-os"][0]) self.assertIn("Anbernic", siteguides.DEVICE_GROUPS["handheld"][0]) lines = GUIDES_YAML.splitlines() for number, line in enumerate(lines): if re.match(r" (runs_on|terminal):", line): with self.subTest(line=line.strip()): self.assertTrue(lines[number - 1].lstrip().startswith("#"), "no source cited on the line above") class GetYourFilesOrder(unittest.TestCase): def test_choice_then_tables_then_picker_then_command(self): text = _page("which-pack.md") order = [text.index(mark) for mark in ( "## Pick your setup", "| Setup | Runs on | BIOS folder | ZIP |", "Is it safe?", )] self.assertEqual(order, sorted(order)) def test_a_setup_after_the_release_says_no_zip_yet(self): text = _page("which-pack.md") self.assertNotIn("Not in v", text) record = release_record.load_advertised() pending = [k for k in REGISTRY if REGISTRY[k].get("release_pack") and not release_record.pack_for(k, record)] if record.get("tag") and pending: self.assertIn("No ZIP yet. [Use the command]", text) self.assertIn("Setups added after the last release have no ZIP until the next one.", text) else: self.assertNotIn("No ZIP yet.", text) if any(not entry.get("release_pack") for entry in REGISTRY.values()): self.assertIn("No ZIP. [Use the command]", text) def test_zip_advice_answers_the_usual_mistakes(self): text = _page("which-pack.md") self.assertIn("Download every part", text) self.assertIn("not in a new folder", text) self.assertIn("`neogeo.zip`", text) class GuideRoutes(unittest.TestCase): def test_a_device_system_opens_on_the_computer_route(self): text = _page("platforms/batocera.md") self.assertLess(text.index("## From your computer"), text.index("## On the device")) self.assertIn("{ #install }", "\n".join(generate_site._guide_install( "batocera", siteguides.GUIDES["batocera"], "## On the device { #install }"))) self.assertIn("Unzip them all into the network folder above", text) def test_a_computer_app_keeps_the_command_first(self): text = _page("platforms/retroarch.md") self.assertLess(text.index("## Install"), text.index("## Download")) def test_each_device_tab_says_how_to_open_its_terminal(self): shared = siteguides.TERMINALS["device"][1] for name, guide in siteguides.GUIDES.items(): if "device" not in guide.run: continue with self.subTest(platform=name): self.assertTrue(guide.terminal, "no terminal text of its own") text = _guide_part(_page(f"platforms/{name}.md")) self.assertIn(guide.terminal.split(".")[0], text) self.assertNotIn(shared, text) def test_retroarch_names_the_menu_before_the_folders(self): text = _page("platforms/retroarch.md") self.assertLess(text.index("Settings > Directory > System/BIOS"), text.index("| Where | BIOS folder |")) def test_emudeck_names_the_card_and_ends_the_job(self): text = _guide_part(_page("platforms/emudeck.md")) self.assertIn("installed on the SD card", text) self.assertIn("Return to Gaming Mode", text) self.assertNotIn("it is on the card", text) class PackManualReadsThePageFolders(unittest.TestCase): def test_every_path_in_a_pack_manual_is_a_folder_of_its_page(self): for name, guide in siteguides.GUIDES.items(): if not guide.pack_manual: continue known = " ".join(path.replace("`", "") for _w, path in siteguides.folder_list(name)) known += " " + str(REGISTRY[name].get("extract_to") or "") for path in ABSOLUTE_PATH.findall(siteguides.pack_manual(name)): with self.subTest(platform=name, path=path): self.assertIn(path.rstrip(".:"), known) def test_the_retroarch_manual_lists_the_detected_systems(self): manual = siteguides.pack_manual("retroarch") self.assertNotIn("{folders}", manual) self.assertIn("/run/muos/storage/bios", manual) self.assertIn("~/.var/app/org.libretro.RetroArch/config/retroarch/system/", manual) class DownloadNamesTheRelease(unittest.TestCase): def test_tag_date_checksums_and_signature(self): record = release_record.load_record() if not release_record.pack_for("batocera", record): self.skipTest("release.json has no Batocera pack") tag = record["tag"] lines = generate_site.release_line("batocera", record) self.assertTrue(lines[0].startswith(f"Pack {tag}, built ")) self.assertIn(f"/releases/download/{tag}/SHA256SUMS.txt)", lines[0]) self.assertIn(f"/releases/download/{tag}/SHA256SUMS.txt.sig)", lines[0]) self.assertEqual(generate_site._release_date("v2026.10.10"), "10 October 2026") def test_a_newer_platform_than_the_pack_is_named(self): record = {"tag": "v2026.10.10", "packs": {"RetroBat_1.0_BIOS_Pack.zip": { "assets": ["RetroBat_1.0_BIOS_Pack.zip"], "platforms": ["retrobat"]}}} version = load_platform_config("retrobat", str(REPO_ROOT / "platforms"))["version"] lines = generate_site.release_line("retrobat", record) self.assertIn(f"built for RetroBat 1.0. The command installs the files for " f"RetroBat {version}.", lines[-1]) class CommandBlocks(unittest.TestCase): def test_every_command_on_an_entry_page_has_a_title(self): names = ["index.md", "which-pack.md", "contributing.md"] + [ f"platforms/{key}.md" for key in REGISTRY] for name in names: with self.subTest(page=name): self.assertIsNone(UNTITLED_FENCE.search(_guide_part(_page(name)))) def test_shell_lines_have_a_powershell_form(self): text = _page("which-pack.md") groups = re.findall(r"|", text, re.S) commands = [group for group in groups if "/i | sh" in group] self.assertGreaterEqual(len(commands), 3) for group in commands: self.assertIn("/i.ps1", group) block = "\n".join(generate_site._console_install_block( "PlayStation", [("batocera", "psx"), ("recalbox", "psx")])) # Every setup's options in both forms: the list of every command, # and the one command under the OS tabs. sh_args = re.findall(r"/i \| sh -s -- ([^<\n]+)", block) ps_args = re.findall(r"/i\.ps1\)\)\) ([^<\n]+)", block) self.assertEqual(sorted(set(sh_args)), sorted(set(ps_args))) self.assertEqual(len(set(sh_args)), 2) self.assertIn('```powershell title="PowerShell"', block) for key, guide in siteguides.GUIDES.items(): if "windows" in guide.run: with self.subTest(platform=key): self.assertIn("/i.ps1", _guide_part(_page(f"platforms/{key}.md"))) class ContributingOpensOnTheAction(unittest.TestCase): def test_both_generators_open_on_the_upload(self): for text in (generate_readme.generate_contributing(), generate_site.generate_contributing()): body = text.split("## Add a BIOS file", 1)[1] self.assertTrue(body.lstrip().startswith("Have a file the collection lacks?")) self.assertLess(body.index(generate_readme.UPLOAD_URL), body.index("## What happens next")) self.assertLess(body.index("gaps"), body.index("## What happens next")) self.assertLess(body.index("## What happens next"), body.index("CI hashes")) def test_description_is_a_sentence(self): text = generate_site._page_description("", Path("contributing.md"), "Contributing") self.assertTrue(text.endswith(".")) fallback = generate_site._page_description( "One file needs no clone and no local setup at all here:\n\n" "A plain sentence that describes the page well enough.", Path("other.md"), "Other") self.assertEqual(fallback, "A plain sentence that describes the page well enough.") class HomeLinksTheOtherTasks(unittest.TestCase): def test_troubleshooting_file_check_and_legal(self): home = _body(_page("index.md")) for target in ("/retrobios/wiki/troubleshooting/", "/retrobios/files/", "/retrobios/wiki/legal/"): self.assertIn(f"]({target})", home) self.assertLess(home.index("/retrobios/wiki/troubleshooting/"), home.index("## Do I need it?")) GUIDE = siteguides.HANDHELD_GUIDE def _plain_guide_part(text: str) -> str: """The guide part of a setup page, glossary marks taken off.""" match = re.search(r"^## Reference\b", text, re.M) text = text[: match.start()] if match else text return re.sub(r"]*>", "", text) def _display(key: str) -> str: return load_platform_config(key, str(REPO_ROOT / "platforms")).get("platform", key) class HandheldWayIn(unittest.TestCase): def test_android_and_linux_tiles_on_home_and_get_your_files(self): tiles = siteguides.GROUP_TILES.get("handheld", ()) self.assertEqual([tile.name for tile in tiles], ["Android", "Linux handhelds", "iPhone and iPad"]) for name in ("index.md", "which-pack.md"): text = _page(name) with self.subTest(page=name): self.assertIn('href="/retrobios/platforms/retroarch/#where-the-files-go"', text) self.assertIn('href="/retrobios/platforms/linux-handhelds/"', text) self.assertIn(">Retroid, AYN Odin and other Android handhelds", text) def test_the_closed_note_is_gone(self): self.assertNotIn("Other handhelds that run RetroArch", _page("which-pack.md")) def test_one_row_per_detected_system(self): detected = [name for name, _folder in siteguides.detected_systems(REGISTRY, GUIDE.uses)] self.assertEqual(sorted((s.name for s in GUIDE.systems), key=str.casefold), detected) text = _page(GUIDE.path) for system in GUIDE.systems: with self.subTest(system=system.name): self.assertIn(f"| {system.name} |", text) def test_every_folder_and_device_list_is_cited(self): for system in GUIDE.systems: with self.subTest(system=system.name): if system.folder: self.assertTrue(system.folder_source.startswith("https://")) if system.devices: self.assertTrue(system.devices_source.startswith("https://")) for name in ("ArkOS", "muOS", "Onion", "spruceOS"): system = next(s for s in GUIDE.systems if s.name == name) with self.subTest(system=name): self.assertTrue(system.folder and system.devices) def test_a_value_without_its_page_is_refused(self): with self.assertRaises(siteguides.GuideError): siteguides._handheld_system({"name": "Onion", "folder": "`BIOS`"}, "x") with self.assertRaises(siteguides.GuideError): siteguides._handheld_system( {"name": "Onion", "devices": ["Miyoo Mini"], "devices_source": "a blog"}, "x") def test_the_guide_rejects_a_missing_or_unknown_system(self): data = {"page": "x", "title": "X", "summary": "S", "uses": "retroarch", "systems": [{"name": "Onion"}]} with self.assertRaises(siteguides.GuideError): siteguides._handheld_guide(data, REGISTRY, siteguides.GUIDES, "x") def test_devices_are_a_search_field_with_no_text(self): text = _page(GUIDE.path) match = re.search(r'', text) self.assertIsNotNone(match) self.assertIn("R36S", match[1]) self.assertIn("Miyoo Mini", match[1]) self.assertNotIn(",", match[1]) # The names typed most often are also on the page itself. self.assertIn("R36S", text.split(match[0], 1)[1]) def test_the_guide_is_in_the_handheld_group_of_the_navigation(self): coverages = {key: {"platform": _display(key)} for key in REGISTRY} nav = generate_site.generate_nav(coverages, {}, {}, REGISTRY) setup = next(tab["Your setup"] for tab in nav if "Your setup" in tab) groups = {next(iter(item)): next(iter(item.values())) for item in setup if isinstance(item, dict)} label = siteguides.DEVICE_GROUPS["handheld"][0] self.assertIn({GUIDE.title: GUIDE.path}, groups[label]) def test_the_guide_fills_the_card_and_never_its_root(self): text = _page(GUIDE.path) self.assertIn("--platform retroarch --dest E:\\BIOS", text) self.assertNotRegex(text, r"--dest E:\\\s") class PackNamesItsVersion(unittest.TestCase): def test_the_line_under_the_release(self): record = release_record.load_record() if not release_record.pack_for("batocera", record): self.skipTest("release.json has no Batocera pack") block = generate_site._pack_buttons("batocera") name = release_record.pack_name_for("batocera", record) version = generate_site._pack_version(name, ["batocera"]) self.assertIn(f"Made for Batocera {version}. " + generate_site.other_version("batocera"), block) def test_a_version_gap_is_said_once(self): record = {"tag": "v2026.10.10", "packs": {"RetroBat_1.0_BIOS_Pack.zip": { "assets": ["RetroBat_1.0_BIOS_Pack.zip"], "platforms": ["retrobat"]}}} released = generate_site.release_line("retrobat", record) line = generate_site.version_line("retrobat", record, named=len(released) > 1) self.assertEqual(line, generate_site.other_version("retrobat")) def test_a_version_with_an_underscore_is_read_whole(self): """AmberELEC's prerelease-20261019_1835 read as 1835, and the page said the pack was built for another version.""" from generate_pack import _platform_pack_stem version = load_platform_config("amberelec", "platforms")["version"] name = _platform_pack_stem(["amberelec"], "amberelec", "platforms") + "_BIOS_Pack.zip" self.assertEqual(generate_site._pack_version(name, ["amberelec"]), version) record = {"tag": "v2026.10.10", "packs": {name: { "assets": [name], "platforms": ["amberelec"]}}} self.assertEqual(len(generate_site.release_line("amberelec", record)), 1) def test_another_version_is_sent_to_the_check_commands(self): """A ZIP reader read "Run `--check` after copying." with no command.""" line = generate_site.other_version("emudeck") self.assertNotIn("Run `--check`", line) self.assertIn("](../platforms/emudeck.md#how-to-check)", line) page = _page("platforms/emudeck.md") self.assertIn("{ #how-to-check }", page) def test_the_v_prefix_is_dropped(self): self.assertEqual(generate_site.setup_version("retroarch", {}), f"RetroArch {load_platform_config('retroarch', 'platforms')['version'].lstrip('v')}") def test_every_setup_page_names_a_version(self): """A setup with a release ZIP names the version it was made for. One without says there is no ZIP: the command installs current files.""" for key in REGISTRY: with self.subTest(platform=key): text = _plain_guide_part(_page(f"platforms/{key}.md")) if generate_site.has_release_pack(key): self.assertRegex(text, r"(?:[Mm]ade for|This pack was built for) \S") else: self.assertIn(generate_site.no_zip_line(_display(key)), text) class SetupPagesOfferPartOfThePack(unittest.TestCase): def test_pick_them_and_press_c(self): for key in REGISTRY: with self.subTest(platform=key): text = _plain_guide_part(_page(f"platforms/{key}.md")) self.assertIn(f"/retrobios/which-pack/?platform={key}#only-some-consoles", text) # The screen's own row: the line menu's c is not what the # one-liner opens. self.assertIn("or the command's **Choose systems**.", text) # Never "choose **Choose systems**". self.assertNotIn("choose **Choose systems**", text) def test_the_anchor_exists(self): self.assertIn("## Only some consoles?", _page("which-pack.md")) def test_a_file_in_place_but_missing_is_answered(self): for key in REGISTRY: with self.subTest(platform=key): self.assertIn("Reported missing anyway?", _plain_guide_part(_page(f"platforms/{key}.md"))) class SafetyListIsLinked(unittest.TestCase): def test_setup_pages_link_the_home_list(self): for key in REGISTRY: with self.subTest(platform=key): text = _plain_guide_part(_page(f"platforms/{key}.md")) self.assertIn("[Is it safe?](/retrobios/#is-it-safe)", text) self.assertNotIn("Is it safe?", text) self.assertIn("## Is it safe? { #is-it-safe }", _page("index.md")) class OfflineLine(unittest.TestCase): def test_under_from_your_computer(self): text = _page("platforms/batocera.md") start = text.index("## From your computer") line = text.index("No internet on the device?") self.assertLess(start, line) self.assertLess(line, text.index("## On the device")) self.assertIn("/retrobios/wiki/installer/#without-a-network-offline-install", text) self.assertIn("{ #without-a-network-offline-install }", (REPO_ROOT / "wiki" / "installer.md").read_text(encoding="utf-8")) class GetYourFilesListsEachSetupOnce(unittest.TestCase): def setUp(self): self.text = _page("which-pack.md") def test_the_tables_are_in_one_closed_block(self): block = re.search(r'
\s*Every folder and ZIP ' r'in one table(.*?)
', self.text, re.S) self.assertIsNotNone(block) outside = self.text.replace(block[0], "") self.assertNotIn("| Setup | Runs on | BIOS folder | ZIP |", outside) for key in REGISTRY: with self.subTest(platform=key): self.assertEqual(block[1].count(f"| [{_display(key)}](/retrobios/platforms/{key}/) |"), 1) def test_single_setup_groups_share_one_table(self): self.assertIn("**Other setups**", self.text) self.assertNotIn("**MiSTer FPGA**", self.text) self.assertNotIn("**Self-hosted game library**", self.text) def test_no_unzip_into_without_a_zip(self): record = release_record.load_record() for line in self.text.splitlines(): if "Unzip into" in line: key = re.search(r"/retrobios/platforms/([a-z0-9]+)/", line)[1] with self.subTest(platform=key): self.assertTrue(release_record.pack_for(key, record)) def test_parts_are_buttons_with_the_size_after(self): """A row links the setup's get/ page, which lists every part, with the download size after the button.""" rows = [line for line in self.text.splitlines() if ">ZIP" in line] self.assertTrue(rows) for row in rows: self.assertRegex(row, r'ZIP') self.assertRegex(row, r" \(\d[\d.]* [KMG]B\) \|$") def test_the_zip_note(self): text = re.sub(r"]*>", "", self.text) for line in ("A big ZIP comes in parts, like `Pack.part1of2.zip`. Download every part.", "Leave the ZIPs inside, like `neogeo.zip`, as they are.", "Their setup's page says where to unzip them."): self.assertIn(line, text) self.assertNotIn("**Unzip into** means", self.text) class YourSetupIndexInPlainWords(unittest.TestCase): def test_the_index_links_the_table(self): text = _page("platforms/index.md") self.assertNotIn("| Platform |", text) self.assertNotIn("at a glance", text.split("---", 2)[-1].lower().replace( "every setup at a glance", "")) self.assertIn("/retrobios/wiki/verification-modes/#every-setup-at-a-glance", text) def test_the_table_on_verification_modes(self): text = _page("wiki/verification-modes.md") self.assertIn("| Setup | Files on its own list | Files in the ZIP | How it checks |", text) table = text.split("## Every setup at a glance", 1)[1].split("\n## ", 1)[0] self.assertNotIn("existence", table) self.assertIn("| name only |", table) self.assertIn("No list of its own", table) self.assertLess(text.index("## Every setup at a glance"), text.index("## Next steps")) def test_insertion_keeps_next_steps_last(self): page = "# T\n\nBody.\n\n## Next steps\n\n- [A](a.md)\n" out = generate_site.with_setup_table(page, {}) self.assertTrue(out.rstrip().endswith("- [A](a.md)")) self.assertIn("## Every setup at a glance", out) class HomeCopy(unittest.TestCase): def test_dreamcast_and_the_check_link(self): home = re.sub(r"]*>", "", _page("index.md")) self.assertIn("- **Yes** for PlayStation 1 and 2, Saturn and Neo Geo.", home) self.assertIn("- **Better with it** for Dreamcast: some games need it.", home) # A question and the link that answers it: no level label, which # read as a broken crumb beside the Reference tab. self.assertNotIn("Reference:", home.split("\n---\n", 1)[-1]) self.assertIn("- Have a file? [Check it]", home) self.assertIn("BIOS missing, even with the file there?", home) def test_flycast_boots_without_the_dreamcast_bios(self): import yaml profile = yaml.safe_load((REPO_ROOT / "emulators" / "flycast.yml").read_text(encoding="utf-8")) boot = next(f for f in profile["files"] if f.get("name") == "dc_boot.bin") self.assertFalse(boot.get("required")) self.assertTrue(boot.get("hle_fallback")) class TileLogosLoadWhenSeen(unittest.TestCase): def test_the_helper(self): tiles = [(f"S{i}", "x.md", f"{i}.png", "here") for i in range(4)] html = "".join(sitemarkup.platform_tiles(tiles, eager=2)) logos = re.findall(r"]*>", html) self.assertEqual(['loading="lazy"' in logo for logo in logos], [False, False, True, True]) self.assertNotIn("loading=", "".join(sitemarkup.platform_tiles(tiles[:1], eager=1))) def test_only_the_first_row_is_eager_on_entry_pages(self): for name in ("index.md", "which-pack.md"): logos = re.findall(r']*>', _page(name)) with self.subTest(page=name): self.assertGreater(len(logos), sitemarkup.FIRST_ROW_TILES) eager = [logo for logo in logos if 'loading="lazy"' not in logo] self.assertEqual(eager, logos[: sitemarkup.FIRST_ROW_TILES]) class DownloadLine(unittest.TestCase): def test_checksums_and_signature_read_as_two(self): record = release_record.load_record() if not release_record.pack_for("batocera", record): self.skipTest("release.json has no Batocera pack") line = generate_site.release_line("batocera", record)[0] self.assertRegex(line, r"\[SHA256SUMS\.txt\]\([^)]+\) and its \[signature\]\([^)]+\)\.$") HANDHELDS = siteguides.HANDHELD_GUIDE OFFLINE_ANCHOR = "#without-a-network-offline-install" def _plain_page(name: str) -> str: sitepages.require() return re.sub(r"]*>", "", sitepages.page(name).read_text(encoding="utf-8")) def _with_zip(name: str) -> bool: pack = release_record.pack_for(name, release_record.load_advertised()) return bool(pack and pack.get("assets")) SETUPS = [name for name in siteguides.GUIDES if name in REGISTRY] class NoZipNoUnzipText(unittest.TestCase): def test_a_setup_without_a_zip_never_says_unzip_or_download_pack(self): for name in SETUPS: if _with_zip(name): continue text = _guide_part(_plain_page(f"platforms/{name}.md")) with self.subTest(setup=name): self.assertNotIn("Unzip the pack into", text) self.assertNotIn("pack holds its own", text) self.assertNotRegex(text, r"Download \S+ Pack") self.assertNotIn("/releases/download/", text) def test_the_minui_family_makes_each_folder(self): for name in ("minui", "myminui", "nextui"): with self.subTest(setup=name): self.assertIn("The command makes each console's folder for you.", _plain_page(f"platforms/{name}.md")) def test_knulli_is_filled_over_the_network_with_the_samba_switch_cited(self): text = _guide_part(_plain_page("platforms/knulli.md")) self.assertIn("## From your computer { #from-your-computer }", text) self.assertIn("--platform knulli --dest \\\\KNULLI\\share\\bios\n", text) self.assertRegex(text, r"turn on \*\*SAMBA\*\* in \*\*System Settings > Services\*\*\s*" r"\(\[KNULLI docs\]\(https://github\.com/knulli-cfw/knulli\.org/blob/" r"[0-9a-f]{40}/docs/play/add-games/network-transfer\.md") # The computer route comes before the SSH one. self.assertLess(text.index("## From your computer"), text.index("## On the device")) def test_the_elec_systems_say_they_need_the_network(self): for name, share in (("amberelec", "\\\\AmberELEC\\roms"), ("emuelec", "\\\\EMUELEC\\roms")): text = _guide_part(_plain_page(f"platforms/{name}.md")) with self.subTest(setup=name): self.assertIn("Needs the device on your network.", text) self.assertIn(f"--platform {name} --dest {share}\n", text) def test_a_mac_and_a_linux_desktop_reach_the_share_too(self): """The share route was a PowerShell tab alone: a Mac user with Recalbox on Wi-Fi fell back to SSH or to taking the card out.""" args, how = generate_site._share_tabs("recalbox", "\\\\RECALBOX\\share\\bios") self.assertEqual(args["macos"], "--platform recalbox --dest /Volumes/share/bios") self.assertIn("`smb://recalbox/share`", how["macos"]) self.assertEqual(args["linux"], "--platform recalbox --dest /path/to/share/bios") self.assertIn("`smb://recalbox/share`", how["linux"]) lines = generate_site._setup_routes("recalbox", "Recalbox") routes = "\n".join(lines[:lines.index("## On the device { #install }")]) for label in ("Windows", "macOS", "Linux"): self.assertIn(f'label="{label}"', routes) # The setup page sends a Mac or Linux reader to those tabs. lines = generate_site._setup_routes("recalbox", "Recalbox", page_link=True) self.assertIn("[Mac or Linux](../get/recalbox.md#from-your-computer)", lines) def test_the_network_route_needs_a_share_and_no_zip(self): for name in SETUPS: share = generate_site._network_share(name, siteguides.GUIDES[name]) with self.subTest(setup=name): if _with_zip(name) or siteguides.GUIDES[name].run != ("device",): self.assertEqual(share, "") if share: self.assertTrue(share.startswith("\\\\")) self.assertFalse(share.endswith("\\")) class EveryHandheldRowSaysWhere(unittest.TestCase): def test_no_row_sends_the_reader_to_a_command(self): text = _plain_page(HANDHELDS.path) self.assertNotIn("The command finds it on the device", text) rows = [line for line in text.splitlines() if line.startswith("| ")][1:] self.assertEqual(len(rows), len(HANDHELDS.systems)) for row in rows: folder = row.rstrip(" |").rsplit(" | ", 1)[-1] with self.subTest(row=row[:40]): self.assertTrue("([source](https://" in folder or folder == generate_site.RETROARCH_FOLDER_SETTING, folder) def test_the_installer_folders_are_written_with_their_source(self): systems = {s.name: s for s in HANDHELDS.systems} for name in ("Allium", "JELOS", "KOLOS", "MiyooCFW"): with self.subTest(system=name): self.assertTrue(systems[name].folder) self.assertRegex(systems[name].folder_source, r"/[0-9a-f]{40}/") def test_a_folder_only_linux_reads_says_what_to_do(self): text = _plain_page(HANDHELDS.path) for system in HANDHELDS.systems: row = next(line for line in text.splitlines() if line.startswith(f"| {system.name} |")) with self.subTest(system=system.name): self.assertEqual(generate_site.LINUX_ONLY_HINT in row, system.linux_only) self.assertTrue(any(s.linux_only for s in HANDHELDS.systems if s.name == "ArchR")) self.assertNotIn("Windows and macOS cannot open", text) def test_linux_only_needs_a_folder(self): with self.assertRaises(siteguides.GuideError): siteguides._handheld_system({"name": "X", "linux_only": True}, "test") def test_download_is_one_jump_under_the_lead(self): text = _plain_page(HANDHELDS.path) self.assertIn("[Download](#download)", text) self.assertLess(text.index("[Download](#download)"), text.index("## Where the files go")) class OfflineLineWithEveryZip(unittest.TestCase): def test_every_setup_with_a_zip_links_the_offline_install(self): for name in SETUPS: text = _guide_part(_plain_page(f"platforms/{name}.md")) with self.subTest(setup=name): self.assertEqual(OFFLINE_ANCHOR in text, _with_zip(name)) def test_the_handheld_guide_links_it(self): self.assertEqual(OFFLINE_ANCHOR in _plain_page(HANDHELDS.path), _with_zip(HANDHELDS.uses)) class EntryCopy(unittest.TestCase): def test_no_reference_label_on_home_explore_and_the_setup_index(self): for name in ("index.md", "explore.md", "platforms/index.md"): if not sitepages.page(name).is_file(): continue body = _plain_page(name).split("\n---\n", 1)[-1] with self.subTest(page=name): self.assertNotIn("Reference: [", body) self.assertIn("- Have a file? [Check it](/retrobios/files/)", _plain_page("index.md")) def test_the_explore_cards_have_bare_links(self): source = Path(generate_site.__file__).read_text(encoding="utf-8") self.assertNotIn('"Reference: [How each platform checks files]', source) self.assertNotIn('"Reference: [Data and API]', source) def test_the_setup_index_lists_the_handheld_tiles(self): text = _plain_page("platforms/index.md") for tile in siteguides.GROUP_TILES["handheld"]: target = tile.link.removesuffix(".md").replace(".md#", "/#") with self.subTest(tile=tile.name): self.assertIn(f"**[{tile.name}](/retrobios/{target}", text.replace("/)", ")")) self.assertIn(tile.text, text) class IphoneAndIpad(unittest.TestCase): def test_a_tile_in_the_handheld_group(self): tile = next(t for t in siteguides.GROUP_TILES["handheld"] if t.name == "iPhone and iPad") self.assertEqual(tile.link, "which-pack.md#iphone-and-ipad") for name in ("index.md", "which-pack.md"): with self.subTest(page=name): self.assertIn('href="/retrobios/which-pack/#iphone-and-ipad"', _plain_page(name)) self.assertEqual(sitemarkup.initials("iPhone and iPad"), "iP") def test_each_app_step_is_cited_and_the_picker_replaces_the_zip(self): text = _plain_page("which-pack.md") section = text.split("## iPhone and iPad { #iphone-and-ipad }", 1)[1].split("\n## ", 1)[0] self.assertIn('data-pagefind-meta="devices:iPhone iPad iOS Delta Provenance"', section) self.assertIn("/retrobios/which-pack/?platform=retroarch#only-some-consoles", section) items = [line for line in section.splitlines() if line.startswith("- ")] self.assertEqual([re.match(r"- \*\*(\w+)\*\*", i)[1] for i in items], ["RetroArch", "Delta", "Provenance"]) for item in items: with self.subTest(item=item[:20]): self.assertRegex(item, r"\(\[(docs|code)\]\(https://") self.assertNotIn("/get/retroarch/", section) class WhatItAsks(unittest.TestCase): def test_the_page_says_the_screen_not_a_line_menu(self): # The one-liner opens the full screen: a "What it asks" block of the # line menu showed keys a beginner never sees (owner's note). text = _plain_page("which-pack.md") self.assertNotIn('title="What it asks"', text) for line in generate_site.first_screen_words(): self.assertIn(line, text) COMMAND = re.compile(r"^(?:curl -fsSL |&\(\[scriptblock\]|irm ).*$", re.M) CARD_FAQ = "/retrobios/wiki/faq/#fill-an-sd-card-from-a-computer" def _tab_labels(text: str) -> set[str]: return set(re.findall(r' Directory > System/BIOS**", text) self.assertIn("into RetroBat's `bios` folder", _plain_page("get/retrobat.md")) self.assertNotIn("into `system/`", text) class HandheldRowsSayWhere(unittest.TestCase): def test_retrooz_names_its_folder_with_a_pinned_source(self): system = next(s for s in siteguides.HANDHELD_GUIDE.systems if s.name == "RetroOZ") self.assertIn("`/roms/bios`", system.folder) self.assertRegex(system.folder_source, r"/wiki/Home/[0-9a-f]{40}$") row = next(line for line in _plain_page("platforms/linux-handhelds.md").splitlines() if line.startswith("| RetroOZ |")) self.assertNotIn(generate_site.RETROARCH_FOLDER_SETTING, row) class GetIndex(unittest.TestCase): def test_every_setup_is_linked_under_its_group(self): text = _plain_page("get/index.md") for name in SETUPS: with self.subTest(setup=name): self.assertIn(f"(/retrobios/get/{name}/)", text) for label, _description in siteguides.DEVICE_GROUPS.values(): self.assertIn(f"## {label}", text) class HomeAndWhichPack(unittest.TestCase): def test_the_home_step_does_not_promise_a_zip(self): text = _plain_page("index.md") self.assertIn("**Run one command.** Some setups have a ZIP.", text) self.assertNotIn("or download a ZIP file", text) def test_which_pack_answers_ios(self): text = _plain_page("which-pack.md") meta = re.search(r'data-pagefind-meta="terms:([^"]+)"', text) self.assertIsNotNone(meta) for word in ("ios", "iphone", "ipad", "delta", "provenance"): self.assertIn(word, meta[1].split()) class TheSiteSaysWhatTheInstallerShows(unittest.TestCase): """which-pack showed a line menu (Enter, i, c, q) the one-liner never opens, typed by hand, its keys out of date. The site, the README and the wiki now take the screen's words and the line menu from install.py.""" MENU_LINES = ("Enter) install everything", "i) only what the installed cores read", "c) choose systems, cores and regions") def test_which_pack_describes_the_screen_in_the_installers_words(self): installer = generate_site._installer() text = _plain_page("which-pack.md") for line in generate_site.first_screen_words(): self.assertIn(line, text) self.assertIn(f"**{installer.TUI_INSTALL_ROW}**", text) self.assertIn(f"**{installer.TUI_SYSTEMS_ROW}**", text) for line in self.MENU_LINES: self.assertNotIn(line, text) self.assertNotIn("press **c**", text) def test_the_plan_rows_are_the_words_the_pages_use(self): installer = generate_site._installer() state = installer.TuiState(os_type="linux", home=Path("/home/me")) state.stack = ["plan"] state.check = {"files": 2, "present": 0, "missing": 2, "wrong_hash": 0, "need": 2, "bytes_needed": 10} state.systems = [{"system": name, "missing": 1, "wrong_hash": 0, "present": 0, "files": 1, "bytes_needed": 5} for name in ("a", "b")] state.manifest = {"files": [{"dest": "a.bin", "systems": ["a"]}, {"dest": "b.bin", "systems": ["b"]}]} state.ticked = {"a", "b"} state.checked_flags = installer.tui_flags(state) labels = [row.label for row in installer.tui_rows(state)] self.assertEqual(labels[0], installer.TUI_INSTALL_ROW) self.assertIn(installer.TUI_SYSTEMS_ROW, labels) def test_the_readme_and_the_wiki_take_the_same_words(self): installer = generate_site._installer() readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8") self.assertIn(f"fewer under **{installer.TUI_SYSTEMS_ROW}**", readme) self.assertNotIn("offers to install everything or to choose systems", readme) source = (REPO_ROOT / "scripts" / "generate_readme.py").read_text(encoding="utf-8") self.assertIn("_installer_words().TUI_SYSTEMS_ROW", source) import generate_installer_doc # noqa: PLC0415 page = (REPO_ROOT / "wiki" / "installer.md").read_text(encoding="utf-8") self.assertIn(generate_installer_doc.line_menu(), page) menu = page.split("", 1)[1] outside = page.replace(menu.split("", 1)[0], "") for line in self.MENU_LINES: self.assertNotIn(line, outside) if __name__ == "__main__": unittest.main()