mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 23:19:14 +00:00
133 lines
6.9 KiB
JavaScript
133 lines
6.9 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 sr = () => c.shadowRoot || c.renderRoot;
|
||
const tabs = () => [...sr().querySelectorAll('.modetab')];
|
||
// 1) две вкладки, Просмотра нет, крестиков в неактивных нет
|
||
out.twoTabs = tabs().length === 3; // третья — Редактор подложки (v1.33.0)
|
||
out.labels = tabs().map((t) => t.textContent.trim());
|
||
out.noCrossIdle = sr().querySelectorAll('.modetab .closex').length === 0;
|
||
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');
|
||
out.tabCross = !!tabs()[0].querySelector('.closex');
|
||
// 3) повторный клик по активной вкладке — ничего
|
||
tabs()[0].click(); await c.updateComplete;
|
||
out.reclickNoop = c._mode === 'plan';
|
||
// Let the View -> editor grid transition finish first. A real user cannot
|
||
// switch editors in the same zero-layout frame, and competing WAAPI/CSS
|
||
// height animations in that frame are intentionally suppressed.
|
||
await new Promise((resolve) => setTimeout(resolve, 220));
|
||
// 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');
|
||
out.devBarBtns = sr().querySelectorAll('.editbar.devbar .btn:not(.barclose)').length === 3; // add/show-all/rules (v1.33.2: Reset removed)
|
||
const swapChrome = sr().querySelector('.editorchrome');
|
||
const swapInner = swapChrome.querySelector('.editorchrome-inner');
|
||
out.editorSwapAnimatesHeight = swapChrome.getAnimations()
|
||
.some((animation) => animation.id === 'hp-editor-height-swap');
|
||
out.editorSwapAnimatesContent = swapInner.getAnimations()
|
||
.some((animation) => animation.id === 'hp-editor-content-swap');
|
||
// 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' && !chrome.classList.contains('open')
|
||
&& chrome.getAttribute('aria-hidden') === 'true';
|
||
// The last bar deliberately remains mounted while its grid row collapses.
|
||
await new Promise((resolve) => setTimeout(resolve, 220));
|
||
out.barCloseWorks = c._mode === 'view'
|
||
&& getComputedStyle(chrome).visibility === 'hidden'
|
||
&& chrome.getBoundingClientRect().height < 1;
|
||
// 7) крестик в самой вкладке → Просмотр (и не переключает режим)
|
||
tabs()[1].click(); await c.updateComplete;
|
||
tabs()[1].querySelector('.closex').click(); await c.updateComplete;
|
||
out.tabCrossWorks = c._mode === 'view';
|
||
|
||
// Layout-independent editor history: physical KeyZ works when `key` is the
|
||
// Russian «я», while an input keeps its native browser history.
|
||
tabs()[0].click(); 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('.editbar 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 draft without adding the click as a new point;
|
||
// the same gesture must refuse a draft with fewer than two existing edges.
|
||
const savedPath = c._path;
|
||
const savedRoomDialog = c._roomDialog;
|
||
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;
|
||
c._path = [[-120, -120], [-80, -120]];
|
||
const shortBefore = JSON.stringify(c._path);
|
||
out.ctrlCloseRequiresTwoEdges = ctrlClick()
|
||
&& !c._roomDialog && JSON.stringify(c._path) === shortBefore;
|
||
c._path = [[-120, -120], [-80, -120], [-80, -80]];
|
||
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._path = savedPath;
|
||
c._roomDialog = savedRoomDialog;
|
||
return out;
|
||
});
|
||
// значения зафиксированы прогоном на v1.43.1 и сверены с кодом (audit T1)
|
||
checkAll(res, {
|
||
"labels": ["Plan editor", "Device editor", "Background editor"],
|
||
});
|
||
await finish(browser, res);
|