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();
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();
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();
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();
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();
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();
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();
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();
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();
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();
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();
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 = {};
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" }));
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" }));
fireEvent.click(await screen.findByRole("button", { name: "Finalize" }));
}
it("opens the empty custom-policy wizard from Add value", async () => {
renderWithProviders();
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();
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();
await completeAddValueWizard(CUSTOM_LABEL);
await waitFor(() => {
expect(
screen.queryByPlaceholderText("Policy name"),
).not.toBeInTheDocument();
});
expect(countCustomChips(CUSTOM_LABEL)).toBe(1);
});
});
});