Unify all color picker call sites

Issue: #180
User-Visible: yes
This commit is contained in:
Sergey Matyunin
2026-08-19 11:40:32 +03:00
parent f69ac71ef7
commit fcee724638
15 changed files with 560 additions and 300 deletions
File diff suppressed because one or more lines are too long
+46
View File
@@ -667,6 +667,52 @@ export async function prepareGoldenScenario(page, scenario) {
if (!trigger) throw new Error('golden decor color trigger missing');
trigger.click();
await picker.updateComplete;
} else if (scenario.dialog === 'general-color') {
card._openSettingsDialog();
await card.updateComplete;
const dialog = card.renderRoot.querySelector('hp-dialog');
const picker = [...(dialog?.querySelectorAll('hp-color-opacity') || [])]
.find((item) => item.label === card._t('gs.light_on'));
await picker?.updateComplete;
const trigger = picker?.renderRoot?.querySelector('.trigger');
if (!trigger) throw new Error('golden general-settings color trigger missing');
trigger.scrollIntoView({ block: 'center' });
await frame();
trigger.click();
await picker.updateComplete;
} else if (scenario.dialog === 'device-ripple-color') {
card._setMode('devices');
await card.updateComplete;
await settleMode(card);
const device = card._devices.find((item) => item.id === scenario.deviceId);
if (!device) throw new Error(`golden ripple device missing: ${scenario.deviceId}`);
card._openMarkerDialog(device);
card._markerDialog = { ...card._markerDialog, display: 'icon_ripple' };
card.requestUpdate();
await card.updateComplete;
const dialog = card.renderRoot.querySelector('hp-dialog');
const picker = [...(dialog?.querySelectorAll('hp-color-opacity') || [])]
.find((item) => item.label === card._t('marker.activity_color'));
await picker?.updateComplete;
const trigger = picker?.renderRoot?.querySelector('.trigger');
if (!trigger) throw new Error('golden ripple color trigger missing');
trigger.scrollIntoView({ block: 'center' });
await frame();
trigger.click();
await picker.updateComplete;
} else if (scenario.dialog === 'space-room-color') {
card._openSpaceDialog('edit', scenario.space);
await card.updateComplete;
const dialog = card.renderRoot.querySelector('hp-dialog');
const picker = [...(dialog?.querySelectorAll('hp-color-opacity') || [])]
.find((item) => item.label === card._t('space.room_color'));
await picker?.updateComplete;
const trigger = picker?.renderRoot?.querySelector('.trigger');
if (!trigger) throw new Error('golden space room-color trigger missing');
trigger.scrollIntoView({ block: 'center' });
await frame();
trigger.click();
await picker.updateComplete;
}
if (scenario.dayCycle) {
const environment = card.renderRoot.querySelector('.hp-day-cycle-env');
+10 -1
View File
@@ -1,7 +1,7 @@
import { fixtureWallKey } from '../fixtures/visual-matrix.mjs';
/** Data-only HP-QA-01 capture matrix. Bump when framing or scenarios change. */
export const GOLDEN_MATRIX_VERSION = 28;
export const GOLDEN_MATRIX_VERSION = 29;
const stage = { capture: 'stage', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0005 } };
const page = { capture: 'page', threshold: { maxChannelDelta: 10, maxDiffRatio: 0.0008 } };
@@ -280,6 +280,15 @@ export const GOLDEN_SCENARIOS = Object.freeze([
{ id: 'decor-color-popover-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'decor-color', language: 'en', theme: 'light',
viewport: { width: 760, height: 820 }, ...page },
{ id: 'general-color-popover-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'general-color', language: 'en', theme: 'light',
viewport: { width: 900, height: 900 }, ...page },
{ id: 'device-ripple-color-popover-mobile-ru', fixture: 'visual', space: 'golden-lighting',
dialog: 'device-ripple-color', deviceId: 'golden-light-two',
language: 'ru', theme: 'dark', viewport: { width: 390, height: 1000 }, ...page },
{ id: 'space-room-color-popover-desktop-ru', fixture: 'visual', space: 'golden-geometry',
dialog: 'space-room-color', language: 'ru', theme: 'dark',
viewport: { width: 900, height: 1000 }, ...page },
{ id: 'backup-full-preview-desktop-en', fixture: 'visual', space: 'golden-geometry',
dialog: 'backup-full', language: 'en', theme: 'dark',
viewport: { width: 1000, height: 900 }, ...page },
+119
View File
@@ -0,0 +1,119 @@
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch({ width: 760, height: 920 });
const result = await page.evaluate(async () => {
const out = {};
const card = window.__card;
const root = () => card.shadowRoot || card.renderRoot;
const frame = () => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
const change = (picker, detail) => picker.dispatchEvent(new CustomEvent(
'hp-color-opacity-change', { detail, bubbles: true, composed: true },
));
const pickerByLabel = (dialog, label) => [...dialog.querySelectorAll('hp-color-opacity')]
.find((picker) => picker.label === label);
const nativeColors = () => root().querySelectorAll('input[type="color"]').length;
card._openSettingsDialog();
card._settingsDialog = { ...card._settingsDialog, bgMode: 'static', bgColor: null };
card.requestUpdate();
await card.updateComplete;
let dialog = root().querySelector('hp-dialog');
const generalPickers = [...dialog.querySelectorAll('hp-color-opacity')];
const lightOn = pickerByLabel(dialog, card._t('gs.light_on'));
const tempCold = pickerByLabel(dialog, card._t('gs.temp_cold'));
const globalBg = pickerByLabel(dialog, card._t('gs.bg_color'));
out.generalSettingsUsesTwelveUnifiedPickers = generalPickers.length === 12
&& generalPickers.filter((picker) => picker.showOpacity).length === 11
&& globalBg?.showOpacity === false
&& nativeColors() === 0;
const previousTemp = { ...card._settingsDialog.colors.temp_cold };
change(lightOn, { color: '#123456', opacity: 0.37 });
await card.updateComplete;
out.generalColorAndOpacityStayAtomic = card._settingsDialog.colors.light_on.c === '#123456'
&& card._settingsDialog.colors.light_on.a === 0.37
&& JSON.stringify(card._settingsDialog.colors.temp_cold) === JSON.stringify(previousTemp);
lightOn.renderRoot.querySelector('.trigger').click();
await lightOn.updateComplete;
await frame();
tempCold.renderRoot.querySelector('.trigger').click();
await tempCold.updateComplete;
await frame();
out.generalPickersAreExclusive = lightOn.renderRoot.querySelector('.trigger')
?.getAttribute('aria-expanded') === 'false'
&& tempCold.renderRoot.querySelector('.trigger')?.getAttribute('aria-expanded') === 'true';
tempCold.renderRoot.querySelector('.trigger').click();
await tempCold.updateComplete;
const originalGlobalBg = card._settingsDialog.bgColor;
globalBg.renderRoot.querySelector('.trigger').click();
await globalBg.updateComplete;
globalBg.renderRoot.querySelector('.trigger').click();
await globalBg.updateComplete;
out.globalBackgroundOpenCloseDoesNotMaterialize = originalGlobalBg === null
&& card._settingsDialog.bgColor === null;
change(globalBg, { color: '#0a2a4a', opacity: 0.12 });
await card.updateComplete;
dialog = root().querySelector('hp-dialog');
const defaultButton = [...dialog.querySelectorAll('button')]
.find((button) => button.textContent.trim() === card._t('gs.bg_default'));
const explicitGlobalBg = card._settingsDialog.bgColor;
defaultButton?.click();
await card.updateComplete;
out.globalBackgroundIgnoresAlphaAndResets = explicitGlobalBg === '#0a2a4a'
&& card._settingsDialog.bgColor === null;
card._settingsDialog = null;
card._setMode('devices');
card._openMarkerDialog();
card._markerDialog = { ...card._markerDialog, display: 'icon_ripple', rippleColor: '', rippleSize: 4.5 };
card.requestUpdate();
await card.updateComplete;
dialog = root().querySelector('hp-dialog');
const ripple = pickerByLabel(dialog, card._t('marker.activity_color'));
out.rippleUsesColorOnlyPicker = !!ripple && ripple.showOpacity === false
&& ripple.color === '#3ea6ff' && nativeColors() === 0;
const rippleSize = card._markerDialog.rippleSize;
change(ripple, { color: '#abcdef', opacity: 0.05 });
await card.updateComplete;
out.rippleChangeLeavesSizeAndAlphaModelAlone = card._markerDialog.rippleColor === '#abcdef'
&& card._markerDialog.rippleSize === rippleSize
&& !Object.hasOwn(card._markerDialog, 'rippleOpacity');
card._markerDialog = null;
card._setMode('view');
card._openSpaceDialog('edit', card._space);
card._spaceDialog = { ...card._spaceDialog, bgMode: 'static', bgColor: null };
card.requestUpdate();
await card.updateComplete;
dialog = root().querySelector('hp-dialog');
const roomColor = pickerByLabel(dialog, card._t('space.room_color'));
let spaceBg = pickerByLabel(dialog, card._t('space.bg_color'));
out.spaceUsesOpacityAndColorOnlyModes = roomColor?.showOpacity === true
&& spaceBg?.showOpacity === false && nativeColors() === 0;
change(roomColor, { color: '#fedcba', opacity: 0.63 });
await card.updateComplete;
out.roomColorAndOpacityStayAtomic = card._spaceDialog.roomColor === '#fedcba'
&& card._spaceDialog.roomOpacity === 0.63;
const serverSpaceBefore = JSON.stringify(card._serverCfg.spaces.find((space) => space.id === card._space));
spaceBg = pickerByLabel(root().querySelector('hp-dialog'), card._t('space.bg_color'));
change(spaceBg, { color: '#102030', opacity: 0.2 });
await card.updateComplete;
dialog = root().querySelector('hp-dialog');
const inheritButton = [...dialog.querySelectorAll('button')]
.find((button) => button.textContent.trim() === card._t('space.bg_inherit'));
const explicitSpaceBg = card._spaceDialog.bgColor;
inheritButton?.click();
await card.updateComplete;
card._spaceDialog = null;
await card.updateComplete;
out.spaceBackgroundIgnoresAlphaAndCancelPersistsNothing = explicitSpaceBg === '#102030'
&& JSON.stringify(card._serverCfg.spaces.find((space) => space.id === card._space)) === serverSpaceBefore;
out.noNativeColorInputInAnyMigratedDialog = nativeColors() === 0;
return out;
});
checkAll(result);
await finish(browser, result);
File diff suppressed because one or more lines are too long
+87 -88
View File
File diff suppressed because one or more lines are too long
+9
View File
@@ -1,5 +1,14 @@
# Changelog
## Unreleased
- Every remaining colour control now uses the same House Plan picker, including
the global light/temperature/LQI/Glow/wall palettes, plan and space
backgrounds, room colour and device activity ripple. Existing opacity appears
inside the same surface, while colour-only fields and Default/Inherited
behaviour keep their previous data model
([#180](https://github.com/Matysh/houseplan-card/issues/180)).
## v1.65.0-beta.3 — 2026-08-19
- Open-passage placement now previews the exact future wall cut before the
+9
View File
@@ -6,6 +6,15 @@
> **Правило проекта:** оба файла пополняются в одном коммите с самим
> изменением — как и остальная документация (см. docs/STATUS.md).
## Unreleased
- Все оставшиеся настройки цвета теперь используют одну палитру House Plan:
общие цвета света, температуры, LQI, Glow и стен, фон плана и пространства,
цвет комнат и пульсации активности устройства. Существующая прозрачность
находится в той же поверхности, а поля только с цветом и поведение
«По умолчанию / Наследуется» сохраняют прежнюю модель данных
([#180](https://github.com/Matysh/houseplan-card/issues/180)).
## v1.65.0-beta.3 — 2026-08-19
- Перед размещением открытого проёма теперь виден точный будущий разрыв стены:
+22
View File
@@ -95,6 +95,28 @@
the implementation loop [golden: `decor-color-popover-mobile-ru`,
`decor-color-popover-desktop-en`].
## Unified picker coverage for every color field (#180)
- [ ] A recursive source contract rejects every native `input[type=color]` in
product TypeScript and fixes the complete shared-component inventory at
13 template instances [unit: `color-picker.test.mjs`].
- [ ] The 11 general light/temperature/LQI/Glow/wall palettes and the space room
colour move their existing opacity into the unified picker; color and
alpha update one parent draft atomically [unit: `color-picker.test.mjs`,
auto: `smoke_color_picker_consumers.mjs`].
- [ ] Global background, space background and activity ripple are color-only;
opening/closing does not materialize an inherited/default value, and
Default/Inherit restore `null` without adding alpha
[auto: `smoke_color_picker_consumers.mjs`].
- [ ] General settings keep one exclusive picker open among 12 swatches; marker
ripple size remains independent, and cancelling the space dialog writes
no color draft [auto: `smoke_color_picker_consumers.mjs`].
- [ ] The three new dialog families are reviewed from the complete Linux
artifact before a beta; implementation does not accept their baselines
[golden: `general-color-popover-desktop-en`,
`device-ripple-color-popover-mobile-ru`,
`space-room-color-popover-desktop-ru`].
## Open passage (#157)
+7
View File
@@ -184,6 +184,13 @@ dialog. The Hue track shows the full colour spectrum at a glance, with a
contrasting ring keeping its slider visible. Changes remain a draft until the
owning properties dialog is saved.
This is the same control everywhere: decor and custom fills, the global
light/temperature/LQI/Glow/wall palettes, global and per-space backgrounds,
room colour, marker Glow and the device activity ripple. Settings that already
have opacity show it in the picker; colour-only settings do not acquire one.
**Default** and **Inherited** background actions remain beside the colour
sample and do not save the displayed fallback unless a colour is changed.
<!-- docs-section: input -->
## 6. Navigation, zoom and input
+8
View File
@@ -193,6 +193,14 @@ title: План дома
контур сохраняет видимость ползунка. Изменения остаются черновиком до сохранения
родительского диалога свойств.
Один и тот же элемент выбора используется везде: для декора и своих заливок, общих
палитр света, температуры, LQI, Glow и стен, общего и локального фона
пространства, цвета комнат, Glow маркера и пульсации активности устройства. Если
у настройки уже есть прозрачность, она показывается внутри палитры; поля только
с цветом новую прозрачность не получают. Действия **«По умолчанию»** и
**«Наследовать»** остаются рядом с образцом фона и не сохраняют показанное
значение по умолчанию, пока пользователь не изменит цвет.
<!-- docs-section: input -->
## 6. Навигация, масштаб и жесты
+37 -22
View File
@@ -14499,11 +14499,13 @@ class HouseplanCard extends LitElement {
const d = this._settingsDialog!;
const v = d.colors[key];
return html`<div class="colorrow gsrow">
<span class="gsl">${this._t(labelKey as any)}</span>
<input type="color" .value=${v.c}
@input=${(e: Event) => this._setFillColor(key, { c: (e.target as HTMLInputElement).value })} />
${this._rangeInput(0, 100, 1, Math.round(v.a * 100), (n) => this._setFillColor(key, { a: n / 100 }))}
<span class="opv">${Math.round(v.a * 100)}%</span>
<hp-color-opacity .label=${this._t(labelKey as any)}
.opacityLabel=${this._t('space.opacity')}
.pickerLabels=${this._colorPickerLabels}
.color=${v.c} .opacity=${v.a} .showOpacity=${true}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
this._setFillColor(key, { c: e.detail.color, a: e.detail.opacity });
}}></hp-color-opacity>
</div>`;
}
@@ -15081,10 +15083,13 @@ class HouseplanCard extends LitElement {
</div>
${this._settingsDialog!.bgMode === 'static'
? html`<div class="colorrow gsrow">
<span class="gsl">${this._t('gs.bg_color')}</span>
<input type="color" .value=${this._settingsDialog!.bgColor || this._stageBgHex()}
@input=${(e: Event) =>
(this._settingsDialog = { ...this._settingsDialog!, bgColor: (e.target as HTMLInputElement).value })} />
<hp-color-opacity .label=${this._t('gs.bg_color')}
.pickerLabels=${this._colorPickerLabels}
.color=${this._settingsDialog!.bgColor || this._stageBgHex()}
.opacity=${1} .showOpacity=${false}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string }>) => {
this._settingsDialog = { ...this._settingsDialog!, bgColor: e.detail.color };
}}></hp-color-opacity>
${this._settingsDialog!.bgColor
? html`<button class="btn ghost" @click=${() =>
(this._settingsDialog = { ...this._settingsDialog!, bgColor: null })}>${this._t('gs.bg_default')}</button>`
@@ -19328,9 +19333,12 @@ class HouseplanCard extends LitElement {
</div>`}
${d.display === 'icon_ripple'
? html`<div class="colorrow">
<span class="opl">${this._t('marker.activity_color')}</span>
<input type="color" .value=${d.rippleColor || '#3ea6ff'}
@input=${(e: Event) => (this._markerDialog = { ...d, rippleColor: (e.target as HTMLInputElement).value })} />
<hp-color-opacity .label=${this._t('marker.activity_color')}
.pickerLabels=${this._colorPickerLabels}
.color=${d.rippleColor || '#3ea6ff'} .opacity=${1} .showOpacity=${false}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string }>) => {
this._markerDialog = { ...d, rippleColor: e.detail.color };
}}></hp-color-opacity>
<span class="opl">${this._t('marker.ripple_size')}</span>
${this._rangeInput(2, 8, 0.5, d.rippleSize, (n) => (this._markerDialog = { ...d, rippleSize: n }))}
<span class="opv">×${d.rippleSize}</span>
@@ -19524,13 +19532,16 @@ class HouseplanCard extends LitElement {
<span class="opv">${Math.round(d.cardFontScale * 100)}%</span>
</div>
${this._renderCardPreview(d.cardFontScale, 1, 1)}
<label>${this._t('space.room_color')}</label>
<div class="colorrow">
<input type="color" .value=${d.roomColor}
@input=${(e: Event) => (this._spaceDialog = { ...d, roomColor: (e.target as HTMLInputElement).value })} />
<span class="opl">${this._t('space.opacity')}</span>
${this._rangeInput(0, 100, 1, Math.round(d.roomOpacity * 100), (n) => (this._spaceDialog = { ...d, roomOpacity: n / 100 }))}
<span class="opv">${Math.round(d.roomOpacity * 100)}%</span>
<hp-color-opacity .label=${this._t('space.room_color')}
.opacityLabel=${this._t('space.opacity')}
.pickerLabels=${this._colorPickerLabels}
.color=${d.roomColor} .opacity=${d.roomOpacity} .showOpacity=${true}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string; opacity: number }>) => {
this._spaceDialog = {
...d, roomColor: e.detail.color, roomOpacity: e.detail.opacity,
};
}}></hp-color-opacity>
</div>
<label>${this._t('space.bg_mode')}</label>
<select class="areasel"
@@ -19543,10 +19554,14 @@ class HouseplanCard extends LitElement {
<option value="daynight" ?selected=${d.bgMode === 'daynight'}>${this._t('gs.bg_daynight')}</option>
</select>
${(d.bgMode ?? bgModeOf(this._settings, {})) === 'static'
? html`<label>${this._t('space.bg_color')}</label>
<div class="colorrow">
<input type="color" .value=${d.bgColor || stageBgOf(this._settings, { bgColor: null }) || this._stageBgHex()}
@input=${(e: Event) => (this._spaceDialog = { ...d, bgColor: (e.target as HTMLInputElement).value })} />
? html`<div class="colorrow">
<hp-color-opacity .label=${this._t('space.bg_color')}
.pickerLabels=${this._colorPickerLabels}
.color=${d.bgColor || stageBgOf(this._settings, { bgColor: null }) || this._stageBgHex()}
.opacity=${1} .showOpacity=${false}
@hp-color-opacity-change=${(e: CustomEvent<{ color: string }>) => {
this._spaceDialog = { ...d, bgColor: e.detail.color };
}}></hp-color-opacity>
${d.bgColor
? html`<button class="btn ghost" @click=${() => (this._spaceDialog = { ...d, bgColor: null })}>
${this._t('space.bg_inherit')}</button>`
+3 -8
View File
@@ -2159,14 +2159,9 @@ export const cardStyles = css`
align-items: center;
gap: var(--sp-4);
}
.colorrow input[type='color'] {
width: 42px;
height: 28px;
border: 1px solid var(--hp-line);
border-radius: var(--rad-s);
background: transparent;
padding: 1px;
cursor: pointer;
.gsrow > hp-color-opacity {
min-width: min(100%, 210px);
justify-content: space-between;
}
.colorrow input[type='range'] { flex: 1; }
.colorrow .tempin { width: 70px; flex: none; }
+15 -4
View File
@@ -1,5 +1,5 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { readFileSync, readdirSync } from 'node:fs';
import test from 'node:test';
import {
hexToRgb, hsvToHex, hsvToRgb, normalizeHexColor, normalizeHue, rgbToHex, rgbToHsv,
@@ -40,13 +40,24 @@ test('HSV helpers wrap hue, clamp finite dimensions and keep grayscale achromati
test('the shared component keeps its API and contains no nested native color picker', () => {
const component = readFileSync(new URL('../src/hp-color-opacity.ts', import.meta.url), 'utf8');
const card = readFileSync(new URL('../src/houseplan-card.ts', import.meta.url), 'utf8');
assert.doesNotMatch(component, /type=["']color["']/);
const sourceFiles = (directory) => readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const target = new URL(entry.name + (entry.isDirectory() ? '/' : ''), directory);
return entry.isDirectory() ? sourceFiles(target) : entry.name.endsWith('.ts') ? [target] : [];
});
for (const source of sourceFiles(new URL('../src/', import.meta.url))) {
assert.doesNotMatch(readFileSync(source, 'utf8'), /type\s*=\s*["']color["']/,
`native color input remains in ${source.pathname}`);
}
for (const token of [
'public color', 'public opacity', 'public disabled', 'public showOpacity',
'hp-color-opacity-change', 'detail: { color: normalized, opacity: clamped }',
]) assert.match(component, new RegExp(token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
assert.equal((card.match(/<hp-color-opacity/g) || []).length, 8);
assert.equal((card.match(/\.pickerLabels=\$\{this\._colorPickerLabels\}/g) || []).length, 8);
assert.equal((card.match(/<hp-color-opacity/g) || []).length, 13);
assert.equal((card.match(/\.pickerLabels=\$\{this\._colorPickerLabels\}/g) || []).length, 13);
assert.equal((card.match(/\.showOpacity=\$\{false\}/g) || []).length, 4,
'Glow, ripple and both background pickers stay color-only');
assert.equal((card.match(/\.showOpacity=\$\{true\}/g) || []).length, 2,
'general fill rows and room color explicitly keep their existing opacity');
});
test('the hue range exposes one cyclic spectrum without restyling other ranges', () => {
+14 -1
View File
@@ -211,7 +211,7 @@ test('sun-ray golden requires browser-painted light from a state-only sun entity
assert.ok(scenario);
const fixture = prepareGoldenFixture(scenario);
const space = fixture.config.spaces.find((item) => item.id === scenario.space);
assert.equal(GOLDEN_MATRIX_VERSION, 28);
assert.equal(GOLDEN_MATRIX_VERSION, 29);
assert.equal(space.settings.sun_rays, true);
assert.equal(scenario.northDeg, 90,
'the sign-sensitive golden must keep a non-zero north direction');
@@ -239,6 +239,19 @@ test('the open color picker golden covers dark mobile and light desktop themes',
]);
});
test('all newly unified color-picker dialog families have reviewed-golden candidates', () => {
const scenarios = GOLDEN_SCENARIOS.filter((item) => [
'general-color', 'device-ripple-color', 'space-room-color',
].includes(item.dialog));
assert.deepEqual(scenarios.map(({ id, dialog, theme, viewport }) => ({
id, dialog, theme, width: viewport.width,
})), [
{ id: 'general-color-popover-desktop-en', dialog: 'general-color', theme: 'light', width: 900 },
{ id: 'device-ripple-color-popover-mobile-ru', dialog: 'device-ripple-color', theme: 'dark', width: 390 },
{ id: 'space-room-color-popover-desktop-ru', dialog: 'space-room-color', theme: 'dark', width: 900 },
]);
});
test('value badge golden covers four positions and bottom badge with separate LQI', () => {
const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'device-value-badge-positions-dark');
assert.ok(scenario);