import { expect, test } from "@playwright/test"; function seedAuthStorage(page: import("@playwright/test").Page) { return page.addInitScript(() => { localStorage.setItem("token", "test-token"); localStorage.setItem("userId", "1"); localStorage.setItem("role", "admin"); localStorage.setItem("username", "catalog-user"); }); } async function mockConfig(page: import("@playwright/test").Page) { await page.route("**/config", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ maxFileSizeMB: 20, maxImageDimension: 800, imageQuality: 85, }), }); }); } async function mockHouseholdAndStoreShell(page: import("@playwright/test").Page) { await page.route("**/households", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([ { id: 1, name: "Catalog House", role: "admin", invite_code: "ABCD1234" }, ]), }); }); await page.route("**/stores/household/1", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([ { id: 10, name: "Costco", location: "Warehouse", is_default: true }, ]), }); }); } test("manage stores lets admins import and curate available items", async ({ page }) => { await seedAuthStorage(page); await mockConfig(page); await mockHouseholdAndStoreShell(page); let availableItems = [ { item_id: 501, item_name: "milk", item_image: null, image_mime_type: null, item_type: "dairy", item_group: "Milk", zone: "Dairy & Refrigerated", }, ]; await page.route("**/stores", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ id: 10, name: "Costco" }]), }); }); await page.route("**/households/1/stores/10/available-items/import-current", async (route) => { availableItems = [ ...availableItems, { item_id: 777, item_name: "granola", item_image: null, image_mime_type: null, item_type: null, item_group: null, zone: null, }, ]; await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ message: "Imported current list items", imported_count: 1, }), }); }); await page.route("**/households/1/stores/10/available-items*", async (route) => { const request = route.request(); const url = new URL(request.url()); const query = (url.searchParams.get("query") || "").toLowerCase(); if (request.method() === "GET") { const filteredItems = availableItems.filter((item) => item.item_name.includes(query)); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: filteredItems }), }); return; } if (request.method() === "POST") { availableItems = [ ...availableItems, { item_id: 888, item_name: "trail mix", item_image: null, image_mime_type: null, item_type: "snack", item_group: "Trail Mix", zone: "Snacks & Candy", }, ]; await route.fulfill({ status: 201, contentType: "application/json", body: JSON.stringify({ message: "Available item added", item: availableItems[availableItems.length - 1], }), }); return; } await route.fulfill({ status: 500 }); }); await page.route("**/households/1/stores/10/available-items/888", async (route) => { if (route.request().method() === "DELETE") { availableItems = availableItems.filter((item) => item.item_id !== 888); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ message: "Available item removed" }), }); return; } await route.fulfill({ status: 500 }); }); await page.goto("/manage?tab=stores"); const storeCard = page.locator(".store-card").filter({ hasText: "Costco" }); await expect(storeCard).toBeVisible(); await storeCard.getByRole("button", { name: "Manage" }).click(); await expect(storeCard.getByText("milk")).toBeVisible(); await storeCard.getByRole("button", { name: "Import Current List" }).click(); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Imported current list items"); await expect(storeCard.getByText("granola")).toBeVisible(); await storeCard.getByRole("button", { name: "Add Item" }).click(); const editorModal = page.locator(".available-item-editor-modal"); await expect(editorModal).toBeVisible(); await editorModal.getByLabel("Item Name").fill("trail mix"); await editorModal.locator(".available-item-editor-select").nth(0).selectOption("snack"); await editorModal.locator(".available-item-editor-select").nth(1).selectOption("Trail Mix"); await editorModal.locator(".available-item-editor-select").nth(2).selectOption("Snacks & Candy"); await editorModal.getByRole("button", { name: "Add Item" }).click(); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Added store item"); await expect(storeCard.getByText("trail mix")).toBeVisible(); page.once("dialog", (dialog) => dialog.accept()); await storeCard.locator(".store-available-items-card").filter({ hasText: "trail mix" }).getByRole("button", { name: "Remove" }).click(); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Removed store item"); }); test("grocery picker uses available items and preserves quantity and assignee", async ({ page }) => { await seedAuthStorage(page); await mockConfig(page); await mockHouseholdAndStoreShell(page); const members = [ { id: 1, username: "owner", name: "Owner User", display_name: "Owner User", role: "owner" }, { id: 2, username: "casey", name: "Casey Client", display_name: "Casey Client", role: "member" }, ]; let lastAddBody = ""; let currentItems: Array<{ 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; }> = []; await page.route("**/households/1/members", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(members), }); }); await page.route("**/households/1/stores/10/available-items*", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: [ { item_id: 600, item_name: "bananas", item_image: null, image_mime_type: null, item_type: "produce", item_group: "Fresh Fruit", zone: "Fresh Produce", }, ], }), }); }); await page.route("**/households/1/stores/10/list/recent", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([]), }); }); await page.route("**/households/1/stores/10/list/suggestions**", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ item_name: "bananas" }]), }); }); await page.route("**/households/1/stores/10/list/item**", async (route) => { const request = route.request(); const url = new URL(request.url()); const itemName = (url.searchParams.get("item_name") || "").toLowerCase(); const item = currentItems.find((candidate) => candidate.item_name === itemName); if (request.method() === "GET") { await route.fulfill({ status: item ? 200 : 404, contentType: "application/json", body: JSON.stringify(item || { message: "Item not found" }), }); return; } await route.fulfill({ status: 500 }); }); await page.route("**/households/1/stores/10/list/add", async (route) => { lastAddBody = route.request().postData() || ""; currentItems = [ { id: 201, item_id: 600, item_name: "bananas", quantity: 3, bought: false, item_image: null, image_mime_type: null, added_by_users: ["Casey Client"], last_added_on: "2026-03-28T12:00:00.000Z", item_type: "produce", item_group: "Fresh Fruit", zone: "Fresh Produce", }, ]; await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ message: "Item added", item: { id: 201, item_name: "bananas", quantity: 3, bought: false, }, }), }); }); await page.route("**/households/1/stores/10/list", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: currentItems }), }); }); await page.goto("/"); await page.getByRole("button", { name: "Others" }).click(); const assignModal = page.locator(".assign-item-for-modal"); await assignModal.getByRole("button", { name: "Select member" }).click(); await page.locator("body > .assign-item-for-dropdown-menu").getByRole("option", { name: "Casey Client" }).click(); await assignModal.getByRole("button", { name: "Confirm" }).click(); await page.getByRole("button", { name: "+" }).click(); await page.getByRole("button", { name: "+" }).click(); await expect(page.locator(".add-item-form-quantity-input")).toHaveValue("3"); await page.getByRole("button", { name: "Store Items" }).click(); const pickerModal = page.locator(".available-items-picker-modal"); await expect(pickerModal).toBeVisible(); await pickerModal.getByRole("button", { name: /bananas/i }).click(); await page.getByRole("button", { name: "Skip All" }).click(); await expect(page.locator(".glist-li").filter({ hasText: "bananas" })).toContainText("Casey Client"); expect(lastAddBody).toContain('name="quantity"'); expect(lastAddBody).toContain("3"); expect(lastAddBody).toContain('name="added_for_user_id"'); expect(lastAddBody).toContain("2"); });