WFDASH-WEBAPP/src/lib/pb-helpers.test.ts
SJCoetz 2bd8fcf247 fix: resolve PocketBase expand projection bug
This commit fixes a critical PocketBase projection bug where omitting "expand" from fields lists silently drops related data, leading to missing supplier/customer/order info across the app. It also includes usability improvements and test coverage:

1.  Add "expand" to PB fields projections in invoicing page, quotes lookup API, sprint page, and quote search helper
2.  Update PO Sent tab filter to exclude "collected" and "ready_invoice" statuses
3.  Reorder buyer tasks list to sort by supplier first, then order number
4.  Add comprehensive regression tests for all fixed issues

Included Playwright test capture artifacts from local testing.
2026-08-06 01:33:16 +02:00

93 lines
3.5 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
import { searchQuotesWithItems } from "./pb-helpers";
// ─── PocketBase mocks ────────────────────────────────────────────────────
// `searchQuotesWithItems` issues two PB calls:
// - `quotes.getList(1, cap, { fields, expand, ... })` — to grab quote
// rows matching the free-text query
// - `quote_items.getFullList({ filter, fields, expand })` — to pull
// line items for those quotes (via IN filter or chunked fallback)
//
// We intercept both and capture the options the page is sending. The
// page reads customer/company/rep from the expand block, so PB's
// `fields` projection MUST include "expand" — otherwise the projection
// silently drops the expand payload and the customer/company/rep cells
// on /quotes render "—" everywhere. Mirrors the sprint-page regression
// locked in at `src/app/(app)/sprint/page.test.tsx`.
const { quotesGetListMock, quoteItemsGetFullListMock } = vi.hoisted(() => ({
quotesGetListMock: vi.fn(),
quoteItemsGetFullListMock: vi.fn(),
}));
vi.mock("@/lib/pocketbase", () => ({
__esModule: true,
default: () => ({
collection: (name: string) => {
if (name === "quotes") {
return { getList: (...args: unknown[]) => quotesGetListMock(...args) };
}
if (name === "quote_items") {
return {
getFullList: (...args: unknown[]) => quoteItemsGetFullListMock(...args),
};
}
return { getList: vi.fn(), getFullList: vi.fn() };
},
}),
}));
beforeEach(() => {
vi.clearAllMocks();
// Seed a single quote so `quoteIds` is non-empty — otherwise the
// items fetch short-circuits and the second test can't observe
// what projection was sent for items.
quotesGetListMock.mockResolvedValue({
items: [
{
id: "quote-1",
quote_number: "Q202608018",
customer: "cust-1",
company: "comp-1",
},
],
totalItems: 1,
page: 1,
perPage: 500,
});
quoteItemsGetFullListMock.mockResolvedValue([]);
});
describe("searchQuotesWithItems — fields projection", () => {
it("includes 'expand' in the quotes.getList fields projection", async () => {
await searchQuotesWithItems("test");
expect(quotesGetListMock).toHaveBeenCalledTimes(1);
const opts = quotesGetListMock.mock.calls[0]?.[2] as
| { fields?: string; expand?: string }
| undefined;
expect(opts?.fields).toBeDefined();
// The same projection-drop bug that broke the sprint page row
// also affects the quotes list search path: a `fields` list
// without "expand" tells PB to drop the entire `expand` block,
// so the customer / company / rep cells render "—". Lock this
// in so the bug can't reappear.
expect(opts!.fields!.split(",")).toContain("expand");
});
it("includes 'expand' in the quote_items.getFullList fields projection", async () => {
// The item fetch is what powers the expanded-row chevron on
// /quotes. The per-row supplier name (`it.expand?.supplier
// ?.suppliername`) is read from the expand block — drop it
// and the supplier cell goes blank too.
await searchQuotesWithItems("test");
expect(quoteItemsGetFullListMock).toHaveBeenCalled();
const opts = quoteItemsGetFullListMock.mock.calls[0]?.[0] as
| { fields?: string; expand?: string }
| undefined;
expect(opts?.fields).toBeDefined();
expect(opts!.fields!.split(",")).toContain("expand");
});
});