From ceabcf8a473c650f7c4a372c179e52219aa4809e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 21 May 2026 21:31:39 +0200 Subject: [PATCH] fix(charts): split mood-chart empty-state copy on raw entry count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the health-chart fix. The mood-analytics endpoint already exposes `entries[].samples` per calendar day, so summing it yields the true raw mood-entry count for the window. When chartData collapses to < 3 daily buckets but the raw count is ≥ 3, paint the new "More distinct days needed" copy instead of the misleading "log at least 3 entries" hint. Pin both branches with an SSR test that mocks the analytics fetch. --- .../mood-chart-empty-state-gate.test.tsx | 94 +++++++++++++++++++ src/components/charts/mood-chart.tsx | 38 +++++++- 2 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 src/components/charts/__tests__/mood-chart-empty-state-gate.test.tsx diff --git a/src/components/charts/__tests__/mood-chart-empty-state-gate.test.tsx b/src/components/charts/__tests__/mood-chart-empty-state-gate.test.tsx new file mode 100644 index 000000000..ab360d232 --- /dev/null +++ b/src/components/charts/__tests__/mood-chart-empty-state-gate.test.tsx @@ -0,0 +1,94 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; + +/** + * v1.4.43 W2-CHART-GATE — mood-chart empty-state copy split. + * + * `chartData` collapses entries to one point per calendar day, so a + * user with many mood entries on < 3 distinct days saw the legacy + * "log more entries" copy despite having logged plenty. The gate now + * keys on `rawCount = Σ entries[].samples` so the second message + * paints when the user only needs more spread across days. + */ + +vi.mock("@/hooks/use-auth", () => ({ + useAuth: () => ({ + isAuthenticated: true, + user: null, + isLoading: false, + }), +})); + +describe(" — empty-state gate by raw count", () => { + beforeEach(() => { + vi.resetModules(); + }); + + + it("renders need-more-days copy when chartData.length < 3 but rawCount >= 3", async () => { + const data = { + entries: [ + { date: "2026-05-19", score: 4, samples: 25 }, + { date: "2026-05-20", score: 5, samples: 25 }, + ], + trend: null, + }; + + vi.doMock("@tanstack/react-query", () => ({ + useQuery: () => ({ data, isLoading: false }), + useQueryClient: () => ({ + cancelQueries: () => Promise.resolve(), + getQueryData: () => undefined, + setQueryData: () => undefined, + invalidateQueries: () => Promise.resolve(), + }), + useMutation: () => ({ mutate: () => undefined, isPending: false }), + })); + + const { I18nProvider } = await import("@/lib/i18n/context"); + const { MoodChart } = await import("../mood-chart"); + + const html = renderToStaticMarkup( + + + , + ); + + expect(html).toContain("Mehr Messtage erforderlich"); + expect(html).not.toContain("Erfasse mindestens 3 Einträge"); + + vi.doUnmock("@tanstack/react-query"); + }); + + it("renders no-data copy when rawCount < 3", async () => { + const data = { + entries: [{ date: "2026-05-20", score: 4, samples: 1 }], + trend: null, + }; + + vi.doMock("@tanstack/react-query", () => ({ + useQuery: () => ({ data, isLoading: false }), + useQueryClient: () => ({ + cancelQueries: () => Promise.resolve(), + getQueryData: () => undefined, + setQueryData: () => undefined, + invalidateQueries: () => Promise.resolve(), + }), + useMutation: () => ({ mutate: () => undefined, isPending: false }), + })); + + const { I18nProvider } = await import("@/lib/i18n/context"); + const { MoodChart } = await import("../mood-chart"); + + const html = renderToStaticMarkup( + + + , + ); + + expect(html).toContain("Erfasse mindestens 3 Einträge"); + expect(html).not.toContain("Mehr Messtage erforderlich"); + + vi.doUnmock("@tanstack/react-query"); + }); +}); diff --git a/src/components/charts/mood-chart.tsx b/src/components/charts/mood-chart.tsx index c4b097be5..b9c48d648 100644 --- a/src/components/charts/mood-chart.tsx +++ b/src/components/charts/mood-chart.tsx @@ -340,6 +340,20 @@ export function MoodChart({ refetchOnWindowFocus: false, }); + // v1.4.43 W2-CHART-GATE — raw mood-entry count across every day in + // the window. `entries[].samples` carries the per-day raw count + // straight from the analytics endpoint; summing it produces the + // total entries the user logged, independent of how many calendar + // days they hit. Used by the empty-state gate to distinguish "few + // entries" from "few days". + const rawCount = useMemo(() => { + if (!data?.entries?.length) return 0; + return data.entries.reduce( + (acc, entry) => acc + (Number.isFinite(entry.samples) ? entry.samples : 0), + 0, + ); + }, [data]); + const chartData = useMemo((): ChartDataPoint[] | undefined => { if (!data?.entries?.length) return undefined; @@ -682,11 +696,25 @@ export function MoodChart({ // shared CHART_HEIGHT_PX so the empty state preserves the // trend-row rhythm (v1.4.27 — was 280, now 240 to match every // other dashboard chart card). - + // + // v1.4.43 W2-CHART-GATE — split the copy on the raw mood-entry + // count. A user with 50 mood entries across 2 calendar days + // sees `chartData.length = 2` (daily-aggregated) but the raw + // count (sum of `samples`) is 50. Surface "need more days" + // in that scenario so the call-to-action matches reality. + rawCount >= 3 ? ( + + ) : ( + + ) ) : (