diff --git a/src/features/housekeeping/domains/content/pages/poll-workflow.test.tsx b/src/features/housekeeping/domains/content/pages/poll-workflow.test.tsx index 664f1b1c..8162d299 100644 --- a/src/features/housekeeping/domains/content/pages/poll-workflow.test.tsx +++ b/src/features/housekeeping/domains/content/pages/poll-workflow.test.tsx @@ -1,5 +1,6 @@ import { renderToStaticMarkup } from "react-dom/server"; -import { describe, expect, it } from "vitest"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { executeHousekeepingCommand } from "@/actions/housekeeping-command"; import { PERMS } from "@/lib/permission-slugs"; import { fail, @@ -12,6 +13,30 @@ import type { } from "../queries/content-queries"; import { ContentPollWorkflow } from "./poll-workflow"; +const { actionCallbacks } = vi.hoisted(() => ({ + actionCallbacks: [] as Array< + (previous: unknown, formData: FormData) => Promise + >, +})); + +vi.mock("react", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + useActionState: ( + action: (previous: unknown, formData: FormData) => Promise, + initialState: unknown, + ) => { + actionCallbacks.push(action); + return [initialState, vi.fn(), false] as const; + }, + }; +}); + +vi.mock("@/actions/housekeeping-command", () => ({ + executeHousekeepingCommand: vi.fn(), +})); + function context(granted: readonly string[]): HousekeepingCapabilityContext { const permissions = new Set(granted); return { @@ -118,6 +143,11 @@ function renderList( } describe("Content Poll workflow", () => { + beforeEach(() => { + actionCallbacks.length = 0; + vi.clearAllMocks(); + }); + it("renders explicit loading, forbidden, dependency-error, empty, and malformed states", () => { expect(renderList(viewer)).toContain('data-housekeeping-state="loading"'); expect( @@ -286,6 +316,43 @@ describe("Content Poll workflow", () => { expect(html.match(/name="reason"/gu)).toHaveLength(3); }); + it("submits empty options when changing a choice question to text", async () => { + vi.mocked(executeHousekeepingCommand).mockResolvedValue( + ok({}, "question-text-update"), + ); + renderToStaticMarkup( + , + ); + const questionUpdate = actionCallbacks[1]; + if (!questionUpdate) + throw new Error("Question update action was not bound"); + const formData = new FormData(); + formData.set("question", "Pick a colour"); + formData.set("type", "text"); + formData.set("sortOrder", "0"); + formData.set("options", ""); + + await questionUpdate(null, formData); + + expect(executeHousekeepingCommand).toHaveBeenCalledWith({ + commandId: "content.engagement.poll-question.change", + input: { + action: "update", + id: "21", + question: "Pick a colour", + type: "text", + sortOrder: 0, + options: "", + }, + }); + }); + it("distinguishes true not-found detail from malformed successful data", () => { const render = (items: ContentQueryData["items"]) => renderToStaticMarkup( diff --git a/src/features/housekeeping/domains/content/pages/poll-workflow.tsx b/src/features/housekeeping/domains/content/pages/poll-workflow.tsx index 0346e521..18b4c50e 100644 --- a/src/features/housekeeping/domains/content/pages/poll-workflow.tsx +++ b/src/features/housekeeping/domains/content/pages/poll-workflow.tsx @@ -184,6 +184,7 @@ function questionFields( name: "options", label: "Options (one per line)", type: "textarea", + emptyValue: "", maxLength: 20_000, defaultValue: question?.options.join("\n"), },