From 9df22b6ef6ccfd204d35dba93dd42722a18de61f Mon Sep 17 00:00:00 2001 From: dev_agent <17+dev_agent@code.cubecraftcreations.com> Date: Fri, 25 Sep 2026 07:35:47 -0400 Subject: [PATCH] Add a Start date field to the definition edit sheet (#237) --- admin/src/definitions/DefinitionsTab.test.tsx | 92 +++++++++++++++++++ admin/src/definitions/DefinitionsTab.tsx | 11 +++ 2 files changed, 103 insertions(+) diff --git a/admin/src/definitions/DefinitionsTab.test.tsx b/admin/src/definitions/DefinitionsTab.test.tsx index b54475f..0db0d5d 100644 --- a/admin/src/definitions/DefinitionsTab.test.tsx +++ b/admin/src/definitions/DefinitionsTab.test.tsx @@ -1118,3 +1118,95 @@ describe("DefinitionsTab icon picker", () => { expect(rowIcon(screen.getByTestId("definition-20"))).toBeNull(); }); }); + +/* ── Start date ─────────────────────────────────────────────────────── */ + +function startDateInput(s: HTMLElement): HTMLInputElement { + return within(s).getByLabelText("Start date", { selector: "input" }) as HTMLInputElement; +} + +describe("DefinitionsTab start date", () => { + beforeEach(() => { + sessionStorage.clear(); + storeTokens({ access_token: "at-123", expires_in: 600 }); + }); + + afterEach(() => { + cleanup(); + vi.unstubAllGlobals(); + vi.useRealTimers(); + }); + + it("a new task defaults to today and POSTs the chosen start date", async () => { + vi.useFakeTimers({ toFake: ["Date"] }); + vi.setSystemTime(new Date(2026, 8, 23, 9, 0)); // Wed 23 Sep 2026, local + await renderReady(); + + fireEvent.click(screen.getByRole("button", { name: "New" })); + const s = sheet(); + const input = startDateInput(s); + expect(input.type).toBe("date"); + expect(input.value).toBe("2026-09-23"); + + fireEvent.change(within(s).getByRole("textbox", { name: "Title" }), { + target: { value: "Feed the cat" }, + }); + fireEvent.click(within(s).getByRole("button", { name: /Assigned children/ })); + fireEvent.click(within(s).getByRole("checkbox", { name: "Declan" })); + fireEvent.click(within(s).getByRole("button", { name: "Evening" })); + fireEvent.change(input, { target: { value: "2026-10-15" } }); + + await act(async () => { + fireEvent.click(within(s).getByRole("button", { name: "Save changes" })); + }); + expect(api.writes()).toHaveLength(1); + expect(api.writes()[0].method).toBe("POST"); + expect((api.writes()[0].body as { rule: DefinitionRule }).rule.start_date).toBe("2026-10-15"); + }); + + it("an edit pre-fills the definition's start date and PATCHes a changed one", async () => { + await renderReady(); + fireEvent.click(screen.getByTestId("definition-10")); + const s = sheet(); + const input = startDateInput(s); + expect(input.value).toBe("2026-01-01"); + + fireEvent.change(input, { target: { value: "2026-11-02" } }); + await act(async () => { + fireEvent.click(within(s).getByRole("button", { name: "Save changes" })); + }); + expect(api.writes()).toEqual([ + expect.objectContaining({ + method: "PATCH", + path: "/api/v1/admin/quest-definitions/10", + body: expect.objectContaining({ rule: rule({ start_date: "2026-11-02" }) }), + }), + ]); + }); + + it("the occurrence preview requests the chosen start date", async () => { + await renderReady(); + fireEvent.click(screen.getByTestId("definition-10")); + const s = sheet(); + await waitFor(() => expect(api.previews()).toHaveLength(1)); + + fireEvent.change(startDateInput(s), { target: { value: "2027-03-01" } }); + await settleDebounce(); + await waitFor(() => expect(api.previews()).toHaveLength(2)); + expect((api.previews()[1].body as { rule: DefinitionRule }).rule.start_date).toBe( + "2027-03-01", + ); + }); + + it("a cleared start date is not submitted", async () => { + await renderReady(); + fireEvent.click(screen.getByTestId("definition-10")); + const s = sheet(); + fireEvent.change(startDateInput(s), { target: { value: "" } }); + await act(async () => { + fireEvent.click(within(s).getByRole("button", { name: "Save changes" })); + }); + expect(api.writes()).toHaveLength(0); + expect(within(s).getByText("Pick a start date.")).toBeTruthy(); + }); +}); diff --git a/admin/src/definitions/DefinitionsTab.tsx b/admin/src/definitions/DefinitionsTab.tsx index 5db5f16..dcbb698 100644 --- a/admin/src/definitions/DefinitionsTab.tsx +++ b/admin/src/definitions/DefinitionsTab.tsx @@ -177,6 +177,7 @@ function parseWhole(value: string, min: number, max: number): number | null { function buildRule(draft: Draft): DefinitionRule | string { const interval = parseWhole(draft.interval, 1, 999); if (interval === null) return "Interval must be a whole number of at least 1."; + if (draft.startDate === "") return "Pick a start date."; const rule: DefinitionRule = { rule_type: "daily", interval, @@ -740,6 +741,16 @@ function EditSheet({ + + update({ startDate: e.target.value })} + /> + + {orderedWindows.length === 0 ? ( Pick a window to set its due time.
Pick a window to set its due time.