import { describe, it, expect, beforeEach, vi } from "vitest"; // --- PocketBase mock --------------------------------------------------------- // The mock state has to be hoisted to the top of the file because Vitest // hoists `vi.mock` factories above all imports. The state lives in a // `vi.hoisted` block so the factory can close over it. const { store, pbMock, clearStore } = vi.hoisted(() => { type StoreMap = Map>; const store: Record = {}; let seq = 0; const nextId = () => `mock-${++seq}`; const getOrCreateMap = (name: string): StoreMap => { if (!store[name]) store[name] = new Map(); return store[name]; }; // The mock `where` is intentionally minimal — supports the patterns the // production service actually issues: `field = "value"` (with or without // quotes), `field != "value"`, `&&` (AND), `||` (OR), and `id` lookups by // PB record id. Anything unrecognised passes through (returns all items // for the AND group), so unknown filters don't false-negative tests. const matchesClause = (item: Record, clause: string): boolean => { const m = clause.match(/^(\w+)\s*(=~|!~|=|!=)\s*"?([^"]+?)"?$/); if (!m) return true; const [, field, op, raw] = m; const expected = raw.replace(/^"|"$/g, ""); const actual = item[field]; if (field === "year" || field === "month") { const num = Number(actual); const want = Number(expected); switch (op) { case "=": return num === want; case "!=": return num !== want; default: return true; } } const actualStr = String(actual ?? ""); switch (op) { case "=": return actualStr === expected; case "!=": return actualStr !== expected; case "=~": return actualStr.includes(expected); case "!~": return !actualStr.includes(expected); default: return true; } }; const where = (name: string, filter: string): Record[] => { const items = Array.from(getOrCreateMap(name).values()); if (!filter || !filter.trim()) return items; // Split on `||` first (outer OR), then each alternative is a group of // `&&`-joined AND clauses. An item passes if it satisfies ANY // alternative group. const alternatives = filter.split("||").map((a) => a.trim()).filter(Boolean); return items.filter((item) => alternatives.some((alt) => { const clauses = alt.split("&&").map((c) => c.trim()).filter(Boolean); return clauses.every((clause) => matchesClause(item, clause)); }), ); }; const pbMock = { autoCancellation: () => undefined, collection: (name: string) => ({ async getList( page = 1, perPage = 25, opts: { filter?: string; sort?: string } = {}, ) { const all = where(name, opts.filter || ""); if (opts.sort) { const sign = opts.sort.startsWith("-") ? -1 : 1; const field = opts.sort.replace(/^-/, ""); all.sort((a, b) => { const av = a[field] as string | number | undefined; const bv = b[field] as string | number | undefined; if (av === bv) return 0; if (av === undefined) return 1; if (bv === undefined) return -1; return (av > bv ? 1 : -1) * sign; }); } // Real pagination — mirror PB's getList semantics so the // service code (which relies on totalPages) exercises the same // branches under test as in production. const totalItems = all.length; const totalPages = Math.max(1, Math.ceil(totalItems / perPage)); const start = (page - 1) * perPage; const items = all.slice(start, start + perPage); return { items, totalItems, totalPages, page, perPage }; }, async getFullList(opts: { filter?: string; fields?: string } = {}) { // Mirror production: respect the filter. The fields projection // is a wire-payload optimisation in PB; for tests we just // return the matched records as-is. return where(name, opts.filter || ""); }, async getFirstListItem(filter: string) { const items = where(name, filter); if (items.length === 0) { const err = new Error("not found") as Error & { status: number }; err.status = 404; throw err; } return items[0]; }, async getOne(id: string) { const item = getOrCreateMap(name).get(id); if (!item) { const err = new Error("not found") as Error & { status: number }; err.status = 404; throw err; } return item; }, async create(data: Record) { if (name === "order_counters") { const map = getOrCreateMap(name); for (const item of map.values()) { if (item.year === data.year && item.month === data.month) { const err = new Error("unique constraint") as Error & { status: number }; err.status = 409; throw err; } } } const item = { id: nextId(), created: new Date().toISOString(), updated: new Date().toISOString(), ...data, }; getOrCreateMap(name).set(item.id, item); return item; }, async update(id: string, data: Record) { const map = getOrCreateMap(name); const existing = map.get(id); if (!existing) { const err = new Error("not found") as Error & { status: number }; err.status = 404; throw err; } for (const [key, value] of Object.entries(data)) { if (key.endsWith("+")) { const real = key.slice(0, -1); existing[real] = Number(existing[real] ?? 0) + Number(value); } else { existing[key] = value; } } existing.updated = new Date().toISOString(); return { ...existing }; }, }), }; const clearStore = () => { for (const key of Object.keys(store)) delete store[key]; seq = 0; }; return { store, pbMock, clearStore }; }); vi.mock("pocketbase", () => { return { default: vi.fn().mockImplementation(() => pbMock), }; }); vi.mock("./pocketbase-admin", () => ({ getAdminPocketBase: vi.fn().mockResolvedValue(pbMock), })); import { createOrderFromQuote, clearOrderItemStatusTabCountsCache, getOrderItemStatusTabCounts, getOrderIdsForTabGroup, getOrderItemsCollectionName, getOrdersDashboardData, listCollectionPool, listOrdersWithItems, } from "./orders-service"; beforeEach(() => { clearStore(); }); // --- Tests ------------------------------------------------------------------- describe("getOrderItemsCollectionName", () => { it("defaults to 'order_items' when env var is unset", () => { const original = process.env.ORDER_ITEMS_COLLECTION; delete process.env.ORDER_ITEMS_COLLECTION; try { expect(getOrderItemsCollectionName()).toBe("order_items"); } finally { if (original !== undefined) process.env.ORDER_ITEMS_COLLECTION = original; } }); it("returns the env var when set (e.g. order_items_v2)", () => { process.env.ORDER_ITEMS_COLLECTION = "order_items_v2"; try { expect(getOrderItemsCollectionName()).toBe("order_items_v2"); } finally { delete process.env.ORDER_ITEMS_COLLECTION; } }); }); describe("createOrderFromQuote", () => { it("returns the existing order when one already references the quote", async () => { // Pre-seed an existing order in the store; dedupe check should find it // and short-circuit before the rest of the pipeline runs. await pbMock.collection("orders").create({ id: "order-1", quote: "quote-abc", order_number: "ORD-2025-01-001", }); const result = await createOrderFromQuote("quote-abc"); expect(result.already_exists).toBe(true); expect(result.order.id).toBe("order-1"); expect(result.created_items).toBe(0); }); it("throws when the quote has no items", async () => { await pbMock.collection("quotes").create({ id: "quote-empty", company: "comp-1", customer: "cust-1", notes: "", }); // No quote_items rows → service must throw. await expect(createOrderFromQuote("quote-empty")).rejects.toThrow( /no items to convert/, ); }); it("creates an order, copies every quote_item, and sets the right defaults", async () => { // Seed the dependencies in the mock store. await pbMock.collection("quotes").create({ id: "quote-99", company: "comp-7", customer: "cust-3", notes: "Rush", }); await pbMock.collection("quote_items").create({ id: "qi-1", quote: "quote-99", description: "Widget", quantity: 3, selling_price: 100, supplier: "sup-1", cost_price: 50, markup: 10, notes: "front", }); await pbMock.collection("quote_items").create({ id: "qi-2", quote: "quote-99", description: "Sprocket", quantity: 2, selling_price: 75, supplier: "sup-2", cost_price: 30, markup: 0, notes: "", }); const result = await createOrderFromQuote("quote-99"); expect(result.already_exists).toBe(false); expect(result.created_items).toBe(2); // Pull the created order directly out of the store. const orders = await pbMock.collection("orders").getFullList(); const created = orders.find((o) => o.quote === "quote-99") as | Record | undefined; expect(created).toBeDefined(); expect(created?.quote).toBe("quote-99"); expect(created?.company).toBe("comp-7"); expect(created?.customer).toBe("cust-3"); expect(created?.quote_matching_attempted).toBe(true); expect(created?.is_repair_order).toBe(false); expect(created?.invoiced).toBe(false); // Starts at ORD-{now-year}-{now-month}-001 because the orders table // is empty for this test (no prior order to extend from). const now = new Date(); const expectedPrefix = `ORD-${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-`; expect(String(created?.order_number)).toBe(`${expectedPrefix}001`); // Order-item payload assertions: the two quote_items should each // have produced one order_item row. const orderItems = await pbMock.collection("order_items").getFullList(); const ourItems = orderItems.filter((o) => o.order === created?.id); expect(ourItems.length).toBe(2); const widget = ourItems.find((o) => o.description === "Widget"); expect(widget).toMatchObject({ po_description: "Widget", quantity: 3, order_qty: 3, qty_ordered: "3", selling_price: 100, supplier: "sup-1", cost_price: 50, markup: 10, markup_percent: 10, item_status: "pending", status: "active", drv_status: "unassigned", notes: "front", quote_item: "qi-1", }); const sprocket = ourItems.find((o) => o.description === "Sprocket"); expect(sprocket?.quote_item).toBe("qi-2"); expect(sprocket?.supplier).toBe("sup-2"); expect(sprocket?.drv_status).toBe("unassigned"); }); it("extends the latest order's sequence (no month-boundary reset)", async () => { // The most recent order is in June; we're now in July. The contract // is that the next order continues June's sequence (NOT a fresh // July-001). The old code's bug was using `now` month, which would // have generated `ORD-2026-07-001` here. await pbMock.collection("orders").create({ order_number: "ORD-2026-06-007", quote: "some-other-quote", }); await pbMock.collection("quotes").create({ id: "quote-seq", company: "c", customer: null, notes: "", }); await pbMock.collection("quote_items").create({ id: "qi-1", quote: "quote-seq", description: "x", quantity: 1, selling_price: 1, cost_price: 0, markup: 0, notes: "", }); await createOrderFromQuote("quote-seq"); const orders = await pbMock.collection("orders").getFullList(); const newOrder = orders.find((o) => o.quote === "quote-seq"); expect(newOrder?.order_number).toBe("ORD-2026-06-008"); }); it("starts a fresh month at 001 when the orders table is empty", async () => { // The orders collection is empty → no most-recent order to extend // from. Falls back to wall-clock year/month, sequence at 001. await pbMock.collection("quotes").create({ id: "q-fresh", company: "c", customer: null, notes: "", }); await pbMock.collection("quote_items").create({ id: "qi-1", quote: "q-fresh", description: "x", quantity: 1, selling_price: 1, cost_price: 0, markup: 0, notes: "", }); await createOrderFromQuote("q-fresh"); const orders = await pbMock.collection("orders").getFullList(); const newOrder = orders.find((o) => o.quote === "q-fresh"); const now = new Date(); const expected = `ORD-${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-001`; expect(newOrder?.order_number).toBe(expected); }); it("falls back to wall-clock year/month when the latest order_number is unparseable", async () => { // A junk order_number (legacy import, manual edit, or old data with a // different prefix) should NOT break the generator. The service // detects unparseable prefixes and uses now year/month. await pbMock.collection("orders").create({ order_number: "GARBAGE-2026-06-007", quote: "old", }); await pbMock.collection("quotes").create({ id: "q-junk", company: "c", customer: null, notes: "", }); await pbMock.collection("quote_items").create({ id: "qi-1", quote: "q-junk", description: "x", quantity: 1, selling_price: 1, cost_price: 0, markup: 0, notes: "", }); await createOrderFromQuote("q-junk"); const orders = await pbMock.collection("orders").getFullList(); const newOrder = orders.find((o) => o.quote === "q-junk"); const now = new Date(); const expected = `ORD-${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-001`; expect(newOrder?.order_number).toBe(expected); }); it("writes to the collection named by ORDER_ITEMS_COLLECTION env var", async () => { process.env.ORDER_ITEMS_COLLECTION = "order_items_v2"; await pbMock.collection("quotes").create({ id: "q", company: "c", customer: null, notes: "", }); await pbMock.collection("quote_items").create({ id: "qi-1", quote: "q", description: "x", quantity: 1, selling_price: 1, cost_price: 0, markup: 0, notes: "", }); const result = await createOrderFromQuote("q"); expect(result.order_items_collection).toBe("order_items_v2"); const v2Rows = await pbMock.collection("order_items_v2").getFullList(); expect(v2Rows.length).toBe(1); expect(v2Rows[0].order).toBeDefined(); delete process.env.ORDER_ITEMS_COLLECTION; }); it("concurrent calls produce unique order numbers (no race)", async () => { // Set up the dependencies for a single order. await pbMock.collection("quotes").create({ id: "q-race", company: "c", customer: null, notes: "", }); await pbMock.collection("quote_items").create({ id: "qi-1", quote: "q-race", description: "x", quantity: 1, selling_price: 1, cost_price: 0, markup: 0, notes: "", }); // Run 5 createOrderFromQuote calls in parallel against distinct // quotes. The counter must give each a unique number even under // simulated concurrency. (Note: the test mock is not actually // concurrent — the `await`s in the implementation interleave the // operations — so the assertion verifies the counter pattern, not // true concurrency. Real concurrency safety comes from PB's // `last_seq+` server-side increment.) const quotes = ["q-race-a", "q-race-b", "q-race-c", "q-race-d", "q-race-e"]; for (const id of quotes) { await pbMock.collection("quotes").create({ id, company: "c", customer: null, notes: "" }); await pbMock.collection("quote_items").create({ id: `qi-${id}`, quote: id, description: "x", quantity: 1, selling_price: 1, cost_price: 0, markup: 0, notes: "", }); } const results = await Promise.all(quotes.map((id) => createOrderFromQuote(id))); const numbers = results.map((r) => String((r.order as unknown as { order_number: string }).order_number)); expect(new Set(numbers).size).toBe(5); }); }); // ---------------------------------------------------------------------------- // /orders dashboard read model — tab filter, counts cache, rep lookup. // // These tests cover the bug surface fixed in 2026-07: the old // `getOrdersDashboardData` returned the global orders pagination // (Page 1 of 226, 4520 total orders) regardless of which tab the // operator selected, so "Processing (4)" badges showed empty pages // when the four matching orders happened to fall on later pages. // ---------------------------------------------------------------------------- describe("getOrderIdsForTabGroup", () => { it("returns the distinct order ids with at least one item in the tab group", async () => { // Three orders, each with one item. Statuses cover "new", // "processing", and "delivered" tabs so we can verify partition // membership. await pbMock.collection("orders").create({ id: "o-new", order_number: "ORD-2025-01-001", quote: "q1", }); await pbMock.collection("orders").create({ id: "o-processing", order_number: "ORD-2025-01-002", quote: "q2", }); await pbMock.collection("orders").create({ id: "o-delivered", order_number: "ORD-2025-01-003", quote: "q3", }); await pbMock.collection("order_items").create({ id: "oi-1", order: "o-new", description: "Widget", item_status: "pending", }); await pbMock.collection("order_items").create({ id: "oi-2", order: "o-processing", description: "Sprocket", item_status: "processed", }); await pbMock.collection("order_items").create({ id: "oi-3", order: "o-delivered", description: "Gear", item_status: "delivered", }); const processingIds = await getOrderIdsForTabGroup(pbMock as never, "processing"); expect(processingIds.sort()).toEqual(["o-processing"]); const newIds = await getOrderIdsForTabGroup(pbMock as never, "new"); expect(newIds.sort()).toEqual(["o-new"]); const deliveredIds = await getOrderIdsForTabGroup(pbMock as never, "delivered"); expect(deliveredIds.sort()).toEqual(["o-delivered"]); }); it("dedupes when an order has multiple items in the same tab", async () => { await pbMock.collection("orders").create({ id: "o-multi", order_number: "ORD-2025-01-010", quote: "q", }); await pbMock.collection("order_items").create({ id: "oi-a", order: "o-multi", description: "A", item_status: "pending", }); await pbMock.collection("order_items").create({ id: "oi-b", order: "o-multi", description: "B", item_status: "pending", }); const ids = await getOrderIdsForTabGroup(pbMock as never, "new"); expect(ids).toEqual(["o-multi"]); }); }); describe("getOrderItemStatusTabCounts", () => { beforeEach(() => clearOrderItemStatusTabCountsCache()); it("returns the count of distinct orders per tab", async () => { await pbMock.collection("orders").create({ id: "o-a", order_number: "ORD-2025-01-001", quote: "qa", }); await pbMock.collection("orders").create({ id: "o-b", order_number: "ORD-2025-01-002", quote: "qb", }); await pbMock.collection("order_items").create({ id: "oi-1", order: "o-a", description: "x", item_status: "pending", }); await pbMock.collection("order_items").create({ id: "oi-2", order: "o-a", description: "y", item_status: "processed", }); await pbMock.collection("order_items").create({ id: "oi-3", order: "o-b", description: "z", item_status: "pending", }); const counts = await getOrderItemStatusTabCounts(pbMock as never); expect(counts.new).toBe(2); expect(counts.processing).toBe(1); }); it("bypasses the cache when bypassCache is true", async () => { await pbMock.collection("orders").create({ id: "o-cache", order_number: "ORD-2025-01-001", quote: "qc", }); await pbMock.collection("order_items").create({ id: "oi-cache", order: "o-cache", description: "x", item_status: "pending", }); const first = await getOrderItemStatusTabCounts(pbMock as never); expect(first.new).toBe(1); // Mutate the underlying store directly — bypasses our normal // service writes, but lets us verify the cache is being served // if `bypassCache` is not set. await pbMock.collection("orders").create({ id: "o-cache-2", order_number: "ORD-2025-01-002", quote: "qc2", }); await pbMock.collection("order_items").create({ id: "oi-cache-2", order: "o-cache-2", description: "x", item_status: "pending", }); const cached = await getOrderItemStatusTabCounts(pbMock as never); expect(cached.new).toBe(1); // unchanged — cache hit const fresh = await getOrderItemStatusTabCounts(pbMock as never, { bypassCache: true }); expect(fresh.new).toBe(2); // refresh sees the new order }); it("clearOrderItemStatusTabCountsCache forces a fresh read", async () => { await pbMock.collection("orders").create({ id: "o-clear", order_number: "ORD-2025-01-001", quote: "qclr", }); await pbMock.collection("order_items").create({ id: "oi-clear", order: "o-clear", description: "x", item_status: "pending", }); await getOrderItemStatusTabCounts(pbMock as never); // prime the cache clearOrderItemStatusTabCountsCache(); const fresh = await getOrderItemStatusTabCounts(pbMock as never); expect(fresh.new).toBe(1); }); it("fires a single combined getFullList for all tabs (not 6 separate queries)", async () => { // Regression guard: the old implementation issued 6 parallel // `getFullList` calls (one per tab), each with a separate filter // scan. With ~18k rows in `order_items` and no `item_status` // index, those 6 scans dominated the page-load budget. The new // implementation collapses to 1 combined query. This test // intercepts the mock's `collection()` to count how many times // `getFullList` is called on the `order_items` collection. await pbMock.collection("order_items").create({ id: "oi-1", order: "o1", description: "x", item_status: "pending", }); await pbMock.collection("order_items").create({ id: "oi-2", order: "o2", description: "x", item_status: "processed", }); await pbMock.collection("order_items").create({ id: "oi-3", order: "o3", description: "x", item_status: "delivered", }); const originalCollection = pbMock.collection; let getFullListCalls = 0; pbMock.collection = ((name: string) => { const proxy = originalCollection(name); if (name === "order_items") { const originalGetFullList = proxy.getFullList; proxy.getFullList = ((...args: unknown[]) => { getFullListCalls++; return (originalGetFullList as (...a: unknown[]) => unknown).call( proxy, ...args, ); }) as typeof proxy.getFullList; } return proxy; }) as typeof pbMock.collection; try { await getOrderItemStatusTabCounts(pbMock as never, { bypassCache: true }); expect(getFullListCalls).toBe(1); } finally { pbMock.collection = originalCollection; } }); }); describe("listOrdersWithItems — tab filter", () => { it("returns only orders that have at least one item in the requested tab", async () => { // Two orders: one with a "pending" item, one with a "processed" // item. Filtered to "processing" should yield only the second. await pbMock.collection("orders").create({ id: "o-pending", order_number: "ORD-2025-01-001", quote: "qp", created: "2025-01-01T00:00:00Z", }); await pbMock.collection("orders").create({ id: "o-processed", order_number: "ORD-2025-01-002", quote: "qpr", created: "2025-01-02T00:00:00Z", }); await pbMock.collection("order_items").create({ id: "oi-pending", order: "o-pending", description: "Widget", item_status: "pending", }); await pbMock.collection("order_items").create({ id: "oi-processed", order: "o-processed", description: "Sprocket", item_status: "processed", }); const result = await listOrdersWithItems({ page: 1, perPage: 20, tabGroup: "processing", }); expect(result.items.map((i) => i.order.id)).toEqual(["o-processed"]); expect(result.totalItems).toBe(1); expect(result.totalPages).toBe(1); }); it("returns an empty list with totalPages=0 when no orders match the tab", async () => { await pbMock.collection("orders").create({ id: "o-only-new", order_number: "ORD-2025-01-001", quote: "qn", }); await pbMock.collection("order_items").create({ id: "oi-only-new", order: "o-only-new", description: "x", item_status: "pending", }); const result = await listOrdersWithItems({ page: 1, perPage: 20, tabGroup: "cancelled", }); expect(result.items).toEqual([]); expect(result.totalItems).toBe(0); }); it("reports totalItems from the matching set, not the page slice (pagination honesty)", async () => { // Five orders with processed items, perPage=2. The page-1 response // should have 2 items but totalItems=5 / totalPages=3 so the // footer says "Page 1 of 3" — matching the operator's mental // model of how many orders the tab actually contains. for (let i = 0; i < 5; i++) { await pbMock.collection("orders").create({ id: `o-${i}`, order_number: `ORD-2025-01-00${i + 1}`, quote: `q-${i}`, created: `2025-01-0${i + 1}T00:00:00Z`, }); await pbMock.collection("order_items").create({ id: `oi-${i}`, order: `o-${i}`, description: "x", item_status: "processed", }); } const result = await listOrdersWithItems({ page: 1, perPage: 2, tabGroup: "processing", }); expect(result.items.length).toBe(2); expect(result.totalItems).toBe(5); expect(result.totalPages).toBe(3); }); it("falls back to all-orders pagination when no tab is supplied", async () => { // Sanity check — the existing all-orders path is unchanged. PB's // `getList(page, perPage, { sort })` returns a paginated slice; // the test mock mirrors that with `totalItems = items.length`, // so totalPages = 1 here regardless of the underlying set size. await pbMock.collection("orders").create({ id: "o-a", order_number: "ORD-2025-01-001", quote: "qa", }); await pbMock.collection("orders").create({ id: "o-b", order_number: "ORD-2025-01-002", quote: "qb", }); const result = await listOrdersWithItems({ page: 1, perPage: 20, }); expect(result.items.length).toBe(2); }); it("does not pass a `fields` parameter on the orders list (PB 0.26 breaks expand when fields is set)", async () => { // Regression guard: earlier I tried to limit the wire payload // by passing a `fields` list with per-expand syntax. PB 0.26 // treats `fields` as a hard allowlist of MAIN record columns, // and the moment you set it the `expand` payload is dropped // from the response — so customer/company/rep/quote names // vanish from every order card. The orders list deliberately // omits `fields`; this test pins that behaviour so a future // "let's slim the payload" change doesn't reintroduce the bug. let capturedFields: string | undefined; let capturedExpand: string | undefined; const originalCollection = pbMock.collection; pbMock.collection = ((name: string) => { const proxy = originalCollection(name); if (name === "orders") { const originalGetList = proxy.getList; proxy.getList = ((page: number, perPage: number, opts: { filter?: string; sort?: string; fields?: string; expand?: string }) => { capturedFields = opts?.fields; capturedExpand = opts?.expand; return (originalGetList as (...a: unknown[]) => unknown).call( proxy, page, perPage, opts, ); }) as typeof proxy.getList; } return proxy; }) as typeof pbMock.collection; try { await listOrdersWithItems({ page: 1, perPage: 20 }); // `fields` must NOT be sent — PB 0.26 drops the expand payload // when it sees it. expect(capturedFields).toBeUndefined(); // Expand IS sent, and includes the four relations the list view // reads (company, customer, customer.rep, quote). Importantly // it does NOT include `potential_quote` — the list view only // checks the field's existence, never the expanded record. expect(capturedExpand).toContain("company"); expect(capturedExpand).toContain("customer"); expect(capturedExpand).toContain("customer.rep"); expect(capturedExpand).toContain("quote"); expect(capturedExpand).not.toContain("potential_quote"); } finally { pbMock.collection = originalCollection; } }); }); describe("getOrdersDashboardData — end-to-end", () => { beforeEach(() => clearOrderItemStatusTabCountsCache()); it("returns tab-scoped counts and items in a single response", async () => { // Three orders: a pending one (new tab), a processed one // (processing tab), and a delivered one. await pbMock.collection("orders").create({ id: "o-p", order_number: "ORD-2025-01-001", quote: "qp", created: "2025-01-01T00:00:00Z", }); await pbMock.collection("orders").create({ id: "o-pr", order_number: "ORD-2025-01-002", quote: "qpr", created: "2025-01-02T00:00:00Z", }); await pbMock.collection("orders").create({ id: "o-d", order_number: "ORD-2025-01-003", quote: "qd", created: "2025-01-03T00:00:00Z", }); await pbMock.collection("order_items").create({ id: "oi-p", order: "o-p", description: "Widget", item_status: "pending", }); await pbMock.collection("order_items").create({ id: "oi-pr", order: "o-pr", description: "Sprocket", item_status: "processed", }); await pbMock.collection("order_items").create({ id: "oi-d", order: "o-d", description: "Gear", item_status: "delivered", }); const result = await getOrdersDashboardData({ page: 1, perPage: 20, tabGroup: "processing", }); // Counts are global (always were) — the badge still tells the truth. expect(result.counts.new).toBe(1); expect(result.counts.processing).toBe(1); expect(result.counts.delivered).toBe(1); // Items are scoped to the active tab. expect(result.items.map((i) => i.order.id)).toEqual(["o-pr"]); expect(result.totalPages).toBe(1); expect(result.totalItems).toBe(1); }); });