mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
settings.decor_default_style (all fields optional, validated) seeds _decorStyle once from the first config that arrives; every UI change of the session default flows through one runtime method with a 1s debounce and the ordinary serialized expected_rev write path. The built-in default is stored as the absence of the key; a partial or garbage key falls back per-field. decorStyleFromSettings/decorStyleToSettings are the single snake_case<->camelCase conversion point. User-Visible: yes Issue: #377
55 lines
2.5 KiB
JavaScript
55 lines
2.5 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
DEFAULT_DECOR_STYLE, decorStyleFromSettings, decorStyleToSettings,
|
|
} from '../test-build/editors/decor/geometry.js';
|
|
|
|
// #377 AC2: a stored key merges over the built-in default, per-field.
|
|
test('#377 AC2: settings key merges over DEFAULT_DECOR_STYLE', () => {
|
|
const full = decorStyleFromSettings({
|
|
color: '#8b0000', opacity: 0.9, width_cm: 5, fill: true,
|
|
fill_color: '#112233', fill_opacity: 0.5,
|
|
}, DEFAULT_DECOR_STYLE);
|
|
assert.deepEqual(full, {
|
|
color: '#8b0000', opacity: 0.9, widthCm: 5, fill: true,
|
|
fillColor: '#112233', fillOpacity: 0.5,
|
|
}, 'all six fields cross the snake_case boundary');
|
|
|
|
const partial = decorStyleFromSettings({ color: '#8b0000' }, DEFAULT_DECOR_STYLE);
|
|
assert.deepEqual(partial, { ...DEFAULT_DECOR_STYLE, color: '#8b0000' },
|
|
'a partial key inherits every other field from the default');
|
|
});
|
|
|
|
test('#377 AC2: garbage fields fall back per-field, never explode', () => {
|
|
const noisy = decorStyleFromSettings({
|
|
color: 42, opacity: 'loud', width_cm: -3, fill: 'yes',
|
|
fill_color: null, fill_opacity: 7, unknown_extra: true,
|
|
}, DEFAULT_DECOR_STYLE);
|
|
assert.equal(noisy.color, DEFAULT_DECOR_STYLE.color);
|
|
assert.equal(noisy.widthCm, DEFAULT_DECOR_STYLE.widthCm, 'non-positive width is rejected');
|
|
assert.equal(noisy.fill, DEFAULT_DECOR_STYLE.fill, 'non-boolean fill is rejected');
|
|
assert.equal(noisy.fillColor, DEFAULT_DECOR_STYLE.fillColor);
|
|
assert.equal(noisy.fillOpacity, 1, 'opacity clamps into 0..1');
|
|
assert.ok(!('unknown_extra' in noisy), 'unknown fields are ignored');
|
|
});
|
|
|
|
// #377 AC3: no key -> the built-in default, untouched legacy behaviour.
|
|
test('#377 AC3: absent or non-object key yields the default style', () => {
|
|
for (const raw of [undefined, null, 'x', 7]) {
|
|
assert.deepEqual(decorStyleFromSettings(raw, DEFAULT_DECOR_STYLE), DEFAULT_DECOR_STYLE);
|
|
}
|
|
});
|
|
|
|
// #377: the default is stored as the ABSENCE of the key.
|
|
test('#377: toSettings returns null for the default and a snake_case patch otherwise', () => {
|
|
assert.equal(decorStyleToSettings({ ...DEFAULT_DECOR_STYLE }), null);
|
|
const patch = decorStyleToSettings({ ...DEFAULT_DECOR_STYLE, color: '#8b0000' });
|
|
assert.deepEqual(patch, {
|
|
color: '#8b0000', opacity: 1, width_cm: 3.6, fill: false,
|
|
fill_color: '#607d8b', fill_opacity: 0.25,
|
|
});
|
|
// the pair is a lossless round-trip for any persisted style
|
|
const style = decorStyleFromSettings(patch, DEFAULT_DECOR_STYLE);
|
|
assert.deepEqual(decorStyleToSettings(style), patch);
|
|
});
|