Files
houseplan-card/test/device-pulse.test.mjs
T
2026-08-19 22:37:25 +03:00

109 lines
4.4 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { resolveDevicePulse } from '../test-build/device-pulse.js';
const visual = (status = 'neutral', activity = 'none') => ({
availability: 'available', status, activity,
});
const resolve = (overrides = {}) => resolveDevicePulse({
display: 'icon_ripple', visual: visual(), semanticActivity: 'none',
liveStates: true, effectiveHidden: false, now: 1_000,
...overrides,
});
test('pulse priority is hidden/static, alarm, finite event, continuous state', () => {
assert.equal(resolve({ effectiveHidden: true, visual: visual('alarm') }).kind, 'none');
assert.equal(resolve({ display: 'static_icon', visual: visual('alarm') }).kind, 'none');
assert.equal(resolve({ display: 'badge', visual: visual('alarm') }).kind, 'alarm');
assert.equal(resolve({
shortReason: 'event', shortExpiresAt: 2_000, semanticActivity: 'running',
}).kind, 'short');
assert.equal(resolve({ semanticActivity: 'presence' }).kind, 'continuous');
});
test('ordinary activity belongs only to icon plus activity mode', () => {
for (const display of ['badge', 'value']) {
assert.equal(resolve({ display, semanticActivity: 'running' }).kind, 'none');
}
assert.equal(resolve({ display: 'icon_ripple', semanticActivity: 'running' }).kind, 'continuous');
assert.equal(resolve({ liveStates: false, semanticActivity: 'running' }).kind, 'none');
});
test('critical alarm survives live-state and face-mode gates but not disabled binding', () => {
assert.equal(resolve({ liveStates: false, visual: visual('alarm') }).kind, 'alarm');
for (const display of ['badge', 'value']) {
assert.equal(resolve({ display, liveStates: false, visual: visual('alarm') }).kind, 'alarm');
}
assert.equal(resolve({ bindingUnavailable: true, visual: visual('alarm') }).kind, 'none');
});
test('reduced motion replaces ordinary waves with a dot and keeps alarm semantic', () => {
const ordinary = resolve({
semanticActivity: 'running', reducedMotion: true,
color: '#123456', diameterScale: 5,
});
assert.equal(ordinary.animated, false);
assert.equal(ordinary.reducedMotionIndicator, 'dot');
assert.equal(ordinary.color, '#123456');
assert.equal(ordinary.diameterScale, 1);
const short = resolve({
shortReason: 'event', shortExpiresAt: 2_000, reducedMotion: true,
});
assert.equal(short.kind, 'short');
assert.equal(short.animated, false);
assert.equal(short.reducedMotionIndicator, 'dot');
assert.equal(short.diameterScale, 1);
const alarm = resolve({
visual: visual('alarm'), reducedMotion: true,
color: '#123456', diameterScale: 5,
});
assert.equal(alarm.kind, 'alarm');
assert.equal(alarm.animated, false);
assert.equal(alarm.reducedMotionIndicator, 'none');
assert.equal(alarm.color, '#F0410C');
assert.equal(alarm.diameterScale, 1.5);
});
test('package defaults map presence, running and neutral transition to distinct colors', () => {
const presence = resolve({ semanticActivity: 'presence' });
assert.equal(presence.color, '#1DC21D');
assert.equal(presence.diameterScale, 1.5);
assert.equal(resolve({ semanticActivity: 'running' }).color, '#F0A00C');
assert.equal(resolve({
semanticActivity: 'transition', visual: visual('neutral', 'transition'),
}).color, '#0C82F0');
assert.equal(resolve({
semanticActivity: 'presence', color: '#123456', diameterScale: 4,
}).color, '#123456');
});
test('expired finite runtime never leaves a static ring', () => {
const pulse = resolve({ shortReason: 'event', shortExpiresAt: 999 });
assert.equal(pulse.kind, 'none');
});
test('inactive projection retains configured preview colour and diameter', () => {
const pulse = resolve({
color: '#123456', diameterScale: 4.25,
});
assert.equal(pulse.kind, 'none');
assert.equal(pulse.color, '#123456');
assert.equal(pulse.diameterScale, 4.25);
});
test('unknown and unavailable device state cannot retain an activity pulse', () => {
for (const kind of ['short', 'continuous']) {
const pulse = resolveDevicePulse({
display: 'icon_ripple',
visual: { availability: 'unavailable', status: 'neutral', activity: 'transition' },
semanticActivity: 'transition',
shortReason: kind === 'short' ? 'transition' : null,
shortExpiresAt: kind === 'short' ? Date.now() + 1_000 : null,
liveStates: true,
effectiveHidden: false,
});
assert.equal(pulse.kind, 'none');
}
});