Reminder detail page: * Surfaces a PausedRunBanner above the rest of the surface when the most recent run is in 'paused' state. The banner shows the delivered/total counts, the deadline that closed the window, and Resume / Cancel run buttons that call the matching server actions. * getReminderWithRuns now LEFT JOIN-aggregates run_target counts so the banner has sent/total per run without an N+1 fan-out. Activity tab: * New Paused filter tab between Success and Partial. * Paused rows in the desktop table get an inline ResumeRunButton (emerald play icon, useTransition + error surfacing). * RunStatusBadge picks up a Paused entry — amber, PauseCircle icon. Tests: * PausedRunBanner — 4 SSR cases (resume/cancel CTA rendered, X-of-Y copy, generic fallback, amber styling). * ResumeRunButton — 4 SSR cases (aria, emerald accent, compact / default size variants). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
|
|
vi.mock("@/actions/reminders", () => ({
|
|
resumeReminderRunAction: vi.fn(),
|
|
cancelReminderRunAction: vi.fn(),
|
|
}));
|
|
|
|
import { PausedRunBanner } from "./paused-run-banner";
|
|
|
|
describe("PausedRunBanner — SSR layout", () => {
|
|
it("renders Resume + Cancel buttons inside the banner", () => {
|
|
const html = renderToStaticMarkup(
|
|
<PausedRunBanner
|
|
runId="r-1"
|
|
sent={412}
|
|
total={1000}
|
|
windowEndHour={18}
|
|
timezone="Asia/Kuala_Lumpur"
|
|
/>,
|
|
);
|
|
expect(html).toContain('data-testid="paused-run-banner"');
|
|
expect(html).toContain('data-testid="paused-resume"');
|
|
expect(html).toContain('data-testid="paused-cancel"');
|
|
expect(html).toMatch(/Resume<\/button>/);
|
|
expect(html).toMatch(/Cancel run<\/button>/);
|
|
});
|
|
|
|
it("shows X of Y groups delivered when sent + total are present", () => {
|
|
const html = renderToStaticMarkup(
|
|
<PausedRunBanner
|
|
runId="r-1"
|
|
sent={412}
|
|
total={1000}
|
|
windowEndHour={18}
|
|
timezone="Asia/Kuala_Lumpur"
|
|
/>,
|
|
);
|
|
expect(html).toContain("412 of 1000 groups delivered");
|
|
// Surfaces the window-end deadline so the operator knows why.
|
|
expect(html).toContain("18:00 (Asia/Kuala_Lumpur)");
|
|
// And the remaining count drives the CTA copy.
|
|
expect(html).toContain("send the remaining 588");
|
|
});
|
|
|
|
it("falls back to a generic body when sent / total aren't supplied", () => {
|
|
const html = renderToStaticMarkup(
|
|
<PausedRunBanner
|
|
runId="r-1"
|
|
windowEndHour={18}
|
|
timezone="Asia/Kuala_Lumpur"
|
|
/>,
|
|
);
|
|
expect(html).toMatch(/delivery window closed before/i);
|
|
expect(html).not.toContain("groups delivered");
|
|
});
|
|
|
|
it("uses amber styling so the banner reads as 'attention, not error'", () => {
|
|
const html = renderToStaticMarkup(
|
|
<PausedRunBanner
|
|
runId="r-1"
|
|
sent={1}
|
|
total={2}
|
|
windowEndHour={18}
|
|
timezone="UTC"
|
|
/>,
|
|
);
|
|
expect(html).toMatch(/border-amber-500/);
|
|
expect(html).toMatch(/bg-amber-500/);
|
|
});
|
|
});
|