Files
HealthLog/src/lib/__tests__/export.test.ts
T

196 lines
5.4 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
toCSV,
formatMeasurementsForExport,
formatIntakeEventsForExport,
formatMoodEntriesForExport,
} from "../export";
describe("toCSV", () => {
it("returns empty string for empty array", () => {
expect(toCSV([])).toBe("");
});
it("generates headers and rows", () => {
const records = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 25 },
];
const csv = toCSV(records);
const lines = csv.split("\n");
expect(lines[0]).toBe("name,age");
expect(lines[1]).toBe("Alice,30");
expect(lines[2]).toBe("Bob,25");
});
it("escapes commas in values", () => {
const records = [{ note: "hello, world", value: 1 }];
const csv = toCSV(records);
expect(csv).toContain('"hello, world"');
});
it("escapes quotes in values", () => {
const records = [{ note: 'say "hi"', value: 1 }];
const csv = toCSV(records);
expect(csv).toContain('"say ""hi"""');
});
it("handles null and undefined values", () => {
const records = [{ a: null, b: undefined, c: "ok" }];
const csv = toCSV(records);
expect(csv).toBe("a,b,c\n,,ok");
});
it("converts Date objects to ISO string", () => {
const date = new Date("2025-01-15T12:00:00Z");
const records = [{ date }];
const csv = toCSV(records);
expect(csv).toContain("2025-01-15T12:00:00.000Z");
});
});
describe("formatMeasurementsForExport", () => {
it("formats measurement records", () => {
const measurements = [
{
type: "WEIGHT",
value: 75.5,
unit: "kg",
measuredAt: new Date("2025-01-15T08:00:00Z"),
source: "MANUAL",
notes: null,
},
];
const result = formatMeasurementsForExport(measurements);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({
type: "WEIGHT",
value: 75.5,
unit: "kg",
measuredAt: "2025-01-15T08:00:00.000Z",
source: "MANUAL",
notes: "",
glucoseContext: "",
});
});
it("round-trips glucoseContext on BLOOD_GLUCOSE rows", () => {
const measurements = [
{
type: "BLOOD_GLUCOSE",
value: 92,
unit: "mg/dL",
measuredAt: new Date("2025-01-15T08:00:00Z"),
source: "MANUAL",
notes: null,
glucoseContext: "FASTING",
},
];
const result = formatMeasurementsForExport(measurements);
expect(result[0]).toMatchObject({
type: "BLOOD_GLUCOSE",
glucoseContext: "FASTING",
});
});
// v1.4.25 W7 — per-user timezone offset in exports (issue #167).
it("emits ISO-8601 with user-tz offset when userTz is provided", () => {
const measurements = [
{
type: "WEIGHT",
value: 75.5,
unit: "kg",
// 09:00 UTC = 11:00 Europe/Berlin (CEST in May).
measuredAt: new Date("2026-05-15T09:00:00Z"),
source: "MANUAL",
notes: null,
},
];
const result = formatMeasurementsForExport(measurements, "Europe/Berlin");
expect(result[0].measuredAt).toBe("2026-05-15T11:00:00+02:00");
});
it("emits the user's positive offset for Pacific/Auckland", () => {
const measurements = [
{
type: "WEIGHT",
value: 75.5,
unit: "kg",
measuredAt: new Date("2026-05-15T09:00:00Z"),
source: "MANUAL",
notes: null,
},
];
const result = formatMeasurementsForExport(
measurements,
"Pacific/Auckland",
);
expect(result[0].measuredAt).toBe("2026-05-15T21:00:00+12:00");
});
it("falls back to UTC Z suffix when no userTz is provided", () => {
const measurements = [
{
type: "WEIGHT",
value: 75.5,
unit: "kg",
measuredAt: new Date("2026-05-15T09:00:00Z"),
source: "MANUAL",
notes: null,
},
];
const result = formatMeasurementsForExport(measurements);
expect(result[0].measuredAt).toBe("2026-05-15T09:00:00.000Z");
});
});
describe("formatIntakeEventsForExport with userTz", () => {
it("emits scheduledFor and takenAt with offset", () => {
const events = [
{
medication: { name: "Aspirin" },
scheduledFor: new Date("2026-05-15T06:00:00Z"),
takenAt: new Date("2026-05-15T06:05:00Z"),
skipped: false,
source: "MANUAL",
},
];
const result = formatIntakeEventsForExport(events, "Europe/Berlin");
expect(result[0].scheduledFor).toBe("2026-05-15T08:00:00+02:00");
expect(result[0].takenAt).toBe("2026-05-15T08:05:00+02:00");
});
it("emits empty takenAt for skipped intakes", () => {
const events = [
{
medication: { name: "Aspirin" },
scheduledFor: new Date("2026-05-15T06:00:00Z"),
takenAt: null,
skipped: true,
source: "MANUAL",
},
];
const result = formatIntakeEventsForExport(events, "Europe/Berlin");
expect(result[0].takenAt).toBe("");
});
});
describe("formatMoodEntriesForExport with userTz", () => {
it("emits loggedAt with offset while preserving the stored date column", () => {
const entries = [
{
date: "2026-05-15",
mood: "GREAT",
score: 5,
tags: null,
source: "MANUAL",
moodLoggedAt: new Date("2026-05-15T18:30:00Z"),
},
];
const result = formatMoodEntriesForExport(entries, "America/New_York");
expect(result[0].date).toBe("2026-05-15"); // Stored column unchanged.
expect(result[0].loggedAt).toBe("2026-05-15T14:30:00-04:00");
});
});