From e6539d971a9d1a71e70fcbdb5395e54dad5f7479 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 20 Jun 2026 05:27:47 -0400 Subject: feat(theme-studio): unify per-row widgets across the assignment tables The color/code, UI, and package tables now share one per-row widget set, so the editing surface reads the same whatever view is selected. Column order is the same in all three: element, lock, fg, bg, style, contrast, example, box. Box moves to last in the color/code table, and the package table's inline inherit and size columns fold into the row expander, matching how UI and color/code already carry them. The UI overlay faces drop the inline PASS/FAIL word and the red FAIL badge on the preview swatch. They show a bare colored worst-case number with the WCAG verdict in the hover, like the other two tables. The orphaned .crerr style goes with it. The height picker now clamps a typed value into [0.1, 2.0]. A number input only enforces min/max on its stepper arrows, so a typed or pasted value reached the model unchecked. 0.1 is Emacs's own floor (a smaller height errors out), and 2.0 is the studio ceiling. Clearing the field still unsets to the inherited default. Tests: clampHeight unit tests plus a #heighttest browser gate. The column and contrast gates move to the new positions and the bare-number readout. --- scripts/theme-studio/test-app-core.mjs | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'scripts/theme-studio/test-app-core.mjs') diff --git a/scripts/theme-studio/test-app-core.mjs b/scripts/theme-studio/test-app-core.mjs index 8b2df6849..cdfa0bc1e 100644 --- a/scripts/theme-studio/test-app-core.mjs +++ b/scripts/theme-studio/test-app-core.mjs @@ -11,6 +11,7 @@ import { clearPalettePlan, deletePaletteColumnPlan, groundColumnMembersFromPalette, areAllLocked, lockToggleLabel, toggleLockSet, galleryModel, appViewKeysSorted, faceBoxNonDefaults, overflowNonDefault, stepViewIndex, cssWeight, faceDecoration, boxCss, faceCss, composeHoverTitle, + clampHeight, HEIGHT_MIN, HEIGHT_MAX, } from './app-core.js'; import { planPaletteGenerator, entriesForGeneratedColumn } from './palette-generator-core.js'; import { oklch2hex, deltaE } from './colormath.js'; @@ -1152,3 +1153,39 @@ test('composeHoverTitle: Error — neither doc nor base returns empty string', ( assert.equal(composeHoverTitle(null, null), ''); assert.equal(composeHoverTitle(undefined, ''), ''); }); + +// --- clampHeight: coerce a height-field value to null (unset) or an in-range number --- +test('clampHeight: bounds are the agreed Emacs-floor / studio-ceiling pair', () => { + assert.equal(HEIGHT_MIN, 0.1); + assert.equal(HEIGHT_MAX, 2.0); +}); +test('clampHeight: Normal — an in-range value passes through unchanged', () => { + assert.equal(clampHeight('1.2'), 1.2); + assert.equal(clampHeight('0.5'), 0.5); + assert.equal(clampHeight(1.0), 1.0); +}); +test('clampHeight: Boundary — the exact min and max are kept', () => { + assert.equal(clampHeight('0.1'), 0.1); + assert.equal(clampHeight('2.0'), 2.0); + assert.equal(clampHeight(0.1), 0.1); +}); +test('clampHeight: Boundary — out-of-range snaps to the nearer bound', () => { + assert.equal(clampHeight('5'), 2.0); // above max + assert.equal(clampHeight('0.05'), 0.1); // below the Emacs floor + assert.equal(clampHeight('0'), 0.1); // zero is not unset; it clamps up + assert.equal(clampHeight('-3'), 0.1); // negative clamps up +}); +test('clampHeight: Boundary — blank or whitespace is unset (null)', () => { + assert.equal(clampHeight(''), null); + assert.equal(clampHeight(' '), null); + assert.equal(clampHeight(null), null); + assert.equal(clampHeight(undefined), null); +}); +test('clampHeight: Error — non-numeric text is unset (null), not NaN', () => { + assert.equal(clampHeight('abc'), null); + assert.equal(clampHeight('1.2x'), 1.2); // parseFloat reads the leading number +}); +test('clampHeight: caller may override the bounds', () => { + assert.equal(clampHeight('5', 0.1, 3.0), 3.0); + assert.equal(clampHeight('0.2', 0.5, 3.0), 0.5); +}); -- cgit v1.2.3