mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-24 04:39:06 -05:00
generate_site.py writes the Starlight project in website/ and nothing else: mkdocs.yml, the MkDocs hooks, theme overrides and stylesheet go, and the wiki is written in Starlight's syntax. Both workflows set up Node with the official actions/setup-node pinned by commit, run npm ci from the committed lockfile, build with the strict link validator and check the result with validate_site.py. The search weighs a page by what it answers: Start here pages first, a system page's cards and file names before a guide citing them, deep guides, launchers, aliases and frozen copies after. Status badges, hash blocks and the export change lists leave the index. The ranking sits at the centre of a range where the 13 queries of search_gate.py all answer, in the search box itself. 839 pages, every URL and 45,611 ids match the MkDocs build. The tests read the Starlight content and syntax.
226 lines
10 KiB
Python
226 lines
10 KiB
Python
"""Images on the site: diagrams written from data, and local copies only.
|
|
|
|
The site loaded its platform logos from GitHub avatars and branch tips, and
|
|
its system icons from retroarch-assets ``master``: a rename upstream broke an
|
|
image in silence. Diagrams were drawn at runtime by a mermaid script from a
|
|
CDN. These tests hold the replacements in place.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import xml.etree.ElementTree as ET
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT / "scripts"))
|
|
|
|
import sitediagrams # noqa: E402
|
|
|
|
SVG = "{http://www.w3.org/2000/svg}"
|
|
|
|
|
|
class EveryDiagramIsAccessibleSvg(unittest.TestCase):
|
|
def test_each_layout_parses_and_is_named(self):
|
|
for image_id in sitediagrams.IMAGE_IDS:
|
|
figure = sitediagrams.figures()[image_id]
|
|
for layout in ("wide", "narrow"):
|
|
with self.subTest(image=image_id, layout=layout):
|
|
root = ET.fromstring(sitediagrams.svg(figure, layout, ""))
|
|
self.assertEqual(root.tag, f"{SVG}svg")
|
|
self.assertEqual(root.get("role"), "img")
|
|
title, desc = root.find(f"{SVG}title"), root.find(f"{SVG}desc")
|
|
self.assertTrue(title is not None and title.text)
|
|
self.assertTrue(desc is not None and desc.text)
|
|
self.assertEqual(
|
|
root.get("aria-labelledby").split(), [title.get("id"), desc.get("id")]
|
|
)
|
|
self.assertTrue(root.findall(f".//{SVG}text"))
|
|
|
|
def test_the_output_does_not_change_between_runs(self):
|
|
first = [sitediagrams.figure_html(i) for i in sitediagrams.IMAGE_IDS]
|
|
sitediagrams.figures.cache_clear()
|
|
self.assertEqual(first, [sitediagrams.figure_html(i) for i in sitediagrams.IMAGE_IDS])
|
|
|
|
def test_text_carries_no_typographic_tics(self):
|
|
for image_id in sitediagrams.IMAGE_IDS:
|
|
html = sitediagrams.figure_html(image_id)
|
|
with self.subTest(image=image_id):
|
|
self.assertIsNone(re.search("[\u2013\u2014\u2190-\u21ff\u2500-\u257f\u2026]", html))
|
|
self.assertNotIn("; ", re.sub(r"<[^>]+>", " ", html))
|
|
|
|
def test_a_second_copy_on_one_page_gets_its_own_ids(self):
|
|
page = sitediagrams.resolve_images(
|
|
"<!-- image: install-flow -->\n\ntext\n\n<!-- image: install-flow -->\n"
|
|
)
|
|
ids = re.findall(r'\bid="([^"]+)"', page)
|
|
self.assertEqual(len(ids), len(set(ids)))
|
|
|
|
|
|
class PlaceholdersResolve(unittest.TestCase):
|
|
"""A page asks for a diagram by id. A name no function answers stays a
|
|
comment, which validate_site reports in the built page."""
|
|
|
|
def test_every_placeholder_in_the_sources_names_a_diagram(self):
|
|
sources = [*sorted((ROOT / "wiki").glob("*.md")), *sorted((ROOT / "scripts").glob("*.py"))]
|
|
found = 0
|
|
for path in sources:
|
|
text = path.read_text(encoding="utf-8")
|
|
for image_id in sitediagrams.PLACEHOLDER_ANYWHERE.findall(text):
|
|
found += 1
|
|
with self.subTest(page=path.name, image=image_id):
|
|
self.assertIn(image_id, sitediagrams.IMAGE_IDS)
|
|
self.assertGreater(found, 0)
|
|
|
|
def test_a_block_placeholder_becomes_a_figure(self):
|
|
page = sitediagrams.resolve_images("Intro\n\n<!-- image: bios-chain -->\n\nMore\n")
|
|
self.assertIn('<figure class="rb-figure"', page)
|
|
self.assertNotIn("<!-- image:", page)
|
|
|
|
def test_an_indented_placeholder_keeps_its_indent_and_escapes_markdown(self):
|
|
page = sitediagrams.resolve_images("1. Step\n\n <!-- image: data-layers -->\n")
|
|
line = page.splitlines()[2]
|
|
self.assertTrue(line.startswith(" <span class=\"rb-figure"))
|
|
self.assertNotIn("_shared", line)
|
|
self.assertIn("_shared", line)
|
|
|
|
def test_an_unknown_name_is_left_for_the_validator(self):
|
|
page = "<!-- image: no-such-diagram -->\n"
|
|
self.assertEqual(sitediagrams.resolve_images(page), page)
|
|
self.assertEqual(sitediagrams.unresolved_images(page), ["no-such-diagram"])
|
|
|
|
|
|
class DiagramsMatchTheData(unittest.TestCase):
|
|
def test_every_platform_appears_under_its_mode(self):
|
|
boxes = sitediagrams._mode_boxes()
|
|
text = " ".join(" ".join(box.lines) for box in boxes)
|
|
for display, mode, _ in sitediagrams.platform_modes().values():
|
|
with self.subTest(platform=display):
|
|
self.assertIn(mode, sitediagrams.MODE_TEXT)
|
|
self.assertIn(display, text)
|
|
|
|
def test_each_example_platform_runs_its_emulator(self):
|
|
from common import load_emulator_profiles, load_platform_config, resolve_platform_cores
|
|
|
|
profiles = load_emulator_profiles(str(ROOT / "emulators"), skip_aliases=False)
|
|
for platform, emulator in sitediagrams.CHAIN_EXAMPLES:
|
|
with self.subTest(platform=platform, emulator=emulator):
|
|
config = load_platform_config(platform, str(ROOT / "platforms"))
|
|
self.assertIn(emulator, resolve_platform_cores(config, profiles))
|
|
|
|
def test_minui_tags_are_folders_minui_hands_its_cores(self):
|
|
import yaml
|
|
|
|
config = yaml.safe_load((ROOT / "platforms" / "minui.yml").read_text(encoding="utf-8"))
|
|
folders = set(config["core_system_dirs"])
|
|
for _, tag in sitediagrams.MINUI_EXAMPLES:
|
|
with self.subTest(tag=tag):
|
|
self.assertIn(f"Bios/{tag}", folders)
|
|
self.assertTrue(sitediagrams.minui_files().get(tag))
|
|
|
|
def test_the_pipeline_drawing_is_the_step_list(self):
|
|
from pipeline import STEP_LABELS
|
|
|
|
numbers = [box.number for box in sitediagrams._pipeline_boxes()]
|
|
self.assertEqual(numbers, [label.split(" ", 1)[0] for label in STEP_LABELS.values()])
|
|
|
|
|
|
class NoMermaidAtRuntime(unittest.TestCase):
|
|
"""A mermaid fence made Material load mermaid@11 (3.5 MB) from unpkg."""
|
|
|
|
def test_no_page_source_uses_a_mermaid_fence(self):
|
|
for path in sorted((ROOT / "wiki").glob("*.md")):
|
|
with self.subTest(page=path.name):
|
|
self.assertNotIn("```mermaid", path.read_text(encoding="utf-8"))
|
|
|
|
|
|
class ImagesAreServedFromTheSite(unittest.TestCase):
|
|
def _manifest(self, folder: str) -> list[dict]:
|
|
path = ROOT / "docs_assets" / folder / "sources.json"
|
|
return json.loads(path.read_text(encoding="utf-8"))["images"]
|
|
|
|
def test_every_registry_logo_has_a_local_copy(self):
|
|
import yaml
|
|
|
|
registry = yaml.safe_load((ROOT / "platforms" / "_registry.yml").read_text())["platforms"]
|
|
copies = {entry["platform"]: entry for entry in self._manifest("logos")}
|
|
for name, entry in registry.items():
|
|
if not entry.get("logo"):
|
|
continue
|
|
with self.subTest(platform=name):
|
|
self.assertIn(name, copies, "run scripts/fetch_site_images.py --only logos")
|
|
self.assertEqual(copies[name]["source"], entry["logo"])
|
|
self.assertTrue((ROOT / "docs_assets" / "logos" / copies[name]["file"]).is_file())
|
|
self.assertTrue(copies[name]["owner"] and copies[name]["licence"])
|
|
|
|
def test_every_recorded_icon_exists(self):
|
|
for entry in self._manifest("system-icons"):
|
|
with self.subTest(icon=entry["file"]):
|
|
self.assertTrue((ROOT / "docs_assets" / "system-icons" / entry["file"]).is_file())
|
|
|
|
def test_remote_links_with_a_copy_are_rewritten(self):
|
|
logo = self._manifest("logos")[0]
|
|
page = f"| {{ width=20 }} | <img src=\"{logo['source']}\"> |"
|
|
local = sitediagrams.localize_images(page, Path("platforms/x.md"))
|
|
self.assertNotIn(logo["source"], local)
|
|
self.assertEqual(local.count(f"../assets/logos/{logo['file']}"), 2)
|
|
|
|
def test_icon_paths_are_link_safe(self):
|
|
icon = self._manifest("system-icons")[0]
|
|
local = sitediagrams.localize_images(f"", Path("systems/x.md"))
|
|
self.assertNotIn(" ", local)
|
|
|
|
def test_the_validator_refuses_a_remote_image(self):
|
|
from validate_site import _parse_page
|
|
|
|
with tempfile.TemporaryDirectory(dir=ROOT / "tests") as tmp:
|
|
path = Path(tmp) / "index.html"
|
|
path.write_text(
|
|
'<html lang="en"><body><img alt="x" src="https://example.org/a.png">'
|
|
'<img alt="y" src="../assets/logos/a.png"><!-- image: nope --></body></html>'
|
|
)
|
|
page = _parse_page(path)
|
|
self.assertEqual(page.remote_images, ["https://example.org/a.png"])
|
|
self.assertEqual(page.placeholders, ["nope"])
|
|
|
|
|
|
class SharingCards(unittest.TestCase):
|
|
def test_one_card_per_section_at_the_open_graph_size(self):
|
|
import struct
|
|
|
|
for section in sitediagrams.OG_SECTIONS:
|
|
with self.subTest(section=section):
|
|
data = (ROOT / "docs_assets" / "og" / f"{section}.png").read_bytes()
|
|
self.assertEqual(data[:8], b"\x89PNG\r\n\x1a\n")
|
|
self.assertEqual(struct.unpack(">II", data[16:24]), (1200, 630))
|
|
ET.fromstring(sitediagrams.og_card_svg(section, b"png"))
|
|
|
|
def test_the_template_names_every_section(self):
|
|
import sitestarlight # noqa: PLC0415
|
|
|
|
named = {sitestarlight.og_section("index.md", "")} | {
|
|
sitestarlight.og_section("wiki/page.md", level)
|
|
for level in ("start", "setup", "explore", "reference", "contribute")}
|
|
self.assertEqual(named, set(sitediagrams.OG_SECTIONS))
|
|
for section in named:
|
|
with self.subTest(section=section):
|
|
self.assertTrue((ROOT / "docs_assets" / "og" / f"{section}.png").is_file())
|
|
|
|
|
|
class ImagesStillNeeded(unittest.TestCase):
|
|
"""Screens that need a real device are listed, never drawn as a mock-up."""
|
|
|
|
def test_the_list_gives_each_capture_its_alt_text(self):
|
|
text = (ROOT / "docs_assets" / "images-needed.md").read_text(encoding="utf-8")
|
|
entries = re.findall(r"(?m)^## ", text)
|
|
self.assertGreater(len(entries), 3)
|
|
self.assertEqual(len(entries), len(re.findall(r"(?m)^\*\*Alt text\*\*", text)))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|