aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio/test_generate.py
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-07-02 23:01:54 -0400
committerCraig Jennings <c@cjennings.net>2026-07-02 23:01:54 -0400
commit630cfddc7060c7019815f8e82f87fb629aefebfa (patch)
treeb645a1c56616094eca74efdffe499a51250e1bb1 /scripts/theme-studio/test_generate.py
parentf9b379ed52e4b5947bb2a2fc8d2c54c872e39791 (diff)
downloaddotemacs-630cfddc7060c7019815f8e82f87fb629aefebfa.tar.gz
dotemacs-630cfddc7060c7019815f8e82f87fb629aefebfa.zip
feat(theme-studio): explicit absolute-vs-relative face height kind
JSON collapses 2.0 to 2 on save, so a height's number type can't say whether it's a fixed 1/10pt value or a relative multiplier. The face model now carries an explicit heightMode field (abs/rel) through seed, save/load, and export. build-theme.el coerces :height from the kind: abs exports an integer, rel a float, so a relative 2.0 renders as 2.0, never 2. Faces saved before the field existed infer the kind once on load (JS: integer to abs, fractional to rel; Python keeps the authored type, so a float 2.0 seed stays relative) and persist it on the next save. The mode-line seed carries abs explicitly, and WIP.json's eight seeded heights are stamped with their kinds. Regenerating the theme from the stamped WIP.json produces an identical WIP-theme.el, so the round-trip holds.
Diffstat (limited to 'scripts/theme-studio/test_generate.py')
-rw-r--r--scripts/theme-studio/test_generate.py26
1 files changed, 26 insertions, 0 deletions
diff --git a/scripts/theme-studio/test_generate.py b/scripts/theme-studio/test_generate.py
index 0ba35dcc..28c9b88c 100644
--- a/scripts/theme-studio/test_generate.py
+++ b/scripts/theme-studio/test_generate.py
@@ -254,6 +254,7 @@ class FaceSpecDefaults(unittest.TestCase):
"extend": False,
"inherit": None,
"height": None,
+ "heightMode": None,
})
def test_ui_face_spec_carries_inherit_and_height(self):
@@ -286,6 +287,7 @@ class FaceSpecDefaults(unittest.TestCase):
"extend": False,
"inherit": "base",
"height": 1.2,
+ "heightMode": "rel",
})
def test_generated_color_names_are_base_columns_when_legacy(self):
@@ -345,6 +347,30 @@ class GeneratorStateHelpers(unittest.TestCase):
generate.apply_modeline_height_default(uimap)
self.assertEqual(uimap["mode-line"]["height"], 142)
+ def test_modeline_height_seed_carries_abs_kind(self):
+ # the seed is a fixed 1/10pt pin, so its kind is explicit -- never
+ # left for number-type inference (JSON can't carry the distinction)
+ self.assertEqual(generate.UIMAP["mode-line"]["heightMode"], "abs")
+
+ def test_migrate_legacy_infers_height_kind(self):
+ # mirrors app-core.js migrateLegacyFace: integer -> abs, fractional
+ # -> rel, explicit kind wins, identity/absent/non-number infer none
+ from face_specs import migrate_legacy
+ self.assertEqual(migrate_legacy({"height": 130})["heightMode"], "abs")
+ self.assertEqual(migrate_legacy({"height": 1.2})["heightMode"], "rel")
+ # python keeps the authored type: an integral float is still relative
+ self.assertEqual(migrate_legacy({"height": 2.0})["heightMode"], "rel")
+ self.assertEqual(
+ migrate_legacy({"height": 2, "heightMode": "rel"})["heightMode"], "rel")
+ self.assertNotIn("heightMode", migrate_legacy({}))
+ self.assertNotIn("heightMode", migrate_legacy({"height": 1}))
+ self.assertNotIn("heightMode", migrate_legacy({"height": None}))
+
+ def test_face_spec_defaults_include_height_kind(self):
+ # the model row exists with a null default in both spec shapes
+ self.assertIsNone(ui_face_spec()["heightMode"])
+ self.assertIsNone(package_face_spec()["heightMode"])
+
def test_build_syntax_uses_map_and_style_fallbacks_without_defaults_snapshot(self):
syntax = generate.build_syntax(
{"kw": [None, True]},