mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
460 lines
21 KiB
JavaScript
460 lines
21 KiB
JavaScript
// Issue #68: contextual help trigger parity, dialog overlay ownership and the
|
|
// real non-Popover fallback. Run by the prerelease smoke suite, not on edits.
|
|
import { launch, launchColdView, checkAll, finish } from './serve.mjs';
|
|
|
|
const { page, browser } = await launch({ width: 390, height: 780 }, 1);
|
|
await page.keyboard.press('Tab');
|
|
const keyboardFocus = await page.evaluate(async () => {
|
|
const frame = () => new Promise((resolve) => requestAnimationFrame(resolve));
|
|
const deepActive = () => {
|
|
let active = document.activeElement;
|
|
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
return active;
|
|
};
|
|
const dialog = document.createElement('hp-dialog');
|
|
dialog.title = 'Keyboard help smoke';
|
|
const help = document.createElement('hp-help');
|
|
help.text = 'Keyboard description';
|
|
help.ariaLabel = 'Help: keyboard description';
|
|
dialog.append(help);
|
|
document.body.append(dialog);
|
|
await dialog.updateComplete;
|
|
await help.updateComplete;
|
|
dialog._focusInitial();
|
|
await frame();
|
|
const button = help.shadowRoot?.querySelector('.trigger');
|
|
const result = deepActive() === button
|
|
&& button?.matches(':focus-visible')
|
|
&& button?.getAttribute('aria-expanded') === 'true'
|
|
&& dialog._focusableElements().includes(button);
|
|
dialog.remove();
|
|
return result;
|
|
});
|
|
const res = await page.evaluate(async () => {
|
|
const out = {};
|
|
const card = window.__card;
|
|
const root = () => card.shadowRoot || card.renderRoot;
|
|
const wait = (ms = 0) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
const frame = () => new Promise((resolve) => requestAnimationFrame(resolve));
|
|
const deepActive = () => {
|
|
let active = document.activeElement;
|
|
while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
|
|
return active;
|
|
};
|
|
const helpButton = (key) => root().querySelector(`hp-help[data-help-key="${key}"]`)?.shadowRoot?.querySelector('.trigger');
|
|
const helpSurface = (key) => {
|
|
const help = root().querySelector(`hp-help[data-help-key="${key}"]`);
|
|
return help?.shadowRoot?.querySelector('.tooltip')
|
|
|| root().querySelector('hp-dialog')?.shadowRoot?.querySelector('[data-hp-overlay="help"]')?.shadowRoot?.querySelector('.tooltip');
|
|
};
|
|
const enterWithRealMouse = (target) => {
|
|
target?.dispatchEvent(new PointerEvent('pointerover', {
|
|
pointerType: 'mouse', bubbles: true, composed: true,
|
|
}));
|
|
target?.dispatchEvent(new PointerEvent('pointerenter', {
|
|
pointerType: 'mouse', bubbles: false, composed: true,
|
|
}));
|
|
};
|
|
|
|
// Empty or incomplete content must not leave a dead focus target behind.
|
|
const incompleteHelp = document.createElement('hp-help');
|
|
incompleteHelp.text = ' ';
|
|
incompleteHelp.ariaLabel = 'Help: unavailable description';
|
|
document.body.append(incompleteHelp);
|
|
await incompleteHelp.updateComplete;
|
|
out.emptyTextHidesTrigger = !incompleteHelp.shadowRoot?.querySelector('.trigger')
|
|
&& getComputedStyle(incompleteHelp).display === 'none';
|
|
incompleteHelp.text = 'Available description';
|
|
incompleteHelp.ariaLabel = ' ';
|
|
await incompleteHelp.updateComplete;
|
|
out.emptyAriaHidesTrigger = !incompleteHelp.shadowRoot?.querySelector('.trigger')
|
|
&& getComputedStyle(incompleteHelp).display === 'none';
|
|
incompleteHelp.ariaLabel = 'Help: available description';
|
|
await incompleteHelp.updateComplete;
|
|
const restoredTrigger = incompleteHelp.shadowRoot?.querySelector('.trigger');
|
|
out.circledQuestionIcon = restoredTrigger?.querySelector('svg[viewBox="0 0 24 24"] path')
|
|
?.getAttribute('d')?.length > 0;
|
|
incompleteHelp.text = Array.from({ length: 320 }, (_, index) => `Line ${index + 1}`).join(' · ');
|
|
await incompleteHelp.updateComplete;
|
|
const overflowTrigger = incompleteHelp.shadowRoot?.querySelector('.trigger');
|
|
overflowTrigger?.click();
|
|
await incompleteHelp.updateComplete;
|
|
await frame();
|
|
const longSurface = incompleteHelp.shadowRoot?.querySelector('.tooltip');
|
|
overflowTrigger?.dispatchEvent(new KeyboardEvent('keydown', {
|
|
key: 'PageDown', bubbles: true, composed: true, cancelable: true,
|
|
}));
|
|
await frame();
|
|
out.overflowHelpScrollsFromTrigger = !!longSurface
|
|
&& longSurface.scrollHeight > longSurface.clientHeight && longSurface.scrollTop > 0;
|
|
overflowTrigger?.click();
|
|
incompleteHelp.remove();
|
|
|
|
card._setMode('devices');
|
|
const marker = card._devices[0];
|
|
card._openMarkerDialog(marker);
|
|
await card.updateComplete;
|
|
await frame();
|
|
|
|
const dialogBody = root().querySelector('hp-dialog .body');
|
|
const scrollGeometry = () => dialogBody ? {
|
|
clientHeight: dialogBody.clientHeight,
|
|
scrollHeight: dialogBody.scrollHeight,
|
|
scrollTop: dialogBody.scrollTop,
|
|
} : null;
|
|
|
|
const roleHelp = root().querySelector('hp-help[data-help-key="marker.light_role.help"]');
|
|
const roleButton = helpButton('marker.light_role.help');
|
|
const roleDescription = roleHelp?.shadowRoot?.querySelector('[role="tooltip"]');
|
|
out.realButton = roleButton?.tagName === 'BUTTON'
|
|
&& !!roleDescription?.id
|
|
&& !roleButton.hasAttribute('aria-describedby');
|
|
|
|
const beforeHoverGeometry = scrollGeometry();
|
|
enterWithRealMouse(roleButton);
|
|
await wait(330);
|
|
out.mouseHover = roleButton?.getAttribute('aria-expanded') === 'true'
|
|
&& roleButton.getAttribute('aria-describedby') === roleDescription?.id
|
|
&& !!helpSurface('marker.light_role.help');
|
|
out.hoverKeepsDialogScrollGeometry = JSON.stringify(scrollGeometry())
|
|
=== JSON.stringify(beforeHoverGeometry);
|
|
roleButton?.dispatchEvent(new PointerEvent('pointerleave', { pointerType: 'mouse', bubbles: true, composed: true }));
|
|
await wait(180);
|
|
out.mouseLeave = roleButton?.getAttribute('aria-expanded') === 'false';
|
|
|
|
// Escape dismisses a mouse-hovered bubble without moving keyboard focus
|
|
// away from the form control the user was editing.
|
|
const nameInput = root().querySelector('hp-dialog .namein');
|
|
nameInput?.focus();
|
|
enterWithRealMouse(roleButton);
|
|
await wait(330);
|
|
nameInput?.dispatchEvent(new KeyboardEvent('keydown', {
|
|
key: 'Escape', bubbles: true, composed: true, cancelable: true,
|
|
}));
|
|
await frame();
|
|
out.hoverEscapeKeepsFocus = roleButton?.getAttribute('aria-expanded') === 'false'
|
|
&& deepActive() === nameInput;
|
|
|
|
roleButton?.click();
|
|
await frame();
|
|
document.body.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, composed: true }));
|
|
await frame();
|
|
out.outsidePointer = roleButton?.getAttribute('aria-expanded') === 'false';
|
|
roleButton?.click();
|
|
await frame();
|
|
out.touchClick = roleButton?.getAttribute('aria-expanded') === 'true';
|
|
roleButton?.dispatchEvent(new KeyboardEvent('keydown', {
|
|
key: 'Escape', bubbles: true, composed: true, cancelable: true,
|
|
}));
|
|
await card.updateComplete;
|
|
out.escapeHelpFirst = roleButton?.getAttribute('aria-expanded') === 'false' && !!card._markerDialog;
|
|
|
|
// The help in the first legend remains a real enabled target even while the
|
|
// glow fieldset itself is disabled.
|
|
card._markerDialog = { ...card._markerDialog, lightRole: 'never' };
|
|
await card.updateComplete;
|
|
const disabledHelpHost = root().querySelector('hp-help[data-help-key="marker.glow_mode.help"]');
|
|
const disabledHelp = helpButton('marker.glow_mode.help');
|
|
out.disabledExplanation = !!disabledHelp && !disabledHelp.disabled
|
|
&& !!disabledHelpHost?.closest('fieldset[disabled]')
|
|
&& !!disabledHelpHost?.closest('legend');
|
|
|
|
// Exclusive transient surfaces: opening help dismisses an open colour picker.
|
|
card._markerDialog = { ...card._markerDialog, lightRole: 'always', glowMode: 'fixed' };
|
|
await card.updateComplete;
|
|
const picker = root().querySelector('hp-color-opacity');
|
|
const pickerButton = picker?.shadowRoot?.querySelector('.trigger');
|
|
pickerButton?.click();
|
|
await frame();
|
|
const colorOnlySurface = picker?.shadowRoot?.querySelector('.picker');
|
|
out.glowColorOnlyHasNoAlpha = colorOnlySurface?.querySelectorAll('input[type="range"]').length === 3
|
|
&& !colorOnlySurface?.querySelector('input[type="number"]');
|
|
const glowHelpButton = helpButton('marker.glow_mode.help');
|
|
const focusables = root().querySelector('hp-dialog')?._focusableElements?.() || [];
|
|
out.shadowFocusables = focusables.includes(glowHelpButton) && focusables.includes(pickerButton);
|
|
glowHelpButton?.click();
|
|
await frame();
|
|
out.exclusive = pickerButton?.getAttribute('aria-expanded') === 'false'
|
|
&& glowHelpButton?.getAttribute('aria-expanded') === 'true';
|
|
|
|
const surface = helpSurface('marker.glow_mode.help');
|
|
const box = surface?.getBoundingClientRect();
|
|
const viewport = window.visualViewport;
|
|
out.insideViewport = !!box && box.left >= (viewport?.offsetLeft || 0)
|
|
&& box.top >= (viewport?.offsetTop || 0)
|
|
&& box.right <= (viewport?.offsetLeft || 0) + (viewport?.width || innerWidth)
|
|
&& box.bottom <= (viewport?.offsetTop || 0) + (viewport?.height || innerHeight);
|
|
|
|
pickerButton?.click();
|
|
await frame();
|
|
out.reverseExclusive = glowHelpButton?.getAttribute('aria-expanded') === 'false'
|
|
&& pickerButton?.getAttribute('aria-expanded') === 'true';
|
|
pickerButton?.click();
|
|
glowHelpButton?.click();
|
|
await frame();
|
|
card._showToast('overlay priority');
|
|
await frame();
|
|
out.toastClosesTransient = glowHelpButton?.getAttribute('aria-expanded') === 'false'
|
|
&& !!card._markerDialog;
|
|
glowHelpButton?.click();
|
|
await frame();
|
|
|
|
// Scrolling the owning dialog closes help instead of leaving a detached bubble.
|
|
const body = glowHelpButton?.getRootNode()?.host?.closest('hp-dialog')?.querySelector('.body');
|
|
body?.dispatchEvent(new Event('scroll', { bubbles: false }));
|
|
await frame();
|
|
out.ownScrollCloses = glowHelpButton?.getAttribute('aria-expanded') === 'false';
|
|
|
|
// Force feature detection down the true portal fallback branch.
|
|
const show = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'showPopover');
|
|
const hide = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'hidePopover');
|
|
Object.defineProperty(HTMLElement.prototype, 'showPopover', { configurable: true, value: undefined });
|
|
Object.defineProperty(HTMLElement.prototype, 'hidePopover', { configurable: true, value: undefined });
|
|
const beforeFallbackGeometry = scrollGeometry();
|
|
glowHelpButton?.click();
|
|
await frame();
|
|
const dialog = root().querySelector('hp-dialog');
|
|
const fallback = dialog?.shadowRoot?.querySelector('[data-hp-overlay="help"]');
|
|
const fallbackBox = fallback?.shadowRoot?.querySelector('.tooltip')?.getBoundingClientRect();
|
|
out.realFallback = !!fallback && !!fallbackBox?.width && !!fallbackBox?.height;
|
|
out.fallbackKeepsDialogScrollGeometry = JSON.stringify(scrollGeometry())
|
|
=== JSON.stringify(beforeFallbackGeometry);
|
|
glowHelpButton?.click();
|
|
pickerButton?.click();
|
|
await frame();
|
|
const pickerFallback = dialog?.shadowRoot?.querySelector('[data-hp-overlay="color-opacity"]');
|
|
const pickerControl = pickerFallback?.shadowRoot?.querySelector('input');
|
|
const pickerConfirm = pickerFallback?.shadowRoot?.querySelector('.confirm');
|
|
out.fallbackPickerFocusable = !!pickerControl
|
|
&& dialog?._focusableElements?.().includes(pickerControl);
|
|
out.fallbackPickerHasConfirm = pickerConfirm?.textContent.trim() === 'OK'
|
|
&& pickerFallback?.shadowRoot?.querySelector('.picker')?.lastElementChild === pickerConfirm;
|
|
pickerConfirm?.click();
|
|
await frame();
|
|
out.fallbackPickerConfirmCloses = pickerButton?.getAttribute('aria-expanded') === 'false'
|
|
&& picker?.shadowRoot?.activeElement === pickerButton;
|
|
if (pickerButton?.getAttribute('aria-expanded') === 'true') pickerButton.click();
|
|
if (show) Object.defineProperty(HTMLElement.prototype, 'showPopover', show);
|
|
else delete HTMLElement.prototype.showPopover;
|
|
if (hide) Object.defineProperty(HTMLElement.prototype, 'hidePopover', hide);
|
|
else delete HTMLElement.prototype.hidePopover;
|
|
|
|
// Explicit card language wins over HA locale and updates an existing component.
|
|
card._config = { ...card._config, language: 'ru' };
|
|
card.requestUpdate();
|
|
await card.updateComplete;
|
|
out.cardLanguage = /Определяет|источник/.test(root()
|
|
.querySelector('hp-help[data-help-key="marker.light_role.help"]')?.text || '');
|
|
|
|
const finalButton = helpButton('marker.light_role.help');
|
|
finalButton?.click();
|
|
await frame();
|
|
finalButton?.dispatchEvent(new KeyboardEvent('keydown', {
|
|
key: 'Escape', bubbles: true, composed: true, cancelable: true,
|
|
}));
|
|
await card.updateComplete;
|
|
finalButton?.dispatchEvent(new KeyboardEvent('keydown', {
|
|
key: 'Escape', bubbles: true, composed: true, cancelable: true,
|
|
}));
|
|
await card.updateComplete;
|
|
out.secondEscapeClosesDialog = !card._markerDialog;
|
|
|
|
// #86 Party 1: all general-settings placements are real help controls and
|
|
// opening one of them cannot mutate its associated setting.
|
|
card._openSettingsDialog();
|
|
await card.updateComplete;
|
|
const generalKeys = ['gs.glow_radius.help', 'gs.bg_mode.help', 'gs.north.help'];
|
|
out.party1GeneralInventory = generalKeys.every((key) => !!helpButton(key));
|
|
const generalBefore = JSON.stringify({
|
|
glowRadius: card._settingsDialog?.glowRadius,
|
|
bgMode: card._settingsDialog?.bgMode,
|
|
northDeg: card._settingsDialog?.northDeg,
|
|
});
|
|
helpButton('gs.bg_mode.help')?.click();
|
|
await frame();
|
|
out.party1GeneralHelpIsReadOnly = generalBefore === JSON.stringify({
|
|
glowRadius: card._settingsDialog?.glowRadius,
|
|
bgMode: card._settingsDialog?.bgMode,
|
|
northDeg: card._settingsDialog?.northDeg,
|
|
});
|
|
card._settingsDialog = null;
|
|
card.requestUpdate();
|
|
await card.updateComplete;
|
|
|
|
// The regular editor and cold-install onboarding render separate space
|
|
// dialog templates; this smoke covers the editor copy while the source/i18n
|
|
// contract covers both implementations.
|
|
card._openSpaceDialog('edit', card._space);
|
|
await card.updateComplete;
|
|
const spaceKeys = [
|
|
'space.cell_cm.help', 'space.zero_wall_style.help', 'space.bg_mode.help',
|
|
'space.north.help', 'space.fill_mode.help',
|
|
];
|
|
out.party1SpaceInventory = spaceKeys.every((key) => !!helpButton(key));
|
|
const spaceBefore = JSON.stringify({
|
|
cellCm: card._spaceDialog?.cellCm,
|
|
zeroWallStyle: card._spaceDialog?.zeroWallStyle,
|
|
bgMode: card._spaceDialog?.bgMode,
|
|
northDeg: card._spaceDialog?.northDeg,
|
|
fillMode: card._spaceDialog?.fillMode,
|
|
});
|
|
helpButton('space.zero_wall_style.help')?.click();
|
|
await frame();
|
|
out.party1SpaceHelpIsReadOnly = spaceBefore === JSON.stringify({
|
|
cellCm: card._spaceDialog?.cellCm,
|
|
zeroWallStyle: card._spaceDialog?.zeroWallStyle,
|
|
bgMode: card._spaceDialog?.bgMode,
|
|
northDeg: card._spaceDialog?.northDeg,
|
|
fillMode: card._spaceDialog?.fillMode,
|
|
});
|
|
card._spaceDialog = null;
|
|
card.requestUpdate();
|
|
await card.updateComplete;
|
|
|
|
card._openDeviceInbox();
|
|
await card.updateComplete;
|
|
const hiddenBefore = card._showHidden;
|
|
const hiddenHelpButton = helpButton('device_inbox.show_hidden.help');
|
|
hiddenHelpButton?.click();
|
|
await frame();
|
|
const hiddenSurface = helpSurface('device_inbox.show_hidden.help');
|
|
const hiddenBox = hiddenSurface?.getBoundingClientRect();
|
|
out.party1ShowHiddenHelpIsReadOnly = !!hiddenHelpButton
|
|
&& card._showHidden === hiddenBefore;
|
|
out.party1ShowHiddenHelpInsideNarrowViewport = !!hiddenBox
|
|
&& hiddenBox.left >= (visualViewport?.offsetLeft || 0)
|
|
&& hiddenBox.top >= (visualViewport?.offsetTop || 0)
|
|
&& hiddenBox.right <= (visualViewport?.offsetLeft || 0) + (visualViewport?.width || innerWidth)
|
|
&& hiddenBox.bottom <= (visualViewport?.offsetTop || 0) + (visualViewport?.height || innerHeight);
|
|
|
|
return out;
|
|
});
|
|
|
|
// Empty-install onboarding owns a separate lazy runtime and a second copy of
|
|
// the space dialog. Exercise that production chunk, not merely the regular
|
|
// editor template or its source inventory (#86 review r1 M1).
|
|
const onboarding = await launchColdView({ width: 390, height: 780 });
|
|
await onboarding.page.evaluate(async () => {
|
|
const card = window.__card;
|
|
card._onboardingShown = false;
|
|
card._serverCfg = { ...card._serverCfg, spaces: [] };
|
|
card._model = [];
|
|
card.hass = { ...card.hass, floors: {}, areas: {} };
|
|
card.requestUpdate();
|
|
await card.updateComplete;
|
|
});
|
|
await onboarding.page.waitForFunction(() => {
|
|
const card = window.__card;
|
|
return card._onboardingRuntime && !card._editorRuntime
|
|
&& card.renderRoot.querySelector('hp-dialog');
|
|
});
|
|
const onboardingHelp = await onboarding.page.evaluate(async () => {
|
|
const card = window.__card;
|
|
const root = card.renderRoot;
|
|
const keys = [
|
|
'space.cell_cm.help', 'space.zero_wall_style.help', 'space.bg_mode.help',
|
|
'space.north.help', 'space.fill_mode.help',
|
|
];
|
|
const help = (key) => root.querySelector(`hp-help[data-help-key="${key}"]`);
|
|
const before = JSON.stringify({
|
|
cellCm: card._spaceDialog?.cellCm,
|
|
zeroWallStyle: card._spaceDialog?.zeroWallStyle,
|
|
bgMode: card._spaceDialog?.bgMode,
|
|
northDeg: card._spaceDialog?.northDeg,
|
|
fillMode: card._spaceDialog?.fillMode,
|
|
});
|
|
const zeroWallHelp = help('space.zero_wall_style.help');
|
|
zeroWallHelp?.shadowRoot?.querySelector('.trigger')?.click();
|
|
await zeroWallHelp?.updateComplete;
|
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
const after = JSON.stringify({
|
|
cellCm: card._spaceDialog?.cellCm,
|
|
zeroWallStyle: card._spaceDialog?.zeroWallStyle,
|
|
bgMode: card._spaceDialog?.bgMode,
|
|
northDeg: card._spaceDialog?.northDeg,
|
|
fillMode: card._spaceDialog?.fillMode,
|
|
});
|
|
return {
|
|
inventory: keys.every((key) => !!help(key)?.shadowRoot?.querySelector('.trigger')),
|
|
selectedHelpOpened: zeroWallHelp?.shadowRoot
|
|
?.querySelector('.trigger')?.getAttribute('aria-expanded') === 'true',
|
|
readOnly: before === after,
|
|
editorStillLazy: !!card._onboardingRuntime && !card._editorRuntime,
|
|
};
|
|
});
|
|
res.party1ColdOnboardingInventory = onboardingHelp.inventory;
|
|
res.party1ColdOnboardingHelpOpens = onboardingHelp.selectedHelpOpened;
|
|
res.party1ColdOnboardingHelpIsReadOnly = onboardingHelp.readOnly;
|
|
res.party1ColdOnboardingKeepsEditorLazy = onboardingHelp.editorStillLazy;
|
|
await onboarding.browser.close();
|
|
|
|
// Browser zoom at 200% on a 780 px-wide display exposes a 390 CSS px viewport
|
|
// at DPR 2. Playwright cannot change Chromium's toolbar preference, so create
|
|
// that exact renderer contract directly; unlike CSS `zoom`, this preserves the
|
|
// coordinate space used by fixed/popover surfaces and catches real clipping.
|
|
const zoomed = await launch({ width: 390, height: 900 }, 2);
|
|
const zoomedLayout = await zoomed.page.evaluate(async () => {
|
|
const card = window.__card;
|
|
const root = card.renderRoot;
|
|
const frame = () => new Promise((resolve) => requestAnimationFrame(resolve));
|
|
card._openSettingsDialog();
|
|
await card.updateComplete;
|
|
await frame();
|
|
|
|
const help = root.querySelector('hp-help[data-help-key="gs.bg_mode.help"]');
|
|
const trigger = help?.shadowRoot?.querySelector('.trigger');
|
|
const stage = root.querySelector('.stage');
|
|
const dialogBody = root.querySelector('hp-dialog .body');
|
|
const stageRect = () => {
|
|
const box = stage?.getBoundingClientRect();
|
|
return box ? [box.left, box.top, box.width, box.height].map((value) => Math.round(value)) : null;
|
|
};
|
|
const settleStage = async () => {
|
|
let previous = null;
|
|
for (let attempt = 0; attempt < 8; attempt++) {
|
|
await frame();
|
|
const current = stageRect();
|
|
if (JSON.stringify(current) === JSON.stringify(previous)) return current;
|
|
previous = current;
|
|
}
|
|
return previous;
|
|
};
|
|
trigger?.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
await frame();
|
|
const before = await settleStage();
|
|
trigger?.click();
|
|
await help?.updateComplete;
|
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
const triggerBox = trigger?.getBoundingClientRect();
|
|
const surface = help?.shadowRoot?.querySelector('.tooltip')
|
|
|| root.querySelector('hp-dialog')?.shadowRoot
|
|
?.querySelector('[data-hp-overlay="help"]')?.shadowRoot?.querySelector('.tooltip');
|
|
const surfaceBox = surface?.getBoundingClientRect();
|
|
const viewport = window.visualViewport;
|
|
const left = viewport?.offsetLeft || 0;
|
|
const top = viewport?.offsetTop || 0;
|
|
const right = left + (viewport?.width || innerWidth);
|
|
const bottom = top + (viewport?.height || innerHeight);
|
|
const inside = (box) => !!box && box.width > 0 && box.height > 0
|
|
&& box.left >= left && box.top >= top && box.right <= right && box.bottom <= bottom;
|
|
return {
|
|
zoomApplied: devicePixelRatio === 2 && innerWidth === 390,
|
|
triggerInside: inside(triggerBox),
|
|
tooltipInside: inside(surfaceBox),
|
|
noHorizontalDialogOverflow: !!dialogBody
|
|
&& dialogBody.scrollWidth <= dialogBody.clientWidth + 1,
|
|
stageStable: JSON.stringify(await settleStage()) === JSON.stringify(before),
|
|
};
|
|
});
|
|
res.party1BrowserZoom200Applied = zoomedLayout.zoomApplied;
|
|
res.party1BrowserZoom200TriggerInside = zoomedLayout.triggerInside;
|
|
res.party1BrowserZoom200TooltipInside = zoomedLayout.tooltipInside;
|
|
res.party1BrowserZoom200NoHorizontalOverflow = zoomedLayout.noHorizontalDialogOverflow;
|
|
res.party1BrowserZoom200StageStable = zoomedLayout.stageStable;
|
|
await zoomed.browser.close();
|
|
|
|
res.keyboardFocus = keyboardFocus;
|
|
|
|
checkAll(res);
|
|
await finish(browser, res);
|