Files
houseplan-card/demo/smoke_ha_controls.mjs
T
Matysh c4a80bcb9f DIALOGS: native ha-switch / ha-slider with a hard fallback to plain inputs
Boolean .srcrow checkboxes (14 rows: sun rays, vacuum live position,
opening invert/flipH/flipV, marker show-entities/tap-confirm/climate-temp/
is-light/hide-from-plan, space borders/names/lqi + the 4 room-card label
flags) and every dialog range slider (9: fill opacities, kiosk icon/font,
ripple size, marker size/angle, card font, room opacity, room name/label
scale) render through _boolInput/_rangeInput. Each helper picks the native
HA element via customElements.get(...) at render time - the ha-* API is
undocumented and drifts between HA releases, so the presence check is the
only coupling; without the element (old HA, smoke env) the EXISTING input
renders unchanged, and both branches feed one handler (change/.checked,
input+change/.value). Radios, selects, the decorbar fill flag and the
import-dialog floor rows stay native on purpose. New smoke_ha_controls
covers both branches with ha-* stubs (two-way value flow); the other 83
smokes keep exercising the fallback, which stays pixel-identical.
2026-08-03 11:09:00 +03:00

87 lines
4.2 KiB
JavaScript

// Native HA controls in dialogs (ha-switch / ha-slider) + the fallback.
// The demo env has no HA frontend, so every OTHER smoke already runs the
// fallback branch (plain input[type=checkbox|range]). Here we check both:
// 1) fallback: without ha-* the dialogs render the classic inputs;
// 2) ha branch: after registering lightweight ha-switch/ha-slider stubs
// (checked/value + `change`/`input`, the real contract of the HA ones)
// a re-opened dialog renders them, values flow BOTH ways.
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch();
const res = await page.evaluate(async () => {
const out = {};
const c = window.__card;
const sr = () => c.shadowRoot || c.renderRoot;
// --- 1) fallback branch: no ha-* registered -> classic inputs ----------
c._openMarkerDialog(c._devices[0]); await c.updateComplete;
out.fallbackCheckbox = !!sr().querySelector('.dialog .srcrow input[type=checkbox]');
out.fallbackRange = !!sr().querySelector('.dialog input[type=range]');
out.fallbackNoHaSwitch = !sr().querySelector('.dialog ha-switch');
c._markerDialog = null; await c.updateComplete;
// --- 2) register stubs mimicking the HA contract -----------------------
customElements.define('ha-switch', class extends HTMLElement {
#on = false;
get checked() { return this.#on; }
set checked(v) { this.#on = !!v; }
get disabled() { return this.hasAttribute('disabled'); }
set disabled(v) { v ? this.setAttribute('disabled', '') : this.removeAttribute('disabled'); }
});
customElements.define('ha-slider', class extends HTMLElement {
#v = 0;
get value() { return this.#v; }
set value(v) { this.#v = Number(v); }
set min(_) {} set max(_) {} set step(_) {}
});
c._openMarkerDialog(c._devices[0]); await c.updateComplete;
const switches = [...sr().querySelectorAll('.dialog ha-switch')];
out.haSwitchRendered = switches.length >= 2; // is_light + hide-from-plan at minimum
out.haNoPlainCheckbox = !sr().querySelector('.dialog .srcrow input[type=checkbox]');
const sliders = [...sr().querySelectorAll('.dialog ha-slider')];
out.haSliderRendered = sliders.length >= 2; // size + angle
// --- 3) card -> control: state is pushed into the element --------------
c._markerDialog = { ...c._markerDialog, isLight: true, hideFromPlan: false };
await c.updateComplete;
out.downstreamChecked = [...sr().querySelectorAll('.dialog ha-switch')].some((el) => el.checked === true);
// --- 4) control -> card: change with .checked lands in the dialog ------
const before = c._markerDialog.hideFromPlan;
const sw = [...sr().querySelectorAll('.dialog ha-switch')].find((el) => el.checked === before);
sw.checked = !before;
sw.dispatchEvent(new Event('change', { bubbles: true }));
await c.updateComplete;
out.upstreamChecked = c._markerDialog.hideFromPlan === !before || c._markerDialog.isLight === !before
|| c._markerDialog.useClimateTemp === !before || c._markerDialog.tapConfirm === !before;
// --- 5) slider both ways ----------------------------------------------
const sizeBefore = c._markerDialog.size;
const sl = [...sr().querySelectorAll('.dialog ha-slider')].find((el) => Number(el.value) === Number(sizeBefore));
out.downstreamValue = !!sl;
sl.value = 2;
sl.dispatchEvent(new Event('input', { bubbles: true }));
await c.updateComplete;
out.upstreamValue = c._markerDialog.size === 2;
// `change` (fired on release, HA-version dependent) reaches the same handler
sl.value = 2.5;
sl.dispatchEvent(new Event('change', { bubbles: true }));
await c.updateComplete;
out.upstreamValueChangeEvt = c._markerDialog.size === 2.5;
c._markerDialog = null; await c.updateComplete;
// --- 6) general settings: opacity sliders take the ha branch too -------
c._openSettingsDialog(); await c.updateComplete;
out.gsHaSliders = sr().querySelectorAll('.dialog ha-slider').length >= 9;
const gsl = sr().querySelector('.dialog ha-slider');
gsl.value = 55;
gsl.dispatchEvent(new Event('input', { bubbles: true }));
await c.updateComplete;
out.gsUpstream = Math.round((Object.values(c._settingsDialog.colors)[0].a) * 100) === 55;
c._settingsDialog = null; await c.updateComplete;
return out;
});
checkAll(res, {});
await finish(browser, res);