mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
538 lines
26 KiB
JavaScript
538 lines
26 KiB
JavaScript
// #122/#160/#570 Stage 4: live-floor preservation, low overlays, openings and touch paths.
|
|
import { launch, checkAll, finish } from './serve.mjs';
|
|
|
|
const { page, browser } = await launch(
|
|
{ width: 430, height: 820 }, 1, [], { hasTouch: true, isMobile: true },
|
|
);
|
|
const out = await page.evaluate(async () => {
|
|
const original = window.__card;
|
|
const root = (card) => card.renderRoot;
|
|
const wait = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
const frame = () => new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done)));
|
|
const ensureIsoRuntime = async (card) => {
|
|
if (typeof card._ensureIsoSceneRuntime !== 'function'
|
|
|| !(await card._ensureIsoSceneRuntime())) {
|
|
throw new Error('isometric touch smoke runtime did not load');
|
|
}
|
|
await card.updateComplete;
|
|
await frame();
|
|
};
|
|
history.replaceState(null, '', '?hp_alpha=1#space=f1');
|
|
dispatchEvent(new HashChangeEvent('hashchange'));
|
|
await original.updateComplete;
|
|
await ensureIsoRuntime(original);
|
|
const configSpace = original._serverCfg.spaces.find((space) => space.id === 'f1');
|
|
configSpace.settings = {
|
|
...(configSpace.settings || {}), fill_mode: 'custom',
|
|
custom_fill: { c: '#31465f', a: 0.42 }, glow_enabled: true,
|
|
show_borders: true, show_names: true, north_deg: 0, sun_rays: true,
|
|
};
|
|
configSpace.rooms[0].settings = {
|
|
...(configSpace.rooms[0].settings || {}), fill_mode: 'none', glow: true,
|
|
};
|
|
configSpace.openings = [
|
|
{ id: 'iso-window', type: 'window', x: 0.28, y: 0.14, angle: 0, length: 0.12,
|
|
contact: 'binary_sensor.window' },
|
|
{ id: 'iso-door', type: 'door', x: 0.55, y: 0.36, angle: 90, length: 0.12,
|
|
contact: 'binary_sensor.window', lock: 'lock.front_door' },
|
|
{ id: 'iso-gate', type: 'gate', x: 0.72, y: 0.14, angle: 0, length: 0.16,
|
|
contact: 'binary_sensor.gate', lock: 'lock.front_door' },
|
|
];
|
|
configSpace.partitions = [{
|
|
id: 'iso-live-wall', a: [0.15, 0.12], b: [0.85, 0.12], cm: 15,
|
|
}];
|
|
configSpace.decor = [
|
|
{ id: 'iso-line', kind: 'line', x1: 0.12, y1: 0.7, x2: 0.45, y2: 0.7,
|
|
color: '#667788', opacity: 0.7, width_cm: 1 },
|
|
{ id: 'iso-sofa', kind: 'furniture', symbol: 'sofa', x: 0.62, y: 0.55,
|
|
w: 0.2, h: 0.12, color: '#667788', opacity: 0.7, width_cm: 1 },
|
|
];
|
|
original._serverCfg.markers = original._serverCfg.markers || [];
|
|
original._serverCfg.markers.push({
|
|
id: 'iso-vacuum', binding: 'device:d_mower', space: 'f1', area: 'living_room',
|
|
vacuum: {
|
|
source: 'camera.static_robot_map', trail_mode: 'always',
|
|
calibration: { m1: [0.5, 0, 0, 0, 0.5, 0] },
|
|
},
|
|
});
|
|
// Put one real interactive device almost on the shared wall so this smoke
|
|
// always owns a deterministic non-zero nudge witness.
|
|
original._layout.d_light1 = { ...original._layout.d_light1, s: 'f1', x: 0.548, y: 0.22 };
|
|
original._layout['iso-vacuum'] = { s: 'f1', x: 0.18, y: 0.78 };
|
|
original._hoverRoom = { space: 'f1', room: original._spaceModel('f1').rooms[0] };
|
|
original.hass = {
|
|
...original.hass,
|
|
states: {
|
|
...original.hass.states,
|
|
'light.floor_lamp': {
|
|
...original.hass.states['light.floor_lamp'], state: 'on',
|
|
},
|
|
'vacuum.mower': {
|
|
...original.hass.states['vacuum.mower'], state: 'cleaning',
|
|
},
|
|
'camera.static_robot_map': {
|
|
entity_id: 'camera.static_robot_map', state: 'idle',
|
|
attributes: {
|
|
map_name: 'm1', vacuum_position: { x: 650, y: 700, a: 0 },
|
|
path: [[200, 200], [420, 200], [420, 440], [650, 700]],
|
|
},
|
|
},
|
|
'sun.sun': {
|
|
entity_id: 'sun.sun', state: 'above_horizon',
|
|
attributes: { azimuth: 0, elevation: 24 },
|
|
},
|
|
},
|
|
};
|
|
original._cfgEpoch++;
|
|
original._regSignature = '';
|
|
original.requestUpdate();
|
|
await original.updateComplete;
|
|
await frame();
|
|
|
|
const count = (selector) => root(original).querySelectorAll(selector).length;
|
|
const layers = () => ({
|
|
rooms: count('[data-hp="room"]'),
|
|
roomFills: count('[data-hp="room"].filled'),
|
|
decor: count('.decorlayer [data-hp="decor"]'),
|
|
furniture: count('.decorlayer .dfurn'),
|
|
backdrop: count('.hp-backdrop'),
|
|
glowBase: count('.glow-base-layer .glow-base'),
|
|
glowSources: count('.glowlayer [data-glow-source]'),
|
|
sun: count('.sunlayer'),
|
|
openings: count('[data-hp="opening"]'),
|
|
verticalOpenings: count('[data-hp="iso-openings"] .iso-opening-panel'),
|
|
devices: count('[data-hp="device"]'),
|
|
hover: count('.room-hover-fill'),
|
|
vacuumPucks: count('.vacpuck'),
|
|
vacuumTrails: count('.vactrail .case'),
|
|
});
|
|
const flat = layers();
|
|
const flatSpillParts = [...root(original).querySelectorAll('.glow-pool')]
|
|
.map((node) => node.getAttribute('data-lit-parts'));
|
|
root(original).querySelector('[data-hp="projection-toggle"]')?.click();
|
|
await ensureIsoRuntime(original);
|
|
await original.updateComplete;
|
|
await frame();
|
|
|
|
const before = layers();
|
|
const beforeSpillParts = [...root(original).querySelectorAll('.glow-pool')]
|
|
.map((node) => node.getAttribute('data-lit-parts'));
|
|
const isoVacuumTrailD = root(original).querySelector('.vactrail path.case')?.getAttribute('d') || '';
|
|
const wallFingerprint = root(original).querySelector('[data-hp="iso-walls"]')?.dataset.fingerprint;
|
|
const cachedGeometry = original._isoGeometryCache.get(wallFingerprint)?.geometry;
|
|
const structuralBuildsBeforeLive = Number(root(original).querySelector('.stage')
|
|
?.getAttribute('data-hp-iso-structural-builds'));
|
|
const floorLayoutBeforeLive = JSON.stringify(original._layout);
|
|
const forbiddenWrites = [];
|
|
const storageWrites = [];
|
|
const nativeCallWs = original.hass.callWS.bind(original.hass);
|
|
const nativeStorageSet = Storage.prototype.setItem;
|
|
Storage.prototype.setItem = function instrumentedStorageSet(key, value) {
|
|
storageWrites.push({ key: String(key), value: String(value) });
|
|
return nativeStorageSet.call(this, key, value);
|
|
};
|
|
original.hass = {
|
|
...original.hass,
|
|
callWS: async (message) => {
|
|
if (/^houseplan\/(?:config|layout)\/(?:set|update|delete|save)$/.test(message?.type || ''))
|
|
forbiddenWrites.push(structuredClone(message));
|
|
return nativeCallWs(message);
|
|
},
|
|
};
|
|
await original.updateComplete;
|
|
const light = original.hass.states['light.ceiling'];
|
|
const openingPanelBefore = root(original)
|
|
.querySelector('[data-hp="iso-openings"] [data-id="iso-door"][data-surface="leaf-front"]')
|
|
?.getAttribute('d');
|
|
original.hass = {
|
|
...original.hass,
|
|
states: {
|
|
...original.hass.states,
|
|
'light.ceiling': {
|
|
...light, attributes: { ...light.attributes, rgb_color: [255, 64, 32] },
|
|
},
|
|
'binary_sensor.window': {
|
|
...original.hass.states['binary_sensor.window'], state: 'off',
|
|
},
|
|
},
|
|
};
|
|
await original.updateComplete;
|
|
const after = layers();
|
|
const afterSpillParts = [...root(original).querySelectorAll('.glow-pool')]
|
|
.map((node) => node.getAttribute('data-lit-parts'));
|
|
const sunBefore = original.hass.states['sun.sun'];
|
|
original.hass = {
|
|
...original.hass,
|
|
states: {
|
|
...original.hass.states,
|
|
'sun.sun': {
|
|
...sunBefore,
|
|
attributes: { ...sunBefore.attributes, azimuth: sunBefore.attributes.azimuth + 7 },
|
|
},
|
|
},
|
|
};
|
|
await original.updateComplete;
|
|
await frame();
|
|
const ordered = [
|
|
root(original).querySelector('.iso-underlay-svg'),
|
|
root(original).querySelector('.hp-backdrop'),
|
|
root(original).querySelector('[data-hp="room"]'),
|
|
root(original).querySelector('.glow-base-layer'),
|
|
root(original).querySelector('.decorlayer'),
|
|
root(original).querySelector('.glowlayer'),
|
|
root(original).querySelector('.sunlayer'),
|
|
root(original).querySelector('.iso-shadows-svg'),
|
|
root(original).querySelector('.iso-walls-svg'),
|
|
root(original).querySelector('.iso-overlays-svg'),
|
|
root(original).querySelector('.vacpuck'),
|
|
];
|
|
const overlaySvg = root(original).querySelector('.iso-overlays-svg');
|
|
const wallSvg = root(original).querySelector('.iso-walls-svg');
|
|
const doorResolved = original._openingsR.find((opening) => opening.id === 'iso-door');
|
|
const isoFloorMatrix = (root(original).querySelector('.iso-floor-scene')
|
|
?.getAttribute('transform')?.match(/-?[\d.e+]+/gi) || []).map(Number);
|
|
const wallHeight = 84 * (5 / original._cellCm);
|
|
const openingTopPoint = doorResolved && isoFloorMatrix.length === 6
|
|
? new DOMPoint(
|
|
isoFloorMatrix[0] * doorResolved.rx + isoFloorMatrix[2] * doorResolved.ry + isoFloorMatrix[4],
|
|
isoFloorMatrix[1] * doorResolved.rx + isoFloorMatrix[3] * doorResolved.ry + isoFloorMatrix[5]
|
|
- wallHeight * Math.sin(20 * Math.PI / 180),
|
|
) : null;
|
|
const wallTopPathsBeforeHide = [...root(original).querySelectorAll('.iso-wall-top')]
|
|
.map((path) => path.getAttribute('d'));
|
|
const openingCutIsEmpty = openingTopPoint
|
|
&& ![...root(original).querySelectorAll('.iso-wall-top')]
|
|
.some((path) => path.isPointInFill(openingTopPoint));
|
|
const result = {
|
|
isoOnTouch: root(original).querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '4'
|
|
&& !!root(original).querySelector('[data-hp="iso-walls"]'),
|
|
flatIsoLayerParity: JSON.stringify({ ...flat, openings: 0, verticalOpenings: before.verticalOpenings })
|
|
=== JSON.stringify(before)
|
|
&& JSON.stringify(flatSpillParts) === JSON.stringify(beforeSpillParts),
|
|
liveLayersPresent: before.rooms > 0 && before.roomFills > 0 && before.decor >= 2
|
|
&& before.furniture > 0 && before.backdrop > 0 && before.glowBase > 0
|
|
&& before.glowSources >= 2 && before.sun > 0 && before.openings === 0
|
|
&& before.verticalOpenings >= 5
|
|
&& before.devices > 0 && before.hover > 0 && before.vacuumPucks > 0
|
|
&& before.vacuumTrails >= 2,
|
|
vacuumTrailCurved: (isoVacuumTrailD.match(/Q /g) || []).length === 1,
|
|
liveLayersStable: JSON.stringify(after) === JSON.stringify(before),
|
|
spillBarrierStable: JSON.stringify(afterSpillParts) === JSON.stringify(beforeSpillParts)
|
|
&& beforeSpillParts.length >= 2 && beforeSpillParts.every((parts) => Number(parts) > 0),
|
|
floorToOverlayOrderPreserved: ordered.every(Boolean)
|
|
&& ordered.slice(1).every((node, index) => Boolean(
|
|
ordered[index].compareDocumentPosition(node) & Node.DOCUMENT_POSITION_FOLLOWING
|
|
)),
|
|
overlaysStayAboveWallsWithoutDebugGrounds: !!overlaySvg && !!wallSvg
|
|
&& Boolean(wallSvg.compareDocumentPosition(overlaySvg) & Node.DOCUMENT_POSITION_FOLLOWING)
|
|
&& !root(original).querySelector(
|
|
'.iso-overlay-ground, .iso-overlay-tether, #hp-iso-overlay-ground',
|
|
),
|
|
haColorUpdatePainted: [...root(original).querySelectorAll('radialGradient stop')]
|
|
.some((stop) => stop.getAttribute('stop-color') === '#ff4020'),
|
|
sameWallFingerprint: root(original).querySelector('[data-hp="iso-walls"]')?.dataset.fingerprint === wallFingerprint,
|
|
haUpdateReusesGeometry: original._isoGeometryCache.get(wallFingerprint)?.geometry === cachedGeometry,
|
|
sunUpdateKeepsStructuralFingerprint:
|
|
root(original).querySelector('[data-hp="iso-walls"]')?.dataset.fingerprint === wallFingerprint
|
|
&& Number(root(original).querySelector('.stage')
|
|
?.getAttribute('data-hp-iso-structural-builds')) === structuralBuildsBeforeLive,
|
|
contactUpdateMovesOnlyLivePanel: !!openingPanelBefore
|
|
&& root(original).querySelector(
|
|
'[data-hp="iso-openings"] [data-id="iso-door"][data-surface="leaf-front"]',
|
|
)?.getAttribute('d')
|
|
!== openingPanelBefore
|
|
&& original._isoGeometryCache.size === 1,
|
|
oneLightModel: !root(original).querySelector('.window-light,.iso-window-light,.iso-glow,.iso-sun'),
|
|
oldFloorSymbolsNotDuplicated: before.openings === 0,
|
|
visibleOpeningHasMasonryCut: Boolean(openingCutIsEmpty)
|
|
&& wallTopPathsBeforeHide.length > 0,
|
|
};
|
|
|
|
configSpace.settings.hide_openings = true;
|
|
original._cfgEpoch++;
|
|
original.requestUpdate();
|
|
await original.updateComplete;
|
|
result.hideOpeningsKeepsStructure = !!root(original).querySelector('[data-hp="iso-walls"]')
|
|
&& !root(original).querySelector('[data-hp="iso-openings"]')
|
|
&& !root(original).querySelector('[data-hp="opening"]')
|
|
&& !root(original).querySelector('.iso-contact-shadow, .iso-leaf-shadow')
|
|
&& !!root(original).querySelector('.oplock[data-hp-iso-raised="true"]')
|
|
&& root(original).querySelectorAll('.glowlayer [data-glow-source]').length >= 2
|
|
&& JSON.stringify([...root(original).querySelectorAll('.iso-wall-top')]
|
|
.map((path) => path.getAttribute('d'))) === JSON.stringify(wallTopPathsBeforeHide)
|
|
&& openingTopPoint
|
|
&& ![...root(original).querySelectorAll('.iso-wall-top')]
|
|
.some((path) => path.isPointInFill(openingTopPoint));
|
|
|
|
configSpace.settings.hide_openings = false;
|
|
const runtimeBeforeNoBorders = original._isoSceneRuntime;
|
|
let hiddenTopologyRequests = 0;
|
|
let hiddenTopologyBuilds = 0;
|
|
original._isoSceneRuntime = {
|
|
...runtimeBeforeNoBorders,
|
|
createIsoStructuralSource(input) {
|
|
hiddenTopologyRequests++;
|
|
const source = runtimeBeforeNoBorders.createIsoStructuralSource(input);
|
|
return {
|
|
...source,
|
|
key: `${source.key}|hidden-topology-regression`,
|
|
build() {
|
|
hiddenTopologyBuilds++;
|
|
throw new Error('hidden walls must not build topology');
|
|
},
|
|
};
|
|
},
|
|
};
|
|
configSpace.settings.show_borders = false;
|
|
original._cfgEpoch++;
|
|
original.requestUpdate();
|
|
await original.updateComplete;
|
|
const noBordersFloor = root(original).querySelector('.plan-svg .iso-floor-scene');
|
|
const floorMatrix = (noBordersFloor?.getAttribute('transform')?.match(/-?[\d.e+]+/gi) || [])
|
|
.map(Number);
|
|
const noBordersDevice = root(original).querySelector('[data-hp="device"]');
|
|
const noBordersRoom = root(original).querySelector('[data-hp="room-label"]');
|
|
const noBordersLock = root(original).querySelector('.oplock');
|
|
result.noBordersUsesTrueAffineFloor = original._effectiveProjection() === 'iso'
|
|
&& root(original).querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '4'
|
|
&& root(original).querySelector('.plan-svg')?.getAttribute('data-hp-live-viewbox') === 'camera'
|
|
&& floorMatrix.length === 6 && floorMatrix.every(Number.isFinite)
|
|
&& Math.abs(floorMatrix[0] - 1) < 1e-6
|
|
&& Math.abs(floorMatrix[1]) < 1e-6 && Math.abs(floorMatrix[2]) < 1e-6
|
|
&& Math.abs(floorMatrix[3] - Math.cos(20 * Math.PI / 180)) < 1e-6
|
|
&& root(original).querySelectorAll('[data-hp="opening"]').length === 3
|
|
&& [noBordersDevice, noBordersRoom, noBordersLock].every((node) => node
|
|
&& !node.hasAttribute('data-hp-iso-raised'));
|
|
result.noBordersHasNoRaisedOrVolumeLayers = !root(original).querySelector('[data-hp="iso-walls"]')
|
|
&& !root(original).querySelector('[data-hp="iso-underlay"]')
|
|
&& !root(original).querySelector('[data-hp="iso-shadows"]')
|
|
&& !root(original).querySelector('[data-hp="iso-raised-overlays"]')
|
|
&& !root(original).querySelector('[data-hp="iso-overlay-grounds"]')
|
|
&& !root(original).querySelector('.iso-overlays-svg')
|
|
&& !root(original).querySelector('[data-hp-iso-raised="true"]');
|
|
result.noBordersSkipsHiddenTopology = hiddenTopologyRequests === 0
|
|
&& hiddenTopologyBuilds === 0 && original._effectiveProjection() === 'iso';
|
|
|
|
configSpace.settings.hide_openings = true;
|
|
original._cfgEpoch++;
|
|
original.requestUpdate();
|
|
await original.updateComplete;
|
|
result.noBordersHideOpeningsKeepsOnlyFloorSemantics = original._effectiveProjection() === 'iso'
|
|
&& root(original).querySelectorAll('[data-hp="opening"]').length === 0
|
|
&& !!root(original).querySelector('.oplock:not([data-hp-iso-raised])')
|
|
&& !root(original).querySelector('[data-hp="iso-walls"]')
|
|
&& !root(original).querySelector('[data-hp="iso-raised-overlays"]')
|
|
&& hiddenTopologyRequests === 0 && hiddenTopologyBuilds === 0;
|
|
|
|
original._isoSceneRuntime = runtimeBeforeNoBorders;
|
|
configSpace.settings.hide_openings = false;
|
|
configSpace.settings.show_borders = true;
|
|
original._cfgEpoch++;
|
|
original.requestUpdate();
|
|
await original.updateComplete;
|
|
result.visibleBordersRestoreStage4 = !!root(original).querySelector('[data-hp="iso-openings"]')
|
|
&& !!root(original).querySelector('[data-hp-iso-raised="true"]')
|
|
&& root(original).querySelector('.stage')?.getAttribute('data-hp-iso-stage') === '4'
|
|
&& original._isoGeometryCache.size === 1;
|
|
|
|
original._hoverRoom = null;
|
|
original.requestUpdate();
|
|
await original.updateComplete;
|
|
await frame();
|
|
original._hoverRoom = { space: 'f1', room: original._spaceModel('f1').rooms[0] };
|
|
original.requestUpdate();
|
|
await original.updateComplete;
|
|
await frame();
|
|
result.haAndHoverKeepStructuralCache = Number(root(original).querySelector('.stage')
|
|
?.getAttribute('data-hp-iso-structural-builds')) === structuralBuildsBeforeLive
|
|
&& root(original).querySelector('[data-hp="iso-walls"]')?.dataset.fingerprint === wallFingerprint
|
|
&& original._isoGeometryCache.get(wallFingerprint)?.geometry === cachedGeometry
|
|
&& original._isoGeometryCache.size === 1;
|
|
result.runtimeNudgeNeverPersists = JSON.stringify(original._layout) === floorLayoutBeforeLive
|
|
&& forbiddenWrites.length === 0 && storageWrites.length === 0
|
|
&& !!root(original).querySelector('[data-hp-iso-nudged="true"]')
|
|
&& !root(original).querySelector('.iso-overlay-tether, .iso-overlay-ground');
|
|
Storage.prototype.setItem = nativeStorageSet;
|
|
|
|
const fallbackCalls = [];
|
|
original.hass = {
|
|
...original.hass,
|
|
callService: async (domain, service, data) => fallbackCalls.push({ domain, service, data }),
|
|
};
|
|
await original.updateComplete;
|
|
const supports = CSS.supports;
|
|
CSS.supports = () => false;
|
|
original.requestUpdate();
|
|
await original.updateComplete;
|
|
const fallbackNudged = root(original).querySelector('[data-hp-iso-nudged="true"][data-id]');
|
|
const fallbackId = fallbackNudged?.getAttribute('data-id');
|
|
root(original).querySelector('[data-entity="light.ceiling"]')?.click();
|
|
await frame();
|
|
result.unsupportedDecorationKeepsIsoStructure = !!root(original).querySelector('[data-hp="iso-walls"]')
|
|
&& !!root(original).querySelector('[data-hp="iso-openings"]');
|
|
result.unsupportedDecorationDropsUnsupportedEffects = !root(original).querySelector('[data-hp="iso-shadows"]')
|
|
&& root(original).querySelectorAll('[data-hp-iso-material-def]').length === 0;
|
|
result.unsupportedDecorationKeepsFootprintInvisible = !root(original).querySelector(
|
|
'.iso-overlay-plate, .iso-overlay-plate-texture, #hp-iso-overlay-texture',
|
|
);
|
|
result.unsupportedDecorationAddsNoDebugCue = !!fallbackId
|
|
&& !root(original).querySelector('.iso-overlay-tether, .iso-overlay-ground');
|
|
result.unsupportedDecorationKeepsActions = fallbackCalls.length === 1;
|
|
CSS.supports = supports;
|
|
original.requestUpdate();
|
|
await original.updateComplete;
|
|
|
|
const calls = [];
|
|
original.hass = {
|
|
...original.hass,
|
|
callService: async (domain, service, data) => calls.push({ domain, service, data }),
|
|
};
|
|
await original.updateComplete;
|
|
root(original).querySelector('[data-entity="light.ceiling"]')?.click();
|
|
await frame();
|
|
original._setProjection('flat');
|
|
await original.updateComplete;
|
|
root(original).querySelector('[data-entity="light.ceiling"]')?.click();
|
|
await frame();
|
|
result.flatIsoActionParity = calls.length === 2
|
|
&& JSON.stringify(calls[0]) === JSON.stringify(calls[1]);
|
|
original._setProjection('iso');
|
|
await original.updateComplete;
|
|
|
|
const stage = root(original).querySelector('.stage');
|
|
const stageRect = stage.getBoundingClientRect();
|
|
const zoomBefore = original._zoom;
|
|
const pointer = (type, id, x, y, buttons) => new PointerEvent(type, {
|
|
bubbles: true, composed: true, pointerId: id, pointerType: 'touch',
|
|
clientX: x, clientY: y, button: type === 'pointerdown' ? 0 : -1, buttons,
|
|
});
|
|
const cy = stageRect.top + stageRect.height / 2;
|
|
stage.dispatchEvent(pointer('pointerdown', 8902, stageRect.left + 150, cy, 1));
|
|
stage.dispatchEvent(pointer('pointerdown', 8903, stageRect.left + 270, cy, 1));
|
|
stage.dispatchEvent(pointer('pointermove', 8902, stageRect.left + 115, cy, 1));
|
|
stage.dispatchEvent(pointer('pointermove', 8903, stageRect.left + 305, cy, 1));
|
|
stage.dispatchEvent(pointer('pointerup', 8902, stageRect.left + 115, cy, 0));
|
|
stage.dispatchEvent(pointer('pointerup', 8903, stageRect.left + 305, cy, 0));
|
|
await original.updateComplete;
|
|
result.touchPinchKeepsIso = original._zoom > zoomBefore
|
|
&& !!root(original).querySelector('[data-hp="iso-walls"]');
|
|
|
|
const opening = root(original).querySelector('.iso-opening-panel[data-id="iso-door"]');
|
|
const openingOwner = opening?.closest('[data-hp="iso-openings"]');
|
|
opening?.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
|
|
result.touchOpeningIsSafe = !!opening
|
|
&& openingOwner?.getAttribute('pointer-events') === 'none'
|
|
&& openingOwner?.getAttribute('aria-hidden') === 'true'
|
|
&& !original._openingDialog && !original._openingInfo
|
|
&& original._mode === 'view' && calls.length === 2;
|
|
|
|
const device = root(original).querySelector('[data-hp="device"]');
|
|
if (device) {
|
|
const rect = device.getBoundingClientRect();
|
|
device.dispatchEvent(new PointerEvent('pointerdown', {
|
|
bubbles: true, composed: true, pointerId: 8901, pointerType: 'touch',
|
|
clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2,
|
|
button: 0, buttons: 1,
|
|
}));
|
|
await wait(700);
|
|
result.touchLongPressHitsDevice = !!original._infoCard;
|
|
device.dispatchEvent(new PointerEvent('pointerup', {
|
|
bubbles: true, composed: true, pointerId: 8901, pointerType: 'touch',
|
|
clientX: rect.left + rect.width / 2, clientY: rect.top + rect.height / 2,
|
|
button: 0, buttons: 0,
|
|
}));
|
|
original._closeInfoCard();
|
|
} else result.touchLongPressHitsDevice = false;
|
|
|
|
original._pickSpace('garden');
|
|
await wait(230);
|
|
original._setProjection('iso');
|
|
await original.updateComplete;
|
|
const gardenIso = original._space === 'garden'
|
|
&& root(original).querySelector('[data-hp="projection-toggle"]')?.getAttribute('aria-pressed') === 'true';
|
|
original._pickSpace('f1');
|
|
await wait(230);
|
|
result.touchSpaceSwitchKeepsPerSpaceIso = gardenIso && original._space === 'f1'
|
|
&& !!root(original).querySelector('[data-hp="iso-walls"]');
|
|
|
|
const ownVisibilityState = Object.getOwnPropertyDescriptor(document, 'visibilityState');
|
|
Object.defineProperty(document, 'visibilityState', { configurable: true, value: 'hidden' });
|
|
document.dispatchEvent(new Event('visibilitychange'));
|
|
Object.defineProperty(document, 'visibilityState', { configurable: true, value: 'visible' });
|
|
document.dispatchEvent(new Event('visibilitychange'));
|
|
window.dispatchEvent(new PageTransitionEvent('pageshow', { persisted: true }));
|
|
await original.updateComplete;
|
|
await frame();
|
|
const recoveryStarted = performance.now();
|
|
while (root(original).querySelector('.recoveryoverlay')
|
|
&& performance.now() - recoveryStarted < 2000) await wait(20);
|
|
result.backgroundForegroundKeepsIso = !!root(original).querySelector('[data-hp="iso-walls"]')
|
|
&& !root(original).querySelector('.recoveryoverlay');
|
|
if (ownVisibilityState) Object.defineProperty(document, 'visibilityState', ownVisibilityState);
|
|
else delete document.visibilityState;
|
|
|
|
const host = document.getElementById('host');
|
|
const mount = async (kiosk = false) => {
|
|
const card = document.createElement('houseplan-card');
|
|
card.setConfig({ type: 'custom:houseplan-card', kiosk });
|
|
host.replaceChildren(card);
|
|
card.hass = window.__mkHass();
|
|
const started = performance.now();
|
|
while ((!card._loadOk || card._booting) && performance.now() - started < 9000) await wait(30);
|
|
await ensureIsoRuntime(card);
|
|
await card.updateComplete;
|
|
await frame();
|
|
return card;
|
|
};
|
|
original.remove();
|
|
const warm = await mount(false);
|
|
result.warmRemountIso = !warm._booting
|
|
&& !!root(warm).querySelector('[data-hp="iso-walls"]')
|
|
&& root(warm).querySelector('[data-hp="projection-toggle"]')?.getAttribute('aria-pressed') === 'true';
|
|
warm.remove();
|
|
const kiosk = await mount(true);
|
|
result.kioskReadsPreference = !!root(kiosk).querySelector('[data-hp="iso-walls"]');
|
|
result.kioskHasNoToggle = !root(kiosk).querySelector('[data-hp="projection-toggle"]');
|
|
const kioskStage = root(kiosk).querySelector('.stage');
|
|
const kioskRect = kioskStage?.getBoundingClientRect();
|
|
const kioskViewBefore = JSON.stringify(kiosk._view);
|
|
if (kioskStage && kioskRect) {
|
|
const x = kioskRect.left + kioskRect.width / 2;
|
|
const y = kioskRect.top + kioskRect.height / 2;
|
|
kioskStage.dispatchEvent(pointer('pointerdown', 8910, x, y, 1));
|
|
kioskStage.dispatchEvent(pointer('pointermove', 8910, x + 48, y + 24, 1));
|
|
kioskStage.dispatchEvent(pointer('pointerup', 8910, x + 48, y + 24, 0));
|
|
await kiosk.updateComplete;
|
|
await frame();
|
|
}
|
|
result.kioskTouchPanKeepsIso = JSON.stringify(kiosk._view) !== kioskViewBefore
|
|
&& !!root(kiosk).querySelector('[data-hp="iso-walls"]')
|
|
&& !root(kiosk).querySelector('[data-hp="projection-toggle"]');
|
|
return result;
|
|
});
|
|
|
|
await page.setViewportSize({ width: 820, height: 430 });
|
|
out.orientationResizeKeepsIso = await page.evaluate(async () => {
|
|
const card = document.querySelector('houseplan-card');
|
|
window.dispatchEvent(new Event('resize'));
|
|
await card.updateComplete;
|
|
await new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done)));
|
|
const anchor = card.renderRoot.querySelector('[data-hp="device"]')?.getBoundingClientRect();
|
|
return !!card.renderRoot.querySelector('[data-hp="iso-walls"]')
|
|
&& !!anchor && [anchor.left, anchor.top, anchor.width, anchor.height].every(Number.isFinite);
|
|
});
|
|
out.kioskEmergencyOffIsFlat = await page.evaluate(async () => {
|
|
const card = document.querySelector('houseplan-card');
|
|
history.replaceState(null, '', '?hp_alpha=0#space=f1');
|
|
dispatchEvent(new HashChangeEvent('hashchange'));
|
|
await card.updateComplete;
|
|
await new Promise((done) => requestAnimationFrame(() => requestAnimationFrame(done)));
|
|
return window.__hpAlpha === false
|
|
&& localStorage.getItem('houseplan_card_alpha_v1') === '0'
|
|
&& !card.renderRoot.querySelector('[data-hp="iso-walls"]')
|
|
&& !card.renderRoot.querySelector('[data-hp="projection-toggle"]');
|
|
});
|
|
|
|
checkAll(out);
|
|
await finish(browser, out);
|