import { test, expect, seedStockedItem, seedUnstockedItem, type StockedItem, type UnstockedItem, } from "../fixtures/inventory-visibility"; import { InventoryListPage } from "../pages/inventory-list"; // E2E for the `inventory-visibility` business flow. Read-only in the UI, so // each StockLevel row is produced first through the app's own receiving path // (seedStockedItem / seedUnstockedItem, in the fixture). The deployed frontend // renders the rows via `listStockRollupsBySite`, and the actor — any authorized // viewer, driven here as the Inventory Manager / Store Staff — inspects them on // the inventory list. // Story: inventory-manager--browse-inventory-list test.describe("Browse inventory list", () => { let stocked: StockedItem; // onHand 10, reserved 0, blocked 0 -> available 10 (a freshly receipted item // has nothing reserved or blocked, so available == onHand). test.beforeAll(async () => { stocked = await seedStockedItem(10); }); test("shows a stocked item's Item x Site row with the quantity columns", async ({ page }) => { const inventory = new InventoryListPage(page); await inventory.goto(); await expect(inventory.heading).toBeVisible(); // The default list columns cover onHand / reserved / blocked / available. await expect(inventory.getColumnHeader("On Hand")).toBeVisible(); await expect(inventory.getColumnHeader("Reserved")).toBeVisible(); await expect(inventory.getColumnHeader("Blocked")).toBeVisible(); await expect(inventory.getColumnHeader("Available")).toBeVisible(); // Narrow to the seeded SKU so the assertion is deterministic regardless of // how many other rows exist, then confirm the row renders at its site. await inventory.search(stocked.sku); const row = inventory.getRow(stocked.sku); await expect(row).toBeVisible(); await expect(row).toContainText(stocked.siteName); }); test("renders available as onHand minus reserved minus blocked", async ({ page }) => { const inventory = new InventoryListPage(page); await inventory.goto(); await inventory.search(stocked.sku); const row = inventory.getRow(stocked.sku); await expect(row).toBeVisible(); // available = onHand - reserved - blocked = 10 - 0 - 0 = 10. await expect(row).toContainText(String(stocked.onHand)); }); }); // Story: inventory-manager--filter-inventory-by-item test.describe("Filter inventory by item", () => { let stocked: StockedItem; let unstocked: UnstockedItem; test.beforeAll(async () => { [stocked, unstocked] = await Promise.all([seedStockedItem(7), seedUnstockedItem()]); }); test("a matching SKU fragment narrows the list to that item's rows", async ({ page }) => { const inventory = new InventoryListPage(page); await inventory.goto(); await expect(inventory.heading).toBeVisible(); await inventory.search(stocked.sku); await expect(inventory.getRow(stocked.sku)).toBeVisible(); await expect(inventory.getRow(stocked.sku)).toContainText(stocked.siteName); }); test("an item-name fragment matches the stocked item", async ({ page }) => { const inventory = new InventoryListPage(page); await inventory.goto(); await inventory.search(stocked.name); await expect(inventory.getRow(stocked.sku)).toBeVisible(); }); test("a fragment that matches no stocked item yields an empty result", async ({ page }) => { const inventory = new InventoryListPage(page); await inventory.goto(); // The unstocked item exists but has no StockLevel rows, so searching its // SKU must not surface a row (it is not the full default list either). await inventory.search(unstocked.sku); await expect(inventory.getRow(unstocked.sku)).toHaveCount(0); }); }); // Story: inventory-manager--filter-inventory-by-site test.describe("Filter inventory by site", () => { let stocked: StockedItem; test.beforeAll(async () => { stocked = await seedStockedItem(5); }); test("picking the item's site keeps its row; the All filter spans every site", async ({ page, }) => { const inventory = new InventoryListPage(page); await inventory.goto(); await expect(inventory.heading).toBeVisible(); // Scope to the seeded item first so the site filter's effect is observable // against a single known row. await inventory.search(stocked.sku); await expect(inventory.getRow(stocked.sku)).toBeVisible(); // Pick the seeded item's site; its row stays because that is where it is // stocked. await inventory.siteFilter.click(); await page.getByRole("option", { name: stocked.siteName }).click(); await expect(inventory.getRow(stocked.sku)).toBeVisible(); await expect(inventory.getRow(stocked.sku)).toContainText(stocked.siteName); // Returning to "All sites" restores the unfiltered (still SKU-scoped) view. await inventory.siteFilter.click(); await page.getByRole("option", { name: /all sites/i }).click(); await expect(inventory.getRow(stocked.sku)).toBeVisible(); }); }); // Story: store-staff--check-item-availability test.describe("Check item availability", () => { let available: StockedItem; test.beforeAll(async () => { // A store staff member looks up a SKU and reads the available column to // decide whether it can be fulfilled. Freshly receipted -> available > 0. available = await seedStockedItem(8); }); test("looking up a SKU shows its available quantity per site", async ({ page }) => { const inventory = new InventoryListPage(page); await inventory.goto(); await expect(inventory.heading).toBeVisible(); await inventory.search(available.sku); const row = inventory.getRow(available.sku); await expect(row).toBeVisible(); await expect(row).toContainText(available.siteName); // available == onHand for a freshly receipted item, so 8 is the number the // staff can promise. await expect(row).toContainText(String(available.onHand)); }); test("a SKU that does not exist returns no rows", async ({ page }) => { const inventory = new InventoryListPage(page); await inventory.goto(); await inventory.search(`NONEXISTENT-SKU-${Date.now()}`); await expect(page.getByRole("row").filter({ hasText: /INV-VIS-/ })).toHaveCount(0); }); });