Route kebab Customize through the prefilled policy wizard so existing methods and values can be renamed, reordered, and kept in that field order on the card after Finalize.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -75,20 +75,16 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
);
|
||||
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||
|
||||
const textboxes = within(screen.getByRole("dialog")).getAllByRole("textbox");
|
||||
expect(textboxes.length).toBe(5);
|
||||
const corePrincipleField = textboxes[2] as HTMLTextAreaElement;
|
||||
const textboxes = within(dialog).getAllByRole("textbox");
|
||||
expect(textboxes.length).toBe(3);
|
||||
const corePrincipleField = textboxes[0] as HTMLTextAreaElement;
|
||||
// Preset corePrinciple must seed into the first body textarea so the user
|
||||
// edits a real starting point rather than an empty field.
|
||||
expect(corePrincipleField.value.length).toBeGreaterThan(0);
|
||||
|
||||
fireEvent.change(corePrincipleField, { target: { value: "Custom principle" } });
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
|
||||
fireEvent.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", {
|
||||
within(dialog).getByRole("button", {
|
||||
name: "Add Platform",
|
||||
}),
|
||||
);
|
||||
@@ -162,7 +158,30 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
expect(textareas[2].value).toBe("Saved coc");
|
||||
});
|
||||
|
||||
it("Cancel customize reverts edited preset without persisting (no confirm when unchanged)", async () => {
|
||||
it("opens meaning fields editable without Customize", async () => {
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
onState={() => {
|
||||
/* noop */
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
const textareas = within(dialog).getAllByRole(
|
||||
"textbox",
|
||||
) as HTMLTextAreaElement[];
|
||||
expect(textareas[0]).not.toBeDisabled();
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
expect(
|
||||
screen.getByRole("menuitem", { name: "Customize" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("closing the wizard without Finalize leaves the card modal and does not persist", async () => {
|
||||
let latest: CreateFlowState = {};
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
@@ -179,20 +198,84 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "Cancel" }));
|
||||
expect(
|
||||
await screen.findByPlaceholderText("Policy name"),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
expect(
|
||||
(within(screen.getByRole("dialog")).getAllByRole(
|
||||
"textbox",
|
||||
)[0] as HTMLTextAreaElement).disabled,
|
||||
).toBe(true);
|
||||
expect(screen.queryByPlaceholderText("Policy name")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(latest.communicationMethodDetailsById).toBeUndefined();
|
||||
expect(screen.queryByRole("button", { name: "Discard" })).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
expect(latest.customMethodCardMetaById).toBeUndefined();
|
||||
});
|
||||
|
||||
it("Cancel customize with edits restores snapshot after confirm", async () => {
|
||||
it("Back from the wizard with edits asks to discard and does not persist", async () => {
|
||||
let latest: CreateFlowState = {};
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
onState={(s) => {
|
||||
latest = s;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||
|
||||
const nameInput = await screen.findByPlaceholderText("Policy name");
|
||||
fireEvent.change(nameInput, { target: { value: "Renamed in wizard" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Back" }));
|
||||
|
||||
expect(
|
||||
await screen.findByRole("button", { name: "Keep editing" }),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Discard" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByPlaceholderText("Policy name")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
expect(latest.customMethodCardMetaById).toBeUndefined();
|
||||
});
|
||||
|
||||
it("Escape after a field edit stays open when user declines discard confirm", async () => {
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
onState={() => {
|
||||
/* noop */
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
const textboxes = within(dialog).getAllByRole(
|
||||
"textbox",
|
||||
) as HTMLTextAreaElement[];
|
||||
fireEvent.change(textboxes[0], { target: { value: "Edited principle" } });
|
||||
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
const keepEditing = await screen.findByRole("button", {
|
||||
name: "Keep editing",
|
||||
});
|
||||
expect(keepEditing.parentElement).toHaveClass(
|
||||
"absolute",
|
||||
"left-[16px]",
|
||||
"top-[12px]",
|
||||
);
|
||||
await declineDiscardCustomizeEdits();
|
||||
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Escape after a field edit discards without persisting when confirmed", async () => {
|
||||
let latest: CreateFlowState = {};
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
@@ -216,67 +299,22 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||
|
||||
const textboxes = within(dialog).getAllByRole(
|
||||
"textbox",
|
||||
) as HTMLTextAreaElement[];
|
||||
fireEvent.change(textboxes[2], { target: { value: "Edited principle" } });
|
||||
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "Cancel" }));
|
||||
fireEvent.change(textboxes[0], { target: { value: "Edited principle" } });
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
await confirmDiscardCustomizeEdits();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
(
|
||||
within(screen.getByRole("dialog")).getAllByRole(
|
||||
"textbox",
|
||||
)[0] as HTMLTextAreaElement
|
||||
).value,
|
||||
).toBe("Saved principle");
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
latest.communicationMethodDetailsById?.signal?.corePrinciple,
|
||||
).toBe("Saved principle");
|
||||
});
|
||||
|
||||
it("dirty Escape close stays open when user declines discard confirm", async () => {
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
onState={() => {
|
||||
/* noop */
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||
|
||||
const textboxes = within(dialog).getAllByRole(
|
||||
"textbox",
|
||||
) as HTMLTextAreaElement[];
|
||||
fireEvent.change(textboxes[2], { target: { value: "Edited principle" } });
|
||||
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
const keepEditing = await screen.findByRole("button", {
|
||||
name: "Keep editing",
|
||||
});
|
||||
expect(keepEditing.parentElement).toHaveClass(
|
||||
"absolute",
|
||||
"left-[16px]",
|
||||
"top-[12px]",
|
||||
);
|
||||
await declineDiscardCustomizeEdits();
|
||||
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("persists customized policy title for a custom UUID card on Save", async () => {
|
||||
it("Customize wizard prefills the card title and persists a rename on Finalize", async () => {
|
||||
const customId = "00000000-0000-4000-8000-0000000000aa";
|
||||
let latest: CreateFlowState = {};
|
||||
render(
|
||||
@@ -307,13 +345,21 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||
|
||||
const titleInput = within(screen.getByRole("dialog")).getAllByRole(
|
||||
"textbox",
|
||||
)[0] as HTMLInputElement;
|
||||
fireEvent.change(titleInput, { target: { value: "Renamed policy" } });
|
||||
fireEvent.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", { name: "Save" }),
|
||||
);
|
||||
expect(
|
||||
await screen.findByPlaceholderText("Policy name"),
|
||||
).toBeInTheDocument();
|
||||
const nameInput = screen.getByPlaceholderText("Policy name");
|
||||
expect(nameInput).toHaveValue("Original title");
|
||||
fireEvent.change(nameInput, { target: { value: "Renamed policy" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||
expect(
|
||||
await screen.findByText("Custom policy details"),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Core Principle & Scope" }),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(latest.customMethodCardMetaById?.[customId]?.label).toBe(
|
||||
@@ -322,7 +368,7 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("stores preset id title override in customMethodCardMetaById on Save", async () => {
|
||||
it("stores preset id title override in customMethodCardMetaById on Finalize", async () => {
|
||||
let latest: CreateFlowState = {};
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
@@ -339,15 +385,32 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||
|
||||
const titleInput = within(screen.getByRole("dialog")).getAllByRole(
|
||||
"textbox",
|
||||
)[0] as HTMLInputElement;
|
||||
fireEvent.change(titleInput, {
|
||||
expect(
|
||||
await screen.findByPlaceholderText("Policy name"),
|
||||
).toHaveValue("Signal");
|
||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||
expect(
|
||||
await screen.findByText("Custom policy details"),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(
|
||||
screen.getByRole("button", { name: "Core Principle & Scope" }),
|
||||
);
|
||||
expect(await screen.findByText("Add text block")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByDisplayValue(/We prioritize privacy and security/i),
|
||||
).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Back" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Back" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Back" }));
|
||||
const nameInput = await screen.findByPlaceholderText("Policy name");
|
||||
expect(nameInput).toHaveValue("Signal");
|
||||
fireEvent.change(nameInput, {
|
||||
target: { value: "Custom Signal header" },
|
||||
});
|
||||
fireEvent.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", { name: "Save" }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(latest.customMethodCardMetaById?.signal?.label).toBe(
|
||||
@@ -355,4 +418,79 @@ describe("CommunicationMethodsScreen — Add Platform persistence", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("Customize Finalize shows reordered field blocks in the card modal", async () => {
|
||||
let latest: CreateFlowState = {};
|
||||
render(
|
||||
<ScreenWithStateProbe
|
||||
onState={(s) => {
|
||||
latest = s;
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getAllByRole("button", { name: /Signal: Encrypted messaging/ })[0],
|
||||
);
|
||||
const cardDialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(
|
||||
within(cardDialog).getByRole("button", { name: "More options" }),
|
||||
);
|
||||
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Next" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
||||
expect(
|
||||
await screen.findByText("Custom policy details"),
|
||||
).toBeInTheDocument();
|
||||
|
||||
const handles = screen.getAllByRole("button", {
|
||||
name: "Drag to reorder this field",
|
||||
});
|
||||
const rows = screen.getAllByRole("listitem");
|
||||
const store: Record<string, string> = {};
|
||||
const dataTransfer = {
|
||||
effectAllowed: "all",
|
||||
dropEffect: "move",
|
||||
setData(type: string, value: string) {
|
||||
store[type] = value;
|
||||
},
|
||||
getData(type: string) {
|
||||
return store[type] ?? "";
|
||||
},
|
||||
};
|
||||
fireEvent.pointerDown(handles[0]);
|
||||
fireEvent.dragStart(rows[0], { dataTransfer });
|
||||
fireEvent.drop(rows[2], { dataTransfer });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Finalize" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByPlaceholderText("Policy name"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
latest.customMethodCardFieldBlocksById?.signal?.map((b) => b.id),
|
||||
).toEqual([
|
||||
"facet-logisticsAdmin",
|
||||
"facet-codeOfConduct",
|
||||
"facet-corePrinciple",
|
||||
]);
|
||||
});
|
||||
|
||||
const result = screen.getByRole("dialog");
|
||||
const labels = within(result)
|
||||
.getAllByRole("textbox")
|
||||
.map((el) => {
|
||||
const labelledby = el.getAttribute("aria-labelledby");
|
||||
return labelledby
|
||||
? (document.getElementById(labelledby)?.textContent ?? "").trim()
|
||||
: "";
|
||||
});
|
||||
expect(labels[0]).toMatch(/Logistics, Admin/);
|
||||
expect(labels[1]).toMatch(/Code of Conduct/);
|
||||
expect(labels[2]).toMatch(/Core Principle/);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user