373 lines
14 KiB
TypeScript
373 lines
14 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { screen, fireEvent, waitFor, within } from "@testing-library/react";
|
|
import "@testing-library/jest-dom/vitest";
|
|
import { renderWithProviders } from "../utils/test-utils";
|
|
import { CoreValuesSelectScreen } from "../../app/(app)/create/screens/select/CoreValuesSelectScreen";
|
|
|
|
describe("CoreValuesSelectScreen", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
async function discardPendingValue() {
|
|
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 } });
|
|
}
|
|
|
|
it("opens core value detail modal when a preset chip is clicked", async () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
fireEvent.click(screen.getByText("Accessibility"));
|
|
const dialog = await screen.findByRole("dialog");
|
|
expect(
|
|
within(dialog).getByRole("button", { name: "Add Value" }),
|
|
).toBeInTheDocument();
|
|
fireEvent.click(within(dialog).getByRole("button", { name: "More options" }));
|
|
expect(
|
|
screen.getByRole("menuitem", { name: "Customize" }),
|
|
).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole("menuitem", { name: "Duplicate" }),
|
|
).toBeInTheDocument();
|
|
expect(
|
|
screen.queryByRole("menuitem", { name: "Remove" }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("closes a pending value without confirm when fields are unchanged", async () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
fireEvent.click(screen.getByText("Accessibility"));
|
|
await screen.findByRole("dialog");
|
|
fireEvent.keyDown(document, { key: "Escape" });
|
|
await waitFor(() => {
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
});
|
|
expect(
|
|
screen.queryByRole("button", { name: "Keep editing" }),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("asks to discard when closing a pending value after an edit", async () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
fireEvent.click(screen.getByText("Accessibility"));
|
|
await editMeaningInOpenDialog("Changed meaning");
|
|
fireEvent.keyDown(document, { key: "Escape" });
|
|
expect(
|
|
await screen.findByRole("button", { name: "Keep editing" }),
|
|
).toBeInTheDocument();
|
|
await discardPendingValue();
|
|
await waitFor(() => {
|
|
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("keeps the pending value modal open when Keep editing is chosen", async () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
fireEvent.click(screen.getByText("Accessibility"));
|
|
await editMeaningInOpenDialog("Changed meaning");
|
|
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" }),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("saves details when Add Value 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();
|
|
});
|
|
});
|
|
|
|
it("opens meaning and signals fields 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();
|
|
}
|
|
});
|
|
|
|
it("shows Remove only after the value has been added", 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();
|
|
});
|
|
fireEvent.click(screen.getByText("Accessibility"));
|
|
const editing = await screen.findByRole("dialog");
|
|
fireEvent.click(
|
|
within(editing).getByRole("button", { name: "More options" }),
|
|
);
|
|
expect(screen.getByRole("menuitem", { name: "Remove" })).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole("menuitem", { name: "Customize" }),
|
|
).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole("menuitem", { name: "Duplicate" }),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("Customize walks name and description before policy details", async () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
fireEvent.click(screen.getByText("Accessibility"));
|
|
const dialog = await screen.findByRole("dialog");
|
|
fireEvent.click(
|
|
within(dialog).getByRole("button", { name: "More options" }),
|
|
);
|
|
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
|
|
|
expect(await screen.findByPlaceholderText("Policy name")).toHaveValue(
|
|
"Accessibility",
|
|
);
|
|
expect(screen.queryByText("Custom policy details")).not.toBeInTheDocument();
|
|
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: "What does this value mean to your group?",
|
|
}),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
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();
|
|
});
|
|
fireEvent.click(screen.getByText("Accessibility"));
|
|
const editing = await screen.findByRole("dialog");
|
|
fireEvent.click(
|
|
within(editing).getByRole("button", { name: "More options" }),
|
|
);
|
|
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
|
expect(await screen.findByPlaceholderText("Policy name")).toBeInTheDocument();
|
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
|
fireEvent.click(
|
|
await screen.findByRole("button", {
|
|
name: "What does this value mean to your group?",
|
|
}),
|
|
);
|
|
expect(
|
|
screen.getByDisplayValue("Edited meaning from value modal"),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("closing Customize without Finalize returns to the value modal", async () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
fireEvent.click(screen.getByText("Accessibility"));
|
|
const dialog = await screen.findByRole("dialog");
|
|
fireEvent.click(
|
|
within(dialog).getByRole("button", { name: "More options" }),
|
|
);
|
|
fireEvent.click(screen.getByRole("menuitem", { name: "Customize" }));
|
|
expect(
|
|
await screen.findByPlaceholderText("Policy name"),
|
|
).toBeInTheDocument();
|
|
fireEvent.keyDown(document, { key: "Escape" });
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.queryByPlaceholderText("Policy name"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
const valueDialog = screen.getByRole("dialog");
|
|
expect(
|
|
within(valueDialog).getByRole("button", { name: "Add Value" }),
|
|
).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");
|
|
fireEvent.click(
|
|
within(dialog).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[1], { dataTransfer });
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Finalize policy" }));
|
|
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.queryByPlaceholderText("Policy name"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
|
|
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(/Signals of Violation/);
|
|
expect(labels[1]).toMatch(/What does this value mean to your group/);
|
|
});
|
|
|
|
// 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", () => {
|
|
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", { name: label })
|
|
.filter(
|
|
(el) =>
|
|
!el.closest('[role="dialog"]') &&
|
|
(el.getAttribute("aria-label") ?? "") !== `Remove ${label}`,
|
|
).length;
|
|
}
|
|
|
|
async function completeAddValueWizard(label: string) {
|
|
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
|
|
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");
|
|
fireEvent.change(descriptionInput, {
|
|
target: { value: "A community-authored value." },
|
|
});
|
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
|
expect(
|
|
await screen.findByText("Custom policy details"),
|
|
).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole("button", { name: "Add Custom Field" }),
|
|
).toBeInTheDocument();
|
|
fireEvent.click(await screen.findByRole("button", { name: "Finalize policy" }));
|
|
}
|
|
|
|
it("collapses the field-type picker after adding a custom field", async () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
|
|
fireEvent.change(await screen.findByPlaceholderText("Policy name"), {
|
|
target: { value: CUSTOM_LABEL },
|
|
});
|
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
|
fireEvent.change(
|
|
await screen.findByPlaceholderText("Policy description"),
|
|
{ target: { value: "A community-authored value." } },
|
|
);
|
|
fireEvent.click(screen.getByRole("button", { name: "Next" }));
|
|
expect(
|
|
await screen.findByText("Custom policy details"),
|
|
).toBeInTheDocument();
|
|
|
|
const wizard = screen.getByRole("dialog");
|
|
fireEvent.click(
|
|
within(wizard).getByRole("button", { name: "Add Custom Field" }),
|
|
);
|
|
fireEvent.click(within(wizard).getByRole("button", { name: "Text" }));
|
|
fireEvent.change(
|
|
await screen.findByPlaceholderText("Add your text block title"),
|
|
{ target: { value: "Policy title" } },
|
|
);
|
|
fireEvent.click(screen.getByRole("button", { name: "Add field" }));
|
|
|
|
expect(
|
|
await within(wizard).findByRole("button", { name: "Add Custom Field" }),
|
|
).toBeInTheDocument();
|
|
expect(
|
|
within(wizard).queryByRole("button", { name: "Text" }),
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
within(wizard).getByRole("button", { name: "Policy title" }),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it("opens the empty custom-policy wizard from Add value", async () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
|
|
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 () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
|
|
await screen.findByPlaceholderText("Policy name");
|
|
fireEvent.keyDown(document, { key: "Escape" });
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.queryByPlaceholderText("Policy name"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
expect(countCustomChips(CUSTOM_LABEL)).toBe(0);
|
|
expect(screen.queryByPlaceholderText("Type to add")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("adds a selected chip named from the wizard title after Finalize", async () => {
|
|
renderWithProviders(<CoreValuesSelectScreen />);
|
|
await completeAddValueWizard(CUSTOM_LABEL);
|
|
await waitFor(() => {
|
|
expect(
|
|
screen.queryByPlaceholderText("Policy name"),
|
|
).not.toBeInTheDocument();
|
|
});
|
|
expect(countCustomChips(CUSTOM_LABEL)).toBe(1);
|
|
});
|
|
});
|
|
});
|