diff --git a/tests/test_site_r2_components.py b/tests/test_site_r2_components.py
index 07cbbe09..b69543b9 100644
--- a/tests/test_site_r2_components.py
+++ b/tests/test_site_r2_components.py
@@ -225,8 +225,9 @@ class Picker(unittest.TestCase):
self.assertNotRegex(source, r"`\$\{files\.length\} files")
self.assertIn("
~/.config/retroarch/system/aaaaaaaaaaa/ bios/a.binC:\\\\RetroArch-Win64\\\\system\\\\aaaaaaaaaaaaa~/.config/retroarch/system/aaaaaaaaaaa/ '
+ ' bios/a.binC:\\RetroArch-Win64\\system\\aaaaaaaaaaaaa aaaaaaaaaa/bbbbbbbbbb/cccccccccc/d';"
+ "console.log(JSON.stringify([BREAK_BUDGET, markRawCode(path, room), markRawCode(path, room), room.left]));"
+ )
+ budget, first, second, left = json.loads(out)
+ self.assertEqual(first, "aaaaaaaaaa/bbbbbbbbbb/cccccccccc/d ")
+ self.assertEqual(second, "aaaaaaaaaa/bbbbbbbbbb/cccccccccc/d")
+ self.assertEqual(left, 0)
+ if not DIST.is_dir():
+ self.skipTest("no built site")
+ # Link text the generator breaks itself (a forge address) is not code.
+ code = re.compile(r"]*)?>(?:[^<]|)* ")
+ over = [str(path.relative_to(DIST)) for path in DIST.rglob("index.html")
+ if sum(found.count("", "", page[page.index("sl-markdown-content"):], flags=re.S)
+ for text in re.findall(r"]*)?>((?:[^<]|<(?!/code>))*)", body):
+ plain = ENTITY.sub("_", re.sub(r"", "", text))
+ if len(plain) > 30 and re.search(r"[/\\]", plain[:-1]) \
+ and "" not in text and "<" in text:
+ unbroken.append(f"{path.relative_to(DIST)}: {text}")
+ self.assertEqual(unbroken, [])
+
+ def test_crumb_labels_in_a_span(self):
+ source = _read(COMPONENTS / "PageTitle.astro")
+ self.assertIn("{crumb.label} : {crumb.label}", source)
+ crumbs = _built("platforms/retroarch")
+ crumbs = crumbs[crumbs.index('class="rb-breadcrumbs'):]
+ crumbs = crumbs[:crumbs.index("")]
+ items = re.findall(r"]*>(.*?) ", crumbs)
+ self.assertTrue(items)
+ for item in items:
+ self.assertRegex(item, r"^(?:]*>)?]*>[^<]+")
+
+
+class ShortStackedLabels(unittest.TestCase):
+ """2.3: "Name as declared" and "On its BIOS list (destinations)" wrap
+ to two lines in the stacked label column (V9)."""
+
+ def test_the_rule(self):
+ uri = json.dumps(STACK_TABLES.as_uri())
+ out = _node(
+ f"import {{ stackedLabel }} from {uri};"
+ "console.log(JSON.stringify([stackedLabel('Declared as', 'Name as declared'),"
+ "stackedLabel(undefined, 'Setup'), stackedLabel(' ', 'Setup')]));"
+ )
+ self.assertEqual(json.loads(out), ["Declared as", "Setup", "Setup"])
+
+ def test_plugin_and_runtime_read_the_title(self):
+ plugin = _read(STACK_TABLES)
+ self.assertIn("stackedLabel(cell.properties?.title, labels[column])", plugin)
+ self.assertIn("ctx.setProperty(cell, 'dataLabel', stacked[column])", plugin)
+ runtime = _read(SITE_TS)
+ self.assertIn("cell.getAttribute('title')?.trim() || (cell.textContent?.trim() ?? '')", runtime)
+ self.assertIn("[...table.querySelectorAll('thead th')].map(stackedLabel)", runtime)
+ self.assertIn("'Name as declared': 'Declared as'", runtime)
+ self.assertIn("cell.dataset.label = stackedLabel(head.cells[column]);", runtime)
+
+
+class LookupShowsTheDeclaredHash(unittest.TestCase):
+ """2.4: with the CRC32 pasted, Batocera's row read "Hash matched: CRC32",
+ though Batocera declares an MD5 (J9, N10)."""
+
+ def test_source(self):
+ source = _read(SITE_TS)
+ self.assertIn("declared?: string;", source)
+ self.assertIn("const declaredHash = (item: Item) => (item.declared || item.algorithm).toUpperCase();", source)
+ table = source[source.index("const setupTable"):source.index("const distinct")]
+ self.assertIn("declaredHash(item),", table)
+ self.assertNotIn("item.algorithm", table)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/website/src/components/FilePicker.astro b/website/src/components/FilePicker.astro
index 1c09e871..e37e7daf 100644
--- a/website/src/components/FilePicker.astro
+++ b/website/src/components/FilePicker.astro
@@ -4,19 +4,23 @@
// which convert.py derives from install/.json. The selection
// follows install.py narrow(): a file is kept when one of its systems is
// chosen, a file with no system when "other" is chosen.
-// The list follows the current data; release.json says what the published
-// ZIP of each setup holds, so the page can say when the two differ.
-import release from '../../../release.json';
+// The list follows the current data. release-advertised.json says what the
+// published ZIP of each setup holds, so the page can say when the two
+// differ. generate_site.py writes it from release_record.load_advertised():
+// the release record reduced to the setups the registry marks release_pack,
+// the rule the setup pages and get/ pages follow. A setup the release still
+// holds a pack for but the registry no longer marks gets no ZIP line here.
+import release from '../generated/release-advertised.json';
const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
const packs = Object.fromEntries(
- Object.values(release.packs).flatMap((pack) =>
+ Object.values(release.packs ?? {}).flatMap((pack) =>
pack.platforms.map((platform) => [platform, { files: pack.files, size: pack.extracted_size }]),
),
);
---
-
+