mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
166 lines
7.6 KiB
JavaScript
166 lines
7.6 KiB
JavaScript
/** Issue #173/#478: Walls use canonical partitions and session-only chains. */
|
|
import { launch, checkAll, finish } from './serve.mjs';
|
|
|
|
const { page, browser } = await launch({ width: 1100, height: 820 }, 1);
|
|
|
|
const result = await page.evaluate(async () => {
|
|
const out = {};
|
|
const card = window.__card;
|
|
const root = () => card.shadowRoot || card.renderRoot;
|
|
const update = async () => { card.requestUpdate(); await card.updateComplete; };
|
|
const space = () => card._serverCfg.spaces[0];
|
|
const keyDown = async (key, init = {}) => {
|
|
window.dispatchEvent(new KeyboardEvent('keydown', {
|
|
key, bubbles: true, cancelable: true, ...init,
|
|
}));
|
|
await update();
|
|
};
|
|
const click = async (x, y) => {
|
|
const stage = root().querySelector('.stage');
|
|
const rect = stage.getBoundingClientRect();
|
|
const view = card._viewOr(card._baseVb());
|
|
card._markupClick(new MouseEvent('click', {
|
|
clientX: rect.left + ((x - view.x) / view.w) * rect.width,
|
|
clientY: rect.top + ((y - view.y) / view.h) * rect.height,
|
|
bubbles: true, cancelable: true,
|
|
}));
|
|
await update();
|
|
};
|
|
const reset = async () => {
|
|
card._serverCfg = {
|
|
model_version: 10,
|
|
spaces: [{
|
|
id: 'walls', title: 'Walls', cell_cm: 5, view_box: [0, 0, 1, 0.7],
|
|
rooms: [], wall_segments: [], partitions: [], openings: [], wall_columns: [],
|
|
}],
|
|
markers: [], settings: {},
|
|
};
|
|
card._space = 'walls'; card._layout = {}; card._cfgEpoch++;
|
|
card._modelCache = null; card._frame = null; card._geometryHistory.clear();
|
|
card._path = []; card._activeWallChainId = null;
|
|
card._activeWallChainPartitionIds = []; card._wallChainSegmentCms = [];
|
|
card._wallChainRedo = []; card._wallFaceBatch = null; card._roomDialog = false;
|
|
card._toast = ''; card._setMode('plan'); card._tool = 'draw';
|
|
await update();
|
|
};
|
|
const draw = async (points, cms = []) => {
|
|
card._drawWallField = String(cms[0] ?? 15);
|
|
await click(...points[0]);
|
|
for (let index = 1; index < points.length; index++) {
|
|
card._drawWallField = String(cms[index - 1] ?? 15);
|
|
await click(...points[index]);
|
|
}
|
|
};
|
|
|
|
await reset();
|
|
const toolbarLabels = [...root().querySelectorAll(
|
|
'.planbar .editbar-tools > button, .wallsgroup > button',
|
|
)].map((button) => (button.textContent || '').trim());
|
|
out.oneWallsButton = toolbarLabels.filter((label) => ['Walls', 'Стены'].includes(label)).length === 1;
|
|
out.partitionButtonRemoved = !toolbarLabels.some((label) => ['Partition', 'Перегородка'].includes(label));
|
|
out.splitRemains = toolbarLabels.some((label) => ['Split', 'Разделить'].includes(label));
|
|
|
|
await draw([[100, 100], [300, 100], [300, 250]], [12, 23]);
|
|
out.eachAcceptedSegmentIsCanonical = space().partitions.length === 2
|
|
&& JSON.stringify(space().partitions.map((item) => item.cm)) === JSON.stringify([12, 23])
|
|
&& card._activeWallChainPartitionIds.length === 2
|
|
&& !('room_drafts' in space());
|
|
const historyBeforeFinish = card._geometryHistory.undoName;
|
|
card._activateMarkupTool('select'); await update();
|
|
out.toolChangeOnlyEndsSession = space().partitions.length === 2
|
|
&& card._path.length === 0 && card._activeWallChainPartitionIds.length === 0
|
|
&& card._geometryHistory.undoName === historyBeforeFinish;
|
|
|
|
await reset();
|
|
await draw([[100, 100], [300, 100], [300, 250]], [12, 23]);
|
|
const beforeEscape = JSON.stringify(space().partitions);
|
|
await keyDown('Escape');
|
|
out.escapeKeepsAcceptedWalls = card._tool === 'draw' && card._path.length === 0
|
|
&& JSON.stringify(space().partitions) === beforeEscape;
|
|
const afterFirstEscape = JSON.stringify(space());
|
|
await keyDown('Escape');
|
|
out.repeatedEscapeIsNoop = JSON.stringify(space()) === afterFirstEscape;
|
|
|
|
await reset();
|
|
const beforeFirstPoint = JSON.stringify(space());
|
|
await click(200, 200); await keyDown('Escape');
|
|
out.escapeDropsOnlyTransientFirstPoint = card._path.length === 0
|
|
&& space().partitions.length === 0 && JSON.stringify(space()) === beforeFirstPoint;
|
|
|
|
await reset();
|
|
await draw([[100, 450], [300, 450], [300, 650]]);
|
|
await keyDown('z', { code: 'KeyZ', ctrlKey: true });
|
|
out.ctrlZRemovesLastAcceptedWall = space().partitions.length === 1
|
|
&& card._path.length === 2 && card._activeWallChainPartitionIds.length === 1;
|
|
|
|
await reset();
|
|
await draw([[100, 100], [300, 100], [300, 300], [100, 300], [100, 100]]);
|
|
out.closedFaceOpensDialog = card._roomDialog && card._wallFaceBatch?.candidates.length === 1
|
|
&& space().partitions.length === 4;
|
|
card._nameSel = 'Graph room'; card._saveRoom(); await update();
|
|
out.acceptConsumesCoincidentPartitions = space().rooms.length === 1
|
|
&& space().rooms[0].name === 'Graph room' && !space().partitions?.length
|
|
&& !('room_drafts' in space());
|
|
|
|
await reset();
|
|
await draw([[500, 100], [700, 100], [700, 300], [500, 300], [500, 100]]);
|
|
const beforeCancel = JSON.stringify(space().partitions);
|
|
card._roomDialogCancel(); await update();
|
|
out.cancelKeepsWallsAndRestoresSession = !card._roomDialog && !card._wallFaceBatch
|
|
&& JSON.stringify(space().partitions) === beforeCancel
|
|
&& card._path.length === 5 && card._activeWallChainPartitionIds.length === 4;
|
|
|
|
await reset();
|
|
await draw([[100, 100], [300, 100], [300, 300], [100, 300], [100, 100]]);
|
|
const geometryBeforeNullModel = JSON.stringify(space());
|
|
const originalSpaceModel = card._spaceModel.bind(card);
|
|
card._spaceModel = () => space().rooms.length ? null : originalSpaceModel();
|
|
try {
|
|
card._nameSel = 'Must roll back'; card._saveRoom(); await update();
|
|
} finally {
|
|
card._spaceModel = originalSpaceModel;
|
|
}
|
|
out.nullModelRollsBackWholeRoomMutation = JSON.stringify(space()) === geometryBeforeNullModel
|
|
&& space().rooms.length === 0 && card._path.length === 5
|
|
&& card._activeWallChainPartitionIds.length === 4;
|
|
|
|
await reset();
|
|
card._path = [[100, 100], [300, 100], [300, 300], [100, 300], [100, 100]];
|
|
card._wallChainSegmentCms = [15, 15, 15]; card._closingWallCm = 15;
|
|
const classicBeforeNullModel = JSON.stringify(space());
|
|
const originalClassicSpaceModel = card._spaceModel.bind(card);
|
|
card._spaceModel = () => space().rooms.length ? null : originalClassicSpaceModel();
|
|
try {
|
|
card._nameSel = 'Classic must roll back'; card._commitRoom(); await update();
|
|
} finally {
|
|
card._spaceModel = originalClassicSpaceModel;
|
|
}
|
|
out.classicNullModelRollsBackWholeRoomMutation = JSON.stringify(space()) === classicBeforeNullModel
|
|
&& space().rooms.length === 0 && card._path.length === 5;
|
|
|
|
await reset();
|
|
space().partitions = [{
|
|
id: 'foreign-overlap', a: [0.05, 0.1], b: [0.2, 0.1], cm: 15,
|
|
}, {
|
|
id: 'foreign-unrelated', a: [0.6, 0.5], b: [0.7, 0.5], cm: 15,
|
|
}];
|
|
card._cfgEpoch++; card._modelCache = null;
|
|
await draw([[100, 100], [300, 100], [300, 300], [100, 300], [100, 100]]);
|
|
card._nameSel = 'Partial overlap'; card._saveRoom(); await update();
|
|
const acceptedPartitions = space().partitions || [];
|
|
const tail = acceptedPartitions.find((item) => item.id === 'foreign-overlap');
|
|
const unrelated = acceptedPartitions.find((item) => item.id === 'foreign-unrelated');
|
|
out.acceptSplitsOnlyCoincidentForeignInterval = acceptedPartitions.length === 2
|
|
&& JSON.stringify(tail?.a) === JSON.stringify([0.05, 0.1])
|
|
&& JSON.stringify(tail?.b) === JSON.stringify([0.1, 0.1])
|
|
&& JSON.stringify(unrelated) === JSON.stringify({
|
|
id: 'foreign-unrelated', a: [0.6, 0.5], b: [0.7, 0.5], cm: 15,
|
|
});
|
|
card._openAlignDialog(); await update();
|
|
out.acceptedRoomIsOptimizeFixedPoint = card._alignDialog?.report?.partitionsReconciled === 0
|
|
&& card._alignDialog?.changed === false;
|
|
return out;
|
|
});
|
|
|
|
await finish(browser, checkAll(result));
|