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:
1 parent
dea7dd7fbd
commit
46656f52d3
28 files changed
+1349
-318
No files matched your search
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -37,10 +37,10 @@ describe("Create flow communication-methods page", () => {
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(dialog).toBeInTheDocument();
|
||||
expect(within(dialog).getByText("Signal")).toBeInTheDocument();
|
||||
expect(within(dialog).getByText("Add card")).toBeInTheDocument();
|
||||
expect(within(dialog).getByText("Add to Rule")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("re-opening a selected method shows Save and footer Remove; Remove stays in the kebab", async () => {
|
||||
test("re-opening a selected method shows Save and footer Deselect; Remove stays in the kebab", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CommunicationMethodsScreen />);
|
||||
|
||||
@@ -49,15 +49,15 @@ describe("Create flow communication-methods page", () => {
|
||||
});
|
||||
await user.click(signalCards[0]);
|
||||
const dialog = screen.getByRole("dialog");
|
||||
await user.click(within(dialog).getByRole("button", { name: "Add card" }));
|
||||
await user.click(within(dialog).getByRole("button", { name: "Add to Rule" }));
|
||||
|
||||
await user.click(signalCards[0]);
|
||||
const dialogAgain = screen.getByRole("dialog");
|
||||
expect(
|
||||
within(dialogAgain).getByRole("button", { name: "Remove" }),
|
||||
within(dialogAgain).getByRole("button", { name: "Deselect" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
within(dialogAgain).queryByRole("button", { name: "Add card" }),
|
||||
within(dialogAgain).queryByRole("button", { name: "Add to Rule" }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
within(dialogAgain).getByRole("button", { name: "Save" }),
|
||||
@@ -67,7 +67,7 @@ describe("Create flow communication-methods page", () => {
|
||||
expect(screen.getByRole("menuitem", { name: "Remove" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Remove from the footer deselects the method", async () => {
|
||||
test("Deselect from the footer deselects the method", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CommunicationMethodsScreen />);
|
||||
|
||||
@@ -77,7 +77,7 @@ describe("Create flow communication-methods page", () => {
|
||||
await user.click(signalCards[0]);
|
||||
await user.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", {
|
||||
name: "Add card",
|
||||
name: "Add to Rule",
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -85,7 +85,9 @@ describe("Create flow communication-methods page", () => {
|
||||
|
||||
await user.click(signalCards[0]);
|
||||
await user.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", { name: "Remove" }),
|
||||
within(screen.getByRole("dialog")).getByRole("button", {
|
||||
name: "Deselect",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(signalCards[0]).not.toHaveTextContent("SELECTED");
|
||||
@@ -101,7 +103,7 @@ describe("Create flow communication-methods page", () => {
|
||||
await user.click(signalCards[0]);
|
||||
await user.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", {
|
||||
name: "Add card",
|
||||
name: "Add to Rule",
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -143,11 +145,13 @@ describe("Create flow communication-methods page", () => {
|
||||
await user.click(signalCards[0]);
|
||||
|
||||
const dialog = screen.getByRole("dialog");
|
||||
const textbox = within(dialog).getAllByRole("textbox")[0];
|
||||
expect(textbox).not.toBeDisabled();
|
||||
|
||||
await user.click(within(dialog).getByRole("button", { name: "More options" }));
|
||||
expect(screen.getByRole("menuitem", { name: "Customize" })).toBeInTheDocument();
|
||||
await user.click(
|
||||
within(dialog).getByRole("button", { name: "Core principle and scope" }),
|
||||
);
|
||||
const textbox = within(dialog).getAllByRole("textbox")[0];
|
||||
expect(textbox).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test("meta-only custom policy opens with editable section fields", async () => {
|
||||
@@ -167,14 +171,35 @@ describe("Create flow communication-methods page", () => {
|
||||
screen.getAllByRole("button", { name: /My policy: Desc/ })[0],
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
expect(
|
||||
within(dialog).queryByText("No custom fields yet."),
|
||||
).not.toBeInTheDocument();
|
||||
await user.click(
|
||||
within(dialog).getByRole("button", { name: "Core principle and scope" }),
|
||||
);
|
||||
const textboxes = within(dialog).getAllByRole("textbox");
|
||||
expect(textboxes.length).toBeGreaterThan(0);
|
||||
for (const field of textboxes) {
|
||||
expect(field).not.toBeDisabled();
|
||||
}
|
||||
});
|
||||
|
||||
test("compact stack shows seven cards and See all search", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CommunicationMethodsScreen />);
|
||||
|
||||
const compactCards = screen
|
||||
.getAllByRole("button")
|
||||
.filter((el) => (el.getAttribute("aria-label") || "").includes(": "));
|
||||
expect(compactCards).toHaveLength(7);
|
||||
expect(screen.queryByText("RECOMMENDED")).not.toBeInTheDocument();
|
||||
|
||||
await user.click(
|
||||
screen.getByRole("button", { name: "See all communication cards" }),
|
||||
);
|
||||
expect(
|
||||
within(dialog).queryByText("No custom fields yet."),
|
||||
).not.toBeInTheDocument();
|
||||
screen.getByRole("searchbox", { name: "Search this deck" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders without error", () => {
|
||||
@@ -419,7 +444,7 @@ describe("Create flow communication-methods page", () => {
|
||||
const dupId = dupIds[0];
|
||||
expect(latest.selectedCommunicationMethodIds).toEqual([CUSTOM_POLICY_ID]);
|
||||
expect(
|
||||
within(dialog).getByRole("button", { name: "Add card" }),
|
||||
within(dialog).getByRole("button", { name: "Add to Rule" }),
|
||||
).toBeInTheDocument();
|
||||
expect(metaAfterDup[dupId].label.endsWith(" (copy)")).toBe(true);
|
||||
expect(within(dialog).getByRole("heading", { level: 1 })).toHaveTextContent(
|
||||
|
||||
@@ -185,7 +185,7 @@ describe("Create flow decision-approaches page", () => {
|
||||
await user.click(screen.getByRole("button", { name: /^Consent \(sociocracy\):/ }));
|
||||
await user.click(
|
||||
within(await screen.findByRole("dialog")).getByRole("button", {
|
||||
name: "Add card",
|
||||
name: "Add to Rule",
|
||||
}),
|
||||
);
|
||||
await waitFor(() => {
|
||||
@@ -211,17 +211,20 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
const textboxes = within(dialog).getAllByRole("textbox");
|
||||
expect(textboxes.length).toBeGreaterThan(0);
|
||||
for (const field of textboxes) {
|
||||
expect(field).not.toBeDisabled();
|
||||
}
|
||||
await user.click(
|
||||
within(dialog).getByRole("button", { name: "More options" }),
|
||||
);
|
||||
expect(
|
||||
screen.getByRole("menuitem", { name: "Customize" }),
|
||||
).toBeInTheDocument();
|
||||
await user.click(
|
||||
within(dialog).getByRole("button", { name: "Core principle" }),
|
||||
);
|
||||
const textboxes = within(dialog).getAllByRole("textbox");
|
||||
expect(textboxes.length).toBeGreaterThan(0);
|
||||
for (const field of textboxes) {
|
||||
expect(field).not.toBeDisabled();
|
||||
}
|
||||
});
|
||||
|
||||
test("clicking a card opens the create modal and confirming selects it", async () => {
|
||||
@@ -237,14 +240,14 @@ describe("Create flow decision-approaches page", () => {
|
||||
expect(dialog).toBeInTheDocument();
|
||||
|
||||
const confirmButton = within(dialog).getByRole("button", {
|
||||
name: "Add card",
|
||||
name: "Add to Rule",
|
||||
});
|
||||
await user.click(confirmButton);
|
||||
|
||||
expect(screen.getByText("SELECTED")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("re-opening a selected approach shows Save and footer Remove; Remove stays in the kebab", async () => {
|
||||
test("re-opening a selected approach shows Save and footer Deselect; Remove stays in the kebab", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<DecisionApproachesScreen />);
|
||||
|
||||
@@ -255,17 +258,17 @@ describe("Create flow decision-approaches page", () => {
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(dialog).getByRole("button", {
|
||||
name: "Add card",
|
||||
name: "Add to Rule",
|
||||
}),
|
||||
);
|
||||
|
||||
await user.click(card);
|
||||
const dialogAgain = screen.getByRole("dialog");
|
||||
expect(
|
||||
within(dialogAgain).getByRole("button", { name: "Remove" }),
|
||||
within(dialogAgain).getByRole("button", { name: "Deselect" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
within(dialogAgain).queryByRole("button", { name: "Add card" }),
|
||||
within(dialogAgain).queryByRole("button", { name: "Add to Rule" }),
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
within(dialogAgain).getByRole("button", { name: "Save" }),
|
||||
@@ -275,7 +278,7 @@ describe("Create flow decision-approaches page", () => {
|
||||
expect(screen.getByRole("menuitem", { name: "Remove" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Remove from the footer deselects the approach", async () => {
|
||||
test("Deselect from the footer deselects the approach", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<DecisionApproachesScreen />);
|
||||
|
||||
@@ -285,7 +288,7 @@ describe("Create flow decision-approaches page", () => {
|
||||
await user.click(card);
|
||||
await user.click(
|
||||
within(await screen.findByRole("dialog")).getByRole("button", {
|
||||
name: "Add card",
|
||||
name: "Add to Rule",
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -293,7 +296,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
|
||||
await user.click(card);
|
||||
await user.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", { name: "Remove" }),
|
||||
within(screen.getByRole("dialog")).getByRole("button", {
|
||||
name: "Deselect",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(card).not.toHaveTextContent("SELECTED");
|
||||
@@ -309,17 +314,21 @@ describe("Create flow decision-approaches page", () => {
|
||||
await user.click(card);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(dialog).getByRole("button", { name: "Add card" }),
|
||||
within(dialog).getByRole("button", { name: "Add to Rule" }),
|
||||
);
|
||||
|
||||
await user.click(card);
|
||||
const dialogAgain = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(dialogAgain).getByRole("button", { name: "Core principle" }),
|
||||
);
|
||||
const principleField = within(dialogAgain).getByRole("textbox", {
|
||||
name: /core principle/i,
|
||||
});
|
||||
await user.clear(principleField);
|
||||
await user.type(principleField, "Edited principle");
|
||||
await user.click(within(dialogAgain).getByRole("button", { name: "Save" }));
|
||||
await user.click(within(dialogAgain).getByRole("button", { name: "Save" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
@@ -336,7 +345,7 @@ describe("Create flow decision-approaches page", () => {
|
||||
await user.click(card);
|
||||
await user.click(
|
||||
within(await screen.findByRole("dialog")).getByRole("button", {
|
||||
name: "Add card",
|
||||
name: "Add to Rule",
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -397,6 +406,14 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(dialog).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
expect(
|
||||
within(dialog).getByRole("button", {
|
||||
name: "Select Changing the Rule",
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
within(dialog).getByRole("button", {
|
||||
name: "Select Changing this Rule",
|
||||
@@ -431,6 +448,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const lazyDialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
expect(
|
||||
within(lazyDialog).getByRole("button", {
|
||||
name: "Select Shared money",
|
||||
@@ -448,6 +468,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const lazyDialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", {
|
||||
name: "Select Shared money",
|
||||
@@ -457,7 +480,10 @@ describe("Create flow decision-approaches page", () => {
|
||||
screen.getByRole("checkbox", { name: "Shared money" }),
|
||||
).toBeChecked();
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", { name: "Add card" }),
|
||||
within(lazyDialog).getByRole("button", { name: "Save" }),
|
||||
);
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", { name: "Add to Rule" }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
@@ -469,6 +495,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const doocracyDialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
expect(
|
||||
within(doocracyDialog).getByRole("button", {
|
||||
name: "Select Shared money",
|
||||
@@ -482,6 +511,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
expect(
|
||||
screen.getByRole("checkbox", { name: "Shared money" }),
|
||||
).not.toBeChecked();
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", { name: "Save" }),
|
||||
);
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", { name: "Close" }),
|
||||
);
|
||||
@@ -499,6 +531,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const lazyAgain = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(lazyAgain).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
expect(
|
||||
within(lazyAgain).getByRole("button", {
|
||||
name: "Deselect Shared money",
|
||||
@@ -520,7 +555,7 @@ describe("Create flow decision-approaches page", () => {
|
||||
);
|
||||
const lazyDialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", { name: "Add card" }),
|
||||
within(lazyDialog).getByRole("button", { name: "Add to Rule" }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
@@ -532,6 +567,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const doocracyDialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
await user.click(
|
||||
screen.getByRole("checkbox", { name: "Shared money" }),
|
||||
);
|
||||
@@ -540,6 +578,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
name: "Deselect Shared money",
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", { name: "Save" }),
|
||||
);
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", { name: "Close" }),
|
||||
);
|
||||
@@ -554,6 +595,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const lazyAgain = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(lazyAgain).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
expect(
|
||||
within(lazyAgain).getByRole("button", {
|
||||
name: "Select Shared money",
|
||||
@@ -571,6 +615,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const lazyDialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", {
|
||||
name: "Select Shared money",
|
||||
@@ -580,7 +627,10 @@ describe("Create flow decision-approaches page", () => {
|
||||
screen.getByRole("checkbox", { name: "Shared money" }),
|
||||
).toBeChecked();
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", { name: "Add card" }),
|
||||
within(lazyDialog).getByRole("button", { name: "Save" }),
|
||||
);
|
||||
await user.click(
|
||||
within(lazyDialog).getByRole("button", { name: "Add to Rule" }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
@@ -592,6 +642,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const doocracyDialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", {
|
||||
name: "Select Big decisions",
|
||||
@@ -609,7 +662,10 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", { name: "Add card" }),
|
||||
within(doocracyDialog).getByRole("button", { name: "Save" }),
|
||||
);
|
||||
await user.click(
|
||||
within(doocracyDialog).getByRole("button", { name: "Add to Rule" }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
@@ -628,6 +684,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const lazyAgain = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(lazyAgain).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
expect(
|
||||
within(lazyAgain).getByRole("button", {
|
||||
name: "Deselect Shared money",
|
||||
@@ -638,6 +697,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
name: "Select Big decisions",
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
await user.click(
|
||||
within(lazyAgain).getByRole("button", { name: "Save" }),
|
||||
);
|
||||
await user.click(
|
||||
within(lazyAgain).getByRole("button", { name: "Close" }),
|
||||
);
|
||||
@@ -651,6 +713,9 @@ describe("Create flow decision-approaches page", () => {
|
||||
}),
|
||||
);
|
||||
const doocracyAgain = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(doocracyAgain).getByRole("button", { name: "Applicable scope" }),
|
||||
);
|
||||
expect(
|
||||
within(doocracyAgain).getByRole("button", {
|
||||
name: "Deselect Big decisions",
|
||||
|
||||
@@ -155,6 +155,66 @@ describe("CardStack Component", () => {
|
||||
expect(screen.getAllByText("SELECTED").length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test("compact single-stack shows seven recommended cards and hides the tag", () => {
|
||||
const cards = Array.from({ length: 8 }, (_, index) => ({
|
||||
id: String(index),
|
||||
label: `Card ${index}`,
|
||||
supportText: `Desc ${index}`,
|
||||
recommended: true,
|
||||
}));
|
||||
render(
|
||||
<CardStack cards={cards} layout="singleStack" expanded={false} />,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getAllByRole("button", { name: /Card \d: Desc \d/ }),
|
||||
).toHaveLength(7);
|
||||
expect(screen.queryByText("RECOMMENDED")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("See all search filters the expanded single-stack", () => {
|
||||
render(
|
||||
<CardStack
|
||||
cards={SAMPLE_CARDS}
|
||||
layout="singleStack"
|
||||
expanded={true}
|
||||
searchPlaceholder="Search this deck"
|
||||
/>,
|
||||
);
|
||||
|
||||
const search = screen.getByRole("searchbox", { name: "Search this deck" });
|
||||
fireEvent.change(search, { target: { value: "Option B" } });
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Option B: Description B" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Option A: Description A" }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("compact alternative row opens the named card", () => {
|
||||
const onAlternativeSelect = vi.fn();
|
||||
render(
|
||||
<CardStack
|
||||
cards={[
|
||||
{
|
||||
id: "1",
|
||||
label: "Option A",
|
||||
supportText: "Description A",
|
||||
recommended: true,
|
||||
alternative: { id: "2", label: "Option B" },
|
||||
},
|
||||
]}
|
||||
layout="singleStack"
|
||||
alternativePrefix="Another way communities do this:"
|
||||
onAlternativeSelect={onAlternativeSelect}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Option B" }));
|
||||
expect(onAlternativeSelect).toHaveBeenCalledWith("2");
|
||||
});
|
||||
|
||||
test("calls onCardSelect when re-clicking an already selected card", () => {
|
||||
const onCardSelect = vi.fn();
|
||||
render(
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
selectedKeyResourceLabelsFromCheckedIds,
|
||||
stringArraysEqual,
|
||||
syncDecisionApproachKeyResourceDetails,
|
||||
decisionApproachDefaultScopeChips,
|
||||
withDecisionApproachKeyResourceScopes,
|
||||
} from "../../lib/create/decisionApproachKeyResources";
|
||||
import type { DecisionApproachDetailEntry } from "../../app/(app)/create/types";
|
||||
@@ -158,10 +159,20 @@ describe("decisionApproachKeyResources", () => {
|
||||
expect(decisionApproachKeyResourceLabels()).toHaveLength(7);
|
||||
});
|
||||
|
||||
it("unions key-resource labels onto an existing scope list", () => {
|
||||
it("unions signed-off default chips then key-resource labels onto an existing scope list", () => {
|
||||
expect(decisionApproachDefaultScopeChips()).toEqual([
|
||||
"Changing the Rule",
|
||||
"Money",
|
||||
"Projects",
|
||||
"Asking someone to leave",
|
||||
]);
|
||||
expect(
|
||||
withDecisionApproachKeyResourceScopes(["Daily Operations"]),
|
||||
).toEqual([
|
||||
"Changing the Rule",
|
||||
"Money",
|
||||
"Projects",
|
||||
"Asking someone to leave",
|
||||
"Daily Operations",
|
||||
"Everyday decisions",
|
||||
"Big decisions",
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatRecommendationWhyLine } from "../../lib/create/formatRecommendationWhyLine";
|
||||
import type { GroupAnswers } from "../../lib/create/deckTypes";
|
||||
import messages from "../../messages/en/index";
|
||||
|
||||
const copy = messages.create.customRule.recommendations;
|
||||
const emptyAnswers: GroupAnswers = {
|
||||
size: null,
|
||||
mainKind: null,
|
||||
otherKinds: [],
|
||||
scale: [],
|
||||
stage: null,
|
||||
traits: {},
|
||||
};
|
||||
|
||||
describe("formatRecommendationWhyLine", () => {
|
||||
it("builds a fit line from kind and size", () => {
|
||||
expect(
|
||||
formatRecommendationWhyLine(
|
||||
{ kind: "fit", matched: ["kind"] },
|
||||
{ ...emptyAnswers, mainKind: "workersCoop", size: "sixToTwelve" },
|
||||
copy,
|
||||
),
|
||||
).toBe("Fits a worker co-op of 6 to 12 people");
|
||||
});
|
||||
|
||||
it("builds an added line from the setup answer", () => {
|
||||
expect(
|
||||
formatRecommendationWhyLine(
|
||||
{ kind: "added", answer: "employer" },
|
||||
emptyAnswers,
|
||||
copy,
|
||||
),
|
||||
).toBe("Added because you said you pay some people to do your work");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user