From 25b1efc2144d6aafbe359be0a58a26c8dbd87aff Mon Sep 17 00:00:00 2001 From: Nico Date: Tue, 16 Jun 2026 00:25:45 -0700 Subject: [PATCH] fix: keep added map areas in bounds --- .../src/hooks/useLocationMapObjectActions.js | 4 +- frontend/tests/location-map-manager.spec.ts | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/frontend/src/hooks/useLocationMapObjectActions.js b/frontend/src/hooks/useLocationMapObjectActions.js index e66be8d..8bd6c68 100644 --- a/frontend/src/hooks/useLocationMapObjectActions.js +++ b/frontend/src/hooks/useLocationMapObjectActions.js @@ -54,11 +54,11 @@ export default function useLocationMapObjectActions({ remember(); const usedZoneIds = new Set(objects.map((object) => objectZoneId(object)).filter(Boolean)); const nextZone = zones.find((zone) => !usedZoneIds.has(String(zone.id))) || zones[0]; - const nextObject = createClientObject(nextZone, objects.length); + const nextObject = clampObjectToMap(createClientObject(nextZone, objects.length), mapSize); setObjects((currentObjects) => [...currentObjects, nextObject]); setSelectedObjectKey(getObjectKey(nextObject)); setHasUnsavedChanges(true); - }, [canManage, objects, remember, setHasUnsavedChanges, setObjects, setSelectedObjectKey, zones]); + }, [canManage, mapSize, objects, remember, setHasUnsavedChanges, setObjects, setSelectedObjectKey, zones]); const handleDuplicateObject = useCallback(() => { if (!selectedObject) return; diff --git a/frontend/tests/location-map-manager.spec.ts b/frontend/tests/location-map-manager.spec.ts index d71f202..06d2c69 100644 --- a/frontend/tests/location-map-manager.spec.ts +++ b/frontend/tests/location-map-manager.spec.ts @@ -1184,6 +1184,49 @@ test("admin can add the first area from a blank map canvas", async ({ page }) => await expect(page.getByRole("button", { name: "Save Draft" })).toBeEnabled(); }); +test("admin added map areas stay inside the canvas", async ({ page }) => { + await mockMapShell(page); + + const existingObjects = Array.from({ length: 31 }, (_, index) => ({ + id: 5000 + index, + location_map_id: 900, + zone_id: null, + zone_name: null, + type: "zone", + label: `Existing Area ${index + 1}`, + x: 24 + (index % 5) * 128, + y: 24 + Math.floor(index / 5) * 88, + width: 120, + height: 72, + rotation: 0, + locked: false, + visible: true, + sort_order: index + 1, + })); + const mapState = { + ...draftMapState(existingObjects, true), + zones: [], + }; + + await page.route("**/households/1/locations/10/map", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(mapState), + }); + }); + + await page.goto("/stores/100/locations/10/map"); + await page.getByRole("button", { name: "Continue Editing" }).click(); + await page.getByRole("button", { name: "Add Area" }).click(); + + const newArea = page.getByRole("button", { name: "Map area New Area" }); + await expect(newArea).toHaveAttribute("aria-pressed", "true"); + await expect(newArea).toHaveAttribute("x", "780"); + await expect(newArea).toHaveAttribute("y", "570"); + await expect(page.locator(".location-map-status")).toHaveText("Unsaved Draft"); +}); + test("admin view mode previews the edited draft when a saved draft exists", async ({ page }) => { await mockMapShell(page);