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:
@@ -191,27 +191,7 @@ describe("FinalReviewScreen — prefilled selections", () => {
|
||||
});
|
||||
|
||||
describe("FinalReviewScreen — chip detail modal", () => {
|
||||
async function enterMethodCustomizeFromDialog(dialog: HTMLElement) {
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: /more options/i }),
|
||||
);
|
||||
const customize = await screen.findByRole("menuitem", {
|
||||
name: /^customize$/i,
|
||||
});
|
||||
fireEvent.click(customize);
|
||||
}
|
||||
|
||||
async function enterCoreValueCustomizeFromDialog(dialog: HTMLElement) {
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: /more options/i }),
|
||||
);
|
||||
const customize = await screen.findByRole("menuitem", {
|
||||
name: /^customize$/i,
|
||||
});
|
||||
fireEvent.click(customize);
|
||||
}
|
||||
|
||||
it("opens the read-only detail modal when a chip is clicked, matching the preset copy", async () => {
|
||||
it("opens the chip modal when a chip is clicked, matching the preset copy", async () => {
|
||||
render(<FinalReviewWithCustomizeSelections />);
|
||||
|
||||
const signalChip = await screen.findByRole("button", { name: "Signal" });
|
||||
@@ -274,34 +254,57 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("menuitem", { name: /^customize$/i }),
|
||||
screen.getByRole("menuitem", { name: /^duplicate$/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
screen.getByRole("menuitem", { name: /^duplicate$/i }),
|
||||
screen.getByRole("menuitem", { name: /^customize$/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens method chip modal read-only until Customize, then enables Save after an edit", async () => {
|
||||
it("values Customize starts on the policy name step", async () => {
|
||||
function CoreValuesHarness() {
|
||||
const { replaceState } = useCreateFlow();
|
||||
useLayoutEffect(() => {
|
||||
replaceState({
|
||||
selectedCoreValueIds: ["1"],
|
||||
coreValuesChipsSnapshot: [
|
||||
{ id: "1", label: "Accessibility", state: "selected" },
|
||||
],
|
||||
});
|
||||
}, [replaceState]);
|
||||
return <FinalReviewScreen />;
|
||||
}
|
||||
render(<CoreValuesHarness />);
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: "Accessibility" }),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: /more options/i }),
|
||||
);
|
||||
fireEvent.click(
|
||||
await screen.findByRole("menuitem", { name: /^customize$/i }),
|
||||
);
|
||||
expect(
|
||||
await screen.findByPlaceholderText("Policy name"),
|
||||
).toHaveValue("Accessibility");
|
||||
expect(screen.queryByText("Custom policy details")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens method chip modal editable, with Save disabled until a field changes", async () => {
|
||||
render(<FinalReviewWithCustomizeSelections />);
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
|
||||
expect(
|
||||
within(dialog).queryByRole("button", { name: "Save" }),
|
||||
).not.toBeInTheDocument();
|
||||
const saveButton = within(dialog).getByRole("button", { name: "Save" });
|
||||
expect(saveButton).toBeDisabled();
|
||||
|
||||
const principleField = within(dialog).getByRole("textbox", {
|
||||
name: /core principle/i,
|
||||
});
|
||||
expect(principleField).toBeDisabled();
|
||||
|
||||
await enterMethodCustomizeFromDialog(dialog);
|
||||
|
||||
expect(
|
||||
within(dialog).getByRole("button", { name: "Save" }),
|
||||
).toBeDisabled();
|
||||
expect(principleField).not.toBeDisabled();
|
||||
|
||||
fireEvent.change(principleField, { target: { value: "Edited principle" } });
|
||||
await waitFor(() => {
|
||||
@@ -339,8 +342,8 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens the editable Save modal for a values chip after Customize", async () => {
|
||||
// Customize / plain custom-rule path: snapshot is set, sections is not.
|
||||
it("opens the editable Save modal for a values chip", async () => {
|
||||
// Plain custom-rule path: snapshot is set, sections is not.
|
||||
function CoreValuesHarness() {
|
||||
const { replaceState } = useCreateFlow();
|
||||
useLayoutEffect(() => {
|
||||
@@ -359,10 +362,6 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
||||
await screen.findByRole("button", { name: "Accessibility" }),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
expect(
|
||||
within(dialog).queryByRole("button", { name: "Save" }),
|
||||
).not.toBeInTheDocument();
|
||||
await enterCoreValueCustomizeFromDialog(dialog);
|
||||
expect(
|
||||
within(dialog).getByRole("button", { name: "Save" }),
|
||||
).toBeInTheDocument();
|
||||
@@ -371,7 +370,7 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("opens Save for values chip after Customize (use-without-changes seeded snapshot)", async () => {
|
||||
it("opens Save for a values chip (use-without-changes seeded snapshot)", async () => {
|
||||
// Mirrors the post-fix payload from `handleUseTemplateWithoutChanges`:
|
||||
// template Values section is stripped from `sections`, snapshot +
|
||||
// selected ids are seeded so the chip carries an `overrideKey`.
|
||||
@@ -401,10 +400,6 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
||||
await screen.findByRole("button", { name: "Accessibility" }),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
expect(
|
||||
within(dialog).queryByRole("button", { name: "Save" }),
|
||||
).not.toBeInTheDocument();
|
||||
await enterCoreValueCustomizeFromDialog(dialog);
|
||||
expect(
|
||||
within(dialog).getByRole("button", { name: "Save" }),
|
||||
).toBeInTheDocument();
|
||||
@@ -418,21 +413,10 @@ describe("FinalReviewScreen — chip detail modal", () => {
|
||||
*
|
||||
* 1. Save starts disabled (no edits yet → nothing to persist).
|
||||
* 2. Editing any field flips Save on; clicking it writes the typed
|
||||
* `{group}MethodDetailsById[id]` entry into create-flow state and
|
||||
* closes the modal.
|
||||
* `{group}MethodDetailsById[id]` entry into create-flow state.
|
||||
* 3. Closing without Save discards every typed change.
|
||||
*/
|
||||
describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
async function enterMethodCustomizeFromDialog(dialog: HTMLElement) {
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: /more options/i }),
|
||||
);
|
||||
const customize = await screen.findByRole("menuitem", {
|
||||
name: /^customize$/i,
|
||||
});
|
||||
fireEvent.click(customize);
|
||||
}
|
||||
|
||||
const baseSelections: CreateFlowState = {
|
||||
title: "Oak Park Commons",
|
||||
selectedCommunicationMethodIds: ["signal"],
|
||||
@@ -452,7 +436,6 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await enterMethodCustomizeFromDialog(dialog);
|
||||
const saveButton = within(dialog).getByRole("button", { name: "Save" });
|
||||
expect(saveButton).toBeDisabled();
|
||||
const principleField = within(dialog).getByRole("textbox", {
|
||||
@@ -483,7 +466,6 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await enterMethodCustomizeFromDialog(dialog);
|
||||
const principleField = within(dialog).getByRole("textbox", {
|
||||
name: /core principle/i,
|
||||
});
|
||||
@@ -492,13 +474,6 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
});
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
within(screen.getByRole("dialog")).queryByRole("button", {
|
||||
name: "Save",
|
||||
}),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
latest.communicationMethodDetailsById?.signal?.corePrinciple,
|
||||
@@ -519,7 +494,6 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await enterMethodCustomizeFromDialog(dialog);
|
||||
const principleField = within(dialog).getByRole("textbox", {
|
||||
name: /core principle/i,
|
||||
});
|
||||
@@ -626,7 +600,6 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
await screen.findByRole("button", { name: "Custom Comm" }),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await enterMethodCustomizeFromDialog(dialog);
|
||||
expect(
|
||||
within(dialog).queryByText(/no custom fields yet/i),
|
||||
).not.toBeInTheDocument();
|
||||
@@ -670,7 +643,6 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
await screen.findByRole("button", { name: "Custom Comm" }),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await enterMethodCustomizeFromDialog(dialog);
|
||||
const notesField = within(dialog).getByRole("textbox", { name: /notes/i });
|
||||
fireEvent.change(notesField, { target: { value: "Saved detail" } });
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
|
||||
@@ -685,6 +657,149 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
});
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Customize opens the policy wizard prefilled with the chip title", async () => {
|
||||
render(
|
||||
<FinalReviewWithStateProbe
|
||||
onState={() => {}}
|
||||
initial={baseSelections}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: /more options/i }),
|
||||
);
|
||||
fireEvent.click(
|
||||
await screen.findByRole("menuitem", { name: /^customize$/i }),
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByPlaceholderText("Policy name"),
|
||||
).toHaveValue("Signal");
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByPlaceholderText("Policy name")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
expect(
|
||||
within(screen.getByRole("dialog")).getByText("Signal"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("Customize wizard Finalize persists a renamed chip title", async () => {
|
||||
let latest: CreateFlowState = {};
|
||||
render(
|
||||
<FinalReviewWithStateProbe
|
||||
onState={(s) => {
|
||||
latest = s;
|
||||
}}
|
||||
initial={baseSelections}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: /more options/i }),
|
||||
);
|
||||
fireEvent.click(
|
||||
await screen.findByRole("menuitem", { name: /^customize$/i }),
|
||||
);
|
||||
|
||||
expect(
|
||||
await screen.findByPlaceholderText("Policy name"),
|
||||
).toBeInTheDocument();
|
||||
const nameInput = screen.getByPlaceholderText("Policy name");
|
||||
fireEvent.change(nameInput, { target: { value: "Custom Signal header" } });
|
||||
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(
|
||||
"Custom Signal header",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("Customize Finalize shows reordered field blocks in the chip modal", async () => {
|
||||
let latest: CreateFlowState = {};
|
||||
render(
|
||||
<FinalReviewWithStateProbe
|
||||
onState={(s) => {
|
||||
latest = s;
|
||||
}}
|
||||
initial={baseSelections}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(await screen.findByRole("button", { name: "Signal" }));
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: /more options/i }),
|
||||
);
|
||||
fireEvent.click(
|
||||
await screen.findByRole("menuitem", { name: /^customize$/i }),
|
||||
);
|
||||
|
||||
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/);
|
||||
});
|
||||
});
|
||||
|
||||
function FinalReviewEditPublishedWithStateProbe({
|
||||
|
||||
Reference in New Issue
Block a user