import { describe, it, expect } from "vitest"; import { normaliseSku, escapeForPbString, classifyMatch, matchTierLabel, explainMatch, } from "@/lib/server/price-list-match"; /** * Unit tests for the pure helpers inside the SKU matcher. * PB-dependent helpers (findPriceListBySku, findFirstPriceListBySku) * are exercised via the live-PB smoke test — here we just lock the * canonical-form math. * * The helpers live in src/lib/server/price-list-match.ts (not in * price-list-search.ts) because they're used by the re-price route * + the matcher-bench smoke script and have a single owner. */ describe("normaliseSku", () => { it("uppercases ASCII letters", () => { expect(normaliseSku("jeitd-06")).toBe("JEITD06"); expect(normaliseSku("ts-drl-12-55")).toBe("TSDRL1255"); }); it("strips dashes, slashes, dots, spaces, and other non-alphanumerics", () => { expect(normaliseSku("JEITD-06")).toBe("JEITD06"); expect(normaliseSku("JEI/TD/06")).toBe("JEITD06"); expect(normaliseSku("JEI.TD.06")).toBe("JEITD06"); expect(normaliseSku("JEI TD 06")).toBe("JEITD06"); }); it("collapses buyer-style casing into the same canonical form", () => { expect(normaliseSku("ts-12x55")).toBe("TS12X55"); expect(normaliseSku("TS-12X55")).toBe("TS12X55"); expect(normaliseSku("ts12X55")).toBe("TS12X55"); }); it("returns empty string for empty / null / undefined input", () => { expect(normaliseSku("")).toBe(""); expect(normaliseSku(" ")).toBe(""); expect(normaliseSku(null)).toBe(""); expect(normaliseSku(undefined)).toBe(""); }); it("preserves digits so a fully numeric SKU round-trips", () => { expect(normaliseSku("001-23-456")).toBe("00123456"); }); }); describe("escapeForPbString", () => { it("doubles embedded double quotes", () => { expect(escapeForPbString('Acme "Pro" line')).toBe('Acme \\"Pro\\" line'); }); it("is a no-op on plain text", () => { expect(escapeForPbString("TS-DRL-12-55")).toBe("TS-DRL-12-55"); }); it("returns an empty string for an empty input", () => { expect(escapeForPbString("")).toBe(""); }); }); describe("classifyMatch", () => { it("returns 1 for an exact SKU match", () => { expect( classifyMatch("JEITD-06", { sku: "JEITD-06", sku_normalised: "JEITD06" }), ).toBe(1); }); it("returns 1 when the canonical form matches exactly (raw differs)", () => { expect( classifyMatch("jeitd06", { sku: "JEITD-06", sku_normalised: "JEITD06" }), ).toBe(1); }); it("returns 2 when the canonical form is a substring of the row's normalised SKU", () => { expect( classifyMatch("JEITD", { sku: "JEITD-D11", sku_normalised: "JEITDD11" }), ).toBe(2); }); it("returns 3 when neither SKU nor normalised is a substring match", () => { expect( classifyMatch("bosch grinder", { sku: "JEITD-06", sku_normalised: "JEITD06", }), ).toBe(3); }); }); describe("matchTierLabel", () => { it("labels Tier 1 'Exact SKU'", () => { expect(matchTierLabel(1)).toBe("Exact SKU"); }); it("labels Tier 2 'SKU substring'", () => { expect(matchTierLabel(2)).toBe("SKU substring"); }); it("labels Tier 3 'Description / brand match'", () => { expect(matchTierLabel(3)).toBe("Description / brand match"); }); }); describe("explainMatch", () => { const baseMatch = { id: "row1", sku: "JEITD-06", sku_normalised: "JEITD06", description: "TWIST DRILL 6MM DIA", brand: "JEI", category: "twist_drill", subcategory: "", supplier: "JEISA - WESTSCOTT GROUP", cost_price: 100, selling_price: 125, unit: "each", updated: "", specifications: { material: "HSS", diameter_mm: 6 }, search_text: "", }; it("renders 'exact SKU match' for Tier 1", () => { const why = explainMatch("JEITD-06", baseMatch); expect(why).toContain("JEITD-06"); expect(why).toContain("exact SKU match"); expect(why).toContain("JEISA"); }); it("renders 'SKU substring' for Tier 2", () => { const why = explainMatch("JEITD", baseMatch); expect(why).toContain("JEITD-06"); expect(why).toContain("SKU substring"); }); it("for Tier 3, points at the brand or spec that matched", () => { const why = explainMatch("HSS twist", baseMatch); expect(why).toContain("JEITD-06"); // Either brand or material appears, depending on which word matched. expect(why).toMatch(/(brand|material):/); }); it("never returns empty", () => { const why = explainMatch("XYZNOPE", baseMatch); expect(why.length).toBeGreaterThan(0); }); it("caps the explanation length so the picker modal doesn't wrap strangely", () => { const long = explainMatch("JEITD-06", { ...baseMatch, supplier: "A very long supplier name ".repeat(20).trim(), }); expect(long.length).toBeLessThanOrEqual(240); }); }); // Reference: keep `normaliseSku` reachable through the import // list so dead-code elimination doesn't strip it during refactors. void normaliseSku;