mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 12:49:03 -05:00
The 404 page opens on Start here instead of tabs numbered 0 to 4. A group label is a group, or in Explore one link to its overview, never a link to its first page, and a crumb names the section that introduces the page. Emulators sit one level deep by initial. Previous and next run within a tab (Start here into Your setup) and skip system, emulator and platform pages, and follow a page's Next steps list. Reference pages group under Checking files and Data, a wiki page joining by its group: front matter. redirects.json maps alternative core names to their profile.
259 lines
11 KiB
Python
259 lines
11 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. getting-started.md is kept out on purpose: it only links the
|
|
pages that took its content, for the old links that point to it."""
|
|
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 = {"getting-started.md"}
|
|
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": [{"DuckStation": "emulators/duckstation.md"}],
|
|
"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")
|
|
self.assertTrue(":::tip[Next]" 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()
|