chore: merge the site visual system

This commit is contained in:
Abdessamad Derraz committed 2026-10-22 12:00:19 +02:00
commit f39bcebb22
122 files changed
+2710 -215

No files matched your search

+219
View File
@@ -0,0 +1,219 @@
"""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("[–—←-⇿─-╿…]", 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("&#95;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"| ![X]({logo['source']}){{ 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"![a]({icon['source']})", 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):
template = (ROOT / "docs_assets" / "overrides" / "main.html").read_text(encoding="utf-8")
named = set(re.findall(r"section = '([a-z]+)'", template))
self.assertEqual(named, set(sitediagrams.OG_SECTIONS))
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()
+18 -56
View File
@@ -1,15 +1,10 @@
"""Renderings shared by the site pages, and the icon cache they read.
No network: icon checks are answered by a stub.
"""
"""Renderings shared by the site pages, and the icon copies they read."""
from __future__ import annotations
import json
import sys
import tempfile
import unittest
import urllib.error
from pathlib import Path
from unittest import mock
@@ -19,65 +14,32 @@ sys.path.insert(0, str(REPO_ROOT / "scripts"))
import siterender # noqa: E402
class _Served:
status = 200
def __enter__(self) -> "_Served":
return self
def __exit__(self, *exc: object) -> None:
return None
class IconCacheIsReplacedWhole(unittest.TestCase):
"""The cache was truncated then dumped in place: a concurrent reader saw
half a file, took it for empty, and an offline run then wrote ``{}``
over every recorded icon."""
class IconsComeFromTheLocalCopies(unittest.TestCase):
"""Icons were checked against upstream with a HEAD request per system on
every build, and linked from a branch tip. The build now reads the copies
in docs_assets/system-icons and makes no request."""
def setUp(self) -> None:
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.path = Path(tmp.name) / "system_icons.json"
patcher = mock.patch.object(siterender, "ICON_CACHE_PATH", self.path)
patcher.start()
self.addCleanup(patcher.stop)
saved = dict(siterender._icon_available)
self.addCleanup(lambda: (siterender._icon_available.clear(),
siterender._icon_available.update(saved)))
def test_an_offline_run_leaves_an_unreadable_cache_untouched(self):
self.path.write_text('{"Sega - Mega Drive": tr')
offline = mock.patch.object(
siterender.urllib.request, "urlopen",
side_effect=urllib.error.URLError("offline"))
with offline, mock.patch("sys.stderr"), mock.patch("builtins.print"):
siterender.prime_system_icons({"Sega - Mega Drive"})
self.assertEqual(self.path.read_text(), '{"Sega - Mega Drive": tr')
def test_names_another_run_recorded_meanwhile_are_kept(self):
self.path.write_text(json.dumps({"A - One": True}))
def answer(*_args: object, **_kwargs: object) -> _Served:
# A second build records its own check while this one runs.
self.path.write_text(json.dumps({"A - One": True, "C - Three": False}))
return _Served()
with mock.patch.object(siterender.urllib.request, "urlopen", side_effect=answer), \
def test_availability_is_the_folder(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
folder = Path(tmp.name) / "docs_assets" / "system-icons"
folder.mkdir(parents=True)
(folder / "A - One.png").write_bytes(b"png")
with mock.patch.object(siterender, "ICON_DIR", folder), \
mock.patch("builtins.print"):
siterender.prime_system_icons({"A - One", "B - Two"})
self.assertEqual(json.loads(self.path.read_text()),
{"A - One": True, "B - Two": True, "C - Three": False})
self.assertEqual(sorted(p.name for p in self.path.parent.iterdir()),
["system_icons.json"])
self.assertTrue(siterender._icon_available["A - One"])
self.assertFalse(siterender._icon_available["B - Two"])
self.assertEqual(siterender.system_icon_markdown("B", "Two"), "")
self.assertIn("A%20-%20One.png", siterender.system_icon_markdown("A", "One"))
def test_the_write_goes_through_a_scratch_file(self):
with mock.patch.object(siterender.urllib.request, "urlopen",
return_value=_Served()), \
mock.patch.object(siterender, "write_text_atomic",
wraps=siterender.write_text_atomic) as atomic, \
mock.patch("builtins.print"):
siterender.prime_system_icons({"B - Two"})
atomic.assert_called_once()
def test_no_request_is_made(self):
self.assertNotIn("urllib.request", (REPO_ROOT / "scripts" / "siterender.py").read_text())
class EveryPageAddressesPlatformsAsPeers(unittest.TestCase):