mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
HealthChart was a 1,929-line component. Its parts move to components/charts/health-chart/: - series-math: series names, day keys, moving average, personal baseline, annotation and marker placement (pure) - use-health-chart-series: the fetch window, the batched-slice decision, the daily read and the data-ready latch - use-health-chart-view: the visible slice, bucketing, the average and trend lines, the prior period, the visible-range stats and the y domain - use-health-chart-overlays: value bands, target zones, baselines, the trend read-out, the spoken summary and the table columns - chart-header, band-overlay, chart-tooltip-content - chart-layers: the reference layers and series as plain functions that return the Recharts elements, so the plot's child list is unchanged health-chart.tsx keeps the component and its path (chart-runtime still imports it from there) and shrinks to 488 lines. Output is unchanged. The guards pinned to the file follow the code into the folder, and the baseline-gating check now calls the layer instead of matching source.
81 lines
2.8 KiB
TypeScript
81 lines
2.8 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import { localDayKeyFor, openEndedFetchWindow } from "../fetch-window";
|
|
|
|
/**
|
|
* A chart that shows "the last N days" asks for a window that must still hold
|
|
* a reading saved after the chart mounted. The window used to end at the mount
|
|
* instant, so the invalidated refetch asked for the same old window and a new
|
|
* reading stayed invisible until the range tab was switched.
|
|
*/
|
|
function windowAt(now: Date, tz: string, days = 30) {
|
|
return openEndedFetchWindow(days, localDayKeyFor(now, tz), tz);
|
|
}
|
|
|
|
function inside(w: { from: string; to: string }, at: Date) {
|
|
return at >= new Date(w.from) && at <= new Date(w.to);
|
|
}
|
|
|
|
const previousTz = process.env.TZ;
|
|
afterEach(() => {
|
|
process.env.TZ = previousTz;
|
|
});
|
|
|
|
describe.each([
|
|
["Europe/Berlin", "UTC"],
|
|
["America/Los_Angeles", "America/Los_Angeles"],
|
|
["Pacific/Kiritimati", "Pacific/Kiritimati"],
|
|
])("open-ended chart window, profile %s, host %s", (profileTz, hostTz) => {
|
|
it("holds a reading saved after the chart mounted, under the same key", () => {
|
|
process.env.TZ = hostTz;
|
|
const mountedAt = new Date("2026-10-03T08:00:00.000Z");
|
|
const savedAt = new Date(mountedAt.getTime() + 5 * 60_000);
|
|
|
|
const atMount = windowAt(mountedAt, profileTz);
|
|
const afterSave = windowAt(savedAt, profileTz);
|
|
|
|
expect(inside(atMount, savedAt)).toBe(true);
|
|
// Same day, same window: the query key does not churn, and the
|
|
// invalidation that follows a save refetches a window that holds it.
|
|
expect(afterSave).toEqual(atMount);
|
|
});
|
|
|
|
it("moves on to the next local day by itself", () => {
|
|
process.env.TZ = hostTz;
|
|
const today = new Date("2026-10-03T08:00:00.000Z");
|
|
const tomorrow = new Date(today.getTime() + 86_400_000);
|
|
|
|
const w = windowAt(tomorrow, profileTz);
|
|
|
|
expect(inside(w, tomorrow)).toBe(true);
|
|
expect(w).not.toEqual(windowAt(today, profileTz));
|
|
});
|
|
|
|
it("spans the requested number of days", () => {
|
|
process.env.TZ = hostTz;
|
|
const w = windowAt(new Date("2026-10-03T08:00:00.000Z"), profileTz, 7);
|
|
const span = new Date(w.to).getTime() - new Date(w.from).getTime();
|
|
expect(span).toBe(7 * 86_400_000);
|
|
expect(w.windowDays).toBe(7);
|
|
});
|
|
});
|
|
|
|
describe("health chart wiring", () => {
|
|
it("builds its fetch window from today's key, not the mount instant", () => {
|
|
const chart = readFileSync(
|
|
join(
|
|
process.cwd(),
|
|
"src/components/charts/health-chart/use-health-chart-series.ts",
|
|
),
|
|
"utf8",
|
|
);
|
|
const memo = chart.slice(chart.indexOf("const fetchWindow = useMemo("));
|
|
const body = memo.slice(0, memo.indexOf("}, ["));
|
|
expect(body).toContain("openEndedFetchWindow(");
|
|
expect(body).not.toMatch(/const to = new Date\(\)/);
|
|
expect(memo).toMatch(/\}, \[[^\]]*todayKey[^\]]*\]\)/);
|
|
});
|
|
});
|