"""Navigation (round 2, package 3). The 404 page's sidebar read "0 1 2 3 4". A sidebar group label was a link to its first page ("Handhelds and TV boxes" opened AmberELEC), and a crumb linked a sibling page. Emulators sat three levels deep under "D to F / D". Previous and next ran from Sony to Sord and from one tab into another, and disagreed with the page's own "Next" list. The Reference tab was a flat list of fifteen. These tests read the generator and, where it is built, the site. """ from __future__ import annotations import json import re import struct import sys import tempfile import unittest from html.parser import HTMLParser from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT / "scripts")) import generate_site # noqa: E402 import sitestarlight # noqa: E402 from common import list_registered_platforms, load_platform_config, load_platform_registry # noqa: E402 DIST = REPO_ROOT / "website" / "dist" ROUTE = REPO_ROOT / "website" / "src" / "routeData.ts" TAB_ROOTS = {"/retrobios/", "/retrobios/platforms/", "/retrobios/explore/", "/retrobios/wiki/", "/retrobios/contributing/"} def _nav() -> list: platforms = REPO_ROOT / "platforms" registry = load_platform_registry(str(platforms)) coverages = { key: {"platform": load_platform_config(key, str(platforms))["platform"]} for key in list_registered_platforms(str(platforms), include_archived=True) } manufacturers = {"Sony": {}, "Sega": {}, "ScummVM": {}} profiles = { "duckstation": {"emulator": "DuckStation", "systems": ["sony-playstation"]}, "dolphin": {"emulator": "Dolphin", "systems": ["nintendo-gamecube"]}, "mame": {"emulator": "MAME", "systems": ["arcade"]}, } return generate_site.generate_nav(coverages, manufacturers, profiles, registry) def _targets(value: object) -> set[str]: if isinstance(value, str): return {value} if isinstance(value, list): return set().union(*(_targets(v) for v in value)) if value else set() return set().union(*(_targets(v) for v in value.values())) def _present(nav: list) -> set[str]: return {t for t in _targets(nav) if not t.startswith("http")} ANCHORS = { "platforms/index.md": {"computers-and-steam-deck", "handhelds-and-tv-boxes", "game-systems-for-pcs-and-small-boards", "mister-fpga", "self-hosted-game-library"}, "systems/index.md": {"consoles-and-computers"}, } class NotFoundPage(unittest.TestCase): def test_the_404_opens_on_the_first_tab(self): side = sitestarlight.sidebar(_nav(), _present(_nav()), ANCHORS) self.assertNotIn("/404", side["options"]["exclude"]) self.assertIn("/404", side["options"]["topics"]["start-here"]) def test_the_404_has_no_contents_no_index_and_no_structured_data(self): _target, text = sitestarlight.finalize_page( "404.md", "# Page not found\n\nGone.\n", level="", weight=None, browser_title="Page not found", description="d", structured={"@type": "TechArticle"}, terms={}) front = generate_site._front_matter(text) self.assertIs(front["tableOfContents"], False) self.assertIs(front["prev"], False) self.assertIs(front["next"], False) self.assertIn({"tag": "meta", "attrs": {"name": "robots", "content": "noindex"}}, front["head"]) self.assertNotIn("ld+json", text) class GroupsAreGroups(unittest.TestCase): def setUp(self): nav = _nav() self.side = sitestarlight.sidebar(nav, _present(nav), ANCHORS) self.groups = self.side["groups"] def test_setup_groups_stay_groups_and_point_at_their_section(self): setup = {key: value for key, value in self.groups.items() if key.startswith("Your setup/")} self.assertTrue(setup) for key, group in setup.items(): with self.subTest(group=key): self.assertFalse(group["prune"]) self.assertTrue(group["href"].startswith("/retrobios/platforms/#"), group["href"]) def test_explore_groups_collapse_to_their_overview(self): explore = {key: value for key, value in self.groups.items() if key.startswith("Explore/")} self.assertEqual(explore["Explore/Systems"], {"href": "/retrobios/systems/", "prune": True}) self.assertEqual(explore["Explore/Systems/Consoles and computers"]["href"], "/retrobios/systems/#consoles-and-computers") self.assertEqual(explore["Explore/Emulators/D"], {"href": "/retrobios/emulators/", "prune": True}) def test_no_group_points_at_one_of_its_own_pages(self): """The overview a group opens on is its own index page or a section of the page above it, never the first page it lists.""" overview_pages = {sitestarlight.page_url(rel) for rel in _present(_nav()) if rel == "index.md" or rel.endswith("/index.md") or rel == "explore.md"} for key, group in self.groups.items(): if not group["href"]: continue with self.subTest(group=key): self.assertTrue("#" in group["href"] or group["href"] in overview_pages, group["href"]) def test_the_middleware_reads_the_groups_not_the_first_link(self): route = ROUTE.read_text(encoding="utf-8") self.assertNotIn("firstLink", route) self.assertIn("sidebar.groups", route) self.assertIn("sidebar.reading", route) class EmulatorsOneLevelDeep(unittest.TestCase): def test_initials_hold_pages_not_groups(self): nav = generate_site._emulator_nav({ "duckstation": {"emulator": "DuckStation", "systems": ["s"]}, "dolphin": {"emulator": "Dolphin", "systems": ["s"]}, }) self.assertEqual(nav, ["emulators/index.md", {"D": [ {"Dolphin": "emulators/dolphin.md"}, {"DuckStation": "emulators/duckstation.md"}]}]) def test_every_initial_holds_pages(self): from common import load_emulator_profiles nav = generate_site._emulator_nav(load_emulator_profiles(str(REPO_ROOT / "emulators"))) for group in nav[1:]: (label, pages), = group.items() with self.subTest(initial=label): self.assertTrue(all(isinstance(next(iter(p.values())), str) for p in pages)) class OneNextPerPage(unittest.TestCase): def setUp(self): nav = _nav() self.runs = sitestarlight.sidebar(nav, _present(nav), ANCHORS)["reading"] self.links = [[entry["link"] for entry in run] for run in self.runs] def test_start_here_runs_into_your_setup_and_stops(self): first = self.links[0] self.assertEqual(first[0], "/retrobios/") self.assertIn("/retrobios/platforms/", first) self.assertNotIn("/retrobios/explore/", first) def test_each_other_tab_is_read_on_its_own(self): starts = [run[0] for run in self.links] self.assertEqual(starts, ["/retrobios/", "/retrobios/explore/", "/retrobios/wiki/", "/retrobios/contributing/"]) def test_system_emulator_and_platform_pages_are_not_in_the_reading(self): for link in (link for run in self.links for link in run): with self.subTest(link=link): self.assertNotRegex(link, r"^/retrobios/(systems|emulators|platforms)/[^/]+/$") def test_an_overview_is_named_after_what_it_opens(self): labels = {entry["link"]: entry["label"] for run in self.runs for entry in run} self.assertEqual(labels["/retrobios/platforms/"], "Your setup") self.assertEqual(labels["/retrobios/systems/"], "Systems") self.assertNotIn("Overview", labels.values()) def test_card_pages_turn_pagination_off(self): for rel in ("systems/sony.md", "emulators/duckstation.md", "platforms/batocera.md"): _target, text = sitestarlight.finalize_page( rel, "# T\n\nBody.\n", level="explore", weight=None, browser_title="T", description="d", structured={}, terms={}) front = generate_site._front_matter(text) with self.subTest(page=rel): self.assertIs(front["prev"], False) self.assertIs(front["next"], False) def test_the_next_link_is_the_first_of_next_steps(self): body = ("# Troubleshooting\n\nText.\n\n## Next steps\n\n" "- [Common questions](faq.md)\n- [Glossary](glossary.md)\n") _target, text = sitestarlight.finalize_page( "wiki/troubleshooting.md", body, level="start", weight=None, browser_title="Troubleshooting", description="d", structured={}, terms={}) front = generate_site._front_matter(text) self.assertEqual(front["next"], {"link": "/retrobios/wiki/faq/", "label": "Common questions"}) def test_a_page_without_next_steps_keeps_the_reading_order(self): _target, text = sitestarlight.finalize_page( "wiki/faq.md", "# FAQ\n\nText.\n", level="start", weight=None, browser_title="FAQ", description="d", structured={}, terms={}) self.assertNotIn("next", generate_site._front_matter(text)) class ReferenceGrouped(unittest.TestCase): def _reference(self, wiki: Path | None = None) -> list: nav = generate_site.generate_nav({}, {}, {}, {}, wiki) return next(tab["Reference"] for tab in nav if "Reference" in tab) def test_checking_files_opens_on_find_a_file(self): reference = self._reference() groups = {next(iter(item)): next(iter(item.values())) for item in reference if isinstance(item, dict) and isinstance(next(iter(item.values())), list)} self.assertEqual(groups["Checking files"][0], {"Find a file": "files/index.md"}) self.assertIn({"What the packs do not hold": "gaps.md"}, groups["Checking files"]) self.assertEqual(groups["Data"][:3], [{"Data and API": "data.md"}, {"Data model": "wiki/data-model.md"}, {"Updated platform files": "exports.md"}]) self.assertEqual(reference[0], "wiki/index.md") def test_a_wiki_page_joins_a_group_from_its_front_matter(self): (REPO_ROOT / "tmp").mkdir(exist_ok=True) with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp: wiki = Path(tmp) (wiki / "index.md").write_text("---\nlevel: reference\norder: 0\n---\n# Reference\n") (wiki / "modes.md").write_text( "---\nlevel: reference\norder: 10\ngroup: Checking files\n---\n# How each platform checks files\n") reference = self._reference(wiki) group = next(item["Checking files"] for item in reference if isinstance(item, dict) and "Checking files" in item) self.assertEqual(group[:2], [{"Find a file": "files/index.md"}, {"How each platform checks files": "wiki/modes.md"}]) class Redirects(unittest.TestCase): def test_core_names_lead_to_their_profile(self): profiles = { "beetle_psx": {"cores": ["mednafen_psx", "beetle_psx", "Beetle PSX"]}, "a": {"cores": ["shared"]}, "b": {"cores": ["shared", "dolphin"]}, "dolphin": {"cores": []}, } present = {"emulators/beetle_psx.md", "emulators/a.md", "emulators/b.md", "emulators/dolphin.md", "which-pack.md"} self.assertEqual(sitestarlight.redirects(profiles, present), { "/emulators/mednafen_psx/": "/emulators/beetle_psx/", "/wiki/getting-started/": "/which-pack/", }) def test_a_moved_page_redirects_only_once_its_source_is_gone(self): present = {"wiki/getting-started.md", "which-pack.md"} self.assertEqual(sitestarlight.redirects({}, present), {}) def test_the_generator_writes_the_file(self): source = Path(generate_site.__file__).read_text(encoding="utf-8") self.assertIn('generated / "redirects.json"', source) class PlatformShareCards(unittest.TestCase): def test_every_platform_page_shares_its_own_card(self): for key in list_registered_platforms(str(REPO_ROOT / "platforms"), include_archived=True): with self.subTest(platform=key): card = REPO_ROOT / "docs_assets" / "og" / "platforms" / f"{key}.png" data = card.read_bytes() self.assertEqual(struct.unpack(">II", data[16:24]), (1200, 630)) self.assertEqual(sitestarlight.og_image(f"platforms/{key}.md", "setup"), f"{sitestarlight.SITE_URL}assets/og/platforms/{key}.png") def test_other_pages_keep_their_section_card(self): self.assertEqual(sitestarlight.og_image("platforms/index.md", "setup"), f"{sitestarlight.SITE_URL}assets/og/setup.png") self.assertEqual(sitestarlight.og_image("systems/sony.md", "explore"), f"{sitestarlight.SITE_URL}assets/og/explore.png") # The built site class _Page(HTMLParser): def __init__(self) -> None: super().__init__() self.where: list[str] = [] self.sidebar: list[str] = [] self.crumbs: list[str | None] = [] self.pagination = False self._sidebar = 0 self._crumbs = 0 self._text: list[str] | None = None self._pagination = False def handle_starttag(self, tag: str, attrs: list) -> None: a = dict(attrs) if tag == "nav" and a.get("aria-label") == "Breadcrumb": self._crumbs = 1 elif self._crumbs and tag == "a": self.crumbs.append(a.get("href")) if "sidebar-content" in (a.get("class") or ""): self._sidebar = 1 elif self._sidebar and tag in ("a", "summary"): self._text = [] if "pagination-links" in (a.get("class") or ""): self._pagination = True elif self._pagination and tag == "a": self.pagination = True def handle_endtag(self, tag: str) -> None: if tag == "nav" and self._crumbs: self._crumbs = 0 if tag == "div" and self._pagination and not self.pagination: self._pagination = False if self._text is not None and tag in ("a", "summary"): self.sidebar.append(" ".join("".join(self._text).split())) self._text = None def handle_data(self, data: str) -> None: if self._text is not None: self._text.append(data) def _read(path: Path) -> _Page: page = _Page() page.feed(path.read_text(encoding="utf-8")) return page @unittest.skipUnless((DIST / "index.html").is_file(), "site not built") class BuiltSite(unittest.TestCase): def test_the_404_sidebar_names_pages(self): page = _read(DIST / "404.html") self.assertTrue(page.sidebar) self.assertFalse([label for label in page.sidebar if re.fullmatch(r"\d+", label)], page.sidebar) def test_crumbs_link_a_tab_an_overview_or_a_section(self): overviews = TAB_ROOTS | {"/retrobios/systems/", "/retrobios/emulators/"} pages = [DIST / rel for rel in ( "platforms/batocera/index.html", "platforms/retroarch/index.html", "systems/sony/index.html", "emulators/duckstation/index.html", "wiki/verification-modes/index.html", "files/index.html")] for path in pages: if not path.is_file(): continue for href in _read(path).crumbs: with self.subTest(page=str(path.relative_to(DIST)), href=href): self.assertTrue(href in overviews or "#" in (href or ""), href) def test_a_setup_page_names_its_group(self): page = _read(DIST / "platforms" / "batocera" / "index.html") self.assertEqual(len(page.crumbs), 2, page.crumbs) def test_card_pages_have_no_previous_or_next(self): for rel in ("systems/sony/index.html", "emulators/duckstation/index.html", "platforms/batocera/index.html"): with self.subTest(page=rel): self.assertFalse(_read(DIST / rel).pagination) self.assertTrue(_read(DIST / "wiki" / "faq" / "index.html").pagination) def test_the_redirects_file_is_written(self): data = json.loads((REPO_ROOT / "website" / "src" / "generated" / "redirects.json") .read_text(encoding="utf-8")) self.assertEqual(data.get("/emulators/mednafen_psx/"), "/emulators/beetle_psx/") if __name__ == "__main__": unittest.main()