mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
Glucose is stored canonical mg/dL but the series DTO, CSV export, and detail page emitted raw mg/dL while FHIR already converted to the user preference. Convert at serialization through the single convertGlucose helper: - series route sets unit from the user's glucoseUnit and converts each point - CSV export converts BLOOD_GLUCOSE rows and emits the unit in the unit column - the detail page + stat strip honour the preference via the chart value scale - declare the trailing-90-day window in the glucose median label across locales mmol/L-preference users now see the blood-glucose page, stat strip, iOS series, and CSV in mmol/L; mg/dL users are unchanged.
111 lines
4.7 KiB
TypeScript
111 lines
4.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
||
import {
|
||
mgdlToMmol,
|
||
mmolToMgdl,
|
||
convertGlucose,
|
||
toCanonicalMgdl,
|
||
resolveGlucoseUnit,
|
||
thresholdMetricForContext,
|
||
} from "../glucose";
|
||
|
||
describe("glucose conversion", () => {
|
||
it("converts mg/dL to mmol/L with 1 decimal", () => {
|
||
expect(mgdlToMmol(100)).toBe(5.5);
|
||
expect(mgdlToMmol(126)).toBeCloseTo(7.0, 1); // diabetes threshold
|
||
expect(mgdlToMmol(70)).toBeCloseTo(3.9, 1); // hypoglycemia threshold
|
||
});
|
||
|
||
it("convertGlucose dispatches to the right unit", () => {
|
||
expect(convertGlucose(100, "mg/dL")).toBe(100);
|
||
expect(convertGlucose(100, "mmol/L")).toBe(5.5);
|
||
});
|
||
|
||
it("resolveGlucoseUnit defaults to mg/dL", () => {
|
||
expect(resolveGlucoseUnit(null)).toBe("mg/dL");
|
||
expect(resolveGlucoseUnit(undefined)).toBe("mg/dL");
|
||
expect(resolveGlucoseUnit("mmol/L")).toBe("mmol/L");
|
||
expect(resolveGlucoseUnit("mg/dL")).toBe("mg/dL");
|
||
expect(resolveGlucoseUnit("random-garbage")).toBe("mg/dL");
|
||
});
|
||
|
||
it("thresholdMetricForContext maps every context", () => {
|
||
expect(thresholdMetricForContext("FASTING")).toBe("BLOOD_GLUCOSE_FASTING");
|
||
expect(thresholdMetricForContext("POSTPRANDIAL")).toBe(
|
||
"BLOOD_GLUCOSE_POSTPRANDIAL",
|
||
);
|
||
expect(thresholdMetricForContext("RANDOM")).toBe("BLOOD_GLUCOSE_RANDOM");
|
||
expect(thresholdMetricForContext("BEDTIME")).toBe("BLOOD_GLUCOSE_BEDTIME");
|
||
});
|
||
|
||
// Display-unit logic guard for the targets/dashboard surfaces:
|
||
// canonical mg/dL stays integer; mmol/L stays 1-decimal; ranges convert too.
|
||
it("display unit conversion preserves integer/decimal precision", () => {
|
||
const fastingMin = 70; // mg/dL
|
||
const fastingMax = 99;
|
||
const ada126 = 126; // diabetes threshold
|
||
|
||
expect(convertGlucose(fastingMin, "mg/dL")).toBe(70);
|
||
expect(convertGlucose(fastingMax, "mg/dL")).toBe(99);
|
||
expect(convertGlucose(ada126, "mg/dL")).toBe(126);
|
||
|
||
expect(convertGlucose(fastingMin, "mmol/L")).toBe(3.9);
|
||
expect(convertGlucose(fastingMax, "mmol/L")).toBe(5.5);
|
||
expect(convertGlucose(ada126, "mmol/L")).toBe(7);
|
||
});
|
||
|
||
// The target-editor write path: a value the user typed in their
|
||
// display unit must persist as canonical mg/dL. Before the fix the
|
||
// editor saved the mmol/L number verbatim (a 5.5 mmol/L target stored
|
||
// as 5.5 mg/dL, or rejected outright by the 40–400 mg/dL bounds).
|
||
it("converts a display-unit value back to canonical mg/dL", () => {
|
||
expect(mmolToMgdl(5.5)).toBe(99);
|
||
expect(mmolToMgdl(7.0)).toBe(126); // ADA diabetes threshold
|
||
expect(mmolToMgdl(3.9)).toBe(70); // hypoglycemia threshold
|
||
});
|
||
|
||
// v1.16.16 — "one number, one engine" coherence. The wire series DTO, the
|
||
// CSV export, and the FHIR export each convert a stored mg/dL reading to the
|
||
// user's display unit through the SAME `convertGlucose` helper, so the three
|
||
// surfaces emit an identical value + unit. This guards against any surface
|
||
// drifting back to a render-time or hard-coded conversion.
|
||
it("emits a coherent value+unit across series / CSV / FHIR for mmol/L", () => {
|
||
const storedMgdl = 126; // ADA diabetes threshold
|
||
const unit = resolveGlucoseUnit("mmol/L");
|
||
|
||
// Series route: point value = convertGlucose(row.value, unit).
|
||
const seriesValue = convertGlucose(storedMgdl, unit);
|
||
// CSV export: BLOOD_GLUCOSE row value = convertGlucose(m.value, unit).
|
||
const csvValue = convertGlucose(storedMgdl, unit);
|
||
// FHIR export: valueQuantity.value = convertGlucose(stat.latest, unit).
|
||
const fhirValue = convertGlucose(storedMgdl, unit);
|
||
|
||
expect(unit).toBe("mmol/L");
|
||
expect(seriesValue).toBe(7);
|
||
expect(csvValue).toBe(seriesValue);
|
||
expect(fhirValue).toBe(seriesValue);
|
||
});
|
||
|
||
it("emits a coherent value+unit across series / CSV / FHIR for mg/dL", () => {
|
||
const storedMgdl = 100;
|
||
const unit = resolveGlucoseUnit("mg/dL");
|
||
const seriesValue = convertGlucose(storedMgdl, unit);
|
||
const csvValue = convertGlucose(storedMgdl, unit);
|
||
const fhirValue = convertGlucose(storedMgdl, unit);
|
||
expect(unit).toBe("mg/dL");
|
||
expect(seriesValue).toBe(100);
|
||
expect(csvValue).toBe(seriesValue);
|
||
expect(fhirValue).toBe(seriesValue);
|
||
});
|
||
|
||
it("toCanonicalMgdl is the inverse of convertGlucose at the editor boundary", () => {
|
||
// mg/dL display unit is already canonical — only rounds.
|
||
expect(toCanonicalMgdl(126, "mg/dL")).toBe(126);
|
||
expect(toCanonicalMgdl(99.4, "mg/dL")).toBe(99);
|
||
// mmol/L display unit converts back; the round-trip is stable to the
|
||
// 1-decimal mmol/L the UI shows (≤1 mg/dL drift is unavoidable).
|
||
expect(toCanonicalMgdl(7.0, "mmol/L")).toBe(126);
|
||
expect(toCanonicalMgdl(mgdlToMmol(126), "mmol/L")).toBeCloseTo(126, -0.5);
|
||
expect(toCanonicalMgdl(mgdlToMmol(70), "mmol/L")).toBe(70);
|
||
});
|
||
});
|