mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-26 05:37:20 -05:00
feat: round 4 system pages
This commit is contained in:
commit
5e09301dd8
8 files changed
+1034
-196
No files matched your search
+671
-160
File diff suppressed because it is too large.
Load diff
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user