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 OMIT_FIELD = Symbol("omit optional Content command field");
|
||||||
|
const UNCHECKED_CHECKBOX_VALUE = "false";
|
||||||
function optionalEmptyValue(field: ContentCommandField): unknown {
|
function optionalEmptyValue(field: ContentCommandField): unknown {
|
||||||
return field.emptyValue === null || field.emptyValue === ""
|
return field.emptyValue === null || field.emptyValue === ""
|
||||||
? field.emptyValue
|
? field.emptyValue
|
||||||
@@ -115,7 +116,11 @@ function readSubmittedValues(
|
|||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
if (field.type === "file") continue;
|
if (field.type === "file") continue;
|
||||||
const value = formData.get(field.name);
|
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) {
|
if (requiresReason) {
|
||||||
const reason = formData.get("reason");
|
const reason = formData.get("reason");
|
||||||
@@ -247,8 +252,9 @@ export function ContentCommandForm({
|
|||||||
name={field.name}
|
name={field.name}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
defaultChecked={
|
defaultChecked={
|
||||||
retainedValue === "on" ||
|
retainedValue === undefined
|
||||||
(retainedValue === undefined && field.defaultValue === true)
|
? field.defaultValue === true
|
||||||
|
: retainedValue === "on"
|
||||||
}
|
}
|
||||||
{...fieldErrorProps}
|
{...fieldErrorProps}
|
||||||
/>{" "}
|
/>{" "}
|
||||||
|
|||||||
@@ -20,6 +20,22 @@ import { ContentHelpPage } from "./help";
|
|||||||
import { ContentLocalizationPage } from "./localization";
|
import { ContentLocalizationPage } from "./localization";
|
||||||
import { ContentMediaPage } from "./media";
|
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", () => ({
|
vi.mock("@/actions/housekeeping-command", () => ({
|
||||||
executeHousekeepingCommand: vi.fn(),
|
executeHousekeepingCommand: vi.fn(),
|
||||||
}));
|
}));
|
||||||
@@ -125,7 +141,10 @@ describe.each(cases)("Content %s page", (kind, Component, editPermission) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Content actionable form wiring", () => {
|
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 () => {
|
it("retains configured string values after validation and excludes files", async () => {
|
||||||
vi.mocked(executeHousekeepingCommand).mockResolvedValue(
|
vi.mocked(executeHousekeepingCommand).mockResolvedValue(
|
||||||
@@ -157,6 +176,82 @@ describe("Content actionable form wiring", () => {
|
|||||||
expect(state).not.toHaveProperty("values.cover");
|
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 () => {
|
it("retains submitted fields and reason after a conflict", async () => {
|
||||||
vi.mocked(executeHousekeepingCommand).mockResolvedValue(
|
vi.mocked(executeHousekeepingCommand).mockResolvedValue(
|
||||||
fail("CONFLICT", "errors.housekeeping.conflict", "poll-conflict"),
|
fail("CONFLICT", "errors.housekeeping.conflict", "poll-conflict"),
|
||||||
|
|||||||
Reference in new issue
Block a user