mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
216 lines
8.5 KiB
JavaScript
216 lines
8.5 KiB
JavaScript
// #140: opening and physical-object properties share the existing medium
|
|
// dialog shell. Measure real localized buttons rather than merely checking
|
|
// the `wide` attribute, because the contract is usable spare width plus safe
|
|
// wrapping when the viewport becomes narrow.
|
|
import { launch, checkAll, finish } from './serve.mjs';
|
|
|
|
const DESKTOP_WIDTH = 1000;
|
|
const NARROW_WIDTH = 320;
|
|
const { page, browser } = await launch({ width: DESKTOP_WIDTH, height: 820 }, 1);
|
|
|
|
const desktop = await page.evaluate(async () => {
|
|
const card = window.__card;
|
|
const settle = async () => {
|
|
await card.updateComplete;
|
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
};
|
|
card._serverStorage = true;
|
|
card._serverCfg = {
|
|
spaces: [{
|
|
id: 'dialog-layout', title: 'Dialog layout', cell_cm: 5,
|
|
view_box: [0, 0, 1, 1], rooms: [], settings: {},
|
|
}],
|
|
markers: [], settings: {},
|
|
};
|
|
card._space = 'dialog-layout';
|
|
card._mode = 'plan';
|
|
card._modelCache = null;
|
|
card._saveConfig = () => {};
|
|
await settle();
|
|
|
|
const layout = (kind, language) => {
|
|
const root = card.shadowRoot || card.renderRoot;
|
|
const dialog = root.querySelector('hp-dialog');
|
|
const surface = dialog?.shadowRoot?.querySelector('.surface');
|
|
const footer = dialog?.querySelector('.dialog-action-footer');
|
|
const danger = footer?.querySelector('.dialog-action-danger');
|
|
const commit = footer?.querySelector('.dialog-action-commit');
|
|
const surfaceRect = surface?.getBoundingClientRect();
|
|
const footerRect = footer?.getBoundingClientRect();
|
|
const footerStyle = footer ? getComputedStyle(footer) : null;
|
|
const dangerRect = danger?.getBoundingClientRect();
|
|
const commitRect = commit?.getBoundingClientRect();
|
|
const buttons = [...(footer?.querySelectorAll('button') || [])];
|
|
const innerWidth = footerRect && footerStyle
|
|
? footerRect.width - parseFloat(footerStyle.paddingLeft) - parseFloat(footerStyle.paddingRight)
|
|
: 0;
|
|
const contentWidth = (dangerRect?.width || 0) + (commitRect?.width || 0);
|
|
return {
|
|
kind,
|
|
language,
|
|
widePreset: dialog?.wide === true && dialog.hasAttribute('wide'),
|
|
surfaceWidth: Number((surfaceRect?.width || 0).toFixed(2)),
|
|
innerWidth: Number(innerWidth.toFixed(2)),
|
|
contentWidth: Number(contentWidth.toFixed(2)),
|
|
spareWidth: Number((innerWidth - contentWidth).toFixed(2)),
|
|
buttons: buttons.length,
|
|
oneRow: !!dangerRect && !!commitRect && Math.abs(dangerRect.top - commitRect.top) <= 1,
|
|
noHorizontalOverflow: !!surface && !!footer
|
|
&& surface.scrollWidth <= surface.clientWidth + 1
|
|
&& footer.scrollWidth <= footer.clientWidth + 1,
|
|
};
|
|
};
|
|
|
|
const open = async (kind, language) => {
|
|
card._config = { ...(card._config || {}), language };
|
|
card._openingDialog = null;
|
|
card._physicalDialog = null;
|
|
card._spaceDialog = null;
|
|
if (kind === 'opening') {
|
|
card._openingDialog = {
|
|
id: 'opening-1', type: 'door', lengthCm: 90,
|
|
contact: '', lock: '', invert: false, flipH: false, flipV: false,
|
|
x: 200, y: 200, angle: 0,
|
|
};
|
|
} else if (kind === 'physical') {
|
|
card._physicalDialog = {
|
|
kind: 'partition', id: 'partition-1', cm: '12', length: '1 m',
|
|
};
|
|
} else {
|
|
card._openSpaceDialog('edit', 'dialog-layout');
|
|
}
|
|
await settle();
|
|
const result = layout(kind, language);
|
|
card._openingDialog = null;
|
|
card._physicalDialog = null;
|
|
card._spaceDialog = null;
|
|
await settle();
|
|
return result;
|
|
};
|
|
|
|
const metrics = [];
|
|
for (const language of ['en', 'ru']) {
|
|
metrics.push(await open('opening', language));
|
|
metrics.push(await open('physical', language));
|
|
}
|
|
const space = await open('space', 'ru');
|
|
return { metrics, space };
|
|
});
|
|
|
|
await page.setViewportSize({ width: NARROW_WIDTH, height: 820 });
|
|
|
|
const narrow = await page.evaluate(async () => {
|
|
const card = window.__card;
|
|
const settle = async () => {
|
|
await card.updateComplete;
|
|
await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
|
|
};
|
|
const measure = async (kind) => {
|
|
card._config = { ...(card._config || {}), language: 'ru' };
|
|
card._openingDialog = null;
|
|
card._physicalDialog = null;
|
|
card._spaceDialog = null;
|
|
if (kind === 'opening') {
|
|
card._openingDialog = {
|
|
id: 'opening-1', type: 'door', lengthCm: 90,
|
|
contact: '', lock: '', invert: false, flipH: false, flipV: false,
|
|
x: 200, y: 200, angle: 0,
|
|
};
|
|
} else if (kind === 'physical') {
|
|
card._physicalDialog = {
|
|
kind: 'partition', id: 'partition-1', cm: '12', length: '1 m',
|
|
};
|
|
} else if (kind === 'draft') {
|
|
card._physicalDialog = {
|
|
kind: 'draft', id: 'draft-1', segment: 0, cm: '12', length: '1 m',
|
|
};
|
|
} else {
|
|
card._openSpaceDialog('edit', 'dialog-layout');
|
|
}
|
|
await settle();
|
|
const root = card.shadowRoot || card.renderRoot;
|
|
const dialog = root.querySelector('hp-dialog');
|
|
const surface = dialog?.shadowRoot?.querySelector('.surface');
|
|
const footer = dialog?.querySelector('.dialog-action-footer');
|
|
const danger = footer?.querySelector('.dialog-action-danger');
|
|
const commit = footer?.querySelector('.dialog-action-commit');
|
|
const surfaceRect = surface?.getBoundingClientRect();
|
|
const footerRect = footer?.getBoundingClientRect();
|
|
const footerStyle = footer ? getComputedStyle(footer) : null;
|
|
const dangerRect = danger?.getBoundingClientRect();
|
|
const commitRect = commit?.getBoundingClientRect();
|
|
const innerLeft = footerRect && footerStyle
|
|
? footerRect.left + parseFloat(footerStyle.paddingLeft) : 0;
|
|
const innerRight = footerRect && footerStyle
|
|
? footerRect.right - parseFloat(footerStyle.paddingRight) : 0;
|
|
const buttons = [...(footer?.querySelectorAll('button') || [])];
|
|
const result = {
|
|
kind,
|
|
surfaceWidth: Number((surfaceRect?.width || 0).toFixed(2)),
|
|
insideViewport: !!surfaceRect && surfaceRect.left >= -1
|
|
&& surfaceRect.right <= innerWidth + 1,
|
|
noHorizontalOverflow: !!surface && !!footer
|
|
&& surface.scrollWidth <= surface.clientWidth + 1
|
|
&& footer.scrollWidth <= footer.clientWidth + 1,
|
|
buttonsContained: !!footerRect && buttons.every((button) => {
|
|
const rect = button.getBoundingClientRect();
|
|
return rect.left >= innerLeft - 1 && rect.right <= innerRight + 1
|
|
&& rect.top >= footerRect.top - 1 && rect.bottom <= footerRect.bottom + 1;
|
|
}),
|
|
wrapped: !!dangerRect && !!commitRect && commitRect.top >= dangerRect.bottom - 1,
|
|
buttons: buttons.length,
|
|
};
|
|
card._openingDialog = null;
|
|
card._physicalDialog = null;
|
|
card._spaceDialog = null;
|
|
await settle();
|
|
return result;
|
|
};
|
|
return {
|
|
opening: await measure('opening'),
|
|
physical: await measure('physical'),
|
|
draft: await measure('draft'),
|
|
space: await measure('space'),
|
|
};
|
|
});
|
|
|
|
const checks = {};
|
|
for (const metric of desktop.metrics) {
|
|
const key = `${metric.kind}_${metric.language}`;
|
|
checks[`${key}_medium_shell`] = metric.widePreset
|
|
&& metric.surfaceWidth >= 499 && metric.surfaceWidth <= 501;
|
|
checks[`${key}_three_actions_one_row`] = metric.buttons === 3 && metric.oneRow;
|
|
checks[`${key}_positive_localization_headroom`] = metric.spareWidth > 0;
|
|
checks[`${key}_no_horizontal_overflow`] = metric.noHorizontalOverflow;
|
|
}
|
|
checks.space_dialog_still_uses_medium_shell = desktop.space.widePreset
|
|
&& desktop.space.surfaceWidth >= 499 && desktop.space.surfaceWidth <= 501;
|
|
checks.space_dialog_footer_not_regressed = desktop.space.buttons === 3
|
|
&& desktop.space.oneRow && desktop.space.noHorizontalOverflow;
|
|
|
|
for (const kind of ['opening', 'physical']) {
|
|
const metric = narrow[kind];
|
|
checks[`${kind}_narrow_fits_viewport`] = metric.surfaceWidth <= NARROW_WIDTH * 0.94 + 1
|
|
&& metric.insideViewport && metric.noHorizontalOverflow && metric.buttonsContained;
|
|
checks[`${kind}_narrow_keeps_responsive_wrap`] = metric.buttons === 3 && metric.wrapped;
|
|
}
|
|
checks.draft_four_actions_remain_contained = narrow.draft.buttons === 4
|
|
&& narrow.draft.insideViewport && narrow.draft.noHorizontalOverflow
|
|
&& narrow.draft.buttonsContained;
|
|
checks.space_narrow_not_regressed = narrow.space.buttons === 3
|
|
&& narrow.space.insideViewport && narrow.space.noHorizontalOverflow
|
|
&& narrow.space.buttonsContained;
|
|
|
|
checkAll(checks);
|
|
await finish(browser, {
|
|
checks,
|
|
desktop: {
|
|
dialogs: desktop.metrics,
|
|
longest: desktop.metrics.reduce((longest, metric) => (
|
|
metric.contentWidth > longest.contentWidth ? metric : longest
|
|
)),
|
|
space: desktop.space,
|
|
},
|
|
narrow,
|
|
});
|