Open each core value as a card of meaning and signals steps, so groups write their own words from an empty field with Generate draft, skip values without blocking the flow, and Customize without keeping helper text as the description.

This commit is contained in:
adilallo committed 2026-11-05 21:28:00 -07:00
1 parent 2a7b9e6da7
commit 64a542f456
25 files changed
+877 -354

No files matched your search

+179 -87
View File
@@ -4,6 +4,11 @@ import "@testing-library/jest-dom/vitest";
import { renderWithProviders, dispatchBeforeUnload } from "../utils/test-utils";
import { CoreValuesSelectScreen } from "../../app/(app)/create/screens/select/CoreValuesSelectScreen";
const MEANING_STEP = "What does this value mean to your community?";
const SIGNALS_STEP = "Signals of violation";
const ADD_TO_RULE = "Add to Rule";
const ADD_OWN = "add your own";
describe("CoreValuesSelectScreen", () => {
beforeEach(() => {
vi.clearAllMocks();
@@ -13,19 +18,50 @@ describe("CoreValuesSelectScreen", () => {
fireEvent.click(await screen.findByRole("button", { name: "Discard" }));
}
async function editMeaningInOpenDialog(next: string) {
const dialog = await screen.findByRole("dialog");
const fields = within(dialog).getAllByRole("textbox");
fireEvent.change(fields[0], { target: { value: next } });
async function openValueDialog(label = "Accessibility") {
fireEvent.click(screen.getByRole("button", { name: `Select ${label}` }));
return screen.findByRole("dialog");
}
it("opens core value detail modal when a preset chip is clicked", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
async function editMeaningInOpenDialog(next: string) {
const dialog = await screen.findByRole("dialog");
fireEvent.click(within(dialog).getByRole("button", { name: MEANING_STEP }));
fireEvent.change(within(dialog).getByRole("textbox"), {
target: { value: next },
});
}
async function confirmPendingValue(dialog: HTMLElement) {
fireEvent.click(within(dialog).getByRole("button", { name: ADD_TO_RULE }));
await waitFor(() => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
}
it("opens a card of meaning and signals rows when a preset chip is clicked", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
const dialog = await openValueDialog();
expect(within(dialog).getByText("Accessibility")).toBeInTheDocument();
expect(
within(dialog).getByRole("button", { name: "Add Value" }),
within(dialog).getByRole("button", { name: MEANING_STEP }),
).toBeInTheDocument();
expect(
within(dialog).getByRole("button", { name: SIGNALS_STEP }),
).toBeInTheDocument();
expect(
within(dialog).getByText(
"How do we know when we are not living up to this value?",
),
).toBeInTheDocument();
expect(
within(dialog).getByRole("button", { name: ADD_TO_RULE }),
).toBeInTheDocument();
expect(
within(dialog).getByRole("button", { name: "Cancel" }),
).toBeInTheDocument();
expect(
within(dialog).queryByRole("button", { name: "Deselect" }),
).not.toBeInTheDocument();
expect(
within(dialog).queryByRole("button", { name: "Remove" }),
).not.toBeInTheDocument();
@@ -48,23 +84,73 @@ describe("CoreValuesSelectScreen", () => {
).toBeInTheDocument();
});
it("starts the value modal empty and Generate draft inserts catalog copy", async () => {
it("offers add your own in the header and a of-6 counter", () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByRole("button", { name: "Select Accessibility" }));
const dialog = await screen.findByRole("dialog");
const fields = within(dialog).getAllByRole("textbox") as HTMLTextAreaElement[];
expect(fields[0].value).toBe("");
expect(fields[1].value).toBe("");
expect(
screen.getByRole("heading", { name: "Choose up to six core values" }),
).toBeInTheDocument();
expect(screen.getByRole("button", { name: ADD_OWN })).toBeInTheDocument();
expect(screen.getByText("0 of 6 picked")).toBeInTheDocument();
});
it("starts a meaning step empty and Generate draft inserts catalog copy", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
const dialog = await openValueDialog();
expect(within(dialog).queryByRole("textbox")).not.toBeInTheDocument();
fireEvent.click(within(dialog).getByRole("button", { name: MEANING_STEP }));
const field = within(dialog).getByRole("textbox") as HTMLTextAreaElement;
expect(field.value).toBe("");
expect(field).toHaveAttribute("placeholder", "Write a sentence or two.");
expect(
within(dialog).getByText("See our first draft for this value"),
).toBeInTheDocument();
fireEvent.click(
within(dialog).getAllByRole("button", { name: "Generate draft" })[0],
within(dialog).getByRole("button", { name: "Generate draft" }),
);
expect(fields[0].value).toMatch(/usable by everyone/i);
expect(field.value).toMatch(/usable by everyone/i);
expect(
within(dialog).queryByRole("button", { name: "Generate draft" }),
).not.toBeInTheDocument();
expect(
within(dialog).getByText(
"These are your community's words. Change anything that does not fit.",
),
).toBeInTheDocument();
});
it("opens a signals step with Generate draft on an empty field", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
const dialog = await openValueDialog();
fireEvent.click(within(dialog).getByRole("button", { name: SIGNALS_STEP }));
expect(within(dialog).getByRole("textbox")).toHaveValue("");
expect(
within(dialog).getByRole("button", { name: "Generate draft" }),
).toBeInTheDocument();
expect(
within(dialog).getByText(
"How do we know when we are not living up to Accessibility?",
),
).toBeInTheDocument();
});
it("returns from a step to the card when Cancel is clicked", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
const dialog = await openValueDialog();
fireEvent.click(within(dialog).getByRole("button", { name: MEANING_STEP }));
expect(within(dialog).getByRole("textbox")).toBeInTheDocument();
fireEvent.click(within(dialog).getByRole("button", { name: "Cancel" }));
expect(within(dialog).queryByRole("textbox")).not.toBeInTheDocument();
expect(
within(dialog).getByRole("button", { name: MEANING_STEP }),
).toBeInTheDocument();
expect(
within(dialog).getByRole("button", { name: ADD_TO_RULE }),
).toBeInTheDocument();
});
it("closes a pending value without confirm when fields are unchanged", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
await screen.findByRole("dialog");
await openValueDialog();
fireEvent.keyDown(document, { key: "Escape" });
await waitFor(() => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
@@ -72,6 +158,9 @@ describe("CoreValuesSelectScreen", () => {
expect(
screen.queryByRole("button", { name: "Keep editing" }),
).not.toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Select Accessibility" }),
).toBeInTheDocument();
});
it("asks to discard when closing a pending value after an edit", async () => {
@@ -79,6 +168,7 @@ describe("CoreValuesSelectScreen", () => {
fireEvent.click(screen.getByText("Accessibility"));
await editMeaningInOpenDialog("Changed meaning");
fireEvent.keyDown(document, { key: "Escape" });
fireEvent.keyDown(document, { key: "Escape" });
expect(
await screen.findByRole("button", { name: "Keep editing" }),
).toBeInTheDocument();
@@ -90,8 +180,7 @@ describe("CoreValuesSelectScreen", () => {
it("does not block tab close when a pending value is unchanged", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
await screen.findByRole("dialog");
await openValueDialog();
expect(dispatchBeforeUnload()).toBe(false);
});
@@ -107,53 +196,55 @@ describe("CoreValuesSelectScreen", () => {
fireEvent.click(screen.getByText("Accessibility"));
await editMeaningInOpenDialog("Changed meaning");
fireEvent.keyDown(document, { key: "Escape" });
fireEvent.keyDown(document, { key: "Escape" });
fireEvent.click(await screen.findByRole("button", { name: "Keep editing" }));
const dialog = await screen.findByRole("dialog");
expect(
within(dialog).getByRole("button", { name: "Add Value" }),
within(dialog).getByRole("button", { name: ADD_TO_RULE }),
).toBeInTheDocument();
});
it("saves details when Add Value is clicked", async () => {
it("keeps a value with empty meaning and signals when Add to Rule is clicked", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
const dialog = await screen.findByRole("dialog");
fireEvent.click(
within(dialog).getByRole("button", { name: "Add Value" }),
);
await waitFor(() => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
const dialog = await openValueDialog();
await confirmPendingValue(dialog);
expect(
screen.getByRole("button", { name: "Deselect Accessibility" }),
).toBeInTheDocument();
expect(screen.getByText("1 of 6 picked")).toBeInTheDocument();
});
it("opens meaning and signals fields editable without Customize", async () => {
it("opens meaning and signals steps editable without Customize", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
const dialog = await screen.findByRole("dialog");
const fields = within(dialog).getAllByRole("textbox");
expect(fields.length).toBeGreaterThanOrEqual(2);
for (const field of fields) {
expect(field).toBeEnabled();
}
const dialog = await openValueDialog();
fireEvent.click(within(dialog).getByRole("button", { name: MEANING_STEP }));
expect(within(dialog).getByRole("textbox")).toBeEnabled();
fireEvent.click(within(dialog).getByRole("button", { name: "Cancel" }));
fireEvent.click(within(dialog).getByRole("button", { name: SIGNALS_STEP }));
expect(within(dialog).getByRole("textbox")).toBeEnabled();
});
it("shows Remove only after the value has been added", async () => {
it("shows Deselect after the value has been added, not Remove in the kebab", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
const pending = await screen.findByRole("dialog");
fireEvent.click(within(pending).getByRole("button", { name: "Add Value" }));
await waitFor(() => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
const pending = await openValueDialog();
await confirmPendingValue(pending);
fireEvent.click(screen.getByText("Accessibility"));
const editing = await screen.findByRole("dialog");
expect(
within(editing).getByRole("button", { name: "Remove" }),
within(editing).getByRole("button", { name: "Deselect" }),
).toBeInTheDocument();
expect(
within(editing).getByRole("button", { name: "Save" }),
).toBeInTheDocument();
expect(
within(editing).queryByRole("button", { name: "Remove" }),
).not.toBeInTheDocument();
fireEvent.click(
within(editing).getByRole("button", { name: "More options" }),
);
expect(screen.getByRole("menuitem", { name: "Remove" })).toBeInTheDocument();
expect(
screen.queryByRole("menuitem", { name: "Remove" }),
).not.toBeInTheDocument();
expect(
screen.getByRole("menuitem", { name: "Customize" }),
).toBeInTheDocument();
@@ -162,10 +253,25 @@ describe("CoreValuesSelectScreen", () => {
).toBeInTheDocument();
});
it("Deselect takes a confirmed value out of the selection", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
const pending = await openValueDialog();
await confirmPendingValue(pending);
fireEvent.click(screen.getByText("Accessibility"));
const editing = await screen.findByRole("dialog");
fireEvent.click(within(editing).getByRole("button", { name: "Deselect" }));
await waitFor(() => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
expect(
screen.getByRole("button", { name: "Select Accessibility" }),
).toBeInTheDocument();
expect(screen.getByText("0 of 6 picked")).toBeInTheDocument();
});
it("Customize walks name and description before policy details", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
const dialog = await screen.findByRole("dialog");
const dialog = await openValueDialog();
fireEvent.click(
within(dialog).getByRole("button", { name: "More options" }),
);
@@ -176,13 +282,14 @@ describe("CoreValuesSelectScreen", () => {
);
expect(screen.queryByText("Custom policy details")).not.toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Next" }));
expect(screen.getByPlaceholderText("Policy description")).toHaveValue("");
fireEvent.click(screen.getByRole("button", { name: "Next" }));
expect(
await screen.findByText("Custom policy details"),
).toBeInTheDocument();
expect(
screen.getByRole("button", {
name: "What does this value mean to your group?",
name: MEANING_STEP,
}),
).toBeInTheDocument();
});
@@ -190,15 +297,9 @@ describe("CoreValuesSelectScreen", () => {
it("Customize shows meaning saved from the value modal", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
const pending = await screen.findByRole("dialog");
const fields = within(pending).getAllByRole("textbox");
fireEvent.change(fields[0], {
target: { value: "Edited meaning from value modal" },
});
fireEvent.click(within(pending).getByRole("button", { name: "Add Value" }));
await waitFor(() => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
await editMeaningInOpenDialog("Edited meaning from value modal");
const pending = screen.getByRole("dialog");
await confirmPendingValue(pending);
fireEvent.click(screen.getByText("Accessibility"));
const editing = await screen.findByRole("dialog");
fireEvent.click(
@@ -207,10 +308,11 @@ describe("CoreValuesSelectScreen", () => {
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
expect(await screen.findByPlaceholderText("Policy name")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Next" }));
expect(screen.getByPlaceholderText("Policy description")).toHaveValue("");
fireEvent.click(screen.getByRole("button", { name: "Next" }));
fireEvent.click(
await screen.findByRole("button", {
name: "What does this value mean to your group?",
name: MEANING_STEP,
}),
);
expect(
@@ -220,8 +322,7 @@ describe("CoreValuesSelectScreen", () => {
it("closing Customize without Finalize returns to the value modal", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
const dialog = await screen.findByRole("dialog");
const dialog = await openValueDialog();
fireEvent.click(
within(dialog).getByRole("button", { name: "More options" }),
);
@@ -237,14 +338,13 @@ describe("CoreValuesSelectScreen", () => {
});
const valueDialog = screen.getByRole("dialog");
expect(
within(valueDialog).getByRole("button", { name: "Add Value" }),
within(valueDialog).getByRole("button", { name: ADD_TO_RULE }),
).toBeInTheDocument();
});
it("Customize Finalize shows reordered fields in the value modal", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByText("Accessibility"));
const dialog = await screen.findByRole("dialog");
const dialog = await openValueDialog();
fireEvent.click(
within(dialog).getByRole("button", { name: "More options" }),
);
@@ -292,21 +392,13 @@ describe("CoreValuesSelectScreen", () => {
? (document.getElementById(labelledby)?.textContent ?? "").trim()
: "";
});
expect(labels[0]).toMatch(/Signals of Violation/);
expect(labels[1]).toMatch(/What does this value mean to your group/);
expect(labels[0]).toMatch(/Signals of violation/i);
expect(labels[1]).toMatch(/What does this value mean to your community/);
});
// The "Add value" control opens an empty custom-policy wizard. Dismissing
// without Finalize leaves the chip list unchanged. Finalize writes a
// selected chip named from the wizard title.
describe("Add value wizard", () => {
describe("add your own wizard", () => {
const CUSTOM_LABEL = "ZZTopBespokeValue";
/**
* The label can also appear in the modal header while the modal
* is open, and as the chip's "Remove" button aria-label. Scope to
* chip-row buttons by excluding both.
*/
function countCustomChips(label: string) {
return screen
.queryAllByRole("button", {
@@ -316,12 +408,13 @@ describe("CoreValuesSelectScreen", () => {
}
async function completeAddValueWizard(label: string) {
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
fireEvent.click(screen.getByRole("button", { name: ADD_OWN }));
const nameInput = await screen.findByPlaceholderText("Policy name");
fireEvent.change(nameInput, { target: { value: label } });
fireEvent.click(screen.getByRole("button", { name: "Next" }));
const descriptionInput =
await screen.findByPlaceholderText("Policy description");
expect(descriptionInput).toHaveValue("");
fireEvent.change(descriptionInput, {
target: { value: "A community-authored value." },
});
@@ -337,7 +430,7 @@ describe("CoreValuesSelectScreen", () => {
it("collapses the field-type picker after adding a custom field", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
fireEvent.click(screen.getByRole("button", { name: ADD_OWN }));
fireEvent.change(await screen.findByPlaceholderText("Policy name"), {
target: { value: CUSTOM_LABEL },
});
@@ -373,18 +466,18 @@ describe("CoreValuesSelectScreen", () => {
).toBeInTheDocument();
});
it("opens the empty custom-policy wizard from Add value", async () => {
it("opens the empty custom-policy wizard from add your own", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
fireEvent.click(screen.getByRole("button", { name: ADD_OWN }));
expect(
await screen.findByPlaceholderText("Policy name"),
).toHaveValue("");
expect(screen.queryByPlaceholderText("Type to add")).not.toBeInTheDocument();
});
it("leaves no chip when the Add value wizard is dismissed", async () => {
it("leaves no chip when the add your own wizard is dismissed", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
fireEvent.click(screen.getByRole("button", { name: ADD_OWN }));
await screen.findByPlaceholderText("Policy name");
fireEvent.keyDown(document, { key: "Escape" });
await waitFor(() => {
@@ -412,7 +505,7 @@ describe("CoreValuesSelectScreen", () => {
async function addPresetValue(label: string) {
fireEvent.click(screen.getByText(label));
const dialog = await screen.findByRole("dialog");
fireEvent.click(within(dialog).getByRole("button", { name: "Add Value" }));
fireEvent.click(within(dialog).getByRole("button", { name: ADD_TO_RULE }));
await waitFor(() => {
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
@@ -420,7 +513,7 @@ describe("CoreValuesSelectScreen", () => {
it("shows a counter, disables remaining chips, and announces the limit", async () => {
renderWithProviders(<CoreValuesSelectScreen />);
expect(screen.getByText("0 of 6")).toBeInTheDocument();
expect(screen.getByText("0 of 6 picked")).toBeInTheDocument();
await addPresetValue("Accessibility");
await addPresetValue("Accountability");
@@ -429,7 +522,7 @@ describe("CoreValuesSelectScreen", () => {
await addPresetValue("Anti-oppression");
await addPresetValue("Autonomy");
expect(screen.getByText("6 of 6")).toBeInTheDocument();
expect(screen.getByText("6 of 6 picked")).toBeInTheDocument();
expect(
screen.getByText(
"Six of six values selected. Remaining values are unavailable until one is removed.",
@@ -438,8 +531,7 @@ describe("CoreValuesSelectScreen", () => {
expect(
screen.getByRole("button", { name: "Select Capacity building" }),
).toBeDisabled();
expect(screen.getByRole("button", { name: "Add value" })).toBeDisabled();
expect(screen.getByRole("button", { name: /^add$/i })).toBeDisabled();
expect(screen.getByRole("button", { name: ADD_OWN })).toBeDisabled();
});
});
});
+20
View File
@@ -122,6 +122,26 @@ describe("Create", () => {
expect(onBack).toHaveBeenCalledTimes(1);
});
it("calls onCancel when the trailing Cancel button is clicked", () => {
const onCancel = vi.fn();
renderWithProviders(
<Create
{...defaultProps}
showBackButton={false}
showCancelButton
showNextButton
onCancel={onCancel}
cancelButtonText="Cancel"
nextButtonText="Add to Rule"
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
expect(onCancel).toHaveBeenCalledTimes(1);
expect(
screen.getByRole("button", { name: "Add to Rule" }),
).toBeInTheDocument();
});
it("calls onNext when next button is clicked", () => {
const onNext = vi.fn();
renderWithProviders(
+47 -2
View File
@@ -358,6 +358,9 @@ describe("FinalReviewScreen — chip detail modal", () => {
expect(
screen.getByRole("menuitem", { name: /^customize$/i }),
).toBeInTheDocument();
expect(
screen.queryByRole("menuitem", { name: /^remove$/i }),
).not.toBeInTheDocument();
});
it("values Customize starts on the policy name step", async () => {
@@ -388,6 +391,8 @@ describe("FinalReviewScreen — chip detail modal", () => {
await screen.findByPlaceholderText("Policy name"),
).toHaveValue("Accessibility");
expect(screen.queryByText("Custom policy details")).not.toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Next" }));
expect(screen.getByPlaceholderText("Policy description")).toHaveValue("");
});
it("opens method chip modal editable, with Save disabled until a field changes", async () => {
@@ -432,14 +437,48 @@ describe("FinalReviewScreen — chip detail modal", () => {
await waitFor(() => {
expect(
screen.getByText(/what does this value mean to your group\?/i),
screen.getByRole("button", {
name: /what does this value mean to your community\?/i,
}),
).toBeInTheDocument();
});
expect(
screen.getByText(/signals of violation/i),
screen.getByRole("button", { name: /signals of violation/i }),
).toBeInTheDocument();
});
it("starts a values meaning step empty and Generate draft inserts catalog copy", 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: catalogChipName("Accessibility") }),
);
const dialog = await screen.findByRole("dialog");
fireEvent.click(
within(dialog).getByRole("button", {
name: /what does this value mean to your community\?/i,
}),
);
const field = within(dialog).getByRole("textbox") as HTMLTextAreaElement;
expect(field.value).toBe("");
fireEvent.click(
within(dialog).getByRole("button", { name: "Generate draft" }),
);
expect(field.value).toMatch(/usable by everyone/i);
});
it("opens the editable Save modal for a values chip", async () => {
// Plain custom-rule path: snapshot is set, sections is not.
function CoreValuesHarness() {
@@ -463,6 +502,12 @@ describe("FinalReviewScreen — chip detail modal", () => {
expect(
within(dialog).getByRole("button", { name: "Save" }),
).toBeInTheDocument();
expect(
within(dialog).getByRole("button", { name: "Deselect" }),
).toBeInTheDocument();
expect(
within(dialog).getByRole("button", { name: "Cancel" }),
).toBeInTheDocument();
expect(within(dialog).getAllByRole("button", { name: "Close" })).toHaveLength(
1,
);
+26 -1
View File
@@ -1,4 +1,7 @@
import { describe, vi } from "vitest";
import { describe, it, expect, vi } from "vitest";
import { screen, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/vitest";
import { renderWithProviders } from "../utils/test-utils";
import {
componentTestSuite,
type ComponentTestSuiteConfig,
@@ -27,4 +30,26 @@ const config: ComponentTestSuiteConfig<Props> = {
describe("ModalFooter", () => {
componentTestSuite<Props>(config);
it("renders Cancel left of Next in the trailing group", () => {
const onCancel = vi.fn();
const onNext = vi.fn();
renderWithProviders(
<ModalFooter
showBackButton={false}
showCancelButton
showNextButton
onCancel={onCancel}
onNext={onNext}
cancelButtonText="Cancel"
nextButtonText="Add to Rule"
stepper={false}
/>,
);
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
expect(onCancel).toHaveBeenCalledTimes(1);
expect(onNext).not.toHaveBeenCalled();
fireEvent.click(screen.getByRole("button", { name: "Add to Rule" }));
expect(onNext).toHaveBeenCalledTimes(1);
});
});
@@ -135,6 +135,29 @@ describe("ModalTextAreaField behavior", () => {
expect(onChange).toHaveBeenCalledWith("Catalog first draft");
});
it("hides Generate draft on a filled field and shows the filled hint", () => {
renderWithProviders(
<ModalTextAreaField
label="Core principle"
value="Community words"
onChange={() => {}}
generateDraftText="Catalog first draft"
generateDraftLabel="Generate draft"
generateDraftHint="See our first draft for this value"
filledHint="These are your community's words. Change anything that does not fit."
/>,
);
expect(
screen.queryByRole("button", { name: "Generate draft" }),
).not.toBeInTheDocument();
expect(
screen.getByText(
"These are your community's words. Change anything that does not fit.",
),
).toBeInTheDocument();
});
it("hides Generate draft when there is no catalog copy", () => {
renderWithProviders(
<ModalTextAreaField
+12
View File
@@ -189,6 +189,18 @@ describe("MultiSelect – behaviour specifics", () => {
).not.toBeInTheDocument();
});
it("renders a count-only caption when formHeader is off", () => {
render(
<MultiSelect
options={defaultChipOptions}
formHeader={false}
addButton={false}
selectionCountText="1 of 6 picked"
/>,
);
expect(screen.getByText("1 of 6 picked")).toBeInTheDocument();
});
it("handles custom chip confirm", async () => {
const handleConfirm = vi.fn();
const customOptions = [
+4
View File
@@ -36,4 +36,8 @@ describe("create footer messages", () => {
expect(messages.create.footer.skipForNow).toBe("Skip for now");
expect(messages.create.footer.submitEmail).toBe("Submit email");
});
it("exposes Confirm values for the core-values footer CTA", () => {
expect(messages.create.footer.confirmCoreValues).toBe("Confirm values");
});
});