test(context): OrderItemContextPanel UI tests
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
efd2ad6540
commit
b3a73c0040
1 changed files with 107 additions and 0 deletions
107
src/components/order-item-context-panel.test.tsx
Normal file
107
src/components/order-item-context-panel.test.tsx
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { OrderItemContextPanel } from "@/components/order-item-context-panel";
|
||||
import type { OrderItemContextResponse } from "@/lib/server/order-item-context-service";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const fullContext: OrderItemContextResponse = {
|
||||
generated_at: "2026-08-07T10:00:00Z",
|
||||
match: { sku: "DRL-6MM", match_kind: "sku_exact", match_confidence: 1 },
|
||||
last_quote: {
|
||||
quote_item_id: "qi1", quote_id: "q1", quote_number: "Q-26080701",
|
||||
customer_name: "Acme", company: "Acme Ltd", status: "approved",
|
||||
cost_price: 100, selling_price: 150, markup_percent: 50, days_ago: 5,
|
||||
},
|
||||
last_order: {
|
||||
order_item_id: "oi1", order_id: "o1", order_number: "ORD-26080702",
|
||||
company: "Acme Ltd", cost_price: 110, selling_price: 150,
|
||||
markup_percent: 36.4, item_status: "delivered", updated_at: "2026-08-01T00:00:00Z", days_ago: 6,
|
||||
},
|
||||
last_invoiced: {
|
||||
order_item_id: "oi2", order_id: "o2", order_number: "ORD-26080703",
|
||||
supplier_invoice_number: "INV-99", customer_invoice_number: "C-7",
|
||||
invoiced_date: "2026-08-02T00:00:00Z", cost_price: 105, quantity: 10, qty_invoiced: 10, days_ago: 5,
|
||||
},
|
||||
stock: {
|
||||
stock_item_id: "s1", name: "Drill 6mm", sku: "DRL-6MM",
|
||||
current_qty: 240, min_qty: 20, selling_price: 140, markup_percent: 40, status: "in_stock",
|
||||
},
|
||||
suppliers: [],
|
||||
cheapest: null,
|
||||
};
|
||||
|
||||
describe("OrderItemContextPanel", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("renders all 4 rows when context is fully populated", async () => {
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => fullContext,
|
||||
} as Response);
|
||||
|
||||
render(<OrderItemContextPanel itemId="x" onApply={() => {}} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Last quote/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Last order/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Last invoice/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Stock/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders the dashed empty-state card when context is all-null", async () => {
|
||||
const empty: OrderItemContextResponse = {
|
||||
...fullContext,
|
||||
last_quote: null, last_order: null, last_invoiced: null, stock: null, suppliers: [],
|
||||
};
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => empty,
|
||||
} as Response);
|
||||
|
||||
render(<OrderItemContextPanel itemId="x" onApply={() => {}} />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/No context for this item yet/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("fires onApply with the row's cost and source when Apply is clicked", async () => {
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => fullContext,
|
||||
} as Response);
|
||||
const onApply = vi.fn();
|
||||
|
||||
render(<OrderItemContextPanel itemId="x" onApply={onApply} />);
|
||||
await waitFor(() => screen.getByText(/Last quote/));
|
||||
// Walk up two levels: span (Last quote) → label div → row div (which contains the Apply button).
|
||||
const quoteRow = screen.getByText(/Last quote/).parentElement!.parentElement!;
|
||||
const applyBtn = quoteRow.querySelector("button")!;
|
||||
fireEvent.click(applyBtn);
|
||||
|
||||
expect(onApply).toHaveBeenCalledWith(expect.objectContaining({
|
||||
source: "last_quote",
|
||||
cost_price: 100,
|
||||
source_label: "Q-26080701",
|
||||
}));
|
||||
});
|
||||
|
||||
it("marks the invoice row's selling cell as dimmed", async () => {
|
||||
vi.spyOn(globalThis, "fetch").mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => fullContext,
|
||||
} as Response);
|
||||
|
||||
render(<OrderItemContextPanel itemId="x" onApply={() => {}} />);
|
||||
await waitFor(() => screen.getByText(/Last invoice/));
|
||||
// The dimmed class is applied to the selling cell on the invoice row.
|
||||
const dimmed = document.querySelector(".text-muted-foreground\\/50");
|
||||
expect(dimmed).not.toBeNull();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue