diff --git a/frontend/src/pages/LocationMapManager.jsx b/frontend/src/pages/LocationMapManager.jsx index ef8c4fc..360b3d3 100644 --- a/frontend/src/pages/LocationMapManager.jsx +++ b/frontend/src/pages/LocationMapManager.jsx @@ -166,7 +166,12 @@ export default function LocationMapManager() { const target = event.target instanceof Element ? event.target : null; const button = target?.closest("button"); - if (button?.classList.contains("user-dropdown-logout")) { + const isSwitchingHousehold = Boolean( + button?.classList.contains("household-option") && + !button.classList.contains("create-household-btn") && + !button.closest(".household-option-row.active") + ); + if (button?.classList.contains("user-dropdown-logout") || isSwitchingHousehold) { event.preventDefault(); event.stopPropagation(); guardedClickTargetRef.current = button; diff --git a/frontend/tests/location-map-manager.spec.ts b/frontend/tests/location-map-manager.spec.ts index fdb6792..9947644 100644 --- a/frontend/tests/location-map-manager.spec.ts +++ b/frontend/tests/location-map-manager.spec.ts @@ -154,17 +154,19 @@ function draftAndPublishedMapState( async function mockMapShell( page: Page, - household = adminHousehold, + household: typeof adminHousehold | Array = adminHousehold, locations: Array = [storeLocation] ) { - await seedAuthStorage(page, { username: "map-user", role: household.role }); + const households = Array.isArray(household) ? household : [household]; + const primaryHousehold = households[0] || adminHousehold; + await seedAuthStorage(page, { username: "map-user", role: primaryHousehold.role }); await mockConfig(page); await page.route("**/households", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", - body: JSON.stringify([household]), + body: JSON.stringify(households), }); }); @@ -1565,6 +1567,99 @@ test("admin logout warns when draft edits are unsaved", async ({ page }) => { expect(logoutCalls).toBe(1); }); +test("admin household switch warns when draft edits are unsaved", async ({ page }) => { + const secondHousehold = { + id: 2, + name: "Second House", + role: "admin", + invite_code: "EFGH5678", + }; + await mockMapShell(page, [adminHousehold, secondHousehold]); + + const mapState = draftMapState([ + { + id: 1374, + location_map_id: 900, + zone_id: 501, + zone_name: "Bakery", + type: "zone", + label: "Bakery", + x: 40, + y: 40, + width: 260, + height: 160, + rotation: 0, + locked: false, + visible: true, + sort_order: 1, + }, + ], true); + const secondHouseholdMapState = { + ...noMapState(true), + location: { + ...storeLocation, + household_id: 2, + name: "Second Costco", + display_name: "Second Costco - Eastvale", + }, + }; + + let secondHouseholdMapRequests = 0; + await page.route("**/households/1/locations/10/map", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(mapState), + }); + }); + await page.route("**/households/2/stores", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify([{ ...storeLocation, household_id: 2 }]), + }); + }); + await page.route("**/households/2/locations/10/zones", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ zones }), + }); + }); + await page.route("**/households/2/locations/10/map", async (route) => { + secondHouseholdMapRequests += 1; + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(secondHouseholdMapState), + }); + }); + + await page.goto("/stores/100/locations/10/map"); + + await page.getByRole("button", { name: "Continue Editing" }).click(); + await page.getByRole("button", { name: "Edit Areas" }).click(); + await page.locator(".location-map-object", { hasText: "Bakery" }).locator("rect").first().click(); + await page.getByRole("textbox", { name: "Label" }).fill("Unsaved Household Bakery"); + + await page.locator(".household-switcher-toggle").click(); + await page.getByRole("button", { name: "Second House", exact: true }).click(); + + await expect(page.getByRole("heading", { name: "Leave with unsaved changes?" })).toBeVisible(); + expect(secondHouseholdMapRequests).toBe(0); + await page.getByRole("button", { name: "Cancel" }).click(); + await expect(page.locator(".household-switcher-toggle .household-name")).toHaveText("Map House"); + await expect(page.getByRole("textbox", { name: "Label" })).toHaveValue("Unsaved Household Bakery"); + expect(secondHouseholdMapRequests).toBe(0); + + await page.getByRole("button", { name: "Second House", exact: true }).click(); + await confirmSlide(page); + + await expect(page.locator(".household-switcher-toggle .household-name")).toHaveText("Second House"); + await expect(page.getByRole("heading", { name: "No Map" })).toBeVisible(); + expect(secondHouseholdMapRequests).toBe(1); +}); + test("admin publish saves pending map edits before publishing", async ({ page }) => { await mockMapShell(page);