diff --git a/frontend/app/(app)/dashboard/page.test.tsx b/frontend/app/(app)/dashboard/page.test.tsx index adbed04..97c8445 100644 --- a/frontend/app/(app)/dashboard/page.test.tsx +++ b/frontend/app/(app)/dashboard/page.test.tsx @@ -273,6 +273,20 @@ describe("DashboardPage", () => { ); }); + it("lets the user type a timeout when creating an HTTP check", async () => { + renderDashboard("SIGNAL"); + + fireEvent.click( + (await screen.findAllByRole("button", { name: /new check/i }))[0], + ); + fireEvent.click(await screen.findByRole("button", { name: /^http$/i })); + + const timeout = await screen.findByLabelText("Timeout"); + expect(timeout).toHaveValue(5); + fireEvent.change(timeout, { target: { value: "8" } }); + expect(timeout).toHaveValue(8); + }); + it("uses the SIGNAL creator floor when a SOLO collaborator creates a check", async () => { context.activeOrg.plan = "SIGNAL"; renderDashboard("SOLO"); diff --git a/frontend/app/(app)/dashboard/page.tsx b/frontend/app/(app)/dashboard/page.tsx index 6495553..2c06295 100644 --- a/frontend/app/(app)/dashboard/page.tsx +++ b/frontend/app/(app)/dashboard/page.tsx @@ -34,6 +34,7 @@ import { } from "@/components/ui/select"; import { CircleGauge, Plus, Pause, Play } from "lucide-react"; import { Slider } from "@/components/ui/slider"; +import { DurationSliderField } from "@/components/app/duration-slider-field"; import { formatDuration } from "@/lib/format"; import { usePollWhenVisible } from "@/lib/use-poll-when-visible"; import { CHECK_POLL_INTERVAL_MS } from "@/lib/polling"; @@ -460,15 +461,15 @@ function CreateCheckDialog({ value={[parseInt(intervalSeconds || String(floor), 10)]} onValueChange={(v) => setIntervalSeconds(String(Array.isArray(v) ? v[0] : v))} /> -
-
- - {Math.round(parseInt(timeoutMs || "0", 10) / 1000)} s -
- setTimeoutMs(String(Array.isArray(v) ? v[0] : v))} /> -
+ setTimeoutMs(String(ms))} + minMs={1000} + maxMs={60000} + stepMs={500} + /> {checkType === "HTTP" && (
-
- - setTimeoutMs(e.target.value)} - required - /> -
+ setTimeoutMs(String(ms))} + minMs={1000} + maxMs={60000} + stepMs={500} + /> {type === "HTTP" && ( <>
diff --git a/frontend/test/edit-check-dialog.test.tsx b/frontend/test/edit-check-dialog.test.tsx index 1e132c3..849f7cd 100644 --- a/frontend/test/edit-check-dialog.test.tsx +++ b/frontend/test/edit-check-dialog.test.tsx @@ -203,6 +203,32 @@ describe("EditCheckDialog", () => { expect(screen.queryByLabelText(/period/i)).not.toBeInTheDocument(); }); + it("lets the user type timeout in seconds or set it with a slider", () => { + renderDialog(HTTP); + + const input = screen.getByLabelText("Timeout"); + expect(input).toHaveValue(5); + expect(screen.getByRole("slider", { hidden: true })).toHaveValue("5000"); + + fireEvent.change(input, { target: { value: "10" } }); + expect(input).toHaveValue(10); + expect(screen.getByRole("slider", { hidden: true })).toHaveValue("10000"); + }); + + it("submits typed timeout seconds as milliseconds", async () => { + const { captured } = renderDialogCapturingVariables(HTTP); + await waitForPlan(); + + fireEvent.change(screen.getByLabelText("Timeout"), { + target: { value: "10" }, + }); + submitForm(); + + await waitFor(() => expect(captured).toHaveLength(1)); + const input = captured[0].input as Record; + expect(input.timeoutMs).toBe(10000); + }); + it("uses the paid plan floor for heartbeat periods and active intervals", async () => { const { unmount } = renderDialog(HEARTBEAT); await waitFor(() =>