mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-25 13:19:03 -05:00
chore: merge the site funnel
This commit is contained in:
commit
db1d679e4e
31 files changed
+3049
-1951
No files matched your search
+19
-2
@@ -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
@@ -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:
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user