mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
Merge branch 'release/v1.39.4' into fix/v1394-review-correctness
This commit is contained in:
commit
5173049130
26 files changed
+4034
-54
No files matched your search
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* The value axis of a Coach result chart steps evenly: the chart hands
|
||||
* recharts explicit ticks on an explicit domain instead of letting it place
|
||||
* (and thin) them, which left labels like 75, 90, 105, 135 on a blood
|
||||
* pressure line. Recharts is replaced by pass-through parts so the props the
|
||||
* chart gives its y axis can be read without a browser layout.
|
||||
*/
|
||||
import type { ReactNode } from "react";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const yAxisProps = vi.hoisted(() => [] as Array<Record<string, unknown>>);
|
||||
|
||||
vi.mock("recharts", () => {
|
||||
const Pass = ({ children }: { children?: ReactNode }) => <>{children}</>;
|
||||
const Leaf = () => null;
|
||||
return {
|
||||
ResponsiveContainer: Pass,
|
||||
LineChart: Pass,
|
||||
BarChart: Pass,
|
||||
CartesianGrid: Leaf,
|
||||
Line: Leaf,
|
||||
Bar: Leaf,
|
||||
Tooltip: Leaf,
|
||||
XAxis: Leaf,
|
||||
YAxis: (props: Record<string, unknown>) => {
|
||||
yAxisProps.push(props);
|
||||
return null;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import type { CoachResultTable } from "@/lib/ai/coach/types";
|
||||
|
||||
import { CoachResultChart } from "../result-chart";
|
||||
|
||||
function bpTable(rows: CoachResultTable["rows"]): CoachResultTable {
|
||||
return {
|
||||
ref: "r1",
|
||||
source: {
|
||||
tool: "get_metric_table",
|
||||
domain: "bp",
|
||||
window: "last30days",
|
||||
period: "current",
|
||||
granularity: "day",
|
||||
},
|
||||
shape: "timeSeries",
|
||||
titleKey: "coach.result.title.byDay",
|
||||
title: "Blood pressure by day",
|
||||
rowCount: rows.length,
|
||||
chartKind: "line",
|
||||
displayed: true,
|
||||
columns: [
|
||||
{ key: "day", kind: "period", labelKey: "k", label: "Day" },
|
||||
{ key: "systolic", kind: "number", labelKey: "k", label: "Systolic" },
|
||||
{ key: "diastolic", kind: "number", labelKey: "k", label: "Diastolic" },
|
||||
],
|
||||
rows,
|
||||
truncated: false,
|
||||
chart: { kind: "line", x: "day", series: ["systolic", "diastolic"] },
|
||||
};
|
||||
}
|
||||
|
||||
function renderChart(result: CoachResultTable) {
|
||||
renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="en">
|
||||
<CoachResultChart result={result} otherLabel="Other" label="Chart" />
|
||||
</I18nProvider>,
|
||||
);
|
||||
return yAxisProps.at(-1)!;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
yAxisProps.length = 0;
|
||||
});
|
||||
|
||||
describe("CoachResultChart value axis", () => {
|
||||
it("gives a blood pressure line evenly spaced ticks on its domain", () => {
|
||||
const props = renderChart(
|
||||
bpTable([
|
||||
["2026-09-01", 134, 88],
|
||||
["2026-09-02", null, null],
|
||||
["2026-09-03", 121, 78],
|
||||
]),
|
||||
);
|
||||
const ticks = props.ticks as number[];
|
||||
expect(ticks).toEqual([60, 80, 100, 120, 140]);
|
||||
expect(props.domain).toEqual([60, 140]);
|
||||
// Every tick is drawn; none is thinned into an uneven step.
|
||||
expect(props.interval).toBe(0);
|
||||
});
|
||||
|
||||
it("starts vertical bars at zero", () => {
|
||||
const props = renderChart({
|
||||
...bpTable([
|
||||
["2026-09-01", 3200, null],
|
||||
["2026-09-02", 11050, null],
|
||||
]),
|
||||
chart: {
|
||||
kind: "bar",
|
||||
x: "day",
|
||||
series: ["systolic"],
|
||||
orientation: "vertical",
|
||||
},
|
||||
chartKind: "bar",
|
||||
});
|
||||
expect(props.domain).toEqual([0, 15000]);
|
||||
expect(props.ticks).toEqual([0, 5000, 10000, 15000]);
|
||||
});
|
||||
});
|
||||
@@ -37,6 +37,7 @@ import type {
|
||||
CoachResultTable,
|
||||
} from "@/lib/ai/coach/types";
|
||||
import { makeBucketLabelFormatters } from "@/lib/charts/bucket-label";
|
||||
import { niceAxis } from "@/lib/charts/nice-axis";
|
||||
import { prefersReducedMotion } from "@/lib/charts/reduced-motion";
|
||||
import { useDateFormatPreference, useTranslations } from "@/lib/i18n/context";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -219,6 +220,13 @@ function SeriesChart({
|
||||
const colorOf = (key: string) =>
|
||||
SERIES_COLORS[spec.series.indexOf(key) % SERIES_COLORS.length];
|
||||
const visible = spec.series.filter((key) => !hidden.has(key));
|
||||
// Evenly stepped ticks over what is shown; bars start at the baseline.
|
||||
const yAxis = niceAxis(
|
||||
points.flatMap((point) =>
|
||||
visible.map((key) => point[key] as number | null),
|
||||
),
|
||||
{ zero: spec.kind === "bar" },
|
||||
);
|
||||
|
||||
const tooltip = (props: {
|
||||
active?: boolean;
|
||||
@@ -263,7 +271,11 @@ function SeriesChart({
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
width={40}
|
||||
domain={spec.kind === "bar" ? [0, "auto"] : ["auto", "auto"]}
|
||||
{...(yAxis
|
||||
? { domain: yAxis.domain, ticks: yAxis.ticks, interval: 0 }
|
||||
: {
|
||||
domain: spec.kind === "bar" ? [0, "auto"] : ["auto", "auto"],
|
||||
})}
|
||||
/>
|
||||
<Tooltip content={tooltip} cursor={{ fill: "var(--muted)" }} />
|
||||
</>
|
||||
|
||||
Reference in new issue
Block a user