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 opens a modal to edit and delete household store 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", has_managed_settings: true, }, { item_id: 777, item_name: "apples", item_image: null, image_mime_type: null, item_type: null, item_group: null, zone: null, has_managed_settings: false, }, ]; 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*", 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, catalog_ready: true }), }); return; } await route.fulfill({ status: 500 }); }); await page.route("**/households/1/stores/10/available-items/777", async (route) => { if (route.request().method() === "PATCH") { availableItems = availableItems.map((item) => item.item_id === 777 ? { ...item, item_type: "produce", item_group: "Fruits", zone: "Produce & Fresh Vegetables", has_managed_settings: true, } : item ); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ message: "Available item updated", item: availableItems.find((item) => item.item_id === 777), }), }); return; } await route.fulfill({ status: 500 }); }); await page.route("**/households/1/stores/10/available-items/501", async (route) => { if (route.request().method() === "DELETE") { availableItems = availableItems.filter((item) => item.item_id !== 501); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ message: "Store item deleted" }), }); 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 expect(storeCard.getByRole("button", { name: "Manage Items" })).toBeVisible(); await storeCard.getByRole("button", { name: "Manage Items" }).click(); const managerModal = page.locator(".store-items-modal"); await expect(managerModal).toBeVisible(); await expect(managerModal.getByText("milk", { exact: true })).toBeVisible(); await expect(managerModal.getByText("apples", { exact: true })).toBeVisible(); await managerModal.locator(".store-items-table-row").filter({ hasText: "apples" }).getByRole("button", { name: "Edit Settings" }).click(); const editorModal = page.locator(".available-item-editor-modal"); await expect(editorModal).toBeVisible(); await expect(editorModal.getByLabel("Item Name")).toBeDisabled(); await editorModal.locator(".available-item-editor-select").nth(0).selectOption("produce"); await editorModal.locator(".available-item-editor-select").nth(1).selectOption("Fruits"); await editorModal.locator(".available-item-editor-select").nth(2).selectOption("Produce & Fresh Vegetables"); await editorModal.getByRole("button", { name: "Save Changes" }).click(); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Updated store item"); await expect(managerModal.getByText("produce | Fruits | Produce & Fresh Vegetables")).toBeVisible(); await managerModal.locator(".store-items-table-row").filter({ hasText: "milk" }).getByRole("button", { name: "Delete Item" }).click(); const confirmModal = page.locator(".confirm-slide-modal"); await expect(confirmModal).toBeVisible(); await expect(confirmModal.getByText("Delete milk?")).toBeVisible(); const slider = confirmModal.locator(".confirm-slide-handle"); const track = confirmModal.locator(".confirm-slide-track"); const sliderBox = await slider.boundingBox(); const trackBox = await track.boundingBox(); if (!sliderBox || !trackBox) { throw new Error("Confirm slide control was not measurable"); } await page.mouse.move(sliderBox.x + sliderBox.width / 2, sliderBox.y + sliderBox.height / 2); await page.mouse.down(); await page.mouse.move(trackBox.x + trackBox.width - 4, sliderBox.y + sliderBox.height / 2, { steps: 8 }); await page.mouse.up(); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Deleted store item"); await expect(managerModal.getByText("milk")).toHaveCount(0); }); test("grocery page remains unchanged and does not show a store items picker", async ({ page }) => { await seedAuthStorage(page); await mockConfig(page); await mockHouseholdAndStoreShell(page); 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([]), }); }); 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/item**", async (route) => { await route.fulfill({ status: 404, contentType: "application/json", body: JSON.stringify({ 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: [] }), }); }); await page.goto("/"); await expect(page.getByRole("button", { name: "Store Items" })).toHaveCount(0); await expect(page.locator(".available-items-picker-modal")).toHaveCount(0); });