aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--scripts/theme-studio/generate.py27
-rw-r--r--scripts/theme-studio/test_generate.py84
2 files changed, 102 insertions, 9 deletions
diff --git a/scripts/theme-studio/generate.py b/scripts/theme-studio/generate.py
index e2d72acd..e6926e04 100644
--- a/scripts/theme-studio/generate.py
+++ b/scripts/theme-studio/generate.py
@@ -1,13 +1,20 @@
import json, os
HERE=os.path.dirname(os.path.abspath(__file__))
+
+def strip_exports(src):
+ """Drop ES-module `export` lines so the body loads as a classic <script>.
+
+ A top-level `export` is a syntax error outside a module, so it must go before
+ the body is spliced into the page. test-colormath.mjs applies the identical
+ strip and asserts the page carries the result verbatim (inline-integrity), so
+ the two copies cannot drift. NOTE: this is line-based — the export statement in
+ colormath.js must stay on a single line or the continuation lines survive.
+ """
+ return '\n'.join(l for l in src.splitlines() if not l.startswith('export')).rstrip()
+
# Pure color-math core, inlined verbatim into the page so the browser runs the
-# same code the Node tests import (one source of truth). Strip the ES-module
-# `export` line(s) — a top-level export is a syntax error in a classic <script>.
-# test-colormath.mjs applies the identical strip and asserts the page carries this
-# body verbatim (inline-integrity), so the two copies cannot drift.
-COLORMATH_BODY='\n'.join(
- l for l in open(os.path.join(HERE,'colormath.js')).read().splitlines()
- if not l.startswith('export')).rstrip()
+# same code the Node tests import (one source of truth).
+COLORMATH_BODY=strip_exports(open(os.path.join(HERE,'colormath.js')).read())
ns={}
src=open(os.path.join(HERE,'samples.py')).read()
exec(src[:src.index('cols=')], ns)
@@ -1269,5 +1276,7 @@ HTML=(HTML.replace("COLORMATH_J",COLORMATH_BODY)
.replace("UIFACES_J",json.dumps(UI_FACES)).replace("UIMAP_J",json.dumps(UIMAP)).replace("APPS_J",json.dumps(APPS))
.replace("BOLD_J",json.dumps(BOLD)).replace("MAP_J",json.dumps(MAP)))
OUT=os.path.join(HERE,'theme-studio.html')
-open(OUT,"w").write(HTML)
-print("wrote",OUT)
+
+if __name__=='__main__':
+ open(OUT,"w").write(HTML)
+ print("wrote",OUT)
diff --git a/scripts/theme-studio/test_generate.py b/scripts/theme-studio/test_generate.py
new file mode 100644
index 00000000..e76acdad
--- /dev/null
+++ b/scripts/theme-studio/test_generate.py
@@ -0,0 +1,84 @@
+"""Tests for the theme-studio page generator (generate.py).
+
+The generator's risky logic is the export-strip and the placeholder substitution
+that inline colormath.js and the sample/palette data into the page. A bug there
+ships a broken theme-studio.html that the JS unit tests can't see. These tests
+exercise the strip in isolation and assert the assembled page has every
+placeholder filled and carries the colormath body verbatim.
+
+Run: python3 -m unittest test_generate (from scripts/theme-studio/)
+"""
+import os
+import unittest
+
+import generate # importable without side effects: the file write is __main__-guarded
+
+
+class StripExports(unittest.TestCase):
+ def test_removes_the_export_line_keeps_the_body(self):
+ src = "function f(){return 1;}\nexport { f };"
+ self.assertEqual(generate.strip_exports(src), "function f(){return 1;}")
+
+ def test_preserves_multiline_body_and_rstrips_trailing_blanks(self):
+ src = "const a=1;\nconst b=2;\nexport { a, b };\n\n"
+ self.assertEqual(generate.strip_exports(src), "const a=1;\nconst b=2;")
+
+ def test_no_export_line_returns_body_rstripped(self):
+ self.assertEqual(generate.strip_exports("let x=1;\n"), "let x=1;")
+
+ def test_removes_every_export_line_not_just_the_last(self):
+ src = "export const a=1;\ncode();\nexport { a };"
+ self.assertEqual(generate.strip_exports(src), "code();")
+
+ def test_matches_the_js_side_strip_so_integrity_holds(self):
+ # test-colormath.mjs strips with the same rule: drop lines starting with
+ # 'export', then trim trailing whitespace. Keep the two in lockstep.
+ src = "x();\nexport { x };\n"
+ js_equivalent = "\n".join(
+ l for l in src.split("\n") if not l.startswith("export")
+ ).rstrip()
+ self.assertEqual(generate.strip_exports(src), js_equivalent)
+
+
+class ColormathInlining(unittest.TestCase):
+ def setUp(self):
+ self.cm_src = open(os.path.join(generate.HERE, "colormath.js")).read()
+
+ def test_colormath_export_is_a_single_line(self):
+ # The strip is line-based, so a multi-line `export { ... }` would leave the
+ # continuation lines behind as a dangling block (a real bug this caught).
+ export_lines = [l for l in self.cm_src.splitlines() if l.startswith("export")]
+ self.assertEqual(len(export_lines), 1, "colormath.js must have one export line")
+
+ def test_stripped_body_has_no_export_line_and_ends_cleanly(self):
+ # "export" can still appear inside a comment; what must be gone is any line
+ # that *starts* with export (and the dangling continuation lines a
+ # multi-line export would leave).
+ body = generate.strip_exports(self.cm_src)
+ for line in body.splitlines():
+ self.assertFalse(line.startswith("export"), f"export line survived: {line!r}")
+ self.assertTrue(body.endswith("}"), "body should end at the last function")
+
+
+class AssembledPage(unittest.TestCase):
+ PLACEHOLDERS = [
+ "COLORMATH_J", "SAMPLES_J", "PALETTE_J", "CATS_J",
+ "UIFACES_J", "UIMAP_J", "APPS_J", "BOLD_J", "MAP_J",
+ ]
+
+ def test_every_placeholder_is_substituted(self):
+ for token in self.PLACEHOLDERS:
+ self.assertNotIn(token, generate.HTML, f"{token} left unsubstituted")
+
+ def test_page_carries_the_colormath_body_verbatim(self):
+ # Python-side inline-integrity: the same guarantee the JS test asserts, but
+ # checked at the point the page is built rather than after a round-trip.
+ self.assertIn(generate.COLORMATH_BODY, generate.HTML)
+
+ def test_page_is_a_single_script_document(self):
+ self.assertEqual(generate.HTML.count("<script>"), 1)
+ self.assertEqual(generate.HTML.count("</script>"), 1)
+
+
+if __name__ == "__main__":
+ unittest.main()