mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 12:19:13 -05:00
chore: tests for the round 3 site components
This commit is contained in:
1 parent
b239be4984
commit
e3f7ebd4bc
4 files changed
+402
-10
No files matched your search
@@ -84,8 +84,10 @@ class TablesStackWhenBuilt(unittest.TestCase):
|
||||
"action": {"labels": ["A", "B", "C"], "rows": 40, "action": True},
|
||||
"pair": {"labels": ["Where", "BIOS folder"], "rows": 3, "action": False},
|
||||
"short-start": {"labels": ["System", "Runs", "BIOS folder"], "rows": 8, "action": False, "level": "start"},
|
||||
"long-setup": {"labels": ["System", "Files", "Status", "Emulators"], "rows": 97, "action": False, "level": "setup"},
|
||||
"long-setup": {"labels": ["System", "Files", "Status"], "rows": 97, "action": False, "level": "setup"},
|
||||
"reference": {"labels": ["A", "B", "C"], "rows": 5, "action": False, "level": "reference"},
|
||||
# Four columns do not fit a phone column, on any page (round 3).
|
||||
"wide": {"labels": ["File", "Region", "Required", "Hash"], "rows": 105, "action": False, "level": "reference"},
|
||||
}
|
||||
script = (
|
||||
f"import {{ stackKind }} from {json.dumps(PLUGIN.as_uri())};"
|
||||
@@ -98,7 +100,7 @@ class TablesStackWhenBuilt(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(json.loads(result.stdout), {
|
||||
"zip-header": "true", "action": "true", "pair": "kv",
|
||||
"short-start": "true", "long-setup": None, "reference": None,
|
||||
"short-start": "true", "long-setup": None, "reference": None, "wide": "true",
|
||||
})
|
||||
|
||||
|
||||
@@ -142,7 +144,7 @@ class SearchAnswersAHashAndShowsNoFileDump(unittest.TestCase):
|
||||
self.assertIn("Look up this hash", source)
|
||||
self.assertIn("files/?hash=${value}", source)
|
||||
self.assertIn("endsWith('#_top')", source)
|
||||
self.assertIn("tag.dataset.pagefindUiMeta === 'files'", source)
|
||||
self.assertIn("UNSHOWN_FIELDS.has(tag.dataset.pagefindUiMeta ?? '')", source)
|
||||
# Pagefind's display rules beat the hidden attribute without this.
|
||||
self.assertIn("#starlight__search [hidden] { display: none !important; }", _read(COMPONENTS / "Header.astro"))
|
||||
|
||||
@@ -167,8 +169,8 @@ def _title_block(html: str) -> str:
|
||||
|
||||
class LevelBadgeOnlyWhereItAddsSomething(unittest.TestCase):
|
||||
def test_badge_follows_the_crumb(self):
|
||||
# A tab root has no crumb and keeps its badge.
|
||||
self.assertIn('class="rb-level rb-level-setup', _title_block(_built("platforms")))
|
||||
# A tab root: the header tab already says it (round 3, 2.1).
|
||||
self.assertNotIn('class="rb-level ', _title_block(_built("platforms")))
|
||||
# Under the "Start here" or "Your setup" crumb the badge repeated it.
|
||||
for page in ("which-pack", "platforms/batocera"):
|
||||
with self.subTest(page=page):
|
||||
@@ -181,7 +183,7 @@ class TitleLogo(unittest.TestCase):
|
||||
def test_front_matter_field(self):
|
||||
self.assertIn("logo: z.string().optional()", _read(WEBSITE / "src" / "content.config.ts"))
|
||||
title = _read(COMPONENTS / "PageTitle.astro")
|
||||
self.assertIn('<img src={logoSrc} alt="" height="40" />', title)
|
||||
self.assertIn('<img class="rb-title-mark" src={logoSrc} alt="" width={MARK} height={MARK} />', title)
|
||||
|
||||
|
||||
class FooterSaysWhoAndWhen(unittest.TestCase):
|
||||
@@ -200,10 +202,10 @@ class FooterSaysWhoAndWhen(unittest.TestCase):
|
||||
class FindAFileGivesAVerdict(unittest.TestCase):
|
||||
def test_lookup_renderer(self):
|
||||
source = _read(SITE_TS)
|
||||
self.assertIn("', held in the collection at '", source)
|
||||
self.assertIn("'Held in the collection at '", source)
|
||||
self.assertIn("['Setup', 'Name as declared', 'Destination', 'Hash matched', 'Required']", source)
|
||||
self.assertIn("api/v1/platforms/${item.owner}.json", source)
|
||||
self.assertIn("api/v1/systems/${slug}.json", source)
|
||||
self.assertIn("api/v1/systems/${words.slice(0, size).join('-')}.json", source)
|
||||
style = _read(COMPONENTS / "MarkdownContent.astro")
|
||||
self.assertIn("max-width: 40rem;", style)
|
||||
self.assertIn("width: 100%;", style)
|
||||
|
||||
@@ -192,7 +192,7 @@ class TheIntegrationItems(unittest.TestCase):
|
||||
with self.subTest(platform=key):
|
||||
block = re.search(r'<div class="rb-title-logo[^"]*">.*?</div>', html, re.S)
|
||||
self.assertIsNotNone(block)
|
||||
self.assertRegex(block[0], r'<img src="/retrobios/assets/logos/')
|
||||
self.assertRegex(block[0], r'<img\b[^>]*\bsrc="/retrobios/assets/logos/')
|
||||
self.assertNotIn("align=", html.split("<main", 1)[-1])
|
||||
|
||||
def test_every_emulator_letter_lands_on_its_section(self):
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
"""Round 3, package 2: components and runtime.
|
||||
|
||||
Source checks always run. Checks on the built site read website/dist and
|
||||
skip when no build is there. The hash functions and the table rule run
|
||||
under node and skip without it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import struct
|
||||
import subprocess
|
||||
import unittest
|
||||
import zlib
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
WEBSITE = REPO_ROOT / "website"
|
||||
DIST = WEBSITE / "dist"
|
||||
COMPONENTS = WEBSITE / "src" / "components"
|
||||
SITE_TS = WEBSITE / "src" / "scripts" / "site.ts"
|
||||
CONFIG = WEBSITE / "astro.config.mjs"
|
||||
PLUGIN = WEBSITE / "src" / "plugins" / "rehype-stack-tables.mjs"
|
||||
UNBROKEN = WEBSITE / "src" / "plugins" / "ec-unbroken-tokens.mjs"
|
||||
LOGOS = REPO_ROOT / "docs_assets" / "logos"
|
||||
|
||||
# Shown at 40 px in a tile and beside a setup's title: twice that for a
|
||||
# sharp mark on a dense screen.
|
||||
LOGO_PIXELS = 80
|
||||
TAB_ROOTS = ("", "platforms", "explore", "wiki", "contributing")
|
||||
|
||||
|
||||
def _read(path: Path) -> str:
|
||||
return path.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _built(page: str) -> str:
|
||||
path = DIST / page / "index.html"
|
||||
if not path.is_file():
|
||||
raise unittest.SkipTest(f"no built site at {path}")
|
||||
return _read(path)
|
||||
|
||||
|
||||
def _title_block(html: str) -> str:
|
||||
"""What PageTitle draws: from the main column to the end of the h1."""
|
||||
end = html.index("</h1>", html.index('id="_top"'))
|
||||
return html[html.rfind("<main", 0, end):end]
|
||||
|
||||
|
||||
def _tables(html: str) -> list[str]:
|
||||
return re.findall(r"<table\b.*?</table>", html, re.S)
|
||||
|
||||
|
||||
def _headers(table: str) -> list[str]:
|
||||
head = re.search(r"<thead>(.*?)</thead>", table, re.S)
|
||||
cells = re.findall(r"<th\b[^>]*>(.*?)</th>", head.group(1), re.S) if head else []
|
||||
return [re.sub(r"<[^>]+>", "", cell).strip() for cell in cells]
|
||||
|
||||
|
||||
def _node_module(script: str) -> str:
|
||||
node = shutil.which("node")
|
||||
if not node:
|
||||
raise unittest.SkipTest("node is missing")
|
||||
result = subprocess.run(
|
||||
[node, "--input-type=module-typescript", "-e", script],
|
||||
capture_output=True, text=True, check=True, cwd=WEBSITE,
|
||||
)
|
||||
return result.stdout
|
||||
|
||||
|
||||
def _webp_size(data: bytes) -> tuple[int, int]:
|
||||
"""Width and height from a WebP header (VP8, VP8L or VP8X)."""
|
||||
if data[:4] != b"RIFF" or data[8:12] != b"WEBP":
|
||||
raise ValueError("not a WebP file")
|
||||
chunk = data[12:16]
|
||||
if chunk == b"VP8 ":
|
||||
width, height = struct.unpack("<HH", data[26:30])
|
||||
return width & 0x3FFF, height & 0x3FFF
|
||||
if chunk == b"VP8L":
|
||||
bits = int.from_bytes(data[21:25], "little")
|
||||
return (bits & 0x3FFF) + 1, ((bits >> 14) & 0x3FFF) + 1
|
||||
if chunk == b"VP8X":
|
||||
return int.from_bytes(data[24:27], "little") + 1, int.from_bytes(data[27:30], "little") + 1
|
||||
raise ValueError(f"unknown WebP chunk {chunk!r}")
|
||||
|
||||
|
||||
class TitleLogoOnAPlate(unittest.TestCase):
|
||||
"""The RetroArch and Batocera marks are black: beside the title they
|
||||
vanished on the dark theme, and the image had no width (Lighthouse
|
||||
unsized-images, V3, T-F3)."""
|
||||
|
||||
def test_the_component_draws_the_tile_square(self):
|
||||
title = _read(COMPONENTS / "PageTitle.astro")
|
||||
self.assertIn('width={MARK} height={MARK}', title)
|
||||
self.assertIn("const MARK = 40;", title)
|
||||
style = title[title.index(".rb-title-mark {"):]
|
||||
style = style[:style.index("}")]
|
||||
for rule in ("width: 40px;", "height: 40px;", "padding: 3px;", "border: 1px solid var(--rb-border);",
|
||||
"border-radius: 6px;", "background: #fff;", "object-fit: contain;"):
|
||||
with self.subTest(rule=rule):
|
||||
self.assertIn(rule, style)
|
||||
|
||||
def test_every_setup_page_has_a_sized_mark(self):
|
||||
index = json.loads(_read(WEBSITE / "public" / "data" / "picker" / "index.json"))
|
||||
for entry in index:
|
||||
with self.subTest(setup=entry["platform"]):
|
||||
block = _title_block(_built(f"platforms/{entry['platform']}"))
|
||||
self.assertIn('class="rb-title-mark', block)
|
||||
for image in re.findall(r"<img\b[^>]*>", block):
|
||||
self.assertIn('width="40"', image)
|
||||
self.assertIn('height="40"', image)
|
||||
|
||||
def test_a_setup_with_no_logo_gets_its_initials(self):
|
||||
block = _title_block(_built("platforms/myminui"))
|
||||
self.assertNotIn("<img", block)
|
||||
self.assertRegex(block, r"<svg class=\"rb-title-mark[^>]*>.*?>MM</text>")
|
||||
|
||||
|
||||
class NoLevelBadgeOnATabRoot(unittest.TestCase):
|
||||
def test_tab_roots(self):
|
||||
for page in TAB_ROOTS:
|
||||
with self.subTest(page=page or "home"):
|
||||
self.assertNotIn('class="rb-level ', _title_block(_built(page)))
|
||||
|
||||
|
||||
class FooterLicenceMatchesTheLicenceFile(unittest.TestCase):
|
||||
"""The footer said MIT covers the tooling. LICENSE names the metadata too."""
|
||||
|
||||
def test_footer(self):
|
||||
self.assertIn("metadata", _read(REPO_ROOT / "LICENSE").split("MIT License")[0])
|
||||
html = _built("data")
|
||||
footer = re.sub(r"<[^>]+>", "", html[html.index('class="rb-footer'):])
|
||||
self.assertIn("MIT for the tooling and metadata.", footer)
|
||||
|
||||
|
||||
class WideTablesStackAndFiltersAreBuilt(unittest.TestCase):
|
||||
"""Level 2 and 3 tables overflowed a phone (emulator summary, gaps,
|
||||
verification modes, emulators index, data: V5, J8). The filter box,
|
||||
added after paint, pushed the table down: CLS 0.066 on
|
||||
gaps-files-to-source (T-F10)."""
|
||||
|
||||
def test_every_wide_table_stacks(self):
|
||||
for page in ("emulators/duckstation", "gaps", "gaps-files-to-source", "emulators", "data",
|
||||
"wiki/verification-modes"):
|
||||
html = (DIST / page / "index.html")
|
||||
if not html.is_file():
|
||||
continue
|
||||
for table in _tables(_read(html)):
|
||||
if len(_headers(table)) < 4:
|
||||
continue
|
||||
with self.subTest(page=page, header=_headers(table)):
|
||||
self.assertIn('data-rb-stack="true"', table.split(">", 1)[0])
|
||||
|
||||
def test_long_tables_get_their_labels_at_runtime(self):
|
||||
"""Labels on every cell of a long stacked table were 122 KB of
|
||||
emulators/mame and took it over the 600,000-byte page budget."""
|
||||
self.assertIn("if (kind === 'kv' || rows.length >= RECORD_ROWS) return;", _read(PLUGIN))
|
||||
source = _read(SITE_TS)
|
||||
self.assertIn("const labelCells = (table: HTMLTableElement) => {", source)
|
||||
self.assertIn("labelCells(table);", source)
|
||||
html = _built("emulators/mame")
|
||||
self.assertLess(len(html.encode()), 600_000)
|
||||
|
||||
def test_the_filter_box_is_in_the_html(self):
|
||||
html = _built("gaps-files-to-source")
|
||||
boxes = re.findall(r'<div class="rb-table-filter">(.*?)</div>\s*<table', html, re.S)
|
||||
self.assertTrue(boxes)
|
||||
for box in boxes:
|
||||
self.assertRegex(box, r'<label for="rb-table-filter-\d+">Filter [^<]+</label>')
|
||||
self.assertRegex(box, r'<input id="rb-table-filter-\d+" type="search"')
|
||||
self.assertRegex(box, r'aria-live="polite">[\d,]+ rows</span>')
|
||||
source = _read(SITE_TS)
|
||||
self.assertNotIn("controls.className = 'rb-table-filter'", source)
|
||||
self.assertIn("controls?.matches('.rb-table-filter')", source)
|
||||
|
||||
def test_every_long_table_has_its_box(self):
|
||||
html = _built("systems")
|
||||
for table in _tables(html):
|
||||
rows = len(re.findall(r"<tr\b", table.split("<tbody>", 1)[-1]))
|
||||
if rows < 20:
|
||||
continue
|
||||
start = html.index(table)
|
||||
self.assertIn('class="rb-table-filter"', html[max(0, start - 600):start])
|
||||
|
||||
def test_the_placeholder_names_the_columns(self):
|
||||
"""Every filter offered "Name, platform, system, hash...", over the
|
||||
console list too, which has no hash (V small)."""
|
||||
self.assertNotIn("Name, platform, system, hash", _read(SITE_TS))
|
||||
cases = [["File", "Region", "Required", "Hash"], ["Console", "Files"], ["System", "MD5", "Size"]]
|
||||
out = _node_module(
|
||||
f"import {{ filterPlaceholder }} from {json.dumps(PLUGIN.as_uri())};"
|
||||
f"console.log(JSON.stringify({json.dumps(cases)}.map(filterPlaceholder)));"
|
||||
)
|
||||
self.assertEqual(json.loads(out), ["File, region, required, hash...", "Console, files...", "System, MD5, size..."])
|
||||
html = _built("systems")
|
||||
self.assertRegex(html, r'placeholder="Console[^"]*\.\.\."')
|
||||
self.assertNotRegex(html, r'placeholder="[^"]*hash[^"]*"')
|
||||
|
||||
|
||||
class HashLookupGivesOneVerdict(unittest.TestCase):
|
||||
"""The lookup read "24 matches", "Setups that list it (12)" over 17 rows,
|
||||
and emulators by id. ?md5= showed the empty form (N8, J7)."""
|
||||
|
||||
def test_parameters(self):
|
||||
source = _read(SITE_TS)
|
||||
self.assertIn("const HASH_PARAMS = ['hash', 'md5', 'crc32', 'sha256'];", source)
|
||||
self.assertIn("HASH_PARAMS.map((key) => params.get(key))", source)
|
||||
|
||||
def test_counts_say_what_they_count(self):
|
||||
source = _read(SITE_TS)
|
||||
self.assertNotIn("'match' : 'matches'", source)
|
||||
self.assertIn("count(platforms.length, 'declaration', 'declarations')} by ${count(setups.size, 'setup', 'setups')}", source)
|
||||
self.assertIn("count(setups.size, 'setup lists', 'setups list')", source)
|
||||
self.assertIn("'file in the collection', 'files in the collection'", source)
|
||||
|
||||
def test_emulators_by_name(self):
|
||||
source = _read(SITE_TS)
|
||||
self.assertIn("json(`api/v1/emulators/${owner}.json`)", source)
|
||||
self.assertIn("profile.emulator || profile.display_name || owner", source)
|
||||
profile = json.loads(_read(DIST / "api" / "v1" / "emulators" / "duckstation.json")) \
|
||||
if (DIST / "api" / "v1" / "emulators" / "duckstation.json").is_file() else None
|
||||
if profile is None:
|
||||
self.skipTest("no built site")
|
||||
self.assertEqual(profile["items"][0]["profile"]["emulator"], "DuckStation")
|
||||
|
||||
def test_a_console_page_card_reads_its_manufacturer_file(self):
|
||||
"""Every collection match on a console page (systems/sony-playstation/)
|
||||
asked for api/v1/systems/sony-playstation.json, which does not exist:
|
||||
the verdict lost the file's path."""
|
||||
source = _read(SITE_TS)
|
||||
self.assertIn("api/v1/systems/${words.slice(0, size).join('-')}.json", source)
|
||||
api = DIST / "api" / "v1" / "systems"
|
||||
if not api.is_dir():
|
||||
self.skipTest("no built site")
|
||||
self.assertFalse((api / "sony-playstation.json").exists())
|
||||
self.assertTrue((api / "sony.json").is_file())
|
||||
|
||||
def test_no_match_leads_somewhere(self):
|
||||
source = _read(SITE_TS)
|
||||
self.assertIn("link('contributing/', 'add it')", source)
|
||||
anchor = re.search(r"const WANTED = 'provenance/#([^']+)';", source)
|
||||
self.assertIsNotNone(anchor)
|
||||
self.assertIn(f'id="{anchor[1]}"', _built("provenance"))
|
||||
self.assertNotIn("No file the site knows has this hash.", source)
|
||||
|
||||
|
||||
class HashAFileHere(unittest.TestCase):
|
||||
"""Find a file asked for a hash only: hashing meant another tool (J7)."""
|
||||
|
||||
def test_the_chooser(self):
|
||||
source = _read(SITE_TS)
|
||||
self.assertIn("input.type = 'file';", source)
|
||||
self.assertIn("It is hashed on this device. Nothing is uploaded.", source)
|
||||
self.assertIn("crypto.subtle.digest(name, buffer)", source)
|
||||
self.assertIn("['SHA-1', 'SHA-256']", source)
|
||||
self.assertIn("lookUp(status, result, [found.sha1, found.md5, found.crc32, found.sha256])", source)
|
||||
|
||||
def test_md5_and_crc32_match_python(self):
|
||||
source = _read(SITE_TS)
|
||||
block = source[source.index("// Hashing a file on this device"):source.index("type FileHashes")]
|
||||
sizes = [0, 1, 55, 56, 63, 64, 65, 119, 120, 128, 1000, 70_001]
|
||||
out = _node_module(block + (
|
||||
f"console.log(JSON.stringify({json.dumps(sizes)}.map((n) => {{"
|
||||
"const bytes = new Uint8Array(n);"
|
||||
"for (let i = 0; i < n; i += 1) bytes[i] = (i * 31 + 7) & 0xff;"
|
||||
"return [md5(bytes), crc32(bytes)]; })));"
|
||||
))
|
||||
for size, (md5, crc) in zip(sizes, json.loads(out)):
|
||||
data = bytes((i * 31 + 7) & 0xFF for i in range(size))
|
||||
with self.subTest(size=size):
|
||||
self.assertEqual(md5, hashlib.md5(data).hexdigest())
|
||||
self.assertEqual(crc, f"{zlib.crc32(data):08x}")
|
||||
|
||||
|
||||
class SearchFoldsPagesUnderAHash(unittest.TestCase):
|
||||
"""Under "Look up this hash" came 14 unrelated pages: Magnavox for
|
||||
E490.WAV, Philips for cdi490a.zip (N8)."""
|
||||
|
||||
def test_fold(self):
|
||||
source = _read(SITE_TS)
|
||||
self.assertIn("toggle.textContent = 'Pages that mention these characters';", source)
|
||||
self.assertIn("toggle.setAttribute('aria-expanded', 'false');", source)
|
||||
header = _read(COMPONENTS / "Header.astro")
|
||||
self.assertIn(
|
||||
".pagefind-ui__drawer[data-rb-hash]:not([data-rb-pages-shown]) .pagefind-ui__results-area { display: none; }",
|
||||
header,
|
||||
)
|
||||
|
||||
def test_search_fields_are_weighted_and_never_shown(self):
|
||||
config = _read(CONFIG)
|
||||
self.assertIn("metaWeights: { title: 10, files: 0.5, aliases: 5, devices: 5 },", config)
|
||||
self.assertIn("const UNSHOWN_FIELDS = new Set(['files', 'aliases', 'devices']);", _read(SITE_TS))
|
||||
|
||||
|
||||
class OsTabsKeepTheChoiceInView(unittest.TestCase):
|
||||
def test_the_row_scrolls_not_the_page(self):
|
||||
source = _read(SITE_TS)
|
||||
self.assertIn("list.scrollLeft", source)
|
||||
self.assertNotIn("scrollIntoView", source)
|
||||
self.assertIn("osTabs();", source)
|
||||
|
||||
|
||||
class CommandsNeverBreakInsideAWord(unittest.TestCase):
|
||||
"""`--` / `platform retroarch` at 1440 px, `--` / `system` and
|
||||
`nintendo-` / `64dd` on a phone: a browser breaks a line after a hyphen,
|
||||
whatever overflow-wrap says."""
|
||||
|
||||
def test_the_words_are_wrapped_and_the_text_kept(self):
|
||||
# Shiki cuts `--platform` into two tokens: the span has to hold both.
|
||||
line = {"type": "element", "tagName": "div", "properties": {"className": ["code"]}, "children": [
|
||||
{"type": "element", "tagName": "span", "properties": {"style": "a"}, "children": [
|
||||
{"type": "text", "value": "sh -s "}]},
|
||||
{"type": "element", "tagName": "span", "properties": {"style": "b"}, "children": [
|
||||
{"type": "text", "value": "--"}]},
|
||||
{"type": "element", "tagName": "span", "properties": {"style": "c"}, "children": [
|
||||
{"type": "text", "value": "platform retroarch --system nintendo-64dd"}]},
|
||||
]}
|
||||
out = json.loads(_node_module(
|
||||
f"import {{ unbreakWords }} from {json.dumps(UNBROKEN.as_uri())};"
|
||||
f"console.log(JSON.stringify(unbreakWords({json.dumps(line)})));"
|
||||
))
|
||||
|
||||
def text(node: dict) -> str:
|
||||
return node.get("value", "") + "".join(text(child) for child in node.get("children", []))
|
||||
|
||||
self.assertEqual("".join(text(n) for n in out), "sh -s --platform retroarch --system nintendo-64dd")
|
||||
words = [text(n) for n in out if n.get("properties", {}).get("className") == ["rb-nb"]]
|
||||
self.assertEqual(words, ["-s", "--platform", "--system", "nintendo-64dd"])
|
||||
# The colours of each token survive the cut.
|
||||
platform = next(n for n in out if text(n) == "--platform")
|
||||
self.assertEqual([c["properties"]["style"] for c in platform["children"]], ["b", "c"])
|
||||
|
||||
def test_registered(self):
|
||||
config = _read(CONFIG)
|
||||
self.assertIn("import unbrokenTokens from './src/plugins/ec-unbroken-tokens.mjs';", config)
|
||||
self.assertIn("plugins: [unbrokenTokens()],", config)
|
||||
self.assertIn("baseStyles: `.${CLASS} { white-space: nowrap; }`", _read(UNBROKEN))
|
||||
picker = _read(COMPONENTS / "FilePicker.astro")
|
||||
self.assertIn("showCommand(cmd, `${INSTALL} --platform", picker)
|
||||
self.assertIn(".rb-picker .rb-pick-cmd .rb-nb { white-space: nowrap; }", picker)
|
||||
self.assertIn("navigator.clipboard?.writeText(cmd.textContent ?? '')", picker)
|
||||
|
||||
def test_copy_gives_the_rendered_text(self):
|
||||
"""The copy button's text is the block's text: the spans add nothing.
|
||||
Expressive Code leaves a leading comment line out of the copy."""
|
||||
import html as html_lib
|
||||
|
||||
page = _built("platforms/retroarch")
|
||||
figures = re.findall(r"<figure\b.*?</figure>", page, re.S)
|
||||
self.assertTrue(figures)
|
||||
self.assertIn('<span class="rb-nb">', page)
|
||||
for figure in figures:
|
||||
copied = re.search(r'data-code="([^"]*)"', figure)
|
||||
if not copied:
|
||||
continue
|
||||
lines = re.findall(r'<div class="ec-line">(.*?)</div></div>', figure, re.S)
|
||||
shown = "\n".join(html_lib.unescape(re.sub(r"<[^>]+>", "", line)) for line in lines)
|
||||
code = html_lib.unescape(copied[1]).replace("\x7f", "\n")
|
||||
with self.subTest(code=code[:60]):
|
||||
self.assertTrue(shown.endswith(code), (shown, code))
|
||||
|
||||
|
||||
class LogosAtTheirDisplaySize(unittest.TestCase):
|
||||
"""136 KiB of the home page was 96 px PNG logos shown at 40 px (T-F7)."""
|
||||
|
||||
def test_raster_logos_are_small_webp(self):
|
||||
manifest = json.loads(_read(LOGOS / "sources.json"))["images"]
|
||||
self.assertTrue(manifest)
|
||||
for entry in manifest:
|
||||
path = LOGOS / entry["file"]
|
||||
with self.subTest(file=entry["file"]):
|
||||
self.assertTrue(path.is_file())
|
||||
self.assertIn(path.suffix, (".webp", ".svg"))
|
||||
if path.suffix == ".webp":
|
||||
self.assertEqual(_webp_size(path.read_bytes()), (LOGO_PIXELS, LOGO_PIXELS))
|
||||
self.assertEqual(sorted(p.name for p in LOGOS.glob("*.png")), [])
|
||||
script = _read(REPO_ROOT / "scripts" / "fetch_site_images.py")
|
||||
self.assertIn(f"LOGO_SIZE = {LOGO_PIXELS}", script)
|
||||
self.assertIn('square_image(data, LOGO_SIZE, "WEBP")', script)
|
||||
|
||||
def test_pages_point_at_the_copies(self):
|
||||
html = _built("platforms/emudeck")
|
||||
self.assertIn('src="/retrobios/assets/logos/emudeck.webp"', html)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -54,7 +54,7 @@ Every module in `tests/`, by the area it guards.
|
||||
| Installer and downloads | loopback server | `test_check_release`, `test_download`, `test_install`, `test_install_cores`, `test_install_experience`, `test_install_offline`, `test_install_selection` |
|
||||
| Handheld port | stubbed PortMaster dialogs, fake installer | `test_port` |
|
||||
| Truth and native export | synthetic + cached originals | `test_export_counts`, `test_export_native_layer`, `test_export_report`, `test_exporters`, `test_truth_data_dir_readers`, `test_truth_diff`, `test_truth_layout` |
|
||||
| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_r2_components`, `test_site_r2_css`, `test_site_r2_emulators`, `test_site_r2_entry`, `test_site_r2_gate`, `test_site_r2_nav`, `test_site_r2_p0`, `test_site_r2_reference`, `test_site_r2_systems`, `test_site_r2_wiki`, `test_site_r3_collection`, `test_site_r3_p0`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
|
||||
| Scrapers, upstream and provenance | inline sources, synthetic | `test_batocera_md5_list`, `test_check_freshness`, `test_contributing_text`, `test_coverage_cache`, `test_data_dir_refs`, `test_dataclass_defaults`, `test_dedup`, `test_doc_pages`, `test_doc_surfaces`, `test_elec_scrapers`, `test_fbneo_parser`, `test_find_pinned_revision`, `test_hash_merge`, `test_launchers`, `test_launchers_emudeck`, `test_launchers_recalbox`, `test_launchers_retrobat`, `test_launchers_retrodeck`, `test_launchers_retropie`, `test_launchers_rocknix`, `test_mame_devices`, `test_mame_parser`, `test_mame_ref_audit`, `test_optional_dependencies`, `test_profile_sync`, `test_prose_style`, `test_provenance`, `test_refresh_data_dirs`, `test_refresh_failures`, `test_refresh_stale`, `test_retrodeck_scraper_errors`, `test_romset_recipes`, `test_schema_semantic_gate`, `test_scraper_contract`, `test_site_availability`, `test_site_content_data`, `test_site_copy`, `test_site_cores`, `test_site_emulator_page`, `test_site_exports`, `test_site_funnel`, `test_site_images`, `test_site_picker`, `test_site_platform_hashes`, `test_site_r2_components`, `test_site_r2_css`, `test_site_r2_emulators`, `test_site_r2_entry`, `test_site_r2_gate`, `test_site_r2_nav`, `test_site_r2_p0`, `test_site_r2_reference`, `test_site_r2_systems`, `test_site_r2_wiki`, `test_site_r3_collection`, `test_site_r3_components`, `test_site_r3_p0`, `test_site_render`, `test_site_system_links`, `test_site_validation`, `test_site_weight`, `test_site_write`, `test_upstream`, `test_upstream_index`, `test_validate_pr`, `test_validate_pr_flow`, `test_validate_pr_hashes`, `test_workflow_paths` |
|
||||
|
||||
## Test architecture
|
||||
|
||||
|
||||
Reference in new issue
Block a user