fix(housekeeping): retain unchecked command fields
This commit is contained in:
1 parent
3b2012e704
commit
5edaa0254c
2 files changed
+105
-4
No files matched your search
@@ -41,6 +41,7 @@ interface ContentCommandFormProps extends ContentCommandSubmission {
|
||||
}
|
||||
|
||||
const OMIT_FIELD = Symbol("omit optional Content command field");
|
||||
const UNCHECKED_CHECKBOX_VALUE = "false";
|
||||
function optionalEmptyValue(field: ContentCommandField): unknown {
|
||||
return field.emptyValue === null || field.emptyValue === ""
|
||||
? field.emptyValue
|
||||
@@ -115,7 +116,11 @@ function readSubmittedValues(
|
||||
for (const field of fields) {
|
||||
if (field.type === "file") continue;
|
||||
const value = formData.get(field.name);
|
||||
if (typeof value === "string") values[field.name] = value;
|
||||
if (typeof value === "string") {
|
||||
values[field.name] = value;
|
||||
} else if (field.type === "checkbox" && !field.allowUnchanged) {
|
||||
values[field.name] = UNCHECKED_CHECKBOX_VALUE;
|
||||
}
|
||||
}
|
||||
if (requiresReason) {
|
||||
const reason = formData.get("reason");
|
||||
@@ -247,8 +252,9 @@ export function ContentCommandForm({
|
||||
name={field.name}
|
||||
type="checkbox"
|
||||
defaultChecked={
|
||||
retainedValue === "on" ||
|
||||
(retainedValue === undefined && field.defaultValue === true)
|
||||
retainedValue === undefined
|
||||
? field.defaultValue === true
|
||||
: retainedValue === "on"
|
||||
}
|
||||
{...fieldErrorProps}
|
||||
/>{" "}
|
||||
|
||||
@@ -20,6 +20,22 @@ import { ContentHelpPage } from "./help";
|
||||
import { ContentLocalizationPage } from "./localization";
|
||||
import { ContentMediaPage } from "./media";
|
||||
|
||||
const { actionState } = vi.hoisted(() => ({
|
||||
actionState: { current: null as unknown },
|
||||
}));
|
||||
|
||||
vi.mock("react", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("react")>();
|
||||
return {
|
||||
...actual,
|
||||
useActionState: (_action: unknown, initialState: unknown) => [
|
||||
actionState.current ?? initialState,
|
||||
vi.fn(),
|
||||
false,
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@/actions/housekeeping-command", () => ({
|
||||
executeHousekeepingCommand: vi.fn(),
|
||||
}));
|
||||
@@ -125,7 +141,10 @@ describe.each(cases)("Content %s page", (kind, Component, editPermission) => {
|
||||
});
|
||||
|
||||
describe("Content actionable form wiring", () => {
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
actionState.current = null;
|
||||
});
|
||||
|
||||
it("retains configured string values after validation and excludes files", async () => {
|
||||
vi.mocked(executeHousekeepingCommand).mockResolvedValue(
|
||||
@@ -157,6 +176,82 @@ describe("Content actionable form wiring", () => {
|
||||
expect(state).not.toHaveProperty("values.cover");
|
||||
});
|
||||
|
||||
it("keeps an unchecked default-true checkbox unchecked after a failed remount", async () => {
|
||||
vi.mocked(executeHousekeepingCommand).mockResolvedValue(
|
||||
fail("VALIDATION", "errors.housekeeping.validation", "poll-checkbox"),
|
||||
);
|
||||
const formData = new FormData();
|
||||
const state = await submitContentCommandForm(
|
||||
{
|
||||
commandId: "content.engagement.poll.change",
|
||||
input: { action: "create" },
|
||||
fields: [
|
||||
{
|
||||
name: "showResults",
|
||||
label: "Show results",
|
||||
type: "checkbox",
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
null,
|
||||
formData,
|
||||
);
|
||||
|
||||
expect(executeHousekeepingCommand).toHaveBeenCalledWith({
|
||||
commandId: "content.engagement.poll.change",
|
||||
input: { action: "create", showResults: false },
|
||||
});
|
||||
actionState.current = state;
|
||||
const html = renderToStaticMarkup(
|
||||
<ContentCommandForm
|
||||
commandId="content.engagement.poll.change"
|
||||
buttonLabel="Create poll"
|
||||
input={{ action: "create" }}
|
||||
fields={[
|
||||
{
|
||||
name: "showResults",
|
||||
label: "Show results",
|
||||
type: "checkbox",
|
||||
defaultValue: true,
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
actionState.current = null;
|
||||
const showResults = html.match(/<input[^>]*name="showResults"[^>]*>/u)?.[0];
|
||||
|
||||
expect(showResults).toBeDefined();
|
||||
expect(showResults).not.toContain("checked");
|
||||
});
|
||||
|
||||
it("retains dependency-failed string values", async () => {
|
||||
vi.mocked(executeHousekeepingCommand).mockResolvedValue(
|
||||
fail(
|
||||
"DEPENDENCY_UNAVAILABLE",
|
||||
"errors.housekeeping.dependencyUnavailable",
|
||||
"poll-dependency",
|
||||
),
|
||||
);
|
||||
const formData = new FormData();
|
||||
formData.set("title", "Draft title");
|
||||
|
||||
const state = await submitContentCommandForm(
|
||||
{
|
||||
commandId: "content.engagement.poll.change",
|
||||
input: { action: "create" },
|
||||
fields: [{ name: "title", label: "Title", type: "text" }],
|
||||
},
|
||||
null,
|
||||
formData,
|
||||
);
|
||||
|
||||
expect(state).toMatchObject({
|
||||
result: { ok: false, error: { code: "DEPENDENCY_UNAVAILABLE" } },
|
||||
values: { title: "Draft title" },
|
||||
});
|
||||
});
|
||||
|
||||
it("retains submitted fields and reason after a conflict", async () => {
|
||||
vi.mocked(executeHousekeepingCommand).mockResolvedValue(
|
||||
fail("CONFLICT", "errors.housekeeping.conflict", "poll-conflict"),
|
||||
|
||||
Reference in new issue
Block a user