mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
399 lines
21 KiB
JavaScript
399 lines
21 KiB
JavaScript
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 hp = window.__hpTest;
|
||
const sr = () => c.shadowRoot || c.renderRoot;
|
||
const tabs = () => [...sr().querySelectorAll('.modetab')];
|
||
const headerCross = () => sr().querySelector('.editor-close-slot .closex');
|
||
const settleMode = async () => {
|
||
const started = performance.now();
|
||
do { await new Promise((resolve) => requestAnimationFrame(resolve)); }
|
||
while (c._modeTransitionBusy && performance.now() - started < 1500);
|
||
await c.updateComplete;
|
||
};
|
||
// 1) две вкладки, Просмотра нет, крестиков в неактивных нет
|
||
out.twoTabs = tabs().length === 3; // третья — Редактор подложки (v1.33.0)
|
||
out.labels = tabs().map((t) => t.textContent.trim());
|
||
// #660: X does not live inside a mode button; its slot follows the active
|
||
// button and moves to the group end (empty but same size) in View.
|
||
out.noCrossIdle = sr().querySelectorAll('.modetab .closex').length === 0
|
||
&& !sr().querySelector('.editor-close-slot .closex');
|
||
out.startView = c._mode === 'view';
|
||
// 2) клик по «Редактор плана» → активна, панель с крестиком
|
||
tabs()[0].click(); await c.updateComplete;
|
||
out.planActive = c._mode === 'plan' && tabs()[0].classList.contains('active');
|
||
out.planBar = !!sr().querySelector('.editbar');
|
||
out.planBarClose = !!sr().querySelector('.editbar .barclose');
|
||
const barClose = sr().querySelector('.editbar .barclose');
|
||
const barCloseIcon = barClose?.querySelector('ha-icon');
|
||
const barCloseRect = barClose?.getBoundingClientRect();
|
||
const barCloseIconRect = barCloseIcon?.getBoundingClientRect();
|
||
const enteringChrome = sr().querySelector('.editorchrome');
|
||
// Regression #95 follow-up: the bar is already painted during the 220 ms
|
||
// mode transition, so its explicit Close command must not be inert together
|
||
// with the editing tools.
|
||
out.closeRemainsInteractiveDuringEnter = c._modeTransitionBusy
|
||
&& enteringChrome?.inert === false
|
||
&& sr().querySelector('.editbar-tools')?.inert === true
|
||
&& getComputedStyle(barClose).pointerEvents === 'auto';
|
||
const enteringStage = sr().querySelector('.stage');
|
||
const editorSceneLayer = enteringStage?.querySelector('.hp-editor-only-layer');
|
||
const enteringVisual = c._modeTransitionVisual;
|
||
out.sceneLayersUseTransitionWeights = c._modeTransitionBusy && !!enteringVisual
|
||
&& !!editorSceneLayer
|
||
&& Math.abs(Number(editorSceneLayer.getAttribute('opacity')) - enteringVisual.editorWeight) < 0.001
|
||
&& Math.abs(Number(getComputedStyle(enteringStage).getPropertyValue('--hp-mode-view-weight'))
|
||
- enteringVisual.viewWeight) < 0.001;
|
||
out.planBarCloseIconIsCentered = !!barCloseRect && !!barCloseIconRect
|
||
&& Math.abs((barCloseIconRect.left + barCloseIconRect.width / 2)
|
||
- (barCloseRect.left + barCloseRect.width / 2)) <= 1
|
||
&& Math.abs((barCloseIconRect.top + barCloseIconRect.height / 2)
|
||
- (barCloseRect.top + barCloseRect.height / 2)) <= 1;
|
||
out.tabCross = !!headerCross() && !tabs()[0].querySelector('.closex');
|
||
const planSummary = sr().querySelector('.summary-control');
|
||
const summaryToggle = planSummary?.querySelector('button:last-child');
|
||
const summaryBefore = summaryToggle?.getAttribute('aria-pressed');
|
||
summaryToggle?.click(); await c.updateComplete;
|
||
out.summaryControlsStayInPlan = planSummary?.querySelectorAll('button').length === 2
|
||
&& summaryToggle?.getAttribute('aria-pressed') !== summaryBefore
|
||
&& !sr().querySelector('.summary-overlay');
|
||
summaryToggle?.click(); await c.updateComplete;
|
||
// 3) повторный клик по активной вкладке — ничего
|
||
tabs()[0].click(); await c.updateComplete;
|
||
out.reclickNoop = c._mode === 'plan';
|
||
const enteringTabCross = headerCross();
|
||
out.tabCrossCloseStartsDuringEnter = c._modeTransitionBusy;
|
||
enteringTabCross.click();
|
||
await settleMode();
|
||
out.tabCrossCloseDuringEnterWorks = c._mode === 'view';
|
||
tabs()[0].click(); await settleMode();
|
||
// HP-UX-11: transient tool controls live in a stage overlay. Opening the
|
||
// thickness controls must not resize/refit the stage or move pinned Close.
|
||
const stageBeforeContext = sr().querySelector('.stage').getBoundingClientRect();
|
||
const closeBeforeContext = sr().querySelector('.editbar .barclose').getBoundingClientRect();
|
||
const hdrBeforeContext = c._hdrH;
|
||
c._tool = 'draw'; c.requestUpdate(); await c.updateComplete;
|
||
await new Promise((resolve) => setTimeout(resolve, 40));
|
||
const stageWithContext = sr().querySelector('.stage').getBoundingClientRect();
|
||
const closeWithContext = sr().querySelector('.editbar .barclose').getBoundingClientRect();
|
||
const secondary = sr().querySelector('.editor-secondary-host.open .editor-secondary');
|
||
out.contextTrayLivesInStage = !!secondary
|
||
&& secondary.closest('.stage') === sr().querySelector('.stage')
|
||
&& !secondary.closest('.editorchrome');
|
||
out.contextDoesNotResizeStage = Math.abs(stageWithContext.top - stageBeforeContext.top) <= 1
|
||
&& Math.abs(stageWithContext.height - stageBeforeContext.height) <= 1
|
||
&& c._hdrH === hdrBeforeContext;
|
||
out.contextDoesNotMoveClose = Math.abs(closeWithContext.left - closeBeforeContext.left) <= 1
|
||
&& Math.abs(closeWithContext.top - closeBeforeContext.top) <= 1;
|
||
out.closeHasPinnedEndCap = !!sr().querySelector('.editbar > .editbar-end > .barclose');
|
||
// Replace the real Opening group with a side-effect-free command group to
|
||
// pin the generic launcher/keyboard/restore contract independently of any
|
||
// one editor tool's business logic.
|
||
let groupInvokes = 0;
|
||
Object.defineProperty(c, '_editorToolbarGroups', { configurable: true, get: () => [{
|
||
id: 'smoke-group', label: 'Smoke group', icon: 'mdi:dots-grid', items: [{
|
||
id: 'smoke-command', label: 'Smoke command', icon: 'mdi:check',
|
||
role: 'command', invoke: () => { groupInvokes += 1; },
|
||
}],
|
||
}] });
|
||
c.requestUpdate(); await c.updateComplete;
|
||
const groupLauncher = sr().querySelector('[data-editor-group="smoke-group"]');
|
||
groupLauncher?.focus();
|
||
groupLauncher?.dispatchEvent(new KeyboardEvent('keydown', {
|
||
key: 'ArrowDown', bubbles: true, composed: true, cancelable: true,
|
||
}));
|
||
await c.updateComplete;
|
||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||
out.groupUsesSharedTray = !!sr().querySelector('.editor-secondary.kind-group')
|
||
&& sr().querySelectorAll('.editor-secondary-host').length === 1;
|
||
out.groupArrowDownMovesFocus = sr().activeElement?.classList?.contains('editor-group-item') === true;
|
||
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
|
||
await c.updateComplete;
|
||
await new Promise((resolve) => requestAnimationFrame(resolve));
|
||
out.groupEscapeRestoresToolContext = c._editorSecondary.hasOpenGroup === false
|
||
&& !!sr().querySelector('.editor-secondary.kind-tool')
|
||
&& sr().activeElement === groupLauncher;
|
||
groupLauncher?.click(); await c.updateComplete;
|
||
sr().querySelector('.editor-group-item')?.click(); await c.updateComplete;
|
||
out.groupCommandClosesBeforeInvoke = groupInvokes === 1
|
||
&& c._editorSecondary.hasOpenGroup === false
|
||
&& !!sr().querySelector('.editor-secondary.kind-tool');
|
||
groupLauncher?.click(); await c.updateComplete;
|
||
const outsideTool = document.createElement('button');
|
||
const unrelatedTool = document.createElement('button');
|
||
let outsideInvokes = 0;
|
||
let unrelatedInvokes = 0;
|
||
outsideTool.addEventListener('click', () => outsideInvokes++);
|
||
unrelatedTool.addEventListener('click', () => unrelatedInvokes++);
|
||
document.body.append(outsideTool, unrelatedTool);
|
||
outsideTool.dispatchEvent(new PointerEvent('pointerdown', {
|
||
pointerId: 301, pointerType: 'mouse', button: 0,
|
||
bubbles: true, composed: true, cancelable: true,
|
||
}));
|
||
outsideTool.dispatchEvent(new MouseEvent('click', {
|
||
button: 0, bubbles: true, composed: true, cancelable: true,
|
||
}));
|
||
unrelatedTool.click();
|
||
await c.updateComplete;
|
||
out.groupOutsideDismissIsConsumed = c._editorSecondary.hasOpenGroup === false
|
||
&& outsideInvokes === 0 && c._tool === 'draw';
|
||
out.dismissTailDoesNotEatUnrelatedClick = unrelatedInvokes === 1;
|
||
outsideTool.remove();
|
||
unrelatedTool.remove();
|
||
// An open grouped submenu still consumes ordinary outside clicks, but an
|
||
// explicit editor navigation command has higher priority and must close both
|
||
// surfaces in the same pointer sequence.
|
||
groupLauncher?.click(); await c.updateComplete;
|
||
const closeWithOpenGroup = sr().querySelector('.editbar .barclose');
|
||
closeWithOpenGroup?.dispatchEvent(new PointerEvent('pointerdown', {
|
||
pointerId: 302, pointerType: 'mouse', button: 0,
|
||
bubbles: true, composed: true, cancelable: true,
|
||
}));
|
||
closeWithOpenGroup?.dispatchEvent(new MouseEvent('click', {
|
||
button: 0, bubbles: true, composed: true, cancelable: true,
|
||
}));
|
||
await c.updateComplete;
|
||
out.editorNavigationOutranksGroupDismiss = c._mode === 'view'
|
||
&& c._editorSecondary.hasOpenGroup === false;
|
||
await settleMode();
|
||
tabs()[0].click(); await settleMode();
|
||
delete c._editorToolbarGroups;
|
||
c._tool = 'select'; c.requestUpdate(); await c.updateComplete;
|
||
// 4) прямое переключение План → Устройства
|
||
// Deliberately make the outgoing bar taller: the editor transition must
|
||
// interpolate real content height, not merely fade two equal-height rows.
|
||
const planBarBeforeSwap = sr().querySelector('.editorchrome .editbar');
|
||
planBarBeforeSwap.style.minHeight = `${planBarBeforeSwap.getBoundingClientRect().height + 36}px`;
|
||
tabs()[1].click(); await c.updateComplete;
|
||
out.directSwitch = c._mode === 'devices';
|
||
out.devBar = !!sr().querySelector('.editbar.devbar');
|
||
const deviceToolbarButtons = [...sr().querySelectorAll('.editbar.devbar .btn:not(.barclose)')];
|
||
const addDeviceButton = deviceToolbarButtons[0];
|
||
out.devBarBtns = deviceToolbarButtons.length === 5;
|
||
out.deviceHistoryControlsArePersistent = !!sr().querySelector('[data-device-position-history="undo"]')
|
||
&& !!sr().querySelector('[data-device-position-history="redo"]')
|
||
&& sr().querySelector('[data-device-position-history="undo"]').disabled
|
||
&& sr().querySelector('[data-device-position-history="redo"]').disabled;
|
||
out.addDeviceShortcutUsesNativeKeyboardSemantics = addDeviceButton?.tagName === 'BUTTON'
|
||
&& addDeviceButton.disabled === false;
|
||
out.addDeviceShortcut = addDeviceButton?.textContent.trim() === c._t('devbar.add')
|
||
&& addDeviceButton.title === c._t('title.add_device')
|
||
&& addDeviceButton.querySelector('ha-icon')?.getAttribute('icon') === 'mdi:plus-box-outline'
|
||
&& deviceToolbarButtons[1]?.textContent.trim() === c._t('device_inbox.button');
|
||
const swapChrome = sr().querySelector('.editorchrome');
|
||
const swapInner = swapChrome.querySelector('.editorchrome-inner');
|
||
out.editorSwapAnimatesHeight = c._modeTransitionBusy
|
||
&& sr().querySelector('.stage').classList.contains('mode-transition');
|
||
out.editorSwapAnimatesContent = c._modeTransitionBusy && !!swapInner
|
||
&& (c._modeTransitionVisual?.toolbarContentOpacity ?? 1) < 1;
|
||
await settleMode();
|
||
addDeviceButton.click(); await c.updateComplete;
|
||
out.addDeviceShortcutOpensNewDialog = !!c._markerDialog
|
||
&& c._markerDialog.name === ''
|
||
&& c._markerDialog.bindingMode === 'virtual'
|
||
&& !Object.prototype.hasOwnProperty.call(c._markerDialog, 'devId');
|
||
c._markerDialog = null; await c.updateComplete;
|
||
// 5) инструменты устройств из шапки исчезли (в .bar их больше нет)
|
||
out.headerCleanInDev = !sr().querySelector('.bar > .btn[title*="' + (c._t('title.add_device')) + '"]');
|
||
// 6) крестик на панели → Просмотр
|
||
sr().querySelector('.editbar .barclose').click(); await c.updateComplete;
|
||
const chrome = sr().querySelector('.editorchrome');
|
||
out.barCloseStarts = c._mode === 'view' && c._modeTransitionBusy
|
||
&& sr().querySelector('.stage').inert
|
||
&& sr().querySelector('.stage').classList.contains('mode-transition');
|
||
await settleMode();
|
||
out.barCloseWorks = c._mode === 'view'
|
||
&& getComputedStyle(chrome).visibility === 'hidden'
|
||
&& chrome.getBoundingClientRect().height < 1;
|
||
// 7) #660 (+#195): the header X sits in a fixed 24 x 24 slot after the active
|
||
// mode; the slot is its hit target around a 13 px glyph and has the same size
|
||
// outside an editor, so the header never changes width. Exercise the slot's
|
||
// edge rather than calling the element's centre programmatically.
|
||
const tabCrossChecks = [];
|
||
for (let index = 0; index < tabs().length; index++) {
|
||
tabs()[index].click(); await settleMode();
|
||
const cross = headerCross();
|
||
const rect = cross.getBoundingClientRect();
|
||
const slot = sr().querySelector('.editor-close-slot').getBoundingClientRect();
|
||
const glyph = cross.querySelector('ha-icon');
|
||
const hit = sr().elementFromPoint(rect.left + 1, rect.top + rect.height / 2);
|
||
const editorSummaryVisible = sr().querySelectorAll('.summary-control button').length === 2
|
||
&& !sr().querySelector('.summary-overlay');
|
||
const glyphStays13 = getComputedStyle(glyph).getPropertyValue('--mdc-icon-size').trim() === '13px';
|
||
hit?.dispatchEvent(new MouseEvent('click', {
|
||
bubbles: true,
|
||
composed: true,
|
||
clientX: rect.left + 1,
|
||
clientY: rect.top + rect.height / 2,
|
||
}));
|
||
await settleMode();
|
||
const idleSlot = sr().querySelector('.editor-close-slot').getBoundingClientRect();
|
||
tabCrossChecks.push({
|
||
target: rect.width >= 24 && rect.height >= 24,
|
||
glyph: glyphStays13,
|
||
// The slot is the footprint: same box in the editor and after closing it.
|
||
footprint: Math.abs(slot.width - idleSlot.width) <= 0.5
|
||
&& Math.abs(slot.height - idleSlot.height) <= 0.5 && Math.abs(slot.width - 24) <= 0.5,
|
||
edgeHit: !!hit && (hit === cross || cross.contains(hit)),
|
||
summary: editorSummaryVisible,
|
||
closed: c._mode === 'view',
|
||
});
|
||
}
|
||
out.tabCrossTargetsAtLeast24 = tabCrossChecks.every((check) => check.target);
|
||
out.tabCrossGlyphStays13 = tabCrossChecks.every((check) => check.glyph);
|
||
out.tabCrossKeepsLayoutFootprint = tabCrossChecks.every((check) => check.footprint);
|
||
out.tabCrossExpandedEdgeWorks = tabCrossChecks.every((check) => check.edgeHit && check.closed);
|
||
out.tabCrossWorks = tabCrossChecks.every((check) => check.closed);
|
||
out.summaryControlsStayInEveryEditor = tabCrossChecks.every((check) => check.summary);
|
||
|
||
// Closing the editor only drops session state: every accepted segment was
|
||
// already persisted as an ordinary partition by its own click.
|
||
tabs()[0].click(); await settleMode();
|
||
const chainSpace = c._curSpaceCfg;
|
||
const savedPartitions = chainSpace.partitions ? [...chainSpace.partitions] : undefined;
|
||
const savedSaveConfig = c._saveConfig;
|
||
c._saveConfig = () => {};
|
||
const partitionCount = (chainSpace.partitions || []).length;
|
||
c._tool = 'draw';
|
||
const tabChainIds = ['tab-chain-a', 'tab-chain-b'];
|
||
chainSpace.partitions = [...(chainSpace.partitions || []),
|
||
{ id: tabChainIds[0], a: [0.1, 0.1], b: [0.2, 0.1], cm: 15 },
|
||
{ id: tabChainIds[1], a: [0.2, 0.1], b: [0.25, 0.15], cm: 15 }];
|
||
c._activeWallChainId = 'tab-chain';
|
||
c._activeWallChainPartitionIds = tabChainIds;
|
||
c._path = [[100, 100], [200, 100], [250, 150]];
|
||
c._wallChainSegmentCms = [15, 15];
|
||
headerCross().click(); await settleMode();
|
||
out.tabCrossFinishesWallChain = c._mode === 'view'
|
||
&& (chainSpace.partitions || []).length === partitionCount + 2
|
||
&& c._path.length === 0 && !c._activeWallChainId;
|
||
chainSpace.partitions = savedPartitions;
|
||
out.tabCrossDoesNotWriteAtPartitionLimit = true;
|
||
c._saveConfig = savedSaveConfig;
|
||
c._setMode('view', false); await c.updateComplete;
|
||
|
||
// Layout-independent editor history: physical KeyZ works when `key` is the
|
||
// Russian «я», while an input keeps its native browser history.
|
||
tabs()[0].click(); await settleMode();
|
||
c._tool = 'draw'; c.requestUpdate(); await c.updateComplete;
|
||
const realUndo = c._undoGeometry;
|
||
const realRedo = c._redoGeometry;
|
||
let undoCalls = 0;
|
||
let redoCalls = 0;
|
||
c._undoGeometry = () => { undoCalls += 1; };
|
||
c._redoGeometry = () => { redoCalls += 1; };
|
||
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||
key: 'я', code: 'KeyZ', ctrlKey: true, bubbles: true, composed: true,
|
||
}));
|
||
out.layoutIndependentUndo = undoCalls === 1;
|
||
const editorInput = sr().querySelector('.editor-secondary input');
|
||
editorInput.dispatchEvent(new KeyboardEvent('keydown', {
|
||
key: 'я', code: 'KeyZ', ctrlKey: true, bubbles: true, composed: true,
|
||
}));
|
||
out.inputKeepsNativeUndo = undoCalls === 1;
|
||
// QWERTZ swaps the physical Y/Z codes. The labelled key wins when it is a
|
||
// Latin letter; otherwise both isZ/isY became true and Redo ran first.
|
||
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||
key: 'z', code: 'KeyY', ctrlKey: true, bubbles: true, composed: true,
|
||
}));
|
||
out.qwertzCtrlZIsUndo = undoCalls === 2 && redoCalls === 0;
|
||
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||
key: 'y', code: 'KeyZ', ctrlKey: true, bubbles: true, composed: true,
|
||
}));
|
||
out.qwertzCtrlYIsRedo = undoCalls === 2 && redoCalls === 1;
|
||
// On AZERTY the physical KeyZ carries W: Ctrl+W must remain the browser's
|
||
// close-tab shortcut, never an editor Undo.
|
||
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||
key: 'w', code: 'KeyZ', ctrlKey: true, bubbles: true, composed: true,
|
||
}));
|
||
out.azertyCtrlWIsNotUndo = undoCalls === 2 && redoCalls === 1;
|
||
c._undoGeometry = realUndo;
|
||
c._redoGeometry = realRedo;
|
||
|
||
// Ctrl+click closes a valid active chain without adding an arbitrary point;
|
||
// the same gesture must refuse a chain with fewer than two existing edges.
|
||
const savedPath = c._path;
|
||
const savedRoomDialog = c._roomDialog;
|
||
const savedNameSel = c._nameSel;
|
||
const savedAreaSel = c._areaSel;
|
||
const ctrlClick = () => {
|
||
let prevented = false;
|
||
c._markupClick({
|
||
ctrlKey: true, metaKey: false, clientX: 0, clientY: 0,
|
||
preventDefault() { prevented = true; }, composedPath() { return []; },
|
||
});
|
||
return prevented;
|
||
};
|
||
c._tool = 'draw';
|
||
c._roomDialog = false;
|
||
const beforeCtrlPartitions = [...(chainSpace.partitions || [])];
|
||
chainSpace.partitions = [...beforeCtrlPartitions,
|
||
{ id: 'ctrl-short', a: [-0.12, -0.12], b: [-0.08, -0.12], cm: 15 }];
|
||
c._activeWallChainId = 'ctrl-short-chain';
|
||
c._activeWallChainPartitionIds = ['ctrl-short'];
|
||
c._wallChainSegmentCms = [15];
|
||
c._path = [[-120, -120], [-80, -120]];
|
||
const shortBefore = JSON.stringify(c._path);
|
||
out.ctrlCloseRequiresTwoEdges = ctrlClick()
|
||
&& !c._roomDialog && JSON.stringify(c._path) === shortBefore;
|
||
chainSpace.partitions = [...beforeCtrlPartitions,
|
||
{ id: 'ctrl-a', a: [-0.12, -0.12], b: [-0.08, -0.12], cm: 15 },
|
||
{ id: 'ctrl-b', a: [-0.08, -0.12], b: [-0.08, -0.08], cm: 15 }];
|
||
c._activeWallChainId = 'ctrl-chain';
|
||
c._activeWallChainPartitionIds = ['ctrl-a', 'ctrl-b'];
|
||
c._wallChainSegmentCms = [15, 15];
|
||
c._path = [[-120, -120], [-80, -120], [-80, -80]];
|
||
c._cfgEpoch++; c._modelCache = null;
|
||
out.ctrlCloseValidContour = ctrlClick() && c._roomDialog
|
||
&& c._path.length === 4
|
||
&& c._path[0][0] === c._path[3][0] && c._path[0][1] === c._path[3][1];
|
||
await c.updateComplete;
|
||
const roomDialog = sr().querySelector('hp-dialog.roomdialog');
|
||
const roomBody = roomDialog?.querySelector('.body');
|
||
out.roomDialogUsesMediumWidth = roomDialog?.wide === true
|
||
&& roomDialog.hasAttribute('wide');
|
||
out.roomDialogHasNoHorizontalScroll = !!roomBody
|
||
&& roomBody.scrollWidth <= roomBody.clientWidth + 1;
|
||
c._nameSel = 'No-area room';
|
||
c._areaSel = '';
|
||
c.requestUpdate();
|
||
await c.updateComplete;
|
||
const noAreaSave = sr().querySelector('hp-dialog.roomdialog .room-save');
|
||
out.noAreaUsesRegularSave = !!noAreaSave && !noAreaSave.disabled
|
||
&& sr().querySelectorAll('hp-dialog.roomdialog .room-save').length === 1;
|
||
c._path = savedPath;
|
||
chainSpace.partitions = beforeCtrlPartitions;
|
||
c._activeWallChainId = null;
|
||
c._activeWallChainPartitionIds = [];
|
||
c._wallChainSegmentCms = [];
|
||
c._roomDialog = savedRoomDialog;
|
||
c._nameSel = savedNameSel;
|
||
c._areaSel = savedAreaSel; // private-ok: #660 restore of an existing room-dialog draft; the facade has no draft-state setter
|
||
|
||
// #660: an internal Plan/Background action consumes the first Escape; the
|
||
// next neutral Escape leaves the editor. Device editor exits immediately.
|
||
await hp.setMode('plan');
|
||
await hp.setTool('column');
|
||
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await c.updateComplete;
|
||
out.planEscapeKeepsInternalPriority = c._mode === 'plan' && c._tool === 'draw';
|
||
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await settleMode();
|
||
out.planNeutralEscapeExits = c._mode === 'view';
|
||
await hp.setMode('devices');
|
||
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await settleMode();
|
||
out.deviceNeutralEscapeExits = c._mode === 'view';
|
||
await hp.setMode('decor');
|
||
await hp.setTool('line');
|
||
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await c.updateComplete;
|
||
out.decorEscapeKeepsInternalPriority = c._mode === 'decor' && c._decorTool === 'select';
|
||
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await settleMode();
|
||
out.decorNeutralEscapeExits = c._mode === 'view';
|
||
return out;
|
||
});
|
||
// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1)
|
||
checkAll(res, {
|
||
"labels": ["Plan editor", "Device editor", "Background editor"],
|
||
});
|
||
await finish(browser, res);
|