mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 04:39:06 -05:00
The safety list names the save folder, every install command shows its Windows form, first install reads the last line of a run, the FAQ answers zip or unzip and the arcade version rule, and each page keeps one name.
264 lines
12 KiB
Python
264 lines
12 KiB
Python
"""The site reads as a funnel, from someone who knows nothing to a contributor.
|
|
|
|
Five tabs in reading order, every registered platform reachable from the
|
|
device picker, a level on every page, and no page for beginners sending its
|
|
reader two levels deeper without saying so.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import contextlib
|
|
import os
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
|
|
import generate_site # noqa: E402
|
|
from tests import sitepages # noqa: E402
|
|
from common import list_registered_platforms, load_platform_config, load_platform_registry # noqa: E402
|
|
from siteguides import DEVICE_GROUPS, GROUP_ANCHORS, GUIDES # noqa: E402
|
|
|
|
DOCS = sitepages.CONTENT
|
|
ORDER = list(generate_site.LEVELS)
|
|
# Pages link to site addresses once written for the site.
|
|
LINK = re.compile(r"\[[^\]]*\]\((/retrobios/[^)\s#?]*)(?:[#?][^)]*)?\)")
|
|
FENCED = re.compile(r"^\s*```.*?^\s*```", re.S | re.M)
|
|
|
|
|
|
def _registered() -> list[str]:
|
|
return list_registered_platforms(str(REPO_ROOT / "platforms"), include_archived=True)
|
|
|
|
|
|
def _which_pack() -> str:
|
|
with contextlib.chdir(REPO_ROOT):
|
|
return generate_site.generate_which_pack()
|
|
|
|
|
|
def _docs_pages() -> list[Path]:
|
|
return [p for p in sitepages.pages() if p.name not in ("404.md",)]
|
|
|
|
|
|
def _level(page: Path) -> str:
|
|
return str(generate_site._front_matter(page.read_text(encoding="utf-8")).get("level") or "")
|
|
|
|
|
|
class FiveTabs(unittest.TestCase):
|
|
"""Twelve tabs put the walkthrough at position eleven, under "Wiki"."""
|
|
|
|
def test_the_tabs_in_reading_order(self):
|
|
nav = generate_site.generate_nav({}, {}, {}, {})
|
|
self.assertEqual(
|
|
[next(iter(tab)) for tab in nav],
|
|
["Start here", "Your setup", "Explore", "Reference", "Contribute"],
|
|
)
|
|
|
|
def test_each_tab_opens_on_an_index_or_a_first_page(self):
|
|
nav = generate_site.generate_nav({}, {}, {}, {})
|
|
tabs = {next(iter(tab)): next(iter(tab.values())) for tab in nav}
|
|
self.assertEqual(tabs["Start here"][0], "index.md")
|
|
self.assertEqual(tabs["Your setup"][0], "platforms/index.md")
|
|
self.assertEqual(tabs["Reference"][0], "wiki/index.md")
|
|
self.assertEqual(tabs["Contribute"][0], "contributing.md")
|
|
|
|
def test_the_breadcrumbs_are_on(self):
|
|
title = (REPO_ROOT / "website" / "src" / "components" / "PageTitle.astro").read_text(encoding="utf-8")
|
|
route = (REPO_ROOT / "website" / "src" / "routeData.ts").read_text(encoding="utf-8")
|
|
self.assertIn('aria-label="Breadcrumb"', title)
|
|
self.assertIn("rbBreadcrumbs", route)
|
|
|
|
|
|
class EveryPlatformOnThePicker(unittest.TestCase):
|
|
"""KNULLI, MinUI, AmberELEC, EmuELEC, NextUI and MyMinUI were on no table
|
|
of the download page: a hand-written list leaves out what it forgets."""
|
|
|
|
def test_every_platform_has_a_group_and_a_guide(self):
|
|
registry = load_platform_registry(str(REPO_ROOT / "platforms"))
|
|
for key in _registered():
|
|
with self.subTest(platform=key):
|
|
self.assertIn(registry[key].get("device_group"), DEVICE_GROUPS)
|
|
self.assertIn(key, GUIDES)
|
|
|
|
def test_every_group_has_an_anchor(self):
|
|
self.assertEqual(set(GROUP_ANCHORS), set(DEVICE_GROUPS))
|
|
|
|
def test_every_platform_has_a_row(self):
|
|
page = _which_pack()
|
|
for key in _registered():
|
|
name = load_platform_config(key, str(REPO_ROOT / "platforms"))["platform"]
|
|
with self.subTest(platform=key):
|
|
self.assertIn(f"| [{name}](platforms/{key}.md) |", page)
|
|
|
|
def test_every_wiki_page_is_in_the_navigation(self):
|
|
"""A wiki page is published as soon as it exists, but a page left out
|
|
of the nav is reachable only by a link and MkDocs says so at info
|
|
level."""
|
|
def targets(entries) -> list[str]:
|
|
found = []
|
|
for entry in entries:
|
|
if isinstance(entry, str):
|
|
found.append(entry)
|
|
else:
|
|
for value in entry.values():
|
|
found += targets(value) if isinstance(value, list) else [value]
|
|
return found
|
|
|
|
nav = " ".join(targets(generate_site.generate_nav({}, {}, {}, {})))
|
|
kept_out: set[str] = set()
|
|
wiki = Path(__file__).resolve().parents[1] / "wiki"
|
|
missing = sorted(
|
|
p.name for p in wiki.glob("*.md")
|
|
if p.name not in kept_out and f"wiki/{p.name}" not in nav
|
|
)
|
|
self.assertEqual(missing, [])
|
|
|
|
def test_every_platform_is_in_the_navigation_of_its_group(self):
|
|
registry = load_platform_registry(str(REPO_ROOT / "platforms"))
|
|
coverages = {
|
|
key: {"platform": load_platform_config(key, str(REPO_ROOT / "platforms"))["platform"]}
|
|
for key in _registered()
|
|
}
|
|
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)}
|
|
for key in coverages:
|
|
label = DEVICE_GROUPS[registry[key]["device_group"]][0]
|
|
with self.subTest(platform=key):
|
|
self.assertIn({coverages[key]["platform"]: f"platforms/{key}.md"}, groups[label])
|
|
|
|
def test_handheld_systems_that_run_retroarch_are_named(self):
|
|
"""muOS, ArkOS and the rest are detected as RetroArch by the
|
|
installer and were named on no page a user reads."""
|
|
page = _which_pack()
|
|
self.assertIn("| muOS | [RetroArch](platforms/retroarch.md) | `/run/muos/storage/bios` |", page)
|
|
self.assertNotIn("| TrimUI |", page)
|
|
|
|
|
|
class SampleCoresLeaveTheNavigation(unittest.TestCase):
|
|
"""13 libretro sample cores (testsw, testgl_ff...) sat in the emulators
|
|
navigation among the cores people look up."""
|
|
|
|
def test_a_core_with_no_system_and_no_file_is_left_out(self):
|
|
profiles = {
|
|
"testsw": {"emulator": "testsw", "type": "libretro", "systems": []},
|
|
"duckstation": {"emulator": "DuckStation", "type": "standalone",
|
|
"systems": ["sony-playstation"], "files": [{"name": "a.bin"}]},
|
|
"zx": {"emulator": "zx81", "type": "libretro", "systems": ["sinclair-zx81"]},
|
|
}
|
|
nav = generate_site._emulator_nav(profiles)
|
|
self.assertEqual(nav[0], "emulators/index.md")
|
|
groups = {next(iter(group)): next(iter(group.values())) for group in nav[1:]}
|
|
self.assertEqual(groups, {
|
|
"D to F": [{"DuckStation": "emulators/duckstation.md"}],
|
|
"V to Z": [{"zx81": "emulators/zx.md"}],
|
|
})
|
|
|
|
|
|
class EveryPageHasALevel(unittest.TestCase):
|
|
def test_wiki_sources_declare_one(self):
|
|
for page in sorted((REPO_ROOT / "wiki").glob("*.md")):
|
|
level = generate_site._front_matter(page.read_text(encoding="utf-8")).get("level")
|
|
with self.subTest(page=page.name):
|
|
self.assertIn(level, generate_site.LEVELS)
|
|
|
|
def test_every_generated_page_has_one_and_its_label(self):
|
|
for page in _docs_pages():
|
|
text = page.read_text(encoding="utf-8")
|
|
level = _level(page)
|
|
with self.subTest(page=str(page.relative_to(DOCS))):
|
|
self.assertIn(level, generate_site.LEVELS)
|
|
self.assertIn(f"\nlevel: {level}\n", text)
|
|
# The label above each title comes from the page's level.
|
|
component = (REPO_ROOT / "website" / "src" / "components" / "PageTitle.astro").read_text()
|
|
for level, label in generate_site.LEVELS.items():
|
|
with self.subTest(level=level):
|
|
self.assertIn(f"{level}: '{label}'", component)
|
|
|
|
def test_the_decoration_keeps_an_authored_level(self):
|
|
with tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp") as tmp:
|
|
page = Path(tmp) / "wiki" / "faq.md"
|
|
page.parent.mkdir()
|
|
page.write_text("---\nlevel: start\n---\n\n# FAQ\n\nQuestions people ask first.\n")
|
|
generate_site.decorate_markdown_pages(Path(tmp))
|
|
generate_site.decorate_markdown_pages(Path(tmp))
|
|
text = page.read_text()
|
|
self.assertEqual(text.count("level: start"), 1)
|
|
self.assertIn("searchWeight: 10\n", text)
|
|
self.assertIn("title: FAQ\n", text)
|
|
|
|
|
|
class DeepLinksCarryALabel(unittest.TestCase):
|
|
"""A beginner page sent its reader to Tools and Architecture as the next
|
|
step. A link from a start or setup page to a page two levels deeper or
|
|
more says what it leads to, with that page's level label before it on
|
|
its line, or in the heading of its section ("## Reference: ...")."""
|
|
|
|
def test_start_and_setup_pages(self):
|
|
import sitestarlight # noqa: PLC0415
|
|
|
|
pages = _docs_pages()
|
|
levels = {page: _level(page) for page in pages}
|
|
by_url = {sitestarlight.page_url(sitepages.name(page)): page for page in pages}
|
|
offenders = []
|
|
for page, level in levels.items():
|
|
if level not in ("start", "setup"):
|
|
continue
|
|
text = FENCED.sub("", page.read_text(encoding="utf-8"))
|
|
heading = ""
|
|
for line in text.splitlines():
|
|
if re.match(r"#{1,3} ", line):
|
|
heading = line.lstrip("#").strip()
|
|
for match in LINK.finditer(line):
|
|
target = by_url.get(match.group(1))
|
|
if target not in levels:
|
|
continue
|
|
deeper = levels[target]
|
|
if ORDER.index(deeper) - ORDER.index(level) < 2:
|
|
continue
|
|
label = f"{generate_site.LEVELS[deeper]}:"
|
|
if label not in line[: match.start()] and not heading.startswith(label):
|
|
offenders.append(f"{page.relative_to(DOCS)}: {match.group(0)}")
|
|
self.assertEqual(offenders, [], "\n".join(offenders[:30]))
|
|
|
|
|
|
class GuidePagesEndOnANextStep(unittest.TestCase):
|
|
def test_home_picker_and_platform_page(self):
|
|
for page in ("index.md", "which-pack.md", "platforms/batocera.md",
|
|
"wiki/first-install.md", "wiki/troubleshooting.md",
|
|
"wiki/what-are-bios-files.md", "wiki/faq.md"):
|
|
if page.startswith(("index", "which", "platforms")):
|
|
sitepages.require()
|
|
path = sitepages.page(page)
|
|
else:
|
|
path = REPO_ROOT / page
|
|
with self.subTest(page=page):
|
|
text = path.read_text(encoding="utf-8")
|
|
if page.startswith("wiki/"):
|
|
# A plain list: an aside drew a coloured stripe and an icon.
|
|
self.assertIn("\n## Next steps\n", text)
|
|
self.assertNotIn(":::tip[Next]", text)
|
|
else:
|
|
self.assertTrue(":::tip[Next]" in text or "## Next steps" in text
|
|
or "## What comes next" in text)
|
|
|
|
def test_the_platform_page_opens_on_its_guide(self):
|
|
"""The folder came after four counters and a metadata table, and was
|
|
the pack-relative `bios/` where Batocera reads /userdata/bios/."""
|
|
sitepages.require()
|
|
text = sitepages.page("platforms/batocera.md").read_text(encoding="utf-8")
|
|
order = [text.index(h) for h in (
|
|
"## Where the files go", "## Install", "## Download", "## How to check",
|
|
"## Reference: every file by system",
|
|
)]
|
|
self.assertEqual(order, sorted(order))
|
|
self.assertLess(text.index("`/userdata/bios/`"), text.index("rb-stat-value"))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|