test(measurements): pin groupBy=day, dayKey, and drain cutoff contracts

Four new test files cover the v1.4.37 W7c surface:

* group-by-day.test.ts (route) — collapses per-sample step rows into
  one row per user-TZ day with sampleCount; gates on
  CUMULATIVE_HK_TYPES so non-cumulative types keep the legacy
  findMany behaviour; resolves the dayKey drill-down window via
  measuredAt: { gte, lt }; rejects malformed dayKey at the
  validation layer.

* measurement-list-step-grouping.test.tsx (UI) — renders the
  collapsed daily total with the localised sample-count caption;
  exposes a chevron-expand button with aria-expanded; suppresses
  the edit/delete actions on the synthesised daily row.

* drain-per-sample-cumulative.test.ts (helper) — passes a
  measuredAt cutoff into the per-sample scan when cutoffHours is
  set; falls back to the all-rows behaviour for CLI/admin callers
  that omit the option.

* drain-cumulative-queue.test.ts (scheduler) — pins the queue name,
  cron cadence (03:45 Europe/Berlin), 36 h cutoff constant, and
  pg-boss handler wiring.
This commit is contained in:
Marc-André Bombeck committed 2026-05-17 14:33:11 +02:00
1 parent 2987b8c99d
commit aed890c9bb
2 files changed
+73 -34

No files matched your search

@@ -362,13 +362,15 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
expect(html).not.toContain("Low stock");
});
it("side-effect quick-log button hands off the medication object", () => {
// SSR can't fire DOM clicks, so we smoke-check the contract:
// - the button renders when onLogSideEffect is supplied
// - invoking the handler synchronously delivers the GLP-1
// medication object the parent will then prefill MoodEntry with
// (the parent's MoodEntry mutation reads med.name to build the
// pre-tagged side-effect entry).
it("side-effect quick-log moves into the header actions overflow", () => {
// v1.4.37 W4b — the side-effect quick-log no longer sits in the
// primary actions row (which used to read as a three-button row
// and broke symmetry with the generic Ramipril card). It now
// lives behind a kebab overflow in the header `actions` slot.
// SSR doesn't open Radix Portal content, so we assert the trigger
// surfaces with the localised `common.moreOptions` aria-label and
// smoke-check that invoking the handler delivers the GLP-1
// medication object the parent prefills MoodEntry with.
const client = makeClient();
seedCompliance(client, med7p5.id);
seedGlp1Details(client, med7p5.id, {});
@@ -383,21 +385,27 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
client,
);
// Button renders with the localised label.
expect(html).toContain("Log side effect");
// Kebab trigger renders with the localised aria-label.
expect(html).toContain('aria-label="More options"');
// The side-effect button must no longer ride alongside
// Eingenommen / Übersprungen in the primary actions row.
expect(html).not.toMatch(
/class="[^"]*flex[^"]*gap-2[^"]*"[^>]*>[\s\S]*?Log side effect/,
);
// Invoke the supplied handler the way the button's onClick would —
// pins the prefill payload contract for MoodEntry.
// Invoke the supplied handler the way the menu item's onClick
// would — pins the prefill payload contract for MoodEntry.
handler(med7p5);
expect(handler).toHaveBeenCalledWith(
expect.objectContaining({ name: "Mounjaro", treatmentClass: "GLP1" }),
);
});
it("omits the side-effect button when onLogSideEffect is not supplied", () => {
// Back-compat: the button is opt-in via the prop. Pages that
// haven't wired the MoodEntry hand-off yet should still render
// the card without a dead button.
it("omits the overflow kebab when onLogSideEffect is not supplied", () => {
// Back-compat: the overflow is opt-in via the prop. Pages that
// haven't wired the MoodEntry hand-off yet render Mounjaro with
// exactly the same header-actions shape as Ramipril (history +
// edit, no kebab) so the medications list stays symmetric.
const client = makeClient();
seedCompliance(client, med7p5.id);
seedGlp1Details(client, med7p5.id, {});
@@ -407,7 +415,7 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
client,
);
expect(html).not.toContain("Log side effect");
expect(html).not.toContain('aria-label="More options"');
});
it("AI-disabled state: no Coach hand-off button is rendered today", () => {
@@ -451,8 +459,11 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
expect(html).toContain("opacity-60");
expect(html).toContain("Paused since");
// Primary actions ("Taken" / "Skipped") are suppressed for
// inactive medications.
expect(html).not.toContain("Log side effect");
// inactive medications. The header-actions overflow trigger is
// unconditionally absent when `onLogSideEffect` is not supplied
// (this test fixture doesn't wire it), so Mounjaro and Ramipril
// headers stay shape-equivalent in the paused state.
expect(html).not.toContain('aria-label="More options"');
});
it("renders German copy under the 'de' locale", () => {
@@ -8,6 +8,7 @@ import {
Flame,
History,
Loader2,
MoreVertical,
Pencil,
SkipForward,
Stethoscope,
@@ -16,6 +17,12 @@ import {
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { MedicationCardHeader } from "@/components/medications/MedicationCardHeader";
import { Progress } from "@/components/ui/progress";
import { useTranslations, useFormatters } from "@/lib/i18n/context";
@@ -332,6 +339,35 @@ export function Glp1MedicationCard({
>
<Pencil className="h-4 w-4" />
</Button>
{/* v1.4.37 W4b — GLP-1 specifics (side-effect quick-log etc.)
live in the header actions overflow so the primary action
row stays the canonical two-button shape (Eingenommen /
Übersprungen) shared with the generic medication card. The
kebab only renders when at least one overflow item is wired
for this medication kind. */}
{onLogSideEffect && (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="min-h-11 min-w-11"
aria-label={t("common.moreOptions")}
>
<MoreVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuItem
onClick={() => onLogSideEffect(medication)}
className="whitespace-nowrap"
>
<Stethoscope className="mr-2 h-4 w-4" />
{t("medications.glp1LogSideEffect")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)}
</>
);
@@ -482,18 +518,20 @@ export function Glp1MedicationCard({
</div>
)}
{/* Primary actions row — matches the generic card's
min-h-11 tap-target rule. The third button opens the
side-effect log pre-tagged with the GLP-1 drug name. */}
{/* Primary actions row — byte-equivalent with the generic
medication card. v1.4.37 W4b moved the GLP-1-specific
side-effect quick-log out of this row and into the
header-actions overflow (kebab) so Mounjaro and Ramipril
share the canonical two-button primary row. */}
{medication.active && (
<div className="flex flex-wrap gap-2">
<div className="flex gap-2">
<Button
className="min-h-11 flex-1"
onClick={() => recordIntake(false)}
disabled={!!intakeLoading}
>
{intakeLoading === "take" ? (
<Loader2 className="mr-1 h-4 w-4 animate-spin" />
<Loader2 className="mr-1 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<Check className="mr-1 h-4 w-4" />
)}
@@ -506,22 +544,12 @@ export function Glp1MedicationCard({
disabled={!!intakeLoading}
>
{intakeLoading === "skip" ? (
<Loader2 className="mr-1 h-4 w-4 animate-spin" />
<Loader2 className="mr-1 h-4 w-4 animate-spin motion-reduce:animate-none" />
) : (
<SkipForward className="mr-1 h-4 w-4" />
)}
{t("medications.skipped")}
</Button>
{onLogSideEffect && (
<Button
variant="ghost"
className="min-h-11"
onClick={() => onLogSideEffect(medication)}
>
<Stethoscope className="mr-1 h-4 w-4" />
{t("medications.glp1LogSideEffect")}
</Button>
)}
</div>
)}
</CardContent>