chore: cover the round 5 search package

This commit is contained in:
Abdessamad Derraz committed 2026-10-27 05:40:09 +01:00
1 parent 9b2cddfbe5
commit 6a611cd77a
2 files changed
+289 -1

No files matched your search

+288
View File
@@ -0,0 +1,288 @@
"""Round 5, package 3: search, sidebar and head.
The get/ download pages sat in no tab, and the theme then numbered the tabs
0 to 4. "snes bios" found NES.emu and never the SNES page, which answers
"No file needed" and so never says BIOS where the search reads. An emulator
crumb ran through its letter group ("Emulators / D"). The home page's
structured data named the page and called it part of itself, and an
emulator's source repository published a commit id as its version beside a
placeholder description. These tests read the generator and, where it is
built, the site.
"""
from __future__ import annotations
import ast
import json
import re
import sys
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_emulator_profiles # noqa: E402
from tests.test_site_r2_nav import ANCHORS, _nav, _present # noqa: E402
def _gate_queries() -> dict[str, tuple[tuple[str, ...], int]]:
"""The gate's QUERIES, read from website/tools/search_gate.py: a tool
script, not a module the suite installs."""
tree = ast.parse((REPO_ROOT / "website" / "tools" / "search_gate.py").read_text(encoding="utf-8"))
node = next(node for node in tree.body if isinstance(node, ast.AnnAssign)
and isinstance(node.target, ast.Name) and node.target.id == "QUERIES")
return ast.literal_eval(node.value)
QUERIES = _gate_queries()
DIST = REPO_ROOT / "website" / "dist"
ROUTE = REPO_ROOT / "website" / "src" / "routeData.ts"
PLATFORMS = str(REPO_ROOT / "platforms")
def _sidebar_with_get_pages() -> tuple[dict, list[str]]:
nav = _nav()
names = list_registered_platforms(PLATFORMS, include_archived=True)
get_pages = ["get/index.md", *(f"get/{name}.md" for name in names)]
return sitestarlight.sidebar(nav, _present(nav) | set(get_pages), ANCHORS), get_pages
class GetPagesSitInYourSetup(unittest.TestCase):
"""3.1: a folder the navigation lists nothing of takes the tab of the
pages of the same name, and nothing is excluded from every tab."""
def test_every_get_page_joins_your_setup(self):
side, get_pages = _sidebar_with_get_pages()
self.assertEqual(side["options"]["exclude"], [])
setup = set(side["options"]["topics"].get("your-setup", []))
for rel in get_pages:
with self.subTest(page=rel):
self.assertIn("/" + sitestarlight._sidebar_link(rel).strip("/"), setup)
def test_no_page_is_left_out_of_every_tab(self):
nav = _nav()
present = _present(nav) | {"orphans/alone.md"}
side = sitestarlight.sidebar(nav, present, ANCHORS)
self.assertEqual(side["options"]["exclude"], [])
placed = {path for paths in side["options"]["topics"].values() for path in paths}
self.assertIn("/orphans/alone", placed)
class ConsoleWordsReachTheConsole(unittest.TestCase):
"""3.2: "<name> BIOS" for each console the page names, from its own
`consoles` field, never a word typed in the generator."""
def test_each_console_with_bios(self):
self.assertEqual(sitestarlight.console_words(["SNES", "Super Famicom"]),
["SNES BIOS", "Super Famicom BIOS"])
def test_the_terms_field_carries_them(self):
_target, text = sitestarlight.finalize_page(
"systems/nintendo-snes.md", "# SNES\n\nNo file needed.\n", level="0",
weight=None, browser_title="SNES", description="d",
structured={"@type": "TechArticle"}, terms={},
authored={"consoles": ["SNES"]})
self.assertIn('data-pagefind-meta="consoles:SNES"', text)
self.assertIn('data-pagefind-meta="terms:SNES BIOS"', text)
def test_no_search_word_is_typed_in_the_generator(self):
tree = ast.parse((REPO_ROOT / "scripts" / "sitestarlight.py").read_text(encoding="utf-8"))
docstrings = {id(node.value) for node in ast.walk(tree) if isinstance(node, ast.Expr)}
strings = {node.value.casefold() for node in ast.walk(tree)
if isinstance(node, ast.Constant) and isinstance(node.value, str)
and id(node) not in docstrings}
typed = {text for text in strings for query in QUERIES
if re.search(rf"(?<!\w){re.escape(query)}(?!\w)", text)}
self.assertEqual(typed, set())
class GateQueries(unittest.TestCase):
"""3.2, 3.3 and 3.6: the queries the gate runs, the pages that answer
them and the rank they must reach."""
EXPECTED = {
"snes bios": (("systems/nintendo-snes/",), 1),
"nes bios": (("systems/nintendo-nes/",), 1),
"gamecube bios": (("systems/nintendo-gamecube/",), 1),
"game boy bios": (("systems/nintendo-game-boy/",), 1),
"3ds bios": (("systems/nintendo-3ds/",), 1),
"check my bios folder": (("wiki/troubleshooting/",), 3),
"is my bios correct": (("files/",), 3),
"schema": (("data/",), 3),
"json schema": (("data/",), 3),
"license": (("data/", "wiki/legal/"), 3),
"check bios folder script": (("data/",), 3),
"ios": (("which-pack/", "wiki/faq/"), 3),
}
def test_the_gate_runs_each_query(self):
for query, expected in self.EXPECTED.items():
with self.subTest(query=query):
self.assertEqual(QUERIES.get(query), expected)
def test_each_console_query_names_a_generated_console_page(self):
docs = REPO_ROOT / "website" / "src" / "content" / "docs"
if not docs.is_dir():
self.skipTest("site not generated")
for query, (pages, _rank) in self.EXPECTED.items():
if not query.endswith(" bios"):
continue
stem = pages[0].strip("/")
with self.subTest(query=query):
self.assertTrue(any((docs / f"{stem}{ext}").is_file() for ext in (".md", ".mdx")),
stem)
class LetterGroupsAreNoCrumb(unittest.TestCase):
"""3.4: a group of names by their first letter is a way through a
list, not a place."""
def test_by_initial(self):
names = [{"label": "Dolphin", "link": "/d"}, {"label": "DuckStation", "link": "/e"}]
self.assertTrue(sitestarlight.by_initial("D", names))
self.assertFalse(sitestarlight.by_initial("E", names))
digits = [{"label": "2600.emu", "link": "/a"}, {"label": "81", "link": "/b"}]
self.assertTrue(sitestarlight.by_initial("0 to 9", digits))
self.assertFalse(sitestarlight.by_initial("Sony", names))
self.assertFalse(sitestarlight.by_initial("D", [{"label": "D", "items": names}]))
def test_emulator_letter_groups_are_marked(self):
side = sitestarlight.sidebar(_nav(), _present(_nav()), ANCHORS)
letters = {key: value for key, value in side["groups"].items()
if key.startswith("Explore/Emulators/")}
self.assertTrue(letters)
for key, group in letters.items():
with self.subTest(group=key):
self.assertIs(group["crumb"], False)
self.assertIs(side["groups"]["Explore/Emulators"]["crumb"], True)
self.assertIs(side["groups"]["Explore/Systems"]["crumb"], True)
def test_the_route_middleware_skips_them(self):
self.assertIn("group?.crumb === false", ROUTE.read_text(encoding="utf-8"))
class StructuredData(unittest.TestCase):
"""3.5: the home page is the site, and a source repository states the
release, never a commit id, and no placeholder description."""
def test_versions_name_releases_only(self):
release = generate_site.release_versions
self.assertEqual(release({"core_version": "v0.1-12070"}), {"": "v0.1-12070"})
self.assertEqual(release({"core_version": "1.5.85"}), {"": "1.5.85"})
self.assertEqual(release({"core_version": "8dc77b0"}), {})
self.assertEqual(release({"core_version": "0.8.139-2-g25e1ee2fd5"}), {})
self.assertEqual(release({"core_version": "libretro branch"}), {})
self.assertEqual(release({"core_version": "edge"}), {})
self.assertEqual(release({}), {})
self.assertEqual(release({"core_version": {"standalone": "v1.1", "libretro": "4122fed"}}),
{"standalone": "v1.1"})
# A pin of digits only, in the version: supermodel3.
self.assertEqual(release({"core_version": "0.3a-1481545-mm DirtBagXon (Sinden)",
"upstream_commit": "14815455dc28fca2c8662fb1ed99d447ecf8f364"}),
{})
# A dated release whose digits start no pin stays.
self.assertEqual(release({"core_version": "20200712", "source_commit": "a1626f4e"}),
{"": "20200712"})
def test_the_pin_is_never_a_version(self):
data = generate_site.emulator_structured_data({
"emulator": "Redream", "source": "https://github.com/x/redream",
"source_commit": "0123abcd",
})
self.assertEqual(data["about"], {"@type": "SoftwareSourceCode", "name": "Redream",
"codeRepository": "https://github.com/x/redream"})
class _Head(HTMLParser):
def __init__(self) -> None:
super().__init__()
self.ld: list[dict] = []
self.sidebar: list[str] = []
self.tabs_current = 0
self._ld: list[str] | None = None
self._label: list[str] | None = None
self._tabs = False
def handle_starttag(self, tag: str, attrs: list) -> None:
a = dict(attrs)
if tag == "script" and a.get("type") == "application/ld+json":
self._ld = []
if tag == "summary":
self._label = []
if tag == "nav" and a.get("aria-label") == "Sections":
self._tabs = True
elif self._tabs and tag == "a" and a.get("aria-current"):
self.tabs_current += 1
def handle_endtag(self, tag: str) -> None:
if tag == "nav":
self._tabs = False
if tag == "script" and self._ld is not None:
self.ld.append(json.loads("".join(self._ld)))
self._ld = None
if tag == "summary" and self._label is not None:
self.sidebar.append(" ".join("".join(self._label).split()))
self._label = None
def handle_data(self, data: str) -> None:
for buffer in (self._ld, self._label):
if buffer is not None:
buffer.append(data)
def _head(path: Path) -> _Head:
page = _Head()
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_no_sidebar_group_is_named_by_a_number(self):
for path in sorted(DIST.rglob("*.html")):
labels = _head(path).sidebar
with self.subTest(page=str(path.relative_to(DIST))):
self.assertFalse([label for label in labels if re.fullmatch(r"\d+", label)])
def test_every_get_page_has_one_current_tab(self):
pages = sorted((DIST / "get").rglob("index.html"))
self.assertTrue(pages)
for path in [*pages, DIST / "404.html", DIST / "index.html"]:
with self.subTest(page=str(path.relative_to(DIST))):
self.assertEqual(_head(path).tabs_current, 1)
def test_the_home_page_is_the_site(self):
ld = _head(DIST / "index.html").ld
self.assertEqual(len(ld), 1)
self.assertEqual(ld[0]["@type"], "WebSite")
self.assertEqual(ld[0]["name"], "RetroBIOS")
self.assertNotIn("isPartOf", ld[0])
def test_no_source_repository_states_a_commit_or_a_description(self):
pins = {str(value).casefold()[:7]
for profile in load_emulator_profiles(str(REPO_ROOT / "emulators")).values()
for field in ("source_commit", "upstream_commit")
for value in (profile[field].values() if isinstance(profile.get(field), dict)
else [profile.get(field)])
if value}
self.assertTrue(pins)
seen = 0
for path in sorted((DIST / "emulators").rglob("index.html")):
for data in _head(path).ld:
about = data.get("about") or []
for entry in about if isinstance(about, list) else [about]:
seen += 1
with self.subTest(page=str(path.relative_to(DIST)), repo=entry.get("codeRepository")):
self.assertNotIn("description", entry)
runs = re.findall(r"[0-9a-f]{7,40}", str(entry.get("version", "")).casefold())
self.assertFalse([run for run in runs if run[:7] in pins])
self.assertFalse(generate_site._COMMIT_ID.search(str(entry.get("version", ""))))
self.assertTrue(seen)
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -57,7 +57,7 @@ Every module in `tests/`, by the area it guards.
| Installer and downloads | loopback server | `test_check_release`, `test_download`, `test_install`, `test_install_cores`, `test_install_experience`, `test_install_app`, `test_install_from`, `test_install_offline`, `test_install_selection`, `test_install_tui` |
| Handheld launchers | a pty as the console, fake installer and firmware tools, stubbed PortMaster dialogs | `test_handheld_launchers`, `test_port` |
| Truth and native export | synthetic + cached originals | `test_export_counts`, `test_export_native_layer`, `test_export_report`, `test_exporters`, `test_truth_data_dir_readers`, `test_truth_diff`, `test_truth_layout` |
| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_r2_components`, `test_site_r2_css`, `test_site_r2_emulators`, `test_site_r2_entry`, `test_site_r2_gate`, `test_site_r2_nav`, `test_site_r2_p0`, `test_site_r2_reference`, `test_site_r2_systems`, `test_site_r2_wiki`, `test_site_r3_collection`, `test_site_r3_components`, `test_site_r3_css`, `test_site_r3_emulators`, `test_site_r3_entry`, `test_site_r3_nav`, `test_site_r3_p0`, `test_site_r3_reference`, `test_site_r3_systems`, `test_site_r3_wiki`, `test_site_r4_components`, `test_site_r4_css`, `test_site_r4_emulators`, `test_site_r4_entry`, `test_site_r4_merge`, `test_site_r4_p0`, `test_site_r4_reference`, `test_site_r4_search`, `test_site_r4_systems`, `test_site_r4_wiki`, `test_site_r5_components`, `test_site_r5_css`, `test_site_r5_emulators`, `test_site_r5_entry`, `test_site_r5_reference`, `test_site_r5_systems`, `test_site_r5_wiki`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_source_ref_targets`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_r2_components`, `test_site_r2_css`, `test_site_r2_emulators`, `test_site_r2_entry`, `test_site_r2_gate`, `test_site_r2_nav`, `test_site_r2_p0`, `test_site_r2_reference`, `test_site_r2_systems`, `test_site_r2_wiki`, `test_site_r3_collection`, `test_site_r3_components`, `test_site_r3_css`, `test_site_r3_emulators`, `test_site_r3_entry`, `test_site_r3_nav`, `test_site_r3_p0`, `test_site_r3_reference`, `test_site_r3_systems`, `test_site_r3_wiki`, `test_site_r4_components`, `test_site_r4_css`, `test_site_r4_emulators`, `test_site_r4_entry`, `test_site_r4_merge`, `test_site_r4_p0`, `test_site_r4_reference`, `test_site_r4_search`, `test_site_r4_systems`, `test_site_r4_wiki`, `test_site_r5_components`, `test_site_r5_css`, `test_site_r5_emulators`, `test_site_r5_entry`, `test_site_r5_reference`, `test_site_r5_search`, `test_site_r5_systems`, `test_site_r5_wiki`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_source_ref_targets`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
## Test architecture