Show seven cards on the four method decks and open each as a routing overlay of its parts, so groups write from empty fields with Generate draft, search the full deck, and see unselected scope chips with enough contrast.

This commit is contained in:
adilallo committed 2026-11-05 22:43:30 -07:00
1 parent dea7dd7fbd
commit 46656f52d3
28 files changed
+1349 -318

No files matched your search

+1 -1
View File
@@ -54,7 +54,7 @@ describe("Chip", () => {
expect(chip.style.color).toContain("--color-content-default-brand-primary");
expect(chip.style.backgroundColor).toBe("transparent");
expect(chip.style.borderColor).toContain(
"--color-border-default-control",
"--color-content-default-tertiary",
);
});
@@ -62,6 +62,27 @@ function ScreenWithStateProbe({
* snapping back to preset copy. See {@link CommunicationMethodEditFields}
* and `buildPublishPayload` for the read side.
*/
const COMMUNICATION_STEPS = [
"Core principle and scope",
"How it works",
"Norms",
] as const;
function openPolicyStep(dialog: HTMLElement, name: string) {
fireEvent.click(within(dialog).getByRole("button", { name }));
}
function generateAllCommunicationSteps(dialog: HTMLElement) {
for (const name of COMMUNICATION_STEPS) {
openPolicyStep(dialog, name);
const generate = within(dialog).queryByRole("button", {
name: "Generate draft",
});
if (generate) fireEvent.click(generate);
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
}
}
describe("CommunicationMethodsScreen — Add card persistence", () => {
it("starts empty and Generate draft inserts the catalog preset", async () => {
let latest: CreateFlowState = {};
@@ -79,24 +100,24 @@ describe("CommunicationMethodsScreen — Add card persistence", () => {
const dialog = await screen.findByRole("dialog");
expect(within(dialog).queryByAltText("Help")).not.toBeInTheDocument();
const textboxes = within(dialog).getAllByRole("textbox");
expect(textboxes.length).toBe(3);
const corePrincipleField = textboxes[0] as HTMLTextAreaElement;
openPolicyStep(dialog, "Core principle and scope");
const corePrincipleField = within(dialog).getByRole(
"textbox",
) as HTMLTextAreaElement;
expect(corePrincipleField.value).toBe("");
const generateButtons = within(dialog).getAllByRole("button", {
name: "Generate draft",
});
expect(generateButtons.length).toBe(3);
fireEvent.click(generateButtons[0]);
fireEvent.click(
within(dialog).getByRole("button", { name: "Generate draft" }),
);
expect(corePrincipleField.value).toBe(
communicationPresetFor("signal").corePrinciple,
);
fireEvent.change(corePrincipleField, { target: { value: "Custom principle" } });
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
fireEvent.click(
within(dialog).getByRole("button", {
name: "Add card",
name: "Add to Rule",
}),
);
@@ -160,13 +181,11 @@ describe("CommunicationMethodsScreen — Add card persistence", () => {
screen.getAllByRole("button", { name: /Signal: We coordinate in encrypted Signal groups/ })[0],
);
const dialog = await screen.findByRole("dialog");
const textareas = within(dialog).getAllByRole(
"textbox",
) as HTMLTextAreaElement[];
expect(textareas.length).toBe(3);
expect(textareas[0].value).toBe("Saved principle");
expect(textareas[1].value).toBe("Saved logistics");
expect(textareas[2].value).toBe("Saved coc");
expect(within(dialog).getByText("Saved principle")).toBeInTheDocument();
expect(within(dialog).getByText("Saved logistics")).toBeInTheDocument();
expect(within(dialog).getByText("Saved coc")).toBeInTheDocument();
openPolicyStep(dialog, "Core principle and scope");
expect(within(dialog).getByRole("textbox")).toHaveValue("Saved principle");
});
it("persists edits and closes when Save is clicked on an already-selected platform", async () => {
@@ -193,10 +212,11 @@ describe("CommunicationMethodsScreen — Add card persistence", () => {
screen.getAllByRole("button", { name: /Signal: We coordinate in encrypted Signal groups/ })[0],
);
const dialog = await screen.findByRole("dialog");
const textareas = within(dialog).getAllByRole(
"textbox",
) as HTMLTextAreaElement[];
fireEvent.change(textareas[0], { target: { value: "Edited principle" } });
openPolicyStep(dialog, "Core principle and scope");
fireEvent.change(within(dialog).getByRole("textbox"), {
target: { value: "Edited principle" },
});
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
await waitFor(() => {
@@ -239,14 +259,14 @@ describe("CommunicationMethodsScreen — Add card persistence", () => {
})[0],
);
const dialog = await screen.findByRole("dialog");
const textareas = within(dialog).getAllByRole(
"textbox",
) as HTMLTextAreaElement[];
expect(textareas).toHaveLength(3);
expect(textareas[0].value).toBe(details.corePrinciple);
expect(textareas[1].value).toBe(details.logisticsAdmin);
expect(textareas[2].value).toBe(details.codeOfConduct);
expect(within(dialog).queryByText(noFieldsHint)).not.toBeInTheDocument();
expect(within(dialog).getByText(details.corePrinciple)).toBeInTheDocument();
expect(within(dialog).getByText(details.logisticsAdmin)).toBeInTheDocument();
expect(within(dialog).getByText(details.codeOfConduct)).toBeInTheDocument();
openPolicyStep(dialog, "Core principle and scope");
expect(within(dialog).getByRole("textbox")).toHaveValue(
details.corePrinciple,
);
});
it("opens meaning fields editable without Customize", async () => {
@@ -262,14 +282,12 @@ describe("CommunicationMethodsScreen — Add card persistence", () => {
screen.getAllByRole("button", { name: /Signal: We coordinate in encrypted Signal groups/ })[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();
openPolicyStep(dialog, "Core principle and scope");
expect(within(dialog).getByRole("textbox")).not.toBeDisabled();
});
it("closing the wizard without Finalize leaves the card modal and does not persist", async () => {
@@ -347,11 +365,12 @@ describe("CommunicationMethodsScreen — Add card persistence", () => {
screen.getAllByRole("button", { name: /Signal: We coordinate in encrypted Signal groups/ })[0],
);
const dialog = await screen.findByRole("dialog");
const textboxes = within(dialog).getAllByRole(
"textbox",
) as HTMLTextAreaElement[];
fireEvent.change(textboxes[0], { target: { value: "Edited principle" } });
openPolicyStep(dialog, "Core principle and scope");
fireEvent.change(within(dialog).getByRole("textbox"), {
target: { value: "Edited principle" },
});
fireEvent.keyDown(document, { key: "Escape" });
fireEvent.keyDown(document, { key: "Escape" });
const keepEditing = await screen.findByRole("button", {
name: "Keep editing",
@@ -390,10 +409,11 @@ describe("CommunicationMethodsScreen — Add card persistence", () => {
screen.getAllByRole("button", { name: /Signal: We coordinate in encrypted Signal groups/ })[0],
);
const dialog = await screen.findByRole("dialog");
const textboxes = within(dialog).getAllByRole(
"textbox",
) as HTMLTextAreaElement[];
fireEvent.change(textboxes[0], { target: { value: "Edited principle" } });
openPolicyStep(dialog, "Core principle and scope");
fireEvent.change(within(dialog).getByRole("textbox"), {
target: { value: "Edited principle" },
});
fireEvent.keyDown(document, { key: "Escape" });
fireEvent.keyDown(document, { key: "Escape" });
await confirmDiscardCustomizeEdits();
@@ -473,11 +493,7 @@ describe("CommunicationMethodsScreen — Add card persistence", () => {
screen.getAllByRole("button", { name: /Signal: We coordinate in encrypted Signal groups/ })[0],
);
const dialog = await screen.findByRole("dialog");
for (const button of within(dialog).getAllByRole("button", {
name: "Generate draft",
})) {
fireEvent.click(button);
}
generateAllCommunicationSteps(dialog);
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
@@ -619,10 +635,10 @@ describe("CommunicationMethodsScreen — Add card persistence", () => {
screen.getAllByRole("button", { name: /Signal: We coordinate in encrypted Signal groups/ })[0],
);
const dialog = await screen.findByRole("dialog");
const textboxes = within(dialog).getAllByRole(
"textbox",
) as HTMLTextAreaElement[];
fireEvent.change(textboxes[0], { target: { value: "Unsaved principle" } });
openPolicyStep(dialog, "Core principle and scope");
fireEvent.change(within(dialog).getByRole("textbox"), {
target: { value: "Unsaved principle" },
});
expect(dispatchBeforeUnload()).toBe(true);
});
@@ -22,13 +22,8 @@ describe("ConflictManagementScreen", () => {
})[0],
);
const dialog = await screen.findByRole("dialog");
const fields = within(dialog).getAllByRole("textbox");
expect(fields.length).toBeGreaterThan(0);
for (const field of fields) {
expect(field).toBeEnabled();
}
expect(
within(dialog).queryByRole("button", { name: "Remove" }),
within(dialog).queryByRole("button", { name: "Deselect" }),
).not.toBeInTheDocument();
fireEvent.click(
within(dialog).getByRole("button", { name: "More options" }),
@@ -36,5 +31,13 @@ describe("ConflictManagementScreen", () => {
expect(
screen.getByRole("menuitem", { name: "Customize" }),
).toBeInTheDocument();
fireEvent.click(
within(dialog).getByRole("button", { name: "Core principle" }),
);
const fields = within(dialog).getAllByRole("textbox");
expect(fields.length).toBeGreaterThan(0);
for (const field of fields) {
expect(field).toBeEnabled();
}
});
});
@@ -22,13 +22,8 @@ describe("MembershipMethodsScreen", () => {
})[0],
);
const dialog = await screen.findByRole("dialog");
const fields = within(dialog).getAllByRole("textbox");
expect(fields.length).toBeGreaterThan(0);
for (const field of fields) {
expect(field).toBeEnabled();
}
expect(
within(dialog).queryByRole("button", { name: "Remove" }),
within(dialog).queryByRole("button", { name: "Deselect" }),
).not.toBeInTheDocument();
fireEvent.click(
within(dialog).getByRole("button", { name: "More options" }),
@@ -36,5 +31,13 @@ describe("MembershipMethodsScreen", () => {
expect(
screen.getByRole("menuitem", { name: "Customize" }),
).toBeInTheDocument();
fireEvent.click(
within(dialog).getByRole("button", { name: "Who it covers and why" }),
);
const fields = within(dialog).getAllByRole("textbox");
expect(fields.length).toBeGreaterThan(0);
for (const field of fields) {
expect(field).toBeEnabled();
}
});
});
@@ -0,0 +1,56 @@
import { describe, expect, it, vi } from "vitest";
import { fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
import {
renderWithProviders as render,
screen,
within,
} from "../utils/test-utils";
import { MethodCardPolicyDialog } from "../../app/(app)/create/components/MethodCardPolicyDialog";
import type { MethodCardPolicyStep } from "../../app/(app)/create/components/MethodCardPolicyDialog";
const steps: MethodCardPolicyStep[] = [
{
id: "core",
title: "Core principle and scope",
emptyDescription: "Write this part of the policy.",
done: false,
summary: "",
renderEditor: () => (
<textarea aria-label="Core principle and scope" defaultValue="" />
),
},
];
describe("MethodCardPolicyDialog", () => {
it("routes through a step then Add to Rule", () => {
const onConfirm = vi.fn();
render(
<MethodCardPolicyDialog
isOpen
cardLabel="Signal"
editing={false}
steps={steps}
onClose={() => undefined}
onConfirm={onConfirm}
/>,
);
const dialog = screen.getByRole("dialog");
expect(
within(dialog).getByRole("button", { name: "Add to Rule" }),
).toBeInTheDocument();
fireEvent.click(
within(dialog).getByRole("button", { name: "Core principle and scope" }),
);
expect(
within(dialog).getByRole("textbox", { name: "Core principle and scope" }),
).toBeInTheDocument();
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
expect(
within(dialog).getByRole("button", { name: "Add to Rule" }),
).toBeInTheDocument();
fireEvent.click(within(dialog).getByRole("button", { name: "Add to Rule" }));
expect(onConfirm).toHaveBeenCalledTimes(1);
});
});