feat: round 4 system pages

This commit is contained in:
Abdessamad Derraz committed 2026-10-23 19:23:58 +02:00
commit 5e09301dd8
8 files changed
+1034 -196

No files matched your search

+671 -160
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1056,7 +1056,7 @@ class EveryPageLinksItsOwnData(unittest.TestCase):
"path": "bios/Sony/PlayStation/a.bin"}
page = generate_site.generate_system_page(
"Sony", {"PlayStation": [entry]}, FileConsumers({"files": {}}))
self.assertIn("[JSON](../api/v1/systems/sony.json)", page)
self.assertIn(" as JSON](../api/v1/systems/sony.json)", page)
item = generate_site.system_export_item("sony", "Sony", {"PlayStation": [entry]})
self.assertEqual(item["consoles"]["PlayStation"][0]["sha1"], "a" * 40)
+22 -16
View File
@@ -87,8 +87,18 @@ class TheConsoleBiosComesFirst(unittest.TestCase):
names = [re.search(r"<code>(.*?)</code>", card[2])[1] for _v, card, _s in cards]
self.assertEqual(names, sorted(names, key=str.casefold))
def required_only(self) -> types.SimpleNamespace:
"""The BIOS readers alone: an emulator reading only an optional
file runs without the BIOS, and two of four would say so."""
return _consumers({
"4" * 40: [{"name": "scph5501.bin", "required": True, "priority": 2,
"region": ["north-america"]}],
"5" * 40: [{"name": "scph5500.bin", "required": True, "priority": 5,
"region": ["japan"]}],
})
def test_the_section_opens_on_the_bios_by_region(self):
lines = gs._start_with(self.files, self.consumers)
lines = gs._start_with(self.files, self.required_only())
self.assertEqual(lines[0], "**Start with** the BIOS for the region of your games:")
self.assertIn("- **North America**: `scph5501.bin`", lines)
self.assertIn("- **Japan**: `scph5500.bin`", lines)
@@ -113,7 +123,7 @@ class TheConsoleBiosComesFirst(unittest.TestCase):
self.assertEqual(gs._start_with([_file("a.bin", "a")], _consumers({})), [])
def test_the_page_carries_the_line_under_the_console(self):
page = gs.generate_system_page("Sony", {"PlayStation": self.files}, self.consumers)
page = gs.generate_system_page("Sony", {"PlayStation": self.files}, self.required_only())
heading = page.index("## PlayStation")
self.assertLess(heading, page.index("**Start with**"))
self.assertLess(page.index("**Start with**"), page.index('class="rb-sys-file"'))
@@ -156,23 +166,19 @@ class TheCardLinesSayWhatTheyMean(unittest.TestCase):
def test_labels_and_display_names(self):
text = "\n".join(self.lines)
self.assertIn("<dt data-pagefind-ignore>On the setup's list</dt><dd><a href=\"../"
"platforms/batocera.md#sony-playstation\">Batocera</a>: scph5501.bin</dd>",
text)
self.assertIn("<dt>Packed for its emulators by</dt><dd><a href=\"../platforms/"
"retrobat.md\">RetroBat</a>: bios/scph5501.bin</dd>", text)
# Grouped by where the setups put the file (round 4, 6.9).
self.assertIn("<dt>On the setup's list</dt><dd><code>scph5501.bin</code>: <a href=\"../"
"platforms/batocera.md#sony-playstation\">Batocera</a></dd>", text)
self.assertIn("<dt>Packed for its emulators by</dt><dd><code>bios/scph5501.bin</code>: "
"<a href=\"../platforms/retrobat.md\">RetroBat</a></dd>", text)
self.assertNotIn("Platforms:", text)
self.assertNotIn("Also installed by", text)
def test_the_setup_list_is_searched_and_the_rest_is_not(self):
def test_the_facts_are_out_of_the_search(self):
# The setup list made every card's excerpt a run of names and
# paths (round 4, V6): the card's title is what the search reads.
lists = [line for line in self.lines if line.startswith("<dl")]
self.assertEqual(lists, ['<dl class="rb-facts">', '<dl class="rb-facts" data-pagefind-ignore>'])
searched = "\n".join(self.lines).split('<dl class="rb-facts">', 1)[1].split("</dl>")[0]
self.assertIn("On the setup's list", searched)
self.assertNotIn("SHA1", searched)
# The label stays out of the excerpt: the search ran the two
# together, "On the setup's listAmberELEC".
self.assertIn("<dt data-pagefind-ignore>On the setup's list</dt><dd>", searched)
self.assertEqual(lists, ['<dl class="rb-facts" data-pagefind-ignore>'])
def test_the_page_defines_both_lines(self):
page = gs.generate_system_page("Sony", {"PlayStation": [_file("scph5501.bin", "4")]},
@@ -246,7 +252,7 @@ class TheSystemsIndexListsConsoles(unittest.TestCase):
# A program's folder under Other is named alone, in its own table.
consoles = page.split("## Emulator folders")[0]
self.assertNotIn("ScummVM", consoles)
self.assertIn("| [ScummVM](other.md#scummvm) | 1 | [Other](other.md) |",
self.assertIn("| [ScummVM](../emulators/scummvm.md) | [1 file](other.md#scummvm) |",
page.split("## Emulator folders")[1])
self.assertNotIn("TheXTech", consoles)
self.assertIn("| [TheXTech](game-engines.md#thextech)", page)
+10 -10
View File
@@ -137,12 +137,12 @@ class ArcadeSetsAreNamedWhereverFiled(unittest.TestCase):
"system": "snk-neogeo-mvs"})],
}, db_files=[self.aes, neogeo, mega_cd, shader])
def test_one_line_per_system_and_the_arcade_set_linked(self) -> None:
def test_the_file_most_emulators_read_leads_and_the_arcade_set_is_linked(self) -> None:
lines = gs._start_with([self.aes], self.consumers, section=("SNK", "Neo Geo"))
self.assertEqual(lines[0], "**Start with** the files of your machine:")
link = f"[`neogeo.zip`](/retrobios/files/?sha1={'2' * 40})"
self.assertIn(f"- **SNK Neo Geo MVS**: {link}", lines)
self.assertIn("- **SNK Neo Geo AES**: `aes.zip`", lines)
self.assertEqual(lines[0], f"**Start with** {link}. Keep it zipped. "
"mame0, mame1 and mame2 read it.")
self.assertIn("Geolith reads `aes.zip` instead.", lines)
def test_another_consoles_file_or_an_emulators_folder_is_left_out(self) -> None:
text = "\n".join(gs._start_with([self.aes], self.consumers, section=("SNK", "Neo Geo")))
@@ -190,13 +190,12 @@ class CardsSayWhichReleaseTheSetMatches(unittest.TestCase):
return "\n".join(gs._system_file_card(f, _consumers({})))
def test_the_matching_releases_as_runs(self) -> None:
self.assertIn("<dt>Romset</dt><dd>MAME 0.190 to 0.195, 0.220, FBNeo 1.0.0.03</dd>",
self.card())
self.assertIn("<dt>Romset</dt><dd>Same contents as MAME 0.190 to 0.195, 0.220 and "
"FBNeo 1.0.0.03</dd>", self.card())
def test_no_match_names_what_was_compared(self) -> None:
def test_no_match_counts_what_is_missing(self) -> None:
self.releases["set.zip"] = self.releases["set.zip"][2:]
with mock.patch.object(gs, "_romset_coverage", return_value="MAME 0.190 to 0.289"):
self.assertIn("<dd>No match in MAME 0.190 to 0.289</dd>", self.card())
self.assertIn("<dd>No match: missing 1 ROM of MAME 0.289</dd>", self.card())
def test_no_recipe_no_line(self) -> None:
self.releases = {}
@@ -256,7 +255,8 @@ class TheBuiltPages(unittest.TestCase):
page = (DIST / "systems" / "nintendo" / "index.html").read_text(encoding="utf-8")
start = page.index('id="nintendo-ds"')
section = page[start:page.index('id="nintendo-dsi"')]
opening = section[:section.index('class="rb-sys-file"')]
# The section may have moved to a page of its own: no card follows.
opening = section.split('class="rb-sys-file"')[0]
self.assertRegex(opening, r"No file needed|Start with")
self.assertNotRegex(opening.casefold(), r"dsi_|bios7i|bios9i")
+317
View File
@@ -0,0 +1,317 @@
"""System pages and file cards (round 4, package 6).
The Game Boy, NES and SNES sections said "Start with" a file one core of
sixteen requires, under a home page saying they need none. The romset line
said no MAME release matched sets MAME loads. The Neo Geo opened on files
ares alone reads, and RetroArch had no install line. The Arcade page was
sorted by storage folder: NAOMI, PGM and ZN-1 had no section, and their
names were plain on every emulator page. Cards named emulators by id, part
pages had no parent and no name, and four paragraphs came before the first
console.
"""
from __future__ import annotations
import json
import re
import sys
import types
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT / "scripts"))
import generate_site as gs # noqa: E402
from sitestarlight import toc_slug # noqa: E402
DIST = REPO_ROOT / "website" / "dist"
CONTENT = REPO_ROOT / "website" / "src" / "content" / "docs"
def _file(name: str, char: str, path: str = "") -> dict:
return {"name": name, "sha1": char * 40, "md5": char * 32, "size": 4096,
"path": path or f"bios/Nintendo/Game Boy/{name}"}
def _consumers(readers: dict[str, list[tuple[str, dict]]],
systems: dict[str, dict[str, set[str]]] | None = None) -> types.SimpleNamespace:
"""Consumers where each sha1 is read by the given (emulator, entry)."""
files_of: dict[str, list] = {}
entries: dict[str, dict[str, int]] = {}
emulators: dict[str, set[str]] = {}
for sha1, read_by in readers.items():
for emulator, entry in read_by:
files_of.setdefault(emulator, []).append(entry)
entries.setdefault(sha1, {})[emulator] = len(files_of[emulator]) - 1
emulators.setdefault(sha1, set()).add(emulator)
return types.SimpleNamespace(
platforms={}, emulators=emulators, emulator_entries=entries,
emulator_files=files_of, installed={}, platform_systems=systems or {},
platform_destinations={}, _db={"files": {}},
)
def _page(path: Path) -> str:
if not path.is_file():
raise unittest.SkipTest(f"{path.relative_to(REPO_ROOT)} not built")
return path.read_text(encoding="utf-8")
def _opening(html: str, anchor: str) -> str:
"""The text of the first paragraph under a heading."""
start = html.index("<p>", html.index(f'id="{anchor}"'))
return re.sub(r"<[^>]+>", "", html[start:html.index("</p>", start)]).strip()
class AFileFewRequireIsNoStartFile(unittest.TestCase):
"""6.1: one requiring emulator decided the line for the console."""
def test_one_of_four_says_no_file_needed_and_names_it(self) -> None:
boot = _file("dmg_boot.bin", "1")
optional = {"required": False, "system": "nintendo-gb"}
consumers = _consumers({"1" * 40: [
("emux_gb", {"name": "dmg_boot.bin", "required": True}),
("gambatte", optional), ("mgba", optional), ("sameboy", optional)]})
line = gs._start_with([boot], consumers)[0]
self.assertTrue(line.startswith("**No file needed** with "), line)
self.assertRegex(line, r"Only \S.* requires `dmg_boot.bin`\.$")
def test_most_requiring_keeps_the_start_line(self) -> None:
bios = _file("scph5501.bin", "2")
required = {"name": "scph5501.bin", "required": True}
consumers = _consumers({"2" * 40: [
("a", required), ("b", required), ("c", {"required": False, "hle_fallback": True})]})
self.assertEqual(gs._start_with([bios], consumers)[0], "**Start with**: `scph5501.bin`.")
def test_an_entry_read_for_a_system_with_no_section_is_dropped(self) -> None:
sample = _file("ym2608_adpcm_rom.bin", "3", "bios/Nintendo/NES/ym2608_adpcm_rom.bin")
consumers = _consumers({"3" * 40: [
("fbneo", {"name": "ym2608_adpcm_rom.bin", "required": True,
"system": "no-such-system-anywhere"})]})
self.assertEqual(gs._start_with([sample], consumers, section=("Nintendo", "NES")), [])
def test_the_home_usually_not_consoles_open_on_no_file_needed(self) -> None:
home = _page(DIST / "index.html")
match = re.search(r"<strong>Usually not</strong> for ([^<.]+)", home)
self.assertIsNotNone(match, "the home page no longer says Usually not")
names = re.split(r", | and ", match[1])
sections = {"NES": ("nintendo", "nes"), "Super Nintendo": ("nintendo", "snes"),
"Mega Drive": ("sega", "mega-drive"), "Game Boy": ("nintendo", "game-boy"),
"Game Boy Advance": ("nintendo", "game-boy-advance")}
for name in names:
self.assertIn(name, sections, f"map {name!r} to its section")
page, anchor = sections[name]
html = _page(DIST / "systems" / page / "index.html")
self.assertTrue(_opening(html, anchor).startswith("No file needed"), name)
class TheRomsetLineMatchesByCrc(unittest.TestCase):
"""6.2: a set with an extra BIOS revision read "No match"."""
def test_the_main_neogeo_set_names_every_mame_release(self) -> None:
db = json.loads((REPO_ROOT / "database.json").read_text(encoding="utf-8"))
main = next((f for sha1, f in db["files"].items() if sha1.startswith("447e2375")), None)
if main is None or not (REPO_ROOT / main["path"]).is_file():
self.skipTest("the main neogeo.zip is not in this checkout")
line = gs._romset_line(main)
self.assertIn("Holds every ROM of MAME 0.190 to 0.289", line)
self.assertNotIn("No match", line)
class TheNeoGeoOpensOnItsSet(unittest.TestCase):
"""6.3: the set 26 emulators read leads, RetroArch gets its command."""
def test_the_lead_names_the_exceptions(self) -> None:
neogeo = _file("neogeo.zip", "4", "bios/SNK/Neo Geo/neogeo.zip")
epo = _file("neo-epo.sp1", "5", "bios/SNK/Neo Geo/neo-epo.sp1")
mvs = {"name": "neogeo.zip", "required": True, "system": "snk-neogeo-mvs"}
consumers = _consumers({
"4" * 40: [(f"mame{n}", mvs) for n in range(4)],
"5" * 40: [("ares", {"name": "neo-epo.sp1", "required": True,
"system": "snk-neogeo"})],
})
lines = gs._start_with([neogeo, epo], consumers, section=("SNK", "Neo Geo"))
self.assertTrue(lines[0].startswith("**Start with** `neogeo.zip`. Keep it zipped. "))
self.assertIn("ares reads `neo-epo.sp1` instead.", lines)
cards = gs._console_cards("SNK", [epo, neogeo], consumers, set(),
start=gs._start_order([neogeo, epo], consumers,
("SNK", "Neo Geo")))
self.assertIn("neogeo.zip", cards[0][1][2])
def test_a_setup_filing_the_set_elsewhere_gets_its_command(self) -> None:
consumers = _consumers({}, systems={"a" * 40: {"retroarch": {"arcade"}}})
pairs = gs._install_pairs([("batocera", "snk-neogeo")], "a" * 40, consumers)
self.assertIn(("retroarch", "arcade"), pairs)
block = "\n".join(gs._console_install_block("Neo Geo", pairs))
self.assertIn("# RetroArch\n", block)
def test_the_built_section(self) -> None:
html = _page(DIST / "systems" / "snk" / "index.html")
section = html[html.index('id="neo-geo"'):]
self.assertTrue(_opening(html, "neo-geo").startswith("Start with neogeo.zip"))
self.assertIn("--platform retroarch --system arcade", re.sub(r"<[^>]+>", "", section))
BOARDS = ("Sega NAOMI", "Sammy Atomiswave", "Capcom ZN-1", "IGS PolyGame Master",
"Nintendo PlayChoice-10", "SNK Hyper Neo Geo 64")
class EachArcadeBoardHasASection(unittest.TestCase):
"""6.4: the Arcade page was cut by storage folder."""
def test_each_board_heads_its_page(self) -> None:
for name in BOARDS:
slug = toc_slug(name)
html = _page(DIST / "systems" / f"arcade-{slug}" / "index.html")
self.assertRegex(html, rf'<h2[^>]*id="{re.escape(slug)}"', name)
def test_every_board_on_an_emulator_systems_row_is_a_link(self) -> None:
if not (DIST / "emulators").is_dir():
self.skipTest("site not built")
plain = []
for page in sorted((DIST / "emulators").glob("*/index.html")):
row = re.search(r"<td>Systems</td>\s*<td>(.*?)</td>", page.read_text(encoding="utf-8"),
re.S)
if not row:
continue
text = re.sub(r"<a [^>]*>.*?</a>", "", row[1])
plain += [(page.parent.name, name) for name in BOARDS if name in text]
self.assertEqual(plain, [])
def test_the_arcade_page_lists_the_boards_first(self) -> None:
md = _page(CONTENT / "systems" / "arcade.md")
headings = re.findall(r"^## (.+?) \{", md, re.M)
self.assertIn("Sega NAOMI", headings)
self.assertLess(headings.index("Sega NAOMI"), headings.index(gs.OTHER_ARCADE_LABEL))
class CardsNameEmulatorsByName(unittest.TestCase):
"""6.6: 19,499 card links read the profile id."""
def test_the_display_name_is_the_link_text(self) -> None:
sha1 = "6" * 40
consumers = _consumers({sha1: [("melonds_ds", {"name": "bios7.bin"})]})
link = gs._emulator_links(sha1, consumers)[0]
self.assertTrue(link.startswith("[melonDS DS]("), link)
def test_no_built_card_link_reads_an_id(self) -> None:
if not (DIST / "systems").is_dir():
self.skipTest("site not built")
profiles = gs._profiles_on_disk()
bad = set()
for page in (DIST / "systems").glob("*/index.html"):
for emulator, text in re.findall(
r'<a href="/retrobios/emulators/([^/"#]+)/[^"]*">([^<]+)</a>',
page.read_text(encoding="utf-8")):
display = str((profiles.get(emulator) or {}).get("emulator") or emulator)
if text == emulator and display != emulator:
bad.add(emulator)
self.assertEqual(sorted(bad), [])
class PartPagesKnowWhereTheyAre(unittest.TestCase):
def tearDown(self) -> None:
# system_pages records where consoles went, for every later page.
gs.reset_console_pages()
"""6.7: 128 system pages had no parent, parts were "part 3 of 5"."""
def test_every_part_has_a_parent_and_a_name(self) -> None:
if not (CONTENT / "systems").is_dir():
self.skipTest("pages not generated")
orphans, numbered = [], []
for page in sorted((CONTENT / "systems").glob("*.md*")):
text = page.read_text(encoding="utf-8")
if "Part of " in text and "\nparent:" not in text.split("\n---\n", 1)[0]:
orphans.append(page.name)
if re.search(r"^title: .*part \d+ of \d+", text, re.M | re.I):
numbered.append(page.name)
self.assertEqual(orphans, [])
self.assertEqual(numbered, [])
def test_parts_are_even_and_linked_to_their_neighbours(self) -> None:
files = [_file(f"rom{i:02}.bin", "0123456789"[i], f"bios/Sony/PlayStation 2/rom{i:02}.bin")
for i in range(10)]
consumers = _consumers({})
with gs.markup.using("mkdocs"):
one = sum(len(line) + 1 for line in gs._system_file_card(files[0], consumers))
pages = gs.system_pages("Sony", {"PlayStation 2": files}, consumers, budget=one * 7)
parts = [stem for stem in pages if stem != "sony"]
self.assertEqual(len(parts), 2)
counts = [pages[stem][0].count('class="rb-sys-file"') for stem in parts]
self.assertEqual(counts, [5, 5])
first = pages[parts[0]][0]
self.assertIn("parent:\n label: \"Sony\"\n link: \"/systems/sony/\"", first)
self.assertIn(f"next:\n label: \"PlayStation 2 files rom05.bin to rom09.bin\"\n"
f" link: \"/systems/{parts[1]}/\"", first)
self.assertIn("# PlayStation 2 files rom00.bin to rom04.bin", first)
self.assertIn(f"searchWeight: {gs._part_weight()}", first)
def _consoles(page: str) -> list[str]:
"""The consoles: front matter of a generated page."""
front = page.split("\n---\n", 1)[0] if page.startswith("---\n") else ""
block = re.search(r"^consoles:\n((?: - .*\n?)+)", front + "\n", re.M)
return [json.loads(line[4:]) for line in block[1].splitlines()] if block else []
class ConsolesFieldNamesMachinesOnce(unittest.TestCase):
def tearDown(self) -> None:
# system_pages records where consoles went, for every later page.
gs.reset_console_pages()
"""The consoles search field: only machine sections, and a console
with a page of its own is that page's, or "dolphin" found Other and
"playstation bios" found Sony before the PlayStation page."""
def test_no_emulator_folder_is_a_console(self) -> None:
for maker in ("Other", "Game Engines"):
page = gs.system_pages(maker, {"dolphin": [_file("a.bin", "a")]}, _consumers({}))
self.assertEqual(_consoles(page[gs._slug(maker)][0]), [], maker)
def test_a_console_with_its_own_page_is_left_to_it(self) -> None:
big = [_file(f"r{i:02}.bin", "0123456789"[i], f"bios/Sony/PlayStation/r{i:02}.bin")
for i in range(10)]
small = [_file("pocket.bin", "p", "bios/Sony/PocketStation/pocket.bin")]
consumers = _consumers({})
with gs.markup.using("mkdocs"):
one = sum(len(line) + 1 for line in gs._system_file_card(big[0], consumers))
pages = gs.system_pages("Sony", {"PlayStation": big, "PocketStation": small},
consumers, budget=one * 12)
self.assertIn("sony-playstation", pages)
self.assertEqual(_consoles(pages["sony"][0]), ["PocketStation"])
self.assertEqual(_consoles(pages["sony-playstation"][0]), ["PlayStation"])
class OneLineBeforeTheFirstConsole(unittest.TestCase):
"""6.8: four paragraphs, word for word on every maker's page."""
def test_the_intro_is_one_line_and_the_legend_closed(self) -> None:
page = gs.generate_system_page("SNK", {"Neo Geo": [_file("a.bin", "a")]}, _consumers({}))
body = page.split("# SNK\n", 1)[1]
before = body[:body.index("## Neo Geo")]
self.assertTrue(before.lstrip().startswith(
"Every BIOS file for SNK machines, one card per file.\n"))
legend = before[before.index("<section data-pagefind-ignore>"):]
self.assertIn("<summary>How to read a card</summary>", legend)
self.assertNotIn(" open>", legend)
self.assertNotIn("JSON", before)
self.assertIn("as JSON](../api/v1/systems/snk.json)", page.split("## Next steps")[1])
class SetupsGroupedByPlace(unittest.TestCase):
"""6.9: each setup repeated the name it gives the file."""
def test_one_line_per_place(self) -> None:
places = [("Batocera", "b.md", ["scph5501.bin"]), ("KNULLI", "k.md", ["scph5501.bin"]),
("RetroDECK", "r.md", ["bios/scph5501.bin"]), ("EmuDeck", "e.md", [])]
self.assertEqual(gs._places_lines(places), [
"<code>scph5501.bin</code>: [Batocera](b.md), [KNULLI](k.md)",
"<code>bios/scph5501.bin</code>: [RetroDECK](r.md)",
"[EmuDeck](e.md)",
])
if __name__ == "__main__":
unittest.main()
+7 -7
View File
@@ -160,11 +160,11 @@ class FileCardsLinkTheReadingEntry(unittest.TestCase):
consumers.emulator_entries = {"s": {"duckstation": 1}, "t": {"blastem": 2}}
consumers.emulator_files = {"duckstation": files, "blastem": files}
self.assertEqual(generate_site._emulator_links("s", consumers), [
"[duckstation](../emulators/duckstation.md#file-scph5501-bin)",
"[DuckStation](../emulators/duckstation.md#file-scph5501-bin)",
"[other](../emulators/other.md)",
])
self.assertEqual(generate_site._emulator_links("t", consumers),
["[blastem](../emulators/blastem.md#file-tmss-md-d0fd0c16)"])
["[BlastEm](../emulators/blastem.md#file-tmss-md-d0fd0c16)"])
class DirectoryMembersNameTheirEmulator(unittest.TestCase):
@@ -193,7 +193,7 @@ class DirectoryMembersNameTheirEmulator(unittest.TestCase):
index = self.consumers.emulator_entries[self.SHA1]["alephone"]
self.assertEqual(files[index].get("type"), "directory")
self.assertEqual(generate_site._emulator_links(self.SHA1, self.consumers),
["[alephone](../emulators/alephone.md#file-marathon-plugins)"])
["[Aleph One](../emulators/alephone.md#file-marathon-plugins)"])
def test_the_directory_card_points_at_its_copies(self):
import generate_site
@@ -360,12 +360,12 @@ class TheSearchSkipsLinkLists(unittest.TestCase):
self.assertIn("<code>a.bin</code>", lines[2])
text = "\n".join(lines)
searched, _sep, skipped = text.partition('<dl class="rb-facts" data-pagefind-ignore>')
for label in ("MD5", "Emulators", "Packed for its emulators by"):
for label in ("MD5", "Emulators", "Packed for its emulators by", "On the setup's list"):
self.assertIn(f"<dt>{label}</dt>", skipped)
self.assertNotIn(f">{label}</dt>", searched)
# The platforms line names the file as each setup does: the site
# search reads it, so a file name finds its card.
self.assertRegex(searched, r'<dl class="rb-facts">\n<dt[^>]*>On the setup\'s list</dt>')
# The card's title is what the search reads (round 4, V6): the setup
# list made every excerpt a run of names and paths.
self.assertNotIn('<dl class="rb-facts">', searched)
def test_a_game_data_card_skips_its_name_too(self):
import generate_site
+5 -1
View File
@@ -318,7 +318,11 @@ class EverySystemLinkLandsOnItsSection(unittest.TestCase):
self.assertIn(page, self.headings)
if match["anchor"]:
self.assertIn(match["anchor"], self.headings[page])
_slug, console = self.siterender.system_section(sys_id)
slug, console = self.siterender.system_section(sys_id)
# The Arcade folder of sets no board claims is headed by
# what it holds, its id staying the folder's.
if slug == "arcade":
console = gs._section_label("Arcade", console)
self.assertEqual(self.headings[page][match["anchor"]], console)
self.assertGreater(linked, 200)
+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_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_p0`, `test_site_r4_search`, `test_site_r4_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_p0`, `test_site_r4_search`, `test_site_r4_systems`, `test_site_r4_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