feat v1.42.0: room settings — the third settings tier

- four-tier principle fixed in ARCHITECTURE: global > space > room >
  device, specific overrides general, unset inherits
- room.settings { fill_mode, temp_source, hum_source } with backend
  schema; pure roomFillModeOf + sourceValue (+2 test suites, 114)
- gear on room cards in the Plan editor opens Room settings: rename,
  re-area (current area included in the list), fill override (may opt
  out of glow darkness), explicit temp/hum source with a searchable
  device+entity dropdown; the same section in the creation dialog
- source feeds the room card, tooltip and temp fill; works for rooms
  without an HA area (user-feedback case #1)
- smoke_room_settings.mjs (10 checks); TESTING/CHANGELOG/ARCHITECTURE
  same-commit
This commit is contained in:
Matysh
2026-07-27 09:58:21 +03:00
parent 88dc0d1de7
commit 19e19b5c5f
21 changed files with 721 additions and 146 deletions
+23 -1
View File
@@ -1,6 +1,6 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { buildDevices, lightGroups, primaryEntity, lqiFor, tempFor, humFor, areaLights, areaTemp, areaHum, areaLightStats } from '../test-build/devices.js';
import { buildDevices, lightGroups, primaryEntity, lqiFor, tempFor, humFor, areaLights, areaTemp, areaHum, areaLightStats, sourceValue } from '../test-build/devices.js';
import { compileIconRules, iconFor } from '../test-build/rules.js';
/** Minimal fake hass around the pieces buildDevices reads. */
@@ -337,3 +337,25 @@ test('icon rules: soundbars vs smart speakers (v1.40.2)', () => {
assert.equal(iconFor('Колонка Алисы', ''), 'mdi:speaker');
assert.equal(iconFor('Kitchen speaker', ''), 'mdi:speaker');
});
test('sourceValue: explicit entity and device sources (tier 3)', () => {
const hass = {
states: {
'sensor.custom_temp': { state: '23.456' },
'sensor.custom_hum': { state: '47.8' },
'sensor.bad': { state: 'unknown' },
'sensor.dev_t': { state: '21.5', attributes: { device_class: 'temperature', unit_of_measurement: '°C' } },
},
entities: {
'sensor.dev_t': { device_id: 'dev1' },
'sensor.custom_temp': {}, 'sensor.custom_hum': {}, 'sensor.bad': {},
},
};
assert.equal(sourceValue(hass, 'entity:sensor.custom_temp', 'temp'), 23.5);
assert.equal(sourceValue(hass, 'entity:sensor.custom_hum', 'hum'), 48);
assert.equal(sourceValue(hass, 'entity:sensor.bad', 'temp'), null);
assert.equal(sourceValue(hass, 'device:dev1', 'temp'), 21.5);
assert.equal(sourceValue(hass, 'device:nope', 'temp'), null);
assert.equal(sourceValue(hass, '', 'temp'), null);
assert.equal(sourceValue(hass, 'garbage', 'temp'), null);
});
+9
View File
@@ -11,6 +11,7 @@ import {
alignGuides, segmentAngle, is45,
swipeTarget, clampScale,
migratePdfUrls,
roomFillModeOf,
segmentCm, formatLength, roomEdges, roomPoly, pointOnBoundary, pointStrictlyInside, roomsOverlap,
mergeRooms, splitRoom, polygonArea, closestPointOnBoundary, isActiveState, snapToWall, openingAmount, fillColorsOf, lerpColor, roomFillStyle, stateIcon, lightColorOf, isAlarmState, parseRoomRef, diffNewDevices,
} from '../test-build/logic.js';
@@ -800,3 +801,11 @@ test('migratePdfUrls: rebinding rewrites file urls', () => {
// без смены id — как есть
assert.equal(migratePdfUrls(pdfs, 'x', 'x'), pdfs);
});
test('roomFillModeOf: tier-3 override beats the space, junk inherits', () => {
assert.equal(roomFillModeOf('temp', { settings: { fill_mode: 'light' } }), 'light');
assert.equal(roomFillModeOf('glow', { settings: { fill_mode: 'none' } }), 'none'); // выход из тьмы
assert.equal(roomFillModeOf('temp', {}), 'temp');
assert.equal(roomFillModeOf('temp', null), 'temp');
assert.equal(roomFillModeOf('temp', { settings: { fill_mode: 'glow' } }), 'temp'); // glow нельзя выбрать per-room
});