marmar-context / web / context / test / today.test.ts
today.test.ts
Raw
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" });
  });
});