import test from 'node:test'; import assert from 'node:assert/strict'; import { deviceFaceStyle, deviceTextScale, deviceThemeClass, legacySupplementalMetrics, lqiClassName, renderDeviceFace, valueBadgeClassName, } from '../test-build/device-face.js'; const face = (rippleColor) => ({ display: 'icon_ripple', scale: 1, pulse: { kind: 'continuous', reason: 'running', generation: 1, expiresAt: null, color: rippleColor, diameterScale: 3, animated: true, reducedMotionIndicator: 'none', }, }); test('device face never emits an arbitrary persisted ripple declaration', () => { assert.deepEqual(deviceFaceStyle(face('#12aBcD')), [ '--ripple-scale:3', '--ripple-color:#12aBcD', ]); assert.deepEqual(deviceFaceStyle(face('rgb(12, 140, 250)')), [ '--ripple-scale:3', '--ripple-color:rgb(12, 140, 250)', ]); const hostile = deviceFaceStyle(face('red;position:fixed;inset:0')); assert.deepEqual(hostile, ['--ripple-scale:3']); assert.ok(!hostile.join(';').includes('position')); }); test('HA darkMode selects an exact package theme and otherwise leaves CSS fallback in charge', () => { assert.equal(deviceThemeClass({ themes: { darkMode: true } }), 'theme-dark'); assert.equal(deviceThemeClass({ themes: { darkMode: false } }), 'theme-light'); assert.equal(deviceThemeClass({}), ''); }); test('untouched legacy temperature and humidity keep the second satellite', () => { const presentation = { valueBadge: { configured: false, source: { kind: 'temperature', eid: 'sensor.temp' }, tone: 'temperature', }, tempText: '22.4', humText: '48', }; assert.deepEqual(legacySupplementalMetrics(presentation), [ { kind: 'humidity', text: '48', suffix: '%' }, ]); assert.deepEqual(legacySupplementalMetrics({ ...presentation, valueBadge: { ...presentation.valueBadge, configured: true }, }), []); }); test('value badge classes cover all four positions and only bottom displaces LQI', () => { for (const position of ['right', 'bottom', 'left', 'top']) { const badge = { position, availability: 'available', tone: 'default', }; assert.equal( valueBadgeClassName(badge), `value-badge pos-${position} available tone-default`, ); assert.equal( lqiClassName(badge), position === 'bottom' ? 'lqi below-value-badge' : 'lqi', ); } assert.equal(lqiClassName(null), 'lqi'); }); test('rendered pulse retains the documented activity-ring compatibility hook', () => { const presentation = { ...face('#ff9800'), classes: [], icon: 'mdi:lightbulb', angle: 0, valueText: null, valueFullText: null, valueBadge: null, lqiText: null, lqiColor: null, lqiBand: null, haDisabled: false, tempText: null, humText: null, }; const root = renderDeviceFace(presentation, { surface: 'preview' }); const nested = root.values.find((value) => value?.strings); assert.ok(nested); assert.match(nested.strings.join(''), /device-pulse activity-ring/); }); test('Text and Double use one shared shell and deterministic full-text fitting', () => { assert.equal(deviceTextScale('42 °C'), 0.45); assert.equal(deviceTextScale('A very long localized device value'), 0.25); const presentation = { ...face('#ff9800'), classes: [], icon: 'mdi:lightbulb', angle: 0, valueText: null, valueFullText: null, valueBadge: { configured: true, position: 'left', availability: 'available', tone: 'default', text: '12345678901234567890 W', fullText: '12345678901234567890 W', }, lqiText: '180', lqiColor: '#1DC21D', lqiBand: 'high', haDisabled: false, tempText: null, humText: null, }; const root = renderDeviceFace(presentation, { surface: 'preview' }); const flatten = (value) => value?.strings ? value.strings.join('') + value.values.map(flatten).join('') : Array.isArray(value) ? value.map(flatten).join('') : String(value ?? ''); const markup = flatten(root); assert.match(markup, /device-shell/); assert.match(markup, /device-shell-frame/); assert.match(markup, /device-core/); assert.match(markup, /device-sections/); assert.match(markup, /band-/); });