chore: merge the site funnel

This commit is contained in:
Abdessamad Derraz committed 2026-10-22 11:59:37 +02:00
commit db1d679e4e
31 files changed
+3049 -1951

No files matched your search

+19 -2
View File
@@ -730,7 +730,8 @@ class RetroArchWindowsSystemFolder(unittest.TestCase):
"""Getting started gave %APPDATA%\\RetroArch\\system for the Windows
installer. RetroArch's default is `:\\system`, the folder of
retroarch.exe (platform_win32.c). %APPDATA% is where a retroarch.cfg
can live, and install.py reads it there."""
can live, and install.py reads it there. The RetroArch page of the site
states the folder now, from the guide data."""
def test_page_and_installer_agree(self):
import install
@@ -738,10 +739,26 @@ class RetroArchWindowsSystemFolder(unittest.TestCase):
source = inspect.getsource(install)
self.assertIn('ra_dir or exe_dir / "system"', source)
self.assertTrue(str(install._os_default_dests("windows")["retroarch"]).replace("\\", "/").endswith("/system"))
section = _section(_page("getting-started.md"), "RetroArch")
from siteguides import GUIDES
guide = GUIDES["retroarch"]
section = "\n".join([*(path for _label, path in guide.folders), *guide.notes])
self.assertNotIn("%APPDATA%\\RetroArch\\system", section)
self.assertIn("`system\\` next to `retroarch.exe`", section)
def test_the_macos_folder_is_the_installers(self):
"""The site gave Application Support/RetroArch/system for macOS. A
RetroArch left at its defaults reads ~/Documents/RetroArch/system
(platform_darwin.m), Application Support only for a portable or
Steam build, and install.py looks there in that order."""
import install
from siteguides import GUIDES
source = inspect.getsource(install._macos_retroarch_base)
self.assertIn('Path.home() / "Documents" / "RetroArch"', source)
folders = dict(GUIDES["retroarch"].folders)
self.assertEqual(folders["macOS"], "`~/Documents/RetroArch/system/`")
if __name__ == "__main__":
unittest.main()
+17 -11
View File
@@ -75,22 +75,25 @@ def _which_pack() -> str:
class TheDownloadTablesFollowTheRegistry(unittest.TestCase):
"""EmuDeck has a Windows edition the installer detects, and the Windows
table of the download page left it out."""
table of the download page left it out. The page is grouped by device
now: the row of each platform names every system it is detected on."""
def test_every_detected_system_lists_the_platform(self):
import yaml # noqa: PLC0415
from common import load_platform_config # noqa: PLC0415
registry = yaml.safe_load(_read("platforms/_registry.yml"))
names = {"retroarch": "RetroArch", "retrobat": "RetroBat", "emudeck": "EmuDeck",
"bizhawk": "BizHawk"}
page = _which_pack()
for os_name, heading in (("windows", "### Windows"), ("darwin", "### macOS")):
table = _section(page, heading)
for os_name, label in (("windows", "Windows"), ("darwin", "macOS")):
for platform, entry in registry["platforms"].items():
systems = {rule["os"] for rule in (entry.get("install") or {}).get("detect", [])}
if os_name in systems:
name = load_platform_config(platform, "platforms")["platform"]
row = next(line for line in page.splitlines()
if line.startswith(f"| [{name}]("))
with self.subTest(os=os_name, platform=platform):
self.assertIn(f"[{names[platform]}](", table)
self.assertIn(label, row.split(" | ")[2])
class DownloadLinksNameTheFile(unittest.TestCase):
@@ -159,9 +162,10 @@ class DownloadLinksNameTheFile(unittest.TestCase):
for asset in pack["assets"]:
self.assertIn(f"/releases/download/{RECORD['tag']}/{asset})", page)
def test_a_platform_the_release_lacks_has_no_row(self):
def test_a_platform_the_release_lacks_links_no_release(self):
"""KNULLI, AmberELEC and EmuELEC were registered after the recorded
release: a row for them linked a release page holding no such pack."""
release: a row for them linked a release page holding no such pack.
Every platform is on the picker now, so the row offers the command."""
if not RECORD.get("packs"):
self.skipTest("release.json is not written")
from common import list_registered_platforms, load_platform_config
@@ -172,8 +176,10 @@ class DownloadLinksNameTheFile(unittest.TestCase):
if pack_for(key, RECORD) is not None:
continue
label = load_platform_config(key, "platforms").get("platform", key)
row = next(line for line in page.splitlines() if line.startswith(f"| [{label}]("))
with self.subTest(platform=key):
self.assertNotIn(f"| [{label}](", page)
self.assertNotIn("/releases/", row)
self.assertIn(f"Not in {RECORD['tag']}: [use the command](platforms/{key}.md#install)", row)
class SlicesLeaveWithTheirRelease(unittest.TestCase):
@@ -205,7 +211,7 @@ class EndUsersCheckTheirOwnFolder(unittest.TestCase):
("wiki/troubleshooting.md", "## Game won't start / black screen"),
("wiki/faq.md", "## My game shows a black screen"),
("wiki/faq.md", "## How do I know which BIOS I need?"),
("wiki/getting-started.md", "## Checking what you installed"),
("wiki/troubleshooting.md", "## Checking what you installed"),
)
def test_user_sections_never_send_to_verify(self):
@@ -263,7 +269,7 @@ class ShownOutputIsPrintedOutput(unittest.TestCase):
self.assertIn(literal, source)
def test_samples_match_a_shape(self):
section = _section(_read("wiki/troubleshooting.md"), "### Reading the output")
section = _section(_read("wiki/verification-modes.md"), "### Reading the output")
blocks = re.findall(r"```\n(.*?)```", section, re.DOTALL)
self.assertTrue(blocks)
for block in blocks:
+6 -1
View File
@@ -902,8 +902,13 @@ class TheOneLinerIsWhereUsersLand(unittest.TestCase):
"verified": 0, "missing": 0, "details": [], "config": {"systems": {}}}
page = generate_site.generate_platform_page("batocera", cov)
self.assertIn("install.sh | sh -s -- --platform batocera", page)
self.assertIn("install.ps1))) --platform batocera", page)
self.assertLess(page.index("--platform batocera"), page.index("md-button"))
# Batocera runs the command on the device itself. A platform detected
# on Windows gets the PowerShell form.
cov = dict(cov, platform="RetroArch", mode="existence")
page = generate_site.generate_platform_page("retroarch", cov)
self.assertIn("install.ps1))) --platform retroarch", page)
self.assertIn("install.sh | sh -s -- --platform retroarch", page)
entry = {"name": "a.bin", "sha1": "a" * 40, "md5": "b" * 32, "size": 3,
"path": "bios/Recalbox/MSX/a.bin"}
+227
View File
@@ -0,0 +1,227 @@
"""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 common import list_registered_platforms, load_platform_config, load_platform_registry # noqa: E402
from siteguides import DEVICE_GROUPS, GROUP_ANCHORS, GUIDES # noqa: E402
DOCS = REPO_ROOT / "docs"
ORDER = list(generate_site.LEVELS)
LINK = re.compile(r"\[[^\]]*\]\(([^)\s#]+\.md)(?:#[^)]*)?\)")
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]:
if not DOCS.is_dir() or not (DOCS / "index.md").is_file():
if os.environ.get("CI"):
raise AssertionError("CI generates docs/ before the suite")
raise unittest.SkipTest("docs/ not generated (python scripts/generate_site.py)")
skipped = set(generate_site.LOCAL_NOTE_DIRS)
return [p for p in sorted(DOCS.rglob("*.md"))
if not set(p.relative_to(DOCS).parts[:-1]) & skipped]
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_mkdocs_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_mkdocs_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):
source = (REPO_ROOT / "scripts" / "generate_site.py").read_text(encoding="utf-8")
self.assertIn(" - navigation.path\n", source)
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_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_mkdocs_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(generate_site.level_label(level), text)
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.assertEqual(text.count(generate_site.level_label("start")), 1)
self.assertIn(" boost: 5\n", text)
self.assertIn("# 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):
pages = _docs_pages()
levels = {page: _level(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 = (page.parent / match.group(1)).resolve()
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"):
path = DOCS / page if page.startswith(("index", "which", "platforms")) else REPO_ROOT / page
if not path.is_file():
_docs_pages()
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/."""
text = (DOCS / "platforms" / "batocera.md").read_text(encoding="utf-8") \
if (DOCS / "platforms" / "batocera.md").is_file() else _docs_pages()
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()