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", "toast-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, }), }); }); } test("login failure shows inline error and error toast", async ({ page }) => { await mockConfig(page); await page.route("**/auth/login", async (route) => { await route.fulfill({ status: 401, contentType: "application/json", body: JSON.stringify({ message: "Invalid credentials" }), }); }); await page.goto("/login"); await page.getByPlaceholder("Username").fill("bad-user"); await page.getByPlaceholder("Password").fill("bad-password"); await page.getByRole("button", { name: "Login" }).click(); await expect(page.getByText("Invalid credentials", { exact: true })).toBeVisible(); await expect(page.locator(".action-toast.action-toast-error")).toContainText("Login failed"); await expect(page.locator(".action-toast.action-toast-error")).toContainText("Invalid credentials"); }); test("manage stores add success shows success toast", async ({ page }) => { await seedAuthStorage(page); await mockConfig(page); let stores = [ { id: 10, household_store_id: 100, name: "Costco", location_name: "Default Location", display_name: "Costco", is_default: true, }, ]; await page.route("**/households", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ id: 1, name: "Toast Home", role: "admin", invite_code: "ABCD1234" }]), }); }); await page.route("**/households/1/stores", async (route) => { const request = route.request(); if (request.method() === "GET") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify(stores), }); return; } if (request.method() === "POST") { const body = request.postDataJSON() as { name?: string }; const name = body.name || "New Store"; stores = [ ...stores, { id: 11, household_store_id: 101, name, location_name: "Default Location", display_name: name, is_default: false, }, ]; await route.fulfill({ status: 201, contentType: "application/json", body: JSON.stringify({ store: stores[stores.length - 1] }), }); return; } await route.fulfill({ status: 405 }); }); await page.route("**/households/1/locations/10/zones", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ zones: [] }), }); }); await page.goto("/manage?tab=stores"); const addStoreForm = page.locator(".add-store-inline"); await addStoreForm.getByLabel("Store name").fill("Stater Bros"); await addStoreForm.getByRole("button", { name: "Add" }).click(); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Created store"); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Stater Bros"); }); test("manage stores add failure shows normalized error toast", async ({ page }) => { await seedAuthStorage(page); await mockConfig(page); await page.route("**/households", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ id: 1, name: "Toast Home", role: "admin", invite_code: "ABCD1234" }]), }); }); await page.route("**/households/1/stores", async (route) => { const request = route.request(); if (request.method() === "GET") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([ { id: 10, household_store_id: 100, name: "Costco", location_name: "Default Location", display_name: "Costco", is_default: true, }, ]), }); return; } if (request.method() === "POST") { await route.fulfill({ status: 400, contentType: "application/json", body: JSON.stringify({ error: { message: "Store already linked to household" }, }), }); return; } await route.fulfill({ status: 405 }); }); await page.route("**/households/1/locations/10/zones", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ zones: [] }), }); }); await page.goto("/manage?tab=stores"); const addStoreForm = page.locator(".add-store-inline"); await addStoreForm.getByLabel("Store name").fill("Costco"); await addStoreForm.getByRole("button", { name: "Add" }).click(); await expect(page.locator(".action-toast.action-toast-error")).toContainText("Create store failed"); await expect(page.locator(".action-toast.action-toast-error")).toContainText("Store already linked to household"); }); test("invite accept JOINED shows success toast", async ({ page }) => { await seedAuthStorage(page); await mockConfig(page); await page.route("**/api/invite-links/toast-token", async (route) => { const request = route.request(); if (request.method() === "GET") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ link: { token: "toast-token", status: "ACTIVE", viewerStatus: null, active_policy: "AUTO_ACCEPT", group_name: "Toast Group", }, }), }); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ result: { status: "JOINED", group: { name: "Toast Group" }, }, }), }); }); await page.route("**/households", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ id: 1, name: "Toast Home", role: "member", invite_code: "ABCD1234" }]), }); }); await page.goto("/invite/toast-token"); await page.getByRole("button", { name: "Join Group" }).click(); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Joined group"); await expect(page.locator(".action-toast.action-toast-success")).toContainText("Toast Group"); }); test("invite accept PENDING shows info toast", async ({ page }) => { await seedAuthStorage(page); await mockConfig(page); await page.route("**/api/invite-links/pending-token", async (route) => { const request = route.request(); if (request.method() === "GET") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ link: { token: "pending-token", status: "ACTIVE", viewerStatus: null, active_policy: "APPROVAL_REQUIRED", group_name: "Pending Group", }, }), }); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ result: { status: "PENDING", group: { name: "Pending Group" }, }, }), }); }); await page.route("**/households", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ id: 1, name: "Toast Home", role: "member", invite_code: "ABCD1234" }]), }); }); await page.goto("/invite/pending-token"); await page.getByRole("button", { name: "Join Group" }).click(); await expect(page.locator(".action-toast.action-toast-info")).toContainText("Join request sent"); await expect(page.locator(".action-toast.action-toast-info")).toContainText("Pending Group"); });