Files
houseplan-card/demo/smoke_iso_first_frame.mjs
T
2026-09-26 09:05:20 +03:00

89 lines
4.5 KiB
JavaScript

// #654: the first visible successful frame is already 2.5D with the settled
// floor classification. The same boundary covers ordinary View and kiosk;
// a failed lazy chunk must release to the existing safe Flat fallback.
import { assertFreshDemoBundleUnlessAllowed } from './bundle-freshness.mjs';
import { checkAll, finish, launchPanelCold } from './serve.mjs';
async function observe({ volumetric = true, kiosk = false, delay = 0, fail = false }) {
const { page, browser } = await launchPanelCold({ width: 900, height: 760 });
await page.route('**/iso-scene-render-*.js*', async (route) => {
if (delay) await new Promise((resolve) => setTimeout(resolve, delay));
if (fail) await route.fulfill({ status: 503, body: 'injected iso runtime failure' });
else await route.fallback();
});
await page.evaluate(async ({ wantsIso, isKiosk }) => {
document.documentElement.style.setProperty('--ha-card-background', '#fff');
const hass = window.__mkHass();
const configResult = await hass.callWS({ type: 'houseplan/config/get' });
const layoutResult = await hass.callWS({ type: 'houseplan/layout/get' });
const config = structuredClone(configResult.config);
config.settings = { ...(config.settings || {}), ...(wantsIso ? { volumetric_view: true } : {}) };
const rev = window.__pushServerConfig(config);
localStorage.setItem('houseplan_card_cfg_v1', JSON.stringify({
config, rev, layout: layoutResult.layout, layout_rev: layoutResult.rev,
}));
await import('/assets/houseplan-card.js');
const card = document.createElement('houseplan-card');
card.setConfig({ type: 'custom:houseplan-card', title: 'House Plan', icon_size: 3.4,
...(isKiosk ? { kiosk: true } : {}) });
document.getElementById('host').appendChild(card);
card.hass = hass;
window.__card = card;
}, { wantsIso: volumetric, isKiosk: kiosk });
await page.waitForFunction(() => window.__card?._model?.length > 0, { timeout: 9000 });
await assertFreshDemoBundleUnlessAllowed(page);
const result = await page.evaluate(async (expectFailure) => {
const card = window.__card;
const records = [];
const read = () => {
const stage = card.renderRoot.querySelector('.stage');
const wrap = stage?.querySelector(':scope > .zoomwrap');
const visible = !!wrap && getComputedStyle(wrap).visibility !== 'hidden';
const iso = !!stage?.classList.contains('projection-iso');
const light = !!stage?.querySelector('.dev.iso-floor-light');
const shadow = stage?.querySelector('.iso-tile-shadow');
records.push({ visible, iso, light, readiness: stage?.dataset.hpIsoReadiness || '',
veil: !!stage?.querySelector('.bootveil:not(.off)'),
shadowOpacity: shadow ? getComputedStyle(shadow).opacity : null });
return records.at(-1);
};
const deadline = performance.now() + 7000;
while (performance.now() < deadline) {
const now = read();
if (now.visible && (expectFailure ? !now.iso : now.iso && now.light)) break;
await new Promise((done) => requestAnimationFrame(done));
}
const visible = records.filter((row) => row.visible);
return { records, firstVisible: visible[0] || null };
}, fail);
await browser.close();
return result;
}
const ordinary = await observe({ delay: 1600 });
const kiosk = await observe({ kiosk: true, delay: 350 });
const failed = await observe({ kiosk: true, delay: 50, fail: true });
const flat = await observe({ volumetric: false, kiosk: true });
const out = {
ordinaryNeverShowsIntermediateFlat: ordinary.records
.filter((row) => row.visible).every((row) => row.iso),
ordinaryFirstVisibleIsSettledIso: ordinary.firstVisible?.iso === true
&& ordinary.firstVisible.light === true && ordinary.firstVisible.readiness === 'ready'
&& ordinary.firstVisible.shadowOpacity === '1',
ordinaryPendingOutlivesBootCap: ordinary.records.some((row) =>
!row.visible && row.readiness === 'pending' && row.veil),
kioskNeverShowsIntermediateFlat: kiosk.records
.filter((row) => row.visible).every((row) => row.iso),
kioskFirstVisibleIsSettledIso: kiosk.firstVisible?.iso === true
&& kiosk.firstVisible.light === true && kiosk.firstVisible.readiness === 'ready'
&& kiosk.firstVisible.shadowOpacity === '1',
failedChunkReleasesFiniteFlatFallback: failed.firstVisible?.iso === false
&& failed.firstVisible.readiness === 'fallback' && failed.firstVisible.veil === false,
flatKioskHasNoIsoWait: flat.firstVisible?.iso === false
&& flat.firstVisible.readiness === '' && flat.firstVisible.veil === false,
};
checkAll(out);
await finish(null, out);