243 lines
8.0 KiB
TypeScript
243 lines
8.0 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", "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);
|
|
});
|