import { describe, expect, test } from "bun:test"; import { resolveAnalyte } from "../src/lib/analytes"; import { normalizeMarker } from "../src/lib/normalize"; import { convert } from "../src/lib/units"; describe("units", () => { test("dimensional scaling: ng/mL → µg/L is 1:1", () => { expect(convert(30, "ng/mL", "µg/L")).toBeCloseTo(30); }); test("mass→mole bridge needs molar mass", () => { expect(convert(100, "mg/dL", "mmol/L")).toBeNull(); expect(convert(100, "mg/dL", "mmol/L", 180.16)).toBeCloseTo(5.551, 2); // glucose }); test("mole→mass: testosterone 20 nmol/L → ng/dL", () => { expect(convert(20, "nmol/L", "ng/dL", 288.42)).toBeCloseTo(576.8, 0); }); test("percent only converts to percent", () => { expect(convert(42, "%", "%")).toBe(42); expect(convert(42, "%", "mg/dL")).toBeNull(); }); test("count units: x10^9/L → thousand/uL is 1:1", () => { expect(convert(6.1, "x10^9/L", "thousand/uL")).toBeCloseTo(6.1); }); }); describe("analytes", () => { test("alias resolution is case/space-insensitive", () => { expect(resolveAnalyte("Total Testosterone")?.key).toBe("testosterone_total"); expect(resolveAnalyte("HbA1c")?.key).toBe("hba1c"); expect(resolveAnalyte("definitely not a marker")).toBeNull(); }); }); describe("normalizeMarker", () => { test("known analyte converts to canonical unit and registry panel", () => { const n = normalizeMarker({ panel: "chemistry", name: "Glucose", value: "100", unit: "mg/dL", referenceRange: "70-99", flagged: true }); expect(n.analyteKey).toBe("glucose"); expect(n.panel).toBe("metabolic"); // registry panel wins for consistent grouping expect(n.valueCanonical).toBeCloseTo(5.551, 2); expect(n.canonicalUnit).toBe("mmol/L"); expect(n.value).toBe("100"); // raw preserved expect(n.flagged).toBe(true); }); test("unknown marker keeps raw fields, no canonical value", () => { const n = normalizeMarker({ name: "Exotic Marker X", value: "1.2", unit: "u/L" }); expect(n.analyteKey).toBeNull(); expect(n.marker).toBe("Exotic Marker X"); expect(n.valueCanonical).toBeNull(); }); test("comparator values parse numerically", () => { const n = normalizeMarker({ name: "hs-CRP", value: "<0.3", unit: "mg/L" }); expect(n.valueNum).toBeCloseTo(0.3); expect(n.value).toBe("<0.3"); }); test("BUN is not mapped onto urea (needs N-divisor, not molar mass)", () => { expect(resolveAnalyte("BUN")).toBeNull(); expect(resolveAnalyte("blood urea nitrogen")).toBeNull(); expect(resolveAnalyte("urea")?.key).toBe("urea"); }); test("DHEA-S µg/dL converts with corrected molar mass", () => { const n = normalizeMarker({ name: "DHEA-S", value: "250", unit: "ug/dL" }); expect(n.valueCanonical).toBeCloseTo(6.78, 1); // 250 * 0.02713 }); test("thousands-grouped value parses as 1200, not 1 (parseFloat truncation bug)", () => { const n = normalizeMarker({ name: "Vitamin B12", value: "1,200", unit: "pg/mL" }); expect(n.valueNum).toBe(1200); expect(n.analyteKey).toBe("vitamin_b12"); expect(n.valueCanonical).toBeCloseTo(885.35, 1); expect(n.canonicalUnit).toBe("pmol/L"); }); test("ambiguous European-decimal-looking value is left unmapped, not silently wrong", () => { const n = normalizeMarker({ name: "Glucose", value: "5,5", unit: "mg/dL" }); expect(n.valueNum).toBeNull(); expect(n.valueCanonical).toBeNull(); expect(n.value).toBe("5,5"); // raw value preserved }); test("comparator value still parses after strict-parsing rewrite", () => { const n = normalizeMarker({ name: "hs-CRP", value: "<0.3", unit: "mg/L" }); expect(n.valueNum).toBeCloseTo(0.3); }); });