diff --git a/src/components/insights/__tests__/workout-list.test.tsx b/src/components/insights/__tests__/workout-list.test.tsx index 337fc58b2..d6cb72ec5 100644 --- a/src/components/insights/__tests__/workout-list.test.tsx +++ b/src/components/insights/__tests__/workout-list.test.tsx @@ -47,15 +47,27 @@ describe("", () => { expect(html).toContain("320 kcal"); }); + it("renders aggregate average HR without claiming an HR series", () => { + const html = render( + , + ); + + expect(html).toContain('data-slot="workout-list-avg-hr"'); + expect(html).toContain("145 bpm"); + expect(html).not.toContain('data-slot="workout-list-glyph-hr"'); + }); + it("omits the chip when the field is null", () => { const noDistance: WorkoutListEntry = { ...ROW, distanceM: null, activeEnergyKcal: null, + avgHr: null, }; const html = render(); expect(html).not.toContain('data-slot="workout-list-distance"'); expect(html).not.toContain('data-slot="workout-list-energy"'); + expect(html).not.toContain('data-slot="workout-list-avg-hr"'); }); it("falls back to the canonical sport string for unknown sport types", () => { diff --git a/src/lib/workouts/__tests__/hr-series.test.ts b/src/lib/workouts/__tests__/hr-series.test.ts index 98e43e060..0beab9c57 100644 --- a/src/lib/workouts/__tests__/hr-series.test.ts +++ b/src/lib/workouts/__tests__/hr-series.test.ts @@ -121,6 +121,31 @@ describe("buildWorkoutHrSeries", () => { expect(result).toBeNull(); }); + it("does not synthesize a curve from aggregate HR or zone durations", async () => { + findMany.mockResolvedValue([]); + const result = await buildWorkoutHrSeries({ + ...base, + storedSamples: null, + // Aggregate-only WHOOP fields deliberately are not inputs to the + // curve builder. Keep them on this structural fixture to pin that a + // future widening cannot silently fabricate time-series points. + avgHeartRate: 145, + maxHeartRate: 180, + metadata: { + zoneDurations: { + zone_two_milli: 300_000, + zone_three_milli: 300_000, + }, + }, + } as Parameters[0] & { + avgHeartRate: number; + maxHeartRate: number; + metadata: unknown; + }); + + expect(result).toBeNull(); + }); + it("hides when bucket coverage is below 40 %", async () => { // 10 samples all clustered in the first minute of a 10-minute run → // enough raw samples, but coverage is ~1 bucket of ~75.