grocery-app/frontend/tests/toast-notifications.spec.ts
2026-05-31 21:06:41 -07:00

286 lines
8.3 KiB
TypeScript

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");
});