import { describe, expect, it } from "vitest";
import type { PatientSummary, UrgentAlert } from "../src/api/types";
import {
checkInTab,
clinicHoursLabel,
dosesThisWeek,
greeting,
longDate,
needsAttention,
needsYou,
personLine,
recentCheckIns,
summaryLine,
todayStats,
} from "../src/logic/today";
const quiet = { urgent: false, unreadNotes: 0, newSinceLastCheck: false };
const patient = (name: string, change: Partial<PatientSummary> = {}): PatientSummary => ({
id: name,
kind: "connected",
name,
age: 60,
pronoun: null,
sharing: {
status: "connected",
sharedBy: [name.split(" ")[0]],
scopes: ["medicines", "doses", "check_ins", "records"],
allowsCheckInCalls: true,
withheldSome: false,
changedAt: "2026-10-30T09:00:00Z",
},
medicines: { count: 2 },
doses: { taken: 12, expected: 14, onTime: 12 },
lastCheckIn: null,
attention: quiet,
lastActivityAt: "2026-10-29T09:00:00Z",
...change,
});
const alert = (patientId: string, at: string, text = "Short of breath"): UrgentAlert => ({
id: `${patientId}-${at}`,
checkInId: `call-${at}`,
patientId,
patientName: patientId,
noteId: null,
text,
at,
acknowledgedAt: null,
});
const now = new Date("2026-10-31T10:00:00");
describe("the greeting", () => {
it("says the part of the day and the date in words", () => {
expect(greeting(new Date("2026-10-31T08:00:00"))).toBe("Good morning");
expect(greeting(new Date("2026-10-31T14:00:00"))).toBe("Good afternoon");
expect(greeting(new Date("2026-10-31T19:30:00"))).toBe("Good evening");
expect(longDate(now)).toBe("Saturday 31 October");
});
it("names only the age and pronouns the patient stated", () => {
expect(personLine({ age: 64, pronoun: "she" })).toBe("64 years · she/her");
expect(personLine({ age: null, pronoun: null })).toBe("");
expect(personLine({ age: 41, pronoun: null })).toBe("41 years");
});
});
describe("Needs you", () => {
const list = [
patient("Quiet One"),
patient("New Info", { attention: { ...quiet, newSinceLastCheck: true }, sharing: { ...patient("x").sharing!, sharedBy: ["Funke"] } }),
patient("Unread Notes", { attention: { ...quiet, unreadNotes: 2 }, lastCheckIn: { at: "2026-10-30T09:40:00", kind: "yasmar_call", status: "completed" } }),
patient("Urgent Flag", { attention: { ...quiet, urgent: true, unreadNotes: 1 } }),
];
it("puts one card per patient in PAT-2 order and leaves quiet patients out", () => {
const todos = needsYou(list, [], now);
expect(todos.map((t) => [t.kind, t.patientName])).toEqual([
["urgent", "Urgent Flag"],
["notes", "Unread Notes"],
["new", "New Info"],
]);
expect(todos[1]).toMatchObject({ title: "2 unread notes · Unread Notes", detail: "From the Yasmar call · Yesterday", tab: "check_ins" });
expect(todos[2]).toMatchObject({ title: "New information · New Info", detail: "Shared by Funke since your last check.", tab: "check" });
expect(list.filter(needsAttention).map((p) => p.name)).toEqual(["New Info", "Unread Notes", "Urgent Flag"]);
});
it("links an urgent card to the latest urgent note's call, even for a patient not in the list", () => {
const todos = needsYou(list, [alert("Urgent Flag", "2026-10-31T08:00:00Z", "Older"), alert("Urgent Flag", "2026-10-31T09:42:00Z", "Ankles swollen"), alert("Someone Else", "2026-10-31T07:00:00Z")], now);
expect(todos[0]).toMatchObject({ title: "2 urgent notes · Urgent Flag", detail: "Ankles swollen", checkInId: "call-2026-10-31T09:42:00Z", count: 2 });
expect(todos.filter((t) => t.kind === "urgent").map((t) => t.patientName)).toEqual(["Urgent Flag", "Someone Else"]);
});
it("says what is waiting in one plain line", () => {
const todos = needsYou(list, [alert("Urgent Flag", "2026-10-31T09:42:00Z")], now);
expect(summaryLine(todos, [{ id: "inv" }])).toBe("1 urgent check-in note, unread notes for 1 patient, new information for 1 patient and 1 invitation to answer.");
expect(summaryLine([], [])).toBe("Nothing needs you right now.");
expect(summaryLine(needsYou([list[2], patient("More Notes", { attention: { ...quiet, unreadNotes: 1 } })], [], now), [])).toBe("Unread notes for 2 patients.");
});
});
describe("doses and check-ins", () => {
it("lists patients who share doses, lowest share taken first", () => {
const rows = dosesThisWeek([
patient("Good Week", { doses: { taken: 14, expected: 14, onTime: 14 } }),
patient("Hard Week", { doses: { taken: 3, expected: 14, onTime: 2 } }),
patient("None Due", { doses: { taken: 0, expected: 0, onTime: 0 } }),
patient("Paused", { sharing: { ...patient("x").sharing!, status: "paused" } }),
patient("Not Shared", { sharing: { ...patient("x").sharing!, scopes: ["medicines"] } }),
patient("Clinic Added", { kind: "clinic_added", sharing: null }),
]);
expect(rows.map((r) => [r.patient.name, r.taken, r.expected])).toEqual([
["Hard Week", 3, 14],
["Good Week", 14, 14],
]);
});
it("shows the latest check-ins first and hides them while sharing is paused or stopped", () => {
const at = (iso: string, kind: "yasmar_call" | "daily" = "yasmar_call") => ({ lastCheckIn: { at: iso, kind, status: kind === "daily" ? null : "completed" } });
const list = [
patient("Older", at("2026-10-29T09:00:00")),
patient("Newest", at("2026-10-31T09:00:00", "daily")),
patient("Paused", { ...at("2026-10-31T09:30:00"), sharing: { ...patient("x").sharing!, status: "paused" } }),
patient("No Call"),
];
const recent = recentCheckIns(list);
expect(recent.map((p) => p.name)).toEqual(["Newest", "Older"]);
expect(checkInTab(recent[0])).toBe("shared");
expect(checkInTab(recent[1])).toBe("check_ins");
expect(todayStats(list, now)).toEqual({ total: 4, sharing: 3, checkedInToday: 1, unreadNotes: 0 });
});
});
describe("clinic hours in the clinic's clock", () => {
const hours = [
{ day: 6, open: "08:00", close: "13:00" },
{ day: 6, open: "15:00", close: "18:00" },
{ day: 5, open: "22:00", close: "06:00" },
];
const lagos = 60;
it("says when the clinic closes, or when it opens next", () => {
expect(clinicHoursLabel(hours, new Date("2026-10-31T09:00:00Z"), lagos)).toEqual({ open: true, label: "Open until 13:00" });
expect(clinicHoursLabel(hours, new Date("2026-10-31T12:30:00Z"), lagos)).toEqual({ open: false, label: "Opens at 15:00" });
expect(clinicHoursLabel(hours, new Date("2026-10-31T18:00:00Z"), lagos)).toEqual({ open: false, label: "Closed now" });
});
it("keeps a night span open past midnight", () => {
expect(clinicHoursLabel(hours, new Date("2026-10-31T03:00:00Z"), lagos)).toEqual({ open: true, label: "Open until 06:00" });
});
});