import { expect, test } from "@playwright/test"; import { mockConfig, mockHouseholdAndStoreShell, seedAuthStorage, } from "./helpers/e2e"; type MockItem = { id: number; item_id: number; item_name: string; quantity: number; bought: boolean; item_image: string | null; image_mime_type: string | null; added_by_users: string[]; last_added_on: string; item_type: string | null; item_group: string | null; zone: string | null; }; function makeItem( id: number, itemName: string, quantity: number, overrides: Partial = {} ): MockItem { return { id, item_id: id + 500, item_name: itemName, quantity, bought: false, item_image: null, image_mime_type: null, added_by_users: ["Owner User"], last_added_on: "2026-03-28T12:00:00.000Z", item_type: null, item_group: null, zone: "Produce", ...overrides, }; } async function setupBuyModalRoutes( page: import("@playwright/test").Page, initialItems: MockItem[] ) { let activeItems = initialItems.map((item) => ({ ...item })); let recentItems: MockItem[] = []; await mockHouseholdAndStoreShell(page, { household: { name: "Auto Advance House" }, }); await page.route("**/households/1/members", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([ { id: 1, username: "owner", name: "Owner User", display_name: "Owner User", role: "owner" }, ]), }); }); await page.route("**/households/1/stores/10/list/recent", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(recentItems), }); }); await page.route("**/households/1/stores/10/list/suggestions**", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]), }); }); await page.route("**/households/1/stores/10/list/classification**", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ classification: null }), }); }); await page.route("**/households/1/stores/10/list/item**", async (route) => { const request = route.request(); if (request.method() === "PATCH") { const body = request.postDataJSON() as { item_name?: string; quantity_bought?: number | null; }; const itemName = String(body.item_name || "").toLowerCase(); const quantityBought = Number(body.quantity_bought ?? 0); const currentItem = activeItems.find((item) => item.item_name === itemName); if (!currentItem) { await route.fulfill({ status: 404, contentType: "application/json", body: JSON.stringify({ error: { message: "Item not found" } }), }); return; } const remainingQuantity = currentItem.quantity - quantityBought; recentItems = [ { ...currentItem, quantity: quantityBought, bought: true, }, ...recentItems, ]; if (remainingQuantity <= 0) { activeItems = activeItems.filter((item) => item.id !== currentItem.id); } else { activeItems = activeItems.map((item) => item.id === currentItem.id ? { ...item, quantity: remainingQuantity } : item ); } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ message: "Item updated", item: { id: currentItem.id, item_name: currentItem.item_name, quantity: Math.max(remainingQuantity, 0), bought: remainingQuantity <= 0, }, }), }); return; } const url = new URL(request.url()); const itemName = (url.searchParams.get("item_name") || "").toLowerCase(); const item = activeItems.find((entry) => entry.item_name === itemName); await route.fulfill({ status: item ? 200 : 404, contentType: "application/json", body: JSON.stringify(item || { message: "Item not found" }), }); }); await page.route("**/households/1/stores/10/list", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: activeItems, }), }); }); } async function openBuyModal(page: import("@playwright/test").Page, itemName: string) { const row = page.locator(".glist-li").filter({ hasText: itemName }); await row.click(); await expect(page.locator(".confirm-buy-modal")).toBeVisible(); } test("buying an item advances to the next one in the current sort order", async ({ page }) => { await seedAuthStorage(page, { username: "buy-modal-user" }); await mockConfig(page); await setupBuyModalRoutes(page, [ makeItem(1, "milk", 2), makeItem(2, "bread", 5), makeItem(3, "apples", 3), ]); await page.goto("/"); await page.locator(".glist-sort").selectOption("qty-high"); await openBuyModal(page, "bread"); await page.getByRole("button", { name: "Mark as Bought" }).click(); await expect(page.locator(".confirm-buy-item-name")).toHaveText("apples"); }); test("buying the last item in the current order wraps to the first remaining item", async ({ page }) => { await seedAuthStorage(page, { username: "buy-modal-user" }); await mockConfig(page); await setupBuyModalRoutes(page, [ makeItem(1, "apples", 3), makeItem(2, "bread", 5), makeItem(3, "milk", 2), ]); await page.goto("/"); await page.locator(".glist-sort").selectOption("az"); await openBuyModal(page, "milk"); await page.getByRole("button", { name: "Mark as Bought" }).click(); await expect(page.locator(".confirm-buy-item-name")).toHaveText("apples"); }); test("partial buy keeps the item on the list and advances past it", async ({ page }) => { await seedAuthStorage(page, { username: "buy-modal-user" }); await mockConfig(page); await setupBuyModalRoutes(page, [ makeItem(1, "alpha", 1), makeItem(2, "bravo", 3), makeItem(3, "charlie", 5), ]); await page.goto("/"); await page.locator(".glist-sort").selectOption("qty-low"); await openBuyModal(page, "bravo"); await page.locator(".confirm-buy-counter-btn").nth(0).click(); await page.locator(".confirm-buy-counter-btn").nth(0).click(); await page.getByRole("button", { name: "Mark as Bought" }).click(); await expect(page.locator(".confirm-buy-item-name")).toHaveText("charlie"); await expect(page.locator(".glist-li").filter({ hasText: "bravo" }).first()).toContainText("x2"); }); test("buying the only remaining item closes the modal", async ({ page }) => { await seedAuthStorage(page, { username: "buy-modal-user" }); await mockConfig(page); await setupBuyModalRoutes(page, [ makeItem(1, "solo", 1), ]); await page.goto("/"); await openBuyModal(page, "solo"); await page.getByRole("button", { name: "Mark as Bought" }).click(); await expect(page.locator(".confirm-buy-modal")).toBeHidden(); });