From bddae7d489ab120aec80fe1a71d1ad4388a02c01 Mon Sep 17 00:00:00 2001 From: Nico Date: Thu, 4 Jun 2026 11:38:10 -0700 Subject: [PATCH] fix: advance map buy modal after partial buys --- frontend/src/pages/LocationMapManager.jsx | 6 +- frontend/tests/location-map-manager.spec.ts | 93 +++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/LocationMapManager.jsx b/frontend/src/pages/LocationMapManager.jsx index 10aada2..30e6404 100644 --- a/frontend/src/pages/LocationMapManager.jsx +++ b/frontend/src/pages/LocationMapManager.jsx @@ -178,21 +178,23 @@ export default function LocationMapManager() { await markBought(activeHousehold.id, locationId, item.item_name, quantity, true); + let nextBuyModalItems = buyModalItems; if (quantity >= item.quantity) { + nextBuyModalItems = buyModalItems.filter((candidate) => candidate.id !== item.id); updateMapItems((currentItems) => currentItems.filter((candidate) => candidate.id !== item.id)); - setBuyModalItem(getNextMapBuyItem(buyModalItems, resolvedIndex, item.id)); } else { const response = await getItemByName(activeHousehold.id, locationId, item.item_name); const updatedItem = response.data || { ...item, quantity: Math.max(1, Number(item.quantity || 1) - Number(quantity || 1)), }; + nextBuyModalItems = buyModalItems.map((candidate) => (candidate.id === item.id ? updatedItem : candidate)); updateMapItems((currentItems) => currentItems.map((candidate) => (candidate.id === item.id ? updatedItem : candidate)) ); - setBuyModalItem(updatedItem); } + setBuyModalItem(getNextMapBuyItem(nextBuyModalItems, resolvedIndex, item.id)); toast.success("Marked item bought", `Marked item ${item.item_name} as bought`); } catch (error) { diff --git a/frontend/tests/location-map-manager.spec.ts b/frontend/tests/location-map-manager.spec.ts index 382fc5e..50984dd 100644 --- a/frontend/tests/location-map-manager.spec.ts +++ b/frontend/tests/location-map-manager.spec.ts @@ -2352,6 +2352,99 @@ test("viewer can buy selected zone items from the map", async ({ page }) => { await expect(page.getByText("No items assigned to this zone yet.")).toBeVisible(); }); +test("viewer advances map buy modal after partial quantity buys", async ({ page }) => { + await mockMapShell(page); + + const partialItems = [ + { + id: 4301, + item_name: "flour", + quantity: 3, + bought: false, + zone_id: 501, + zone: "Bakery", + added_by_users: ["map-user"], + }, + { + id: 4302, + item_name: "yeast", + quantity: 1, + bought: false, + zone_id: 501, + zone: "Bakery", + added_by_users: ["map-user"], + }, + ]; + const updatedFlour = { ...partialItems[0], quantity: 2 }; + const mapState = { + ...publishedMapState([ + { + id: 1534, + location_map_id: 901, + 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), + items: partialItems, + }; + const boughtPayloads: Array> = []; + + 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/1/locations/10/list/item**", async (route) => { + if (route.request().method() === "GET") { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify(updatedFlour), + }); + return; + } + + boughtPayloads.push(await route.request().postDataJSON()); + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ success: true }), + }); + }); + + await page.goto("/stores/100/locations/10/map"); + await page.locator('[data-object-key="1534"]').locator("rect").first().click(); + await page.getByRole("button", { name: "Mark flour bought" }).click(); + await expect(page.locator(".confirm-buy-item-name")).toHaveText("flour"); + + await page.locator(".confirm-buy-counter-btn").first().click(); + await page.locator(".confirm-buy-counter-btn").first().click(); + await expect(page.locator(".confirm-buy-counter-display")).toHaveValue("1"); + await page.getByRole("button", { name: "Mark as Bought" }).click(); + + await expect.poll(() => boughtPayloads.length).toBe(1); + expect(boughtPayloads[0]).toMatchObject({ + item_name: "flour", + bought: true, + quantity_bought: 1, + }); + await expect(page.locator(".confirm-buy-item-name")).toHaveText("yeast"); + await expect(page.getByRole("button", { name: "Mark flour bought" }).locator("small")).toHaveText("x2"); +}); + test("viewer shows completed zone items without rebuy actions", async ({ page }) => { await mockMapShell(page);