fix: show furniture-occupied tiles in the floor plan editor

The editor never requested occupied tiles, so tiles holding furniture
were indistinguishable from empty floor and could be edited/voided.

- request GetOccupiedTilesMessageComposer when the editor opens
- handle RoomOccupiedTilesMessageEvent -> SET_OCCUPIED_TILES
- new Tile.occupied flag (kept separate from `blocked`/void): occupied
  tiles render with a distinct marker and are protected from PAINT/
  ERASE/ADJUST and brush-to-selection edits
- occupied is purely informational and never changes the saved tilemap
  (no accidental voiding of floor under furni)

Tests: reducer cases for SET_OCCUPIED_TILES + edit protection; container
test asserts the occupied event is non-destructive on save; route the
canvas pointer test through elementFromPoint (jsdom has no getScreenCTM).
This commit is contained in:
medievalshell
2026-05-28 09:20:20 +02:00
parent 7a65e5bf6d
commit 48ed3ad7ba
7 changed files with 87 additions and 7 deletions
@@ -40,11 +40,18 @@ describe('FloorplanCanvasSVG', () =>
const dispatch = vi.fn();
const { container } = render(<FloorplanCanvasSVG state={ state } dispatch={ dispatch } />);
const svg = container.querySelector('svg') as SVGSVGElement;
svg.getBoundingClientRect = () => ({ left: 0, top: 0, right: 2048, bottom: 1024, width: 2048, height: 1024, x: 0, y: 0, toJSON: () => ({}) });
// usePointerToTile resolves the tile via document.elementFromPoint first
// (the tile polygons carry data-row/data-col). jsdom returns null and has
// no SVGSVGElement.getScreenCTM, so point the hit-test at the tile polygon.
const tilePoly = container.querySelector('polygon[data-row="0"][data-col="0"]') as Element;
// jsdom's document has no elementFromPoint at all — define it for this test.
const prevEfp = (document as { elementFromPoint?: unknown }).elementFromPoint;
(document as unknown as { elementFromPoint: (x: number, y: number) => Element | null }).elementFromPoint = () => tilePoly;
fireEvent.pointerDown(svg, { clientX: 1024, clientY: 0, pointerId: 1 });
expect(dispatch).toHaveBeenCalled();
const call = dispatch.mock.calls[0][0];
expect(call.type).toBe('PAINT_TILE');
(document as { elementFromPoint?: unknown }).elementFromPoint = prevEfp;
});
it('zoom in/out buttons adjust the viewBox', () =>