feat: preview furniture placement

Issue: #359
User-Visible: yes
This commit is contained in:
Matysh
2026-08-29 00:34:06 +03:00
parent 3c281bb12c
commit 8b66d67d3d
29 changed files with 942 additions and 493 deletions
+34 -1
View File
@@ -6,7 +6,8 @@ import assert from 'node:assert/strict';
import {
FURNITURE, FURNITURE_GROUPS, furnitureSymbol, furnitureOfGroup,
furnitureDefaultCm, furniturePathD, furnitureGraphic, furnitureCorners, furnitureResize,
snapFurnitureToWall, cmToNorm, normToCm, clampFurnSize, clampFurnCm,
snapFurnitureToWall, resolveFurniturePlacement,
cmToNorm, normToCm, clampFurnSize, clampFurnCm,
FURN_MIN_N, FURN_MIN_CM, FURN_MAX_CM, FURN_WALL_CELLS,
} from '../test-build/furniture.js';
import { roomEdges } from '../test-build/logic.js';
@@ -185,6 +186,38 @@ test('the default reach is six cells — thirty centimetres on a default plan',
closeTo(GRID_PITCH * FURN_WALL_CELLS * (5 / GRID_PITCH), 30); // cells x cell_cm
});
test('preview and commit share one deterministic furniture placement resolver', () => {
const input = {
symbol: 'sofa', widthCm: 180, depthCm: 90,
point: [300, 112], canvasW: 1000, canvasH: 1000,
cellCm: 5, gridPitch: GRID_PITCH, walls: EDGES,
wallReach: 30,
};
const preview = resolveFurniturePlacement(input);
const commit = resolveFurniturePlacement({ ...input });
assert.deepEqual(preview, commit);
assert.equal(preview.symbol, 'sofa');
closeTo((preview.x + preview.w / 2) * 1000, 300, GRID_PITCH);
closeTo((preview.y + preview.h / 2) * 1000, 100 + preview.h * 500);
assert.equal(preview.angle, 0);
});
test('the shared placement resolver supports Shift/free, canvas guards and unknown symbols', () => {
const base = {
symbol: 'sofa', widthCm: 180, depthCm: 90,
point: [300, 112], canvasW: 1000, canvasH: 1000,
cellCm: 5, gridPitch: GRID_PITCH, walls: EDGES,
wallReach: 30,
};
const free = resolveFurniturePlacement({ ...base, free: true });
closeTo((free.x + free.w / 2) * 1000, 300);
closeTo((free.y + free.h / 2) * 1000, 112);
const guarded = resolveFurniturePlacement({ ...base, point: [-1e9, -1e9], free: true });
assert.equal(guarded.x, -5000);
assert.equal(guarded.y, -5000);
assert.equal(resolveFurniturePlacement({ ...base, symbol: 'future_unknown_symbol' }), null);
});
// ------------------------------- the frame ----------------------------------
test('the corners of an unrotated box are the box; a rotated one turns about its centre', () => {
+11
View File
@@ -835,6 +835,17 @@ test('issue 159 goldens cover both palette levels and every plan-art compatibili
new Set(['sofa', 'fridge', 'sofa_corner_right']));
});
test('issue 359 has one deterministic light-theme furniture placement preview golden', () => {
const scenario = GOLDEN_SCENARIOS.find((item) => item.id === 'furniture-placement-preview-light');
assert.ok(scenario);
assert.equal(scenario.mode, 'decor');
assert.equal(scenario.theme, 'light');
assert.equal(scenario.capture, 'stage');
assert.deepEqual(scenario.furniturePlacementPreview, {
symbol: 'sofa', widthCm: 180, depthCm: 90, pointer: [0.35, 0.90],
});
});
test('device dialog goldens expose the complete light-source controls at desktop and mobile widths', () => {
const dialogs = GOLDEN_SCENARIOS.filter((scenario) => scenario.id.startsWith('device-dialog-'));
assert.equal(dialogs.length, 3);