From 0a0667fd1c21b73089c2cae74c591b6f19090ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 16 Oct 2026 01:23:03 +0200 Subject: [PATCH] test(admin): pin when the pulse day-mean card offers apply --- .../__tests__/pulse-day-mean-card.test.tsx | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 src/components/admin/__tests__/pulse-day-mean-card.test.tsx diff --git a/src/components/admin/__tests__/pulse-day-mean-card.test.tsx b/src/components/admin/__tests__/pulse-day-mean-card.test.tsx new file mode 100644 index 000000000..926de7ea6 --- /dev/null +++ b/src/components/admin/__tests__/pulse-day-mean-card.test.tsx @@ -0,0 +1,116 @@ +/** + * The pulse day-mean admin card. + * + * 1. apply is offered only after a dry run finished in the last seven days, + * and never while a dry run is queued or running; + * 2. a finished dry run shows its figures and the per-account ordinals; + * 3. the active method reads in the header, in German too. + * + * Mutation check: drop `hasFreshDryRun` from `canApply` and case 1 goes red. + */ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +import { PulseDayMeanCard } from "@/components/admin/pulse-day-mean-card"; +import { I18nProvider } from "@/lib/i18n/context"; +import { queryKeys } from "@/lib/query-keys"; +import type { PulseDayMeanStatus } from "@/lib/validations/pulse-day-mean"; + +const RESULT = { + accounts: 2, + daysCompared: 412, + daysChangedOver1Bpm: 37, + maxChangeBpm: 6.4, + distribution: [ + { bucket: "0-1" as const, days: 375 }, + { bucket: "1-2" as const, days: 30 }, + { bucket: "2-5" as const, days: 6 }, + { bucket: "5+" as const, days: 1 }, + ], + perAccount: [ + { account: "#1", days: 400, changedOver1Bpm: 36 }, + { account: "#2", days: 12, changedOver1Bpm: 1 }, + ], +}; + +const status = ( + lastDryRun: PulseDayMeanStatus["lastDryRun"], + method: PulseDayMeanStatus["method"] = "hour_mean_v1", +): PulseDayMeanStatus => ({ method, appliedAt: null, lastDryRun }); + +const done = (finishedAt: string) => ({ + status: "done" as const, + startedAt: finishedAt, + finishedAt, + result: RESULT, +}); + +function render(s: PulseDayMeanStatus, locale: "en" | "de" = "en"): string { + const client = new QueryClient(); + client.setQueryData(queryKeys.adminPulseDayMean(), s); + return renderToStaticMarkup( + + + + + , + ); +} + +/** Whether the apply button renders disabled. */ +function applyDisabled(html: string): boolean { + const at = html.indexOf('data-testid="admin-pulse-day-mean-apply"'); + const tag = html.slice( + html.lastIndexOf("", at), + ); + return /\sdisabled=""/.test(tag); +} + +beforeEach(() => { + vi.useFakeTimers({ toFake: ["Date"] }); + vi.setSystemTime(new Date("2026-10-16T12:00:00Z")); +}); +afterEach(() => vi.useRealTimers()); + +describe("PulseDayMeanCard", () => { + it("offers apply only after a fresh, finished dry run", () => { + expect(applyDisabled(render(status(null)))).toBe(true); + expect( + applyDisabled(render(status(done("2026-10-08T12:00:00.000Z")))), + ).toBe(true); + expect( + applyDisabled( + render( + status({ + status: "running", + startedAt: "2026-10-16T11:00:00.000Z", + finishedAt: null, + result: null, + }), + ), + ), + ).toBe(true); + expect( + applyDisabled(render(status(done("2026-10-15T12:00:00.000Z")))), + ).toBe(false); + }); + + it("shows what the dry run found", () => { + const html = render(status(done("2026-10-15T12:00:00.000Z"))); + expect(html).toContain("412"); + expect(html).toContain("37"); + expect(html).toContain("6.4 bpm"); + expect(html).toContain("5+ bpm"); + expect(html).toContain("Account #2"); + expect(html).toContain("36 of 400 days move"); + }); + + it("names the active method, in German too", () => { + expect(render(status(null))).toContain("Every hour equal"); + const de = render(status(null, "weighted_hour_mean_v2"), "de"); + expect(de).toContain("Dünne Stunden gewichtet"); + expect(de).toContain("Probelauf"); + }); +});