Files
HealthLog/src/lib/analytics/__tests__/trend-delta.test.ts
T
Marc-André BombeckandClaude Opus 4.7 4df6dac036 fix(charts): "7-Tage-Trend" label + numeric trend indicator on every chart incl. mood
Marc reported v1.4.15 A4 didn't fully land:
- DE label still read "7-Tage-Schnitt" / "7T-Schnitt" in places
- mood tile showed no numeric trend delta even though other tiles did

i18n rename:
- de.json + en.json: trend7dShort, movingAverage7d, moodMA all read
  "7-Tage-Trend" / "7-day trend". The legacy "7T:" / "7d:" header on
  the avg7 line is preserved for tiles that haven't been migrated to
  the trend7Delta API.

trend-delta fallback:
- summaryToTrend7Delta() now falls back to slope30 (projected onto
  7 days) when slope7 is null. Mood entries are typically logged
  less than daily, so slope7 needs ≥ 2 points within the trailing
  7-day window — for sparser users that came back null and the tile
  silently dropped the delta indicator. slope30 still answers "is
  this metric drifting?" so we project that onto the 7-day window
  for label parity.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:30:42 +02:00

66 lines
2.1 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { summaryToTrend7Delta } from "../trend-delta";
const STABLE = { slope: 0, direction: "stable" as const, confidence: 0.1 };
const RISING_HALF_PER_DAY = {
slope: 0.5,
direction: "up" as const,
confidence: 0.8,
};
const FALLING_TENTH_PER_DAY = {
slope: -0.1,
direction: "down" as const,
confidence: 0.7,
};
describe("summaryToTrend7Delta()", () => {
it("returns null when the summary is missing", () => {
expect(summaryToTrend7Delta(null)).toBeNull();
expect(summaryToTrend7Delta(undefined)).toBeNull();
});
it("returns null when neither slope7 nor slope30 is computable", () => {
expect(summaryToTrend7Delta({ slope7: null })).toBeNull();
expect(summaryToTrend7Delta({ slope7: null, slope30: null })).toBeNull();
});
it("projects slope (units per day) over a 7-day window", () => {
expect(summaryToTrend7Delta({ slope7: RISING_HALF_PER_DAY })).toBe(3.5);
// Negative slope produces a negative delta — the tile shows it
// with the unicode minus sign in the formatter.
expect(summaryToTrend7Delta({ slope7: FALLING_TENTH_PER_DAY })).toBeCloseTo(
-0.7,
3,
);
});
it("returns 0 for a stable slope (the tile then paints ±0)", () => {
expect(summaryToTrend7Delta({ slope7: STABLE })).toBe(0);
});
it("falls back to slope30 when slope7 is null (sparse metrics like mood)", () => {
// v1.4.16 fix A4: mood-tile showed no trend number for users who
// log mood < daily because slope7 needs ≥2 points within the
// trailing 7-day window. slope30 still answers "is this metric
// drifting?" so we project that onto 7 days for label parity.
expect(
summaryToTrend7Delta({ slope7: null, slope30: RISING_HALF_PER_DAY }),
).toBe(3.5);
expect(
summaryToTrend7Delta({
slope7: null,
slope30: FALLING_TENTH_PER_DAY,
}),
).toBeCloseTo(-0.7, 3);
});
it("prefers slope7 over slope30 when both are present", () => {
expect(
summaryToTrend7Delta({
slope7: RISING_HALF_PER_DAY,
slope30: FALLING_TENTH_PER_DAY,
}),
).toBe(3.5);
});
});