import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { act, cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react"; import HistoryTab, { FOOTER_NOTE, computeStats } from "./HistoryTab"; import type { HistoryFilter, HistoryRow } from "../api/history"; import { storeTokens } from "../auth/oidc"; import { TransitionsProvider } from "../api/useTransitions"; import { frame, testStream, type TestStream } from "../api/testStream"; import { AppTimezoneProvider } from "../time/AppTimezone"; const TODAY = "2026-09-23"; function row(overrides: Partial): HistoryRow { return { occurred_at: null, event_type: "completed", child_name: "Declan", child_id: 1, quest_title: "Brush teeth", instance_id: 1, window: "morning", due_date: TODAY, due_time: null, actor: "panel (Declan)", was_on_time: true, ...overrides, }; } // America/Los_Angeles is UTC-7 in September. const BRUSH = row({ instance_id: 1, occurred_at: "2026-09-23T14:04:00Z", // 7:04 AM due_time: "07:30", }); const BED_DONE = row({ instance_id: 2, quest_title: "Make bed", child_name: "Maeve", child_id: 2, occurred_at: "2026-09-23T14:50:00Z", // 7:50 AM actor: "panel (Maeve)", }); const BED_UNDONE = row({ instance_id: 2, event_type: "uncompleted", quest_title: "Make bed", child_name: "Maeve", child_id: 2, occurred_at: "2026-09-23T15:10:00Z", // 8:10 AM actor: "joshua (admin)", was_on_time: null, }); const CAT_LATE = row({ instance_id: 3, quest_title: "Feed cat", due_date: "2026-09-22", due_time: "07:00", occurred_at: "2026-09-22T14:22:00Z", // 7:22 AM, 22 min after due was_on_time: false, }); // 7:30 PM local on Sep 22 — already Sep 23 in UTC. const HOMEWORK_LATE = row({ instance_id: 4, quest_title: "Homework", child_name: "Rory", child_id: 3, due_date: "2026-09-22", due_time: null, occurred_at: "2026-09-23T02:30:00Z", actor: "panel (Rory)", was_on_time: false, }); const PLANTS_MISSED = row({ instance_id: 5, event_type: "missed", quest_title: "Water plants", child_name: "Rory", child_id: 3, due_date: "2026-09-21", due_time: "18:00", actor: "nightly sweep", was_on_time: null, }); // The API's order: due_date, then instance, then event id. const ROWS: Record = { all: [CAT_LATE, HOMEWORK_LATE, BRUSH, BED_DONE, BED_UNDONE], reversals: [BED_UNDONE], missed: [PLANTS_MISSED], }; function jsonResponse(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" }, }); } const CSV_BODY = "occurred_at,event_type\n2026-09-23T14:04:00Z,completed\n"; function csvResponse(filter: string, start: string, end: string): Response { return new Response(`${filter}\n${CSV_BODY}`, { status: 200, headers: { "Content-Type": "text/csv; charset=utf-8", "Content-Disposition": `attachment; filename="nestquest-history-${start}-to-${end}.csv"`, }, }); } function routeFetch(url: string): Response { const parsed = new URL(url, "http://x"); if (parsed.pathname.endsWith("/api/v1/admin/history.csv")) { return csvResponse( parsed.searchParams.get("filter") ?? "", parsed.searchParams.get("start") ?? "", parsed.searchParams.get("end") ?? "", ); } if (!parsed.pathname.endsWith("/api/v1/admin/history")) { return jsonResponse({ detail: "unexpected" }, 500); } const filter = parsed.searchParams.get("filter") as HistoryFilter; const rows = ROWS[filter]; return jsonResponse({ filter, start: parsed.searchParams.get("start"), end: parsed.searchParams.get("end"), count: rows.length, rows, }); } let fetchMock: ReturnType; function historyCalls(): URL[] { return fetchMock.mock.calls.map(([url]) => new URL(String(url), "http://x")); } function metas(section: HTMLElement): string[] { return within(section) .getAllByTestId("history-meta") .map((el) => el.textContent ?? ""); } async function renderReady() { render(); await screen.findByTestId("stat-on-time"); } describe("HistoryTab", () => { beforeAll(() => { vi.stubEnv("TZ", "America/Los_Angeles"); }); afterAll(() => { vi.unstubAllEnvs(); }); beforeEach(() => { sessionStorage.clear(); storeTokens({ access_token: "at-123", expires_in: 600 }); fetchMock = vi.fn((url: string) => Promise.resolve(routeFetch(url))); vi.stubGlobal("fetch", fetchMock); }); afterEach(() => { cleanup(); vi.unstubAllGlobals(); }); it("renders the header and queries the last 14 days with the Bearer token", async () => { await renderReady(); expect(screen.getByRole("heading", { name: "History" })).toBeTruthy(); expect(screen.getByText("Append-only event log · last 14 days")).toBeTruthy(); const calls = historyCalls(); expect(calls.length).toBeGreaterThan(0); for (const url of calls) { expect(url.pathname).toBe("/api/v1/admin/history"); expect(url.searchParams.get("start")).toBe("2026-09-10"); expect(url.searchParams.get("end")).toBe(TODAY); } expect(calls.map((url) => url.searchParams.get("filter")).sort()).toEqual(["all", "missed"]); expect(fetchMock.mock.calls[0][1].headers.Authorization).toBe("Bearer at-123"); }); it("stat row shows On time %, Completed and Missed for the period", async () => { await renderReady(); // 4 completions (2 on time) in `all`; 1 derived missed row. expect(screen.getByTestId("stat-on-time").textContent).toBe("50%On time"); expect(screen.getByTestId("stat-completed").textContent).toBe("4Completed"); expect(screen.getByTestId("stat-missed").textContent).toBe("1Missed"); }); it("On time shows — when the period has no completions", () => { expect(computeStats([BED_UNDONE], [])).toEqual({ onTimePct: null, completed: 0, missed: 0 }); }); it("groups rows by local day, newest first, with event type, actor and on-time meta", async () => { await renderReady(); const today = screen.getByTestId(`day-${TODAY}`); expect(within(today).getByRole("heading").textContent).toBe("Today"); expect(metas(today)).toEqual([ "uncompleted · joshua (admin)", "completed · panel (Maeve) · on time", "completed · panel (Declan) · on time", ]); expect(within(today).getAllByText("Make bed · Maeve")).toHaveLength(2); expect(within(today).getByText("8:10 AM")).toBeTruthy(); // The 7:30 PM completion is Sep 23 in UTC but Sep 22 locally. const yesterday = screen.getByTestId("day-2026-09-22"); expect(within(yesterday).getByRole("heading").textContent).toBe("Yesterday"); expect(metas(yesterday)).toEqual([ "completed · panel (Rory) · late", "completed · panel (Declan) · late 22 min", ]); expect(within(yesterday).getByText("7:30 PM")).toBeTruthy(); const dots = screen.getAllByTestId("history-dot").map((el) => el.className); expect(dots[0]).toContain("history-dot--uncompleted"); expect(dots[1]).toContain("history-dot--completed"); }); it("each filter chip re-queries the API with its filter and re-renders", async () => { await renderReady(); fireEvent.click(screen.getByRole("button", { name: "Reversals" })); await waitFor(() => expect(historyCalls().some((url) => url.searchParams.get("filter") === "reversals")).toBe(true), ); await waitFor(() => expect(screen.getAllByTestId("history-row")).toHaveLength(1)); expect(screen.getByTestId("history-meta").textContent).toBe("uncompleted · joshua (admin)"); expect(screen.getByRole("button", { name: "Reversals" }).getAttribute("aria-pressed")).toBe("true"); fireEvent.click(screen.getByRole("button", { name: "Missed" })); const missedDay = await screen.findByTestId("day-2026-09-21"); expect(within(missedDay).getByRole("heading").textContent).toBe("Mon, Sep 21"); expect(metas(missedDay)).toEqual(["missed · nightly sweep"]); expect(within(missedDay).getByTestId("history-dot").className).toContain("history-dot--missed"); expect(within(missedDay).getByText("6:00 PM")).toBeTruthy(); // Stats stay the period's numbers whichever chip is selected. expect(screen.getByTestId("stat-completed").textContent).toBe("4Completed"); fetchMock.mockClear(); fireEvent.click(screen.getByRole("button", { name: "All events" })); await screen.findByTestId(`day-${TODAY}`); expect(historyCalls().map((url) => url.searchParams.get("filter"))).toContain("all"); expect(screen.getAllByTestId("history-row")).toHaveLength(5); }); it("shows the append-only footer", async () => { await renderReady(); expect(FOOTER_NOTE).toBe("Events are never edited or deleted. A reversal is its own row."); expect(screen.getByTestId("history-footer").textContent).toBe(FOOTER_NOTE); }); describe("CSV export", () => { let createObjectURL: ReturnType; let revokeObjectURL: ReturnType; let clicked: { href: string; download: string; attached: boolean }[]; beforeEach(() => { createObjectURL = vi.fn(() => "blob:nestquest/csv-1"); revokeObjectURL = vi.fn(); // jsdom has no object URLs; install stubs and remove them afterwards. Object.assign(URL, { createObjectURL, revokeObjectURL }); clicked = []; vi.spyOn(HTMLAnchorElement.prototype, "click").mockImplementation(function (this: HTMLAnchorElement) { clicked.push({ href: this.getAttribute("href") ?? "", download: this.download, attached: document.body.contains(this), }); }); }); afterEach(() => { vi.restoreAllMocks(); const stubbed = URL as unknown as Record; delete stubbed.createObjectURL; delete stubbed.revokeObjectURL; }); function csvCalls(): [URL, RequestInit][] { return fetchMock.mock.calls .map(([url, init]) => [new URL(String(url), "http://x"), init] as [URL, RequestInit]) .filter(([url]) => url.pathname === "/api/v1/admin/history.csv"); } it("requests the CSV for the active filter and displayed range with the Bearer token", async () => { await renderReady(); fireEvent.click(screen.getByRole("button", { name: "CSV" })); await waitFor(() => expect(clicked).toHaveLength(1)); const calls = csvCalls(); expect(calls).toHaveLength(1); const [url, init] = calls[0]; expect(url.searchParams.get("filter")).toBe("all"); expect(url.searchParams.get("start")).toBe("2026-09-10"); expect(url.searchParams.get("end")).toBe(TODAY); expect((init.headers as Record).Authorization).toBe("Bearer at-123"); // The token never travels in the URL. expect(url.toString()).not.toContain("at-123"); }); it("offers the returned CSV for download, then revokes the URL and removes the anchor", async () => { await renderReady(); fireEvent.click(screen.getByRole("button", { name: "CSV" })); await waitFor(() => expect(clicked).toHaveLength(1)); expect(createObjectURL).toHaveBeenCalledTimes(1); const blob = createObjectURL.mock.calls[0][0] as Blob; expect(await blob.text()).toBe(`all\n${CSV_BODY}`); expect(clicked[0]).toEqual({ href: "blob:nestquest/csv-1", download: "nestquest-history-2026-09-10-to-2026-09-23.csv", attached: true, }); expect(revokeObjectURL).toHaveBeenCalledWith("blob:nestquest/csv-1"); expect(document.querySelector("a[download]")).toBeNull(); expect(screen.queryByTestId("history-export-error")).toBeNull(); }); it("falls back to the range filename without a Content-Disposition header", async () => { fetchMock.mockImplementation((url: string) => Promise.resolve( new URL(url, "http://x").pathname.endsWith(".csv") ? new Response(CSV_BODY, { status: 200, headers: { "Content-Type": "text/csv" } }) : routeFetch(url), ), ); await renderReady(); fireEvent.click(screen.getByRole("button", { name: "CSV" })); await waitFor(() => expect(clicked).toHaveLength(1)); expect(clicked[0].download).toBe("nestquest-history-2026-09-10-to-2026-09-23.csv"); }); it("switching the filter chip then exporting uses the new filter", async () => { await renderReady(); fireEvent.click(screen.getByRole("button", { name: "Reversals" })); await waitFor(() => expect(screen.getAllByTestId("history-row")).toHaveLength(1)); fireEvent.click(screen.getByRole("button", { name: "CSV" })); await waitFor(() => expect(clicked).toHaveLength(1)); const calls = csvCalls(); expect(calls).toHaveLength(1); expect(calls[0][0].searchParams.get("filter")).toBe("reversals"); expect(calls[0][0].searchParams.get("start")).toBe("2026-09-10"); expect(calls[0][0].searchParams.get("end")).toBe(TODAY); }); it("a failed export shows a non-blocking error and does not download", async () => { fetchMock.mockImplementation((url: string) => Promise.resolve( new URL(url, "http://x").pathname.endsWith(".csv") ? jsonResponse({ detail: "boom" }, 500) : routeFetch(url), ), ); await renderReady(); fireEvent.click(screen.getByRole("button", { name: "CSV" })); const error = await screen.findByTestId("history-export-error"); expect(error.textContent).toContain("The CSV export failed"); expect(createObjectURL).not.toHaveBeenCalled(); expect(clicked).toHaveLength(0); // The tab stays usable: rows remain, the button is re-enabled, chips still work. expect(screen.getAllByTestId("history-row")).toHaveLength(5); expect((screen.getByRole("button", { name: "CSV" }) as HTMLButtonElement).disabled).toBe(false); fireEvent.click(screen.getByRole("button", { name: "Reversals" })); await waitFor(() => expect(screen.getAllByTestId("history-row")).toHaveLength(1)); fireEvent.click(within(error).getByRole("button", { name: "Dismiss" })); expect(screen.queryByTestId("history-export-error")).toBeNull(); }); it("the button is disabled while the request is in flight (no double-submit)", async () => { let release: (response: Response) => void = () => {}; fetchMock.mockImplementation((url: string) => new URL(url, "http://x").pathname.endsWith(".csv") ? new Promise((resolve) => { release = resolve; }) : Promise.resolve(routeFetch(url)), ); await renderReady(); const csv = screen.getByRole("button", { name: "CSV" }) as HTMLButtonElement; fireEvent.click(csv); await waitFor(() => expect(csv.disabled).toBe(true)); fireEvent.click(csv); expect(csvCalls()).toHaveLength(1); release(csvResponse("all", "2026-09-10", TODAY)); await waitFor(() => expect(clicked).toHaveLength(1)); await waitFor(() => expect(csv.disabled).toBe(false)); expect(csvCalls()).toHaveLength(1); }); }); it("rows are read-only: no row carries a control", async () => { await renderReady(); for (const el of screen.getAllByTestId("history-row")) { expect(within(el).queryByRole("button")).toBeNull(); } }); it("a failed fetch shows the error state and Try again reloads", async () => { fetchMock.mockImplementation(() => Promise.resolve(jsonResponse({ detail: "boom" }, 500))); render(); const error = await screen.findByTestId("history-error"); expect(error.textContent).toContain("The history could not be loaded"); expect(screen.getByTestId("history-footer")).toBeTruthy(); fetchMock.mockImplementation((url: string) => Promise.resolve(routeFetch(url))); fireEvent.click(within(error).getByRole("button", { name: "Try again" })); await screen.findByTestId("stat-on-time"); expect(screen.queryByTestId("history-error")).toBeNull(); }); it("the scroll column ends with 92px bottom padding", async () => { await renderReady(); expect(screen.getByTestId("history-scroll").style.paddingBottom).toBe("92px"); }); }); describe("HistoryTab live updates", () => { let fetchMock: ReturnType; let streams: TestStream[]; function historyRequests(): URL[] { return fetchMock.mock.calls .map(([url]) => new URL(String(url), "http://x")) .filter((url) => url.pathname === "/api/v1/admin/history"); } const PAYLOAD = { child_id: 2, child_name: "Maeve", instance_id: 2, quest_title: "Make bed", window: "morning", due_date: TODAY, due_time: null, occurred_at: "2026-09-23T15:10:00Z", }; beforeAll(() => { vi.stubEnv("TZ", "America/Los_Angeles"); }); afterAll(() => { vi.unstubAllEnvs(); }); beforeEach(() => { sessionStorage.clear(); storeTokens({ access_token: "at-123", expires_in: 600 }); streams = []; fetchMock = vi.fn((url: string) => { if (new URL(url, "http://x").pathname.endsWith("/api/v1/admin/events")) { const stream = testStream(); streams.push(stream); return Promise.resolve(stream.response); } return Promise.resolve(routeFetch(url)); }); vi.stubGlobal("fetch", fetchMock); }); afterEach(() => { cleanup(); vi.unstubAllGlobals(); }); it("a quest transition refetches the active filter; a day-complete does not", async () => { render( , ); await screen.findByTestId("stat-on-time"); fireEvent.click(screen.getByRole("button", { name: "Reversals" })); await waitFor(() => expect(historyRequests().some((u) => u.searchParams.get("filter") === "reversals")).toBe(true), ); await screen.findByTestId("stat-on-time"); await waitFor(() => expect(streams).toHaveLength(1)); const before = historyRequests().length; streams[0].push( frame("nestquest_child_day_complete", { child_id: 2, child_name: "Maeve", quests_due: 4, quests_completed: 4, occurred_at: "2026-09-23T15:00:00Z", }), ); await new Promise((resolve) => setTimeout(resolve, 30)); expect(historyRequests().length).toBe(before); streams[0].push(frame("nestquest_quest_uncompleted", PAYLOAD)); await waitFor(() => expect(historyRequests().length).toBe(before + 3)); const refetched = historyRequests().slice(before); // One refetch (reversals + the two stats queries), same range and filter. expect(refetched.map((u) => u.searchParams.get("filter")).sort()).toEqual([ "all", "missed", "reversals", ]); for (const url of refetched) { expect(url.searchParams.get("start")).toBe("2026-09-10"); expect(url.searchParams.get("end")).toBe(TODAY); } await new Promise((resolve) => setTimeout(resolve, 30)); expect(historyRequests().length).toBe(before + 3); expect(screen.queryByTestId("history-loading")).toBeNull(); expect(screen.getByRole("button", { name: "Reversals" }).getAttribute("aria-pressed")).toBe("true"); // Rows stay read-only (D-005): no row carries a control. for (const row of screen.getAllByTestId("history-row")) { expect(within(row).queryByRole("button")).toBeNull(); } }); describe("a filter change interleaved with a quest transition", () => { // Both land before the load effect runs, so one load serves both. async function interleave(order: "sse-first" | "chip-first") { await act(async () => { const click = () => fireEvent.click(screen.getByRole("button", { name: "Reversals" })); if (order === "chip-first") click(); streams[0].push(frame("nestquest_quest_uncompleted", PAYLOAD)); await new Promise((resolve) => setTimeout(resolve, 30)); if (order === "sse-first") click(); }); } async function renderLive() { render( , ); await screen.findByTestId("stat-on-time"); await waitFor(() => expect(streams).toHaveLength(1)); expect(screen.getAllByTestId("history-row")).toHaveLength(ROWS.all.length); } function titles(): string[] { return screen.getAllByTestId("history-row").map((el) => el.textContent ?? ""); } for (const order of ["sse-first", "chip-first"] as const) { it(`${order}: shows loading, then only the active filter's rows`, async () => { let release!: () => void; const gate = new Promise((resolve) => { release = resolve; }); const route = fetchMock.getMockImplementation()!; await renderLive(); fetchMock.mockImplementation((url: string) => { const parsed = new URL(url, "http://x"); if (parsed.searchParams.get("filter") === "reversals") { return gate.then(() => route(url)); } return route(url); }); await interleave(order); expect(screen.getByRole("button", { name: "Reversals" }).getAttribute("aria-pressed")).toBe("true"); // The previous filter's rows never show under the new chip. expect(screen.getByTestId("history-loading")).toBeTruthy(); expect(screen.queryAllByTestId("history-row")).toHaveLength(0); release(); await screen.findByTestId("stat-on-time"); expect(titles()).toHaveLength(1); expect(titles()[0]).toContain("Make bed · Maeve"); expect(screen.getAllByTestId("history-dot")[0].className).toContain("history-dot--uncompleted"); }); it(`${order}: a failed filter-change load shows the error state, not stale rows`, async () => { const route = fetchMock.getMockImplementation()!; await renderLive(); fetchMock.mockImplementation((url: string) => { const parsed = new URL(url, "http://x"); if (parsed.pathname === "/api/v1/admin/history") { return Promise.resolve(jsonResponse({ detail: "boom" }, 500)); } return route(url); }); await interleave(order); const error = await screen.findByTestId("history-error"); expect(error.textContent).toContain("The history could not be loaded"); expect(screen.getByRole("button", { name: "Reversals" }).getAttribute("aria-pressed")).toBe("true"); expect(screen.queryAllByTestId("history-row")).toHaveLength(0); expect(screen.queryByTestId("stat-on-time")).toBeNull(); }); } }); it("a failed transition refresh keeps the active filter's rows on screen", async () => { render( , ); await screen.findByTestId("stat-on-time"); await waitFor(() => expect(streams).toHaveLength(1)); const route = fetchMock.getMockImplementation()!; fetchMock.mockImplementation((url: string) => { if (new URL(url, "http://x").pathname === "/api/v1/admin/history") { return Promise.resolve(jsonResponse({ detail: "boom" }, 500)); } return route(url); }); const before = historyRequests().length; streams[0].push(frame("nestquest_quest_uncompleted", PAYLOAD)); // The "all" filter plus the missed stats query. await waitFor(() => expect(historyRequests().length).toBe(before + 2)); await new Promise((resolve) => setTimeout(resolve, 30)); expect(screen.queryByTestId("history-error")).toBeNull(); expect(screen.queryByTestId("history-loading")).toBeNull(); expect(screen.getAllByTestId("history-row")).toHaveLength(ROWS.all.length); }); }); describe("HistoryTab in the stored application timezone", () => { // 13:00 UTC on Sep 23 is 01:00 on Sep 24 in Auckland (NZST, UTC+12). const NOW = new Date("2026-09-23T13:00:00Z"); // Due 00:10 Auckland on Sep 24, done 00:30 Auckland (12:30 UTC on Sep 23). const LATE_NZ = row({ instance_id: 7, quest_title: "Feed cat", due_date: "2026-09-24", due_time: "00:10", occurred_at: "2026-09-23T12:30:00Z", was_on_time: false, }); // 22:00 Auckland on Sep 23; 10:00 UTC. const EVENING_NZ = row({ instance_id: 8, occurred_at: "2026-09-23T10:00:00Z" }); let fetchMock: ReturnType; function historyRequests(): URL[] { return fetchMock.mock.calls .map(([url]) => new URL(String(url), "http://x")) .filter((url) => url.pathname === "/api/v1/admin/history"); } async function renderWithZone(timezone: string, effective_timezone = timezone) { fetchMock = vi.fn((url: string) => { const parsed = new URL(url, "http://x"); if (parsed.pathname.endsWith("/api/v1/admin/settings")) { return Promise.resolve(jsonResponse({ timezone, effective_timezone })); } const filter = parsed.searchParams.get("filter"); const rows = filter === "all" ? [EVENING_NZ, LATE_NZ] : []; return Promise.resolve( jsonResponse({ filter, start: parsed.searchParams.get("start"), end: parsed.searchParams.get("end"), count: rows.length, rows, }), ); }); vi.stubGlobal("fetch", fetchMock); render( , ); await screen.findByTestId("stat-on-time"); } function times(section: HTMLElement): string[] { return Array.from(section.querySelectorAll(".history-event-time")).map( (el) => el.textContent ?? "", ); } beforeAll(() => { vi.stubEnv("TZ", "UTC"); }); afterAll(() => { vi.unstubAllEnvs(); }); beforeEach(() => { sessionStorage.clear(); storeTokens({ access_token: "at-123", expires_in: 600 }); vi.useFakeTimers({ toFake: ["Date"], now: NOW }); }); afterEach(() => { cleanup(); vi.useRealTimers(); vi.unstubAllGlobals(); }); it.each([ ["the stored zone", "Pacific/Auckland"], ["the API host's published zone when none is stored", ""], ])("today, day grouping, clock and lateness follow %s", async (_label, stored) => { await renderWithZone(stored, "Pacific/Auckland"); for (const url of historyRequests()) { expect(url.searchParams.get("end")).toBe("2026-09-24"); expect(url.searchParams.get("start")).toBe("2026-09-11"); } const today = screen.getByTestId("day-2026-09-24"); expect(within(today).getByText("Today")).toBeTruthy(); expect(times(today)).toEqual(["12:30 AM"]); expect(metas(today)).toEqual(["completed · panel (Declan) · late 20 min"]); const yesterday = screen.getByTestId("day-2026-09-23"); expect(within(yesterday).getByText("Yesterday")).toBeTruthy(); expect(times(yesterday)).toEqual(["10:00 PM"]); }); it("only an API that publishes no zone leaves the browser's local day and clock", async () => { await renderWithZone("", ""); for (const url of historyRequests()) { expect(url.searchParams.get("end")).toBe("2026-09-23"); } expect(screen.queryByTestId("day-2026-09-24")).toBeNull(); const today = screen.getByTestId("day-2026-09-23"); expect(within(today).getByText("Today")).toBeTruthy(); expect(times(today)).toEqual(["12:30 PM", "10:00 AM"]); // Due 00:10 UTC on Sep 24 is after the 12:30 UTC completion: no minutes. expect(metas(today)).toEqual([ "completed · panel (Declan) · late", "completed · panel (Declan) · on time", ]); }); });