diff options
Diffstat (limited to 'scripts/theme-studio')
| -rw-r--r-- | scripts/theme-studio/generate.py | 27 | ||||
| -rw-r--r-- | scripts/theme-studio/test_generate.py | 84 |
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() |
