Add custom intervention modals
This commit is contained in:
@@ -400,6 +400,124 @@ describe("FinalReviewScreen — chip edit modal save semantics", () => {
|
||||
|
||||
expect(latest.communicationMethodDetailsById).toBeUndefined();
|
||||
});
|
||||
|
||||
it("shows consolidated placeholder for user-authored communication chips", async () => {
|
||||
const customId = "550e8400-e29b-41d4-a716-446655440000";
|
||||
render(
|
||||
<FinalReviewWithStateProbe
|
||||
onState={() => {}}
|
||||
initial={{
|
||||
title: "Oak Park Commons",
|
||||
selectedCommunicationMethodIds: [customId],
|
||||
customMethodCardMetaById: {
|
||||
[customId]: {
|
||||
label: "Custom Comm",
|
||||
supportText: "Support line from wizard",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: "Custom Comm" }),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
expect(
|
||||
within(dialog).getByText(/title and description you set/i),
|
||||
).toBeInTheDocument();
|
||||
expect(within(dialog).queryByRole("textbox")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows editable field blocks for user-authored communication chips when configured", async () => {
|
||||
const customId = "550e8400-e29b-41d4-a716-446655440000";
|
||||
render(
|
||||
<FinalReviewWithStateProbe
|
||||
onState={() => {}}
|
||||
initial={{
|
||||
title: "Oak Park Commons",
|
||||
selectedCommunicationMethodIds: [customId],
|
||||
customMethodCardMetaById: {
|
||||
[customId]: {
|
||||
label: "Custom Comm",
|
||||
supportText: "Support line from wizard",
|
||||
},
|
||||
},
|
||||
customMethodCardFieldBlocksById: {
|
||||
[customId]: [
|
||||
{
|
||||
kind: "text",
|
||||
id: "f1",
|
||||
blockTitle: "Notes",
|
||||
placeholderText: "Detail here",
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: "Custom Comm" }),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
expect(
|
||||
within(dialog).queryByText(/title and description you set/i),
|
||||
).not.toBeInTheDocument();
|
||||
const textarea = within(dialog).getByRole("textbox");
|
||||
expect(textarea).not.toBeDisabled();
|
||||
expect(textarea).toHaveValue("Detail here");
|
||||
});
|
||||
|
||||
it("persists field block edits for user-authored communication chips on Save", async () => {
|
||||
const customId = "550e8400-e29b-41d4-a716-446655440000";
|
||||
let latest: CreateFlowState = {};
|
||||
render(
|
||||
<FinalReviewWithStateProbe
|
||||
onState={(s) => {
|
||||
latest = s;
|
||||
}}
|
||||
initial={{
|
||||
title: "Oak Park Commons",
|
||||
selectedCommunicationMethodIds: [customId],
|
||||
customMethodCardMetaById: {
|
||||
[customId]: {
|
||||
label: "Custom Comm",
|
||||
supportText: "Support line from wizard",
|
||||
},
|
||||
},
|
||||
customMethodCardFieldBlocksById: {
|
||||
[customId]: [
|
||||
{
|
||||
kind: "text",
|
||||
id: "f1",
|
||||
blockTitle: "Notes",
|
||||
placeholderText: "Detail here",
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
await screen.findByRole("button", { name: "Custom Comm" }),
|
||||
);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
const textarea = within(dialog).getByRole("textbox");
|
||||
fireEvent.change(textarea, { target: { value: "Saved detail" } });
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "Save" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
latest.customMethodCardFieldBlocksById?.[customId]?.[0],
|
||||
).toMatchObject({
|
||||
kind: "text",
|
||||
placeholderText: "Saved detail",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function FinalReviewEditPublishedWithStateProbe({
|
||||
|
||||
@@ -6,7 +6,19 @@ import {
|
||||
} from "../utils/test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, test, expect, afterEach } from "vitest";
|
||||
import { useLayoutEffect } from "react";
|
||||
import { CommunicationMethodsScreen } from "../../app/(app)/create/screens/card/CommunicationMethodsScreen";
|
||||
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";
|
||||
|
||||
const CUSTOM_POLICY_ID = "550e8400-e29b-41d4-a716-446655440000";
|
||||
|
||||
function CommunicationMethodsScreenWithState({ initial }) {
|
||||
const { replaceState } = useCreateFlow();
|
||||
useLayoutEffect(() => {
|
||||
replaceState(initial);
|
||||
}, [replaceState, initial]);
|
||||
return <CommunicationMethodsScreen />;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
@@ -28,6 +40,48 @@ describe("Create flow communication-methods page", () => {
|
||||
expect(within(dialog).getByText("Add Platform")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("re-opening a selected method shows Remove as the modal primary action", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CommunicationMethodsScreen />);
|
||||
|
||||
const signalCards = screen.getAllByRole("button", {
|
||||
name: /Signal: Encrypted messaging/,
|
||||
});
|
||||
await user.click(signalCards[0]);
|
||||
const dialog = screen.getByRole("dialog");
|
||||
await user.click(within(dialog).getByRole("button", { name: "Add Platform" }));
|
||||
|
||||
await user.click(signalCards[0]);
|
||||
const dialogAgain = screen.getByRole("dialog");
|
||||
expect(
|
||||
within(dialogAgain).getByRole("button", { name: "Remove" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Remove in the modal deselects the method", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CommunicationMethodsScreen />);
|
||||
|
||||
const signalCards = screen.getAllByRole("button", {
|
||||
name: /Signal: Encrypted messaging/,
|
||||
});
|
||||
await user.click(signalCards[0]);
|
||||
await user.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", {
|
||||
name: "Add Platform",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(signalCards[0]).toHaveTextContent("SELECTED");
|
||||
|
||||
await user.click(signalCards[0]);
|
||||
await user.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", { name: "Remove" }),
|
||||
);
|
||||
|
||||
expect(signalCards[0]).not.toHaveTextContent("SELECTED");
|
||||
});
|
||||
|
||||
test("renders without error", () => {
|
||||
render(<CommunicationMethodsScreen />);
|
||||
|
||||
@@ -44,12 +98,38 @@ describe("Create flow communication-methods page", () => {
|
||||
expect(
|
||||
screen.getByText(/You can select multiple methods for different needs or/),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "add" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /^add$/i })).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "See all communication approaches" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("with a finalized custom policy, inline add link still opens the custom wizard", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<CommunicationMethodsScreenWithState
|
||||
initial={{
|
||||
selectedCommunicationMethodIds: [CUSTOM_POLICY_ID],
|
||||
customMethodCardMetaById: {
|
||||
[CUSTOM_POLICY_ID]: { label: "My policy", supportText: "Desc" },
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Remove policy" }),
|
||||
).not.toBeInTheDocument();
|
||||
const addButtons = screen.getAllByRole("button", { name: /^add$/i });
|
||||
expect(addButtons.length).toBeGreaterThanOrEqual(1);
|
||||
await user.click(addButtons[0]);
|
||||
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
expect(
|
||||
within(dialog).getByText("What do you call your group's new policy?"),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("toggle expands and shows Show less", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CommunicationMethodsScreen />);
|
||||
@@ -67,5 +147,122 @@ describe("Create flow communication-methods page", () => {
|
||||
"What method should this community use to communicate with eachother?",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: /^add$/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("opening Create modal for custom policy shows saved field blocks", async () => {
|
||||
const user = userEvent.setup();
|
||||
const initial = {
|
||||
selectedCommunicationMethodIds: [CUSTOM_POLICY_ID],
|
||||
methodSectionsPinCommitted: { communication: true },
|
||||
customMethodCardMetaById: {
|
||||
[CUSTOM_POLICY_ID]: { label: "My policy", supportText: "Support copy" },
|
||||
},
|
||||
customMethodCardFieldBlocksById: {
|
||||
[CUSTOM_POLICY_ID]: [
|
||||
{
|
||||
kind: "text",
|
||||
id: "f1",
|
||||
blockTitle: "Guidelines",
|
||||
placeholderText: "Enter norms here",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
render(<CommunicationMethodsScreenWithState initial={initial} />);
|
||||
|
||||
const policyTiles = screen.getAllByRole("button", {
|
||||
name: /My policy: Support copy/,
|
||||
});
|
||||
await user.click(policyTiles[0]);
|
||||
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(within(dialog).getByText("Guidelines")).toBeInTheDocument();
|
||||
const textarea = within(dialog).getByRole("textbox");
|
||||
expect(textarea).not.toBeDisabled();
|
||||
expect(textarea).toHaveValue("Enter norms here");
|
||||
});
|
||||
|
||||
test("opening Create modal for custom policy shows badge options as chips", async () => {
|
||||
const user = userEvent.setup();
|
||||
const initial = {
|
||||
selectedCommunicationMethodIds: [CUSTOM_POLICY_ID],
|
||||
methodSectionsPinCommitted: { communication: true },
|
||||
customMethodCardMetaById: {
|
||||
[CUSTOM_POLICY_ID]: { label: "My policy", supportText: "Support copy" },
|
||||
},
|
||||
customMethodCardFieldBlocksById: {
|
||||
[CUSTOM_POLICY_ID]: [
|
||||
{
|
||||
kind: "badges",
|
||||
id: "b1",
|
||||
blockTitle: "Choose channels",
|
||||
options: ["Alpha", "Beta"],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
render(<CommunicationMethodsScreenWithState initial={initial} />);
|
||||
|
||||
const policyTiles = screen.getAllByRole("button", {
|
||||
name: /My policy: Support copy/,
|
||||
});
|
||||
await user.click(policyTiles[0]);
|
||||
|
||||
const dialog = screen.getByRole("dialog");
|
||||
expect(within(dialog).getByText("Choose channels")).toBeInTheDocument();
|
||||
const alpha = within(dialog).getByRole("button", { name: /Deselect Alpha/ });
|
||||
const beta = within(dialog).getByRole("button", { name: /Deselect Beta/ });
|
||||
expect(alpha).not.toBeDisabled();
|
||||
expect(beta).not.toBeDisabled();
|
||||
});
|
||||
|
||||
test("editing custom policy field blocks updates draft state", async () => {
|
||||
const user = userEvent.setup();
|
||||
let latest = {};
|
||||
function Probe({ initial }) {
|
||||
const { replaceState, state } = useCreateFlow();
|
||||
useLayoutEffect(() => {
|
||||
replaceState(initial);
|
||||
}, [replaceState, initial]);
|
||||
useLayoutEffect(() => {
|
||||
latest = state;
|
||||
}, [state]);
|
||||
return <CommunicationMethodsScreen />;
|
||||
}
|
||||
const initial = {
|
||||
selectedCommunicationMethodIds: [CUSTOM_POLICY_ID],
|
||||
customMethodCardMetaById: {
|
||||
[CUSTOM_POLICY_ID]: { label: "My policy", supportText: "Support copy" },
|
||||
},
|
||||
customMethodCardFieldBlocksById: {
|
||||
[CUSTOM_POLICY_ID]: [
|
||||
{
|
||||
kind: "text",
|
||||
id: "f1",
|
||||
blockTitle: "Guidelines",
|
||||
placeholderText: "Original",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
render(<Probe initial={initial} />);
|
||||
|
||||
const policyTiles = screen.getAllByRole("button", {
|
||||
name: /My policy: Support copy/,
|
||||
});
|
||||
await user.click(policyTiles[0]);
|
||||
const textarea = within(screen.getByRole("dialog")).getByRole("textbox");
|
||||
await user.clear(textarea);
|
||||
await user.type(textarea, "Updated norms");
|
||||
|
||||
const row = latest.customMethodCardFieldBlocksById?.[CUSTOM_POLICY_ID]?.[0];
|
||||
expect(row).toMatchObject({
|
||||
kind: "text",
|
||||
placeholderText: "Updated norms",
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -6,7 +6,19 @@ import {
|
||||
} from "../utils/test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, test, expect, afterEach } from "vitest";
|
||||
import { useLayoutEffect } from "react";
|
||||
import { DecisionApproachesScreen } from "../../app/(app)/create/screens/right-rail/DecisionApproachesScreen";
|
||||
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";
|
||||
|
||||
const CUSTOM_APPROACH_ID = "550e8400-e29b-41d4-a716-446655440000";
|
||||
|
||||
function DecisionApproachesScreenWithState({ initial }) {
|
||||
const { replaceState } = useCreateFlow();
|
||||
useLayoutEffect(() => {
|
||||
replaceState(initial);
|
||||
}, [replaceState, initial]);
|
||||
return <DecisionApproachesScreen />;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
@@ -27,7 +39,7 @@ describe("Create flow decision-approaches page", () => {
|
||||
render(<DecisionApproachesScreen />);
|
||||
|
||||
const addControl = screen.getByRole("button", {
|
||||
name: /^add$/,
|
||||
name: /^Add$/i,
|
||||
});
|
||||
expect(addControl).toBeInTheDocument();
|
||||
|
||||
@@ -37,6 +49,31 @@ describe("Create flow decision-approaches page", () => {
|
||||
expect(description?.textContent).toMatch(/new decision making approaches/);
|
||||
});
|
||||
|
||||
test("with a finalized custom approach, sidebar still shows add (not Remove policy)", () => {
|
||||
render(
|
||||
<DecisionApproachesScreenWithState
|
||||
initial={{
|
||||
selectedDecisionApproachIds: [CUSTOM_APPROACH_ID],
|
||||
customMethodCardMetaById: {
|
||||
[CUSTOM_APPROACH_ID]: {
|
||||
label: "My approach",
|
||||
supportText: "Desc",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Remove policy" }),
|
||||
).not.toBeInTheDocument();
|
||||
const addControl = screen.getByRole("button", { name: /^Add$/i });
|
||||
expect(addControl).toBeInTheDocument();
|
||||
const description = addControl.parentElement;
|
||||
expect(description?.textContent).toMatch(/Select as many as you need/);
|
||||
expect(description?.textContent).toMatch(/new decision making approaches/);
|
||||
});
|
||||
|
||||
test("renders message box with title and checkboxes", () => {
|
||||
render(<DecisionApproachesScreen />);
|
||||
|
||||
@@ -103,6 +140,7 @@ describe("Create flow decision-approaches page", () => {
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Show less" }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getAllByRole("button", { name: /^add$/i })).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("expanded view reveals additional non-recommended approaches", async () => {
|
||||
@@ -143,6 +181,52 @@ describe("Create flow decision-approaches page", () => {
|
||||
expect(screen.getByText("SELECTED")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("re-opening a selected approach shows Remove as the modal primary action", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<DecisionApproachesScreen />);
|
||||
|
||||
const card = screen.getByRole("button", {
|
||||
name: /Lazy Consensus: A decision is assumed approved/,
|
||||
});
|
||||
await user.click(card);
|
||||
const dialog = await screen.findByRole("dialog");
|
||||
await user.click(
|
||||
within(dialog).getByRole("button", {
|
||||
name: "Add Approach",
|
||||
}),
|
||||
);
|
||||
|
||||
await user.click(card);
|
||||
const dialogAgain = screen.getByRole("dialog");
|
||||
expect(
|
||||
within(dialogAgain).getByRole("button", { name: "Remove" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("Remove in the modal deselects the approach", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<DecisionApproachesScreen />);
|
||||
|
||||
const card = screen.getByRole("button", {
|
||||
name: /Lazy Consensus: A decision is assumed approved/,
|
||||
});
|
||||
await user.click(card);
|
||||
await user.click(
|
||||
within(await screen.findByRole("dialog")).getByRole("button", {
|
||||
name: "Add Approach",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(card).toHaveTextContent("SELECTED");
|
||||
|
||||
await user.click(card);
|
||||
await user.click(
|
||||
within(screen.getByRole("dialog")).getByRole("button", { name: "Remove" }),
|
||||
);
|
||||
|
||||
expect(card).not.toHaveTextContent("SELECTED");
|
||||
});
|
||||
|
||||
test("message box checkboxes are interactive", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<DecisionApproachesScreen />);
|
||||
|
||||
@@ -113,4 +113,23 @@ describe("CardStack Component", () => {
|
||||
|
||||
expect(screen.getAllByText("SELECTED").length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
test("calls onCardSelect when re-clicking an already selected card", () => {
|
||||
const onCardSelect = vi.fn();
|
||||
render(
|
||||
<CardStack
|
||||
cards={SAMPLE_CARDS}
|
||||
selectedIds={["1"]}
|
||||
onCardSelect={onCardSelect}
|
||||
title="Pick an option"
|
||||
/>,
|
||||
);
|
||||
|
||||
const cardButtons = screen.getAllByRole("button", {
|
||||
name: "Option A: Description A",
|
||||
});
|
||||
fireEvent.click(cardButtons[0]);
|
||||
expect(onCardSelect).toHaveBeenCalledWith("1");
|
||||
expect(screen.queryByRole("button", { name: "Remove policy" })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,4 +124,46 @@ describe("applyFinalReviewChipEditPatch", () => {
|
||||
|
||||
expect(Object.keys(result)).toEqual(["conflictManagementDetailsById"]);
|
||||
});
|
||||
|
||||
it("merges customMethodCardFieldBlocksById when the patch carries field blocks", () => {
|
||||
const state: CreateFlowState = {
|
||||
customMethodCardFieldBlocksById: {
|
||||
other: [
|
||||
{
|
||||
kind: "text",
|
||||
id: "x",
|
||||
blockTitle: "T",
|
||||
placeholderText: "keep",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const patch: FinalReviewChipEditPatch = {
|
||||
groupKey: "communication",
|
||||
overrideKey: "550e8400-e29b-41d4-a716-446655440000",
|
||||
value: {
|
||||
corePrinciple: "a",
|
||||
logisticsAdmin: "b",
|
||||
codeOfConduct: "c",
|
||||
},
|
||||
customMethodCardFieldBlocks: [
|
||||
{
|
||||
kind: "text",
|
||||
id: "f1",
|
||||
blockTitle: "Notes",
|
||||
placeholderText: "edited",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = applyFinalReviewChipEditPatch(state, patch);
|
||||
|
||||
expect(result.communicationMethodDetailsById).toEqual({
|
||||
"550e8400-e29b-41d4-a716-446655440000": patch.value,
|
||||
});
|
||||
expect(result.customMethodCardFieldBlocksById).toEqual({
|
||||
other: state.customMethodCardFieldBlocksById?.other,
|
||||
"550e8400-e29b-41d4-a716-446655440000": patch.customMethodCardFieldBlocks,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -59,6 +59,19 @@ describe("buildFinalReviewCategoriesFromState", () => {
|
||||
expect(rows).toEqual([{ name: "Communication", chips: ["Signal"] }]);
|
||||
});
|
||||
|
||||
it("resolves user-authored method ids from customMethodCardMetaById", () => {
|
||||
const customId = "00000000-0000-4000-8000-000000000001";
|
||||
const state: CreateFlowState = {
|
||||
selectedCommunicationMethodIds: ["signal", customId],
|
||||
customMethodCardMetaById: {
|
||||
[customId]: { label: "Our Slack Ritual", supportText: "desc" },
|
||||
},
|
||||
};
|
||||
const rows = buildFinalReviewCategoriesFromState(state, NAMES);
|
||||
const comm = rows.find((r) => r.name === "Communication");
|
||||
expect(comm?.chips).toEqual(["Signal", "Our Slack Ritual"]);
|
||||
});
|
||||
|
||||
it("dedupes repeated labels from duplicate ids", () => {
|
||||
const state: CreateFlowState = {
|
||||
selectedCommunicationMethodIds: ["signal", "signal"],
|
||||
|
||||
@@ -169,6 +169,25 @@ describe("buildPublishPayload — methodSelections", () => {
|
||||
expect(entries?.[0]?.blocks?.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("uses customMethodCardMetaById label when preset id is unknown", () => {
|
||||
const customId = "00000000-0000-4000-8000-000000000002";
|
||||
const r = buildPublishPayload({
|
||||
title: "T",
|
||||
selectedCommunicationMethodIds: [customId],
|
||||
customMethodCardMetaById: {
|
||||
[customId]: { label: "Custom Comm", supportText: "More" },
|
||||
},
|
||||
});
|
||||
expect(r.ok).toBe(true);
|
||||
if (!r.ok) return;
|
||||
const ms = r.document.methodSelections as
|
||||
| { communication?: Array<{ id: string; label: string }> }
|
||||
| undefined;
|
||||
expect(ms?.communication?.length).toBe(1);
|
||||
expect(ms?.communication?.[0]?.id).toBe(customId);
|
||||
expect(ms?.communication?.[0]?.label).toBe("Custom Comm");
|
||||
});
|
||||
|
||||
it("emits preset-only sections when a method is selected without an override", () => {
|
||||
const r = buildPublishPayload({
|
||||
title: "T",
|
||||
|
||||
@@ -126,6 +126,40 @@ describe("createFlowStateSchema", () => {
|
||||
expect(r.success).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts customMethodCardFieldBlocksById", () => {
|
||||
const r = createFlowStateSchema.safeParse({
|
||||
customMethodCardFieldBlocksById: {
|
||||
"card-uuid": [
|
||||
{
|
||||
kind: "text",
|
||||
id: "f1",
|
||||
blockTitle: "Notes",
|
||||
placeholderText: "Optional",
|
||||
},
|
||||
{
|
||||
kind: "badges",
|
||||
id: "f2",
|
||||
blockTitle: "Tags",
|
||||
options: ["a", "b"],
|
||||
},
|
||||
{
|
||||
kind: "upload",
|
||||
id: "f3",
|
||||
blockTitle: "Attachment",
|
||||
fileName: "doc.pdf",
|
||||
},
|
||||
{
|
||||
kind: "proportion",
|
||||
id: "f4",
|
||||
blockTitle: "Share",
|
||||
defaultPercent: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(r.success).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts templateReviewEntryFromCreateFlow", () => {
|
||||
const r = createFlowStateSchema.safeParse({
|
||||
templateReviewEntryFromCreateFlow: true,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isCustomMethodCardId } from "../../lib/create/isCustomMethodCardId";
|
||||
|
||||
describe("isCustomMethodCardId", () => {
|
||||
it("is false when meta is missing or id has no entry", () => {
|
||||
expect(isCustomMethodCardId("signal", undefined)).toBe(false);
|
||||
expect(isCustomMethodCardId("signal", {})).toBe(false);
|
||||
});
|
||||
|
||||
it("is true when customMethodCardMetaById has the id", () => {
|
||||
const id = "550e8400-e29b-41d4-a716-446655440000";
|
||||
expect(
|
||||
isCustomMethodCardId(id, {
|
||||
[id]: { label: "L", supportText: "S" },
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { mergePresetMethodsWithCustom } from "../../lib/create/mergePresetMethodsWithCustom";
|
||||
|
||||
describe("mergePresetMethodsWithCustom", () => {
|
||||
it("appends selected custom ids that have meta after presets", () => {
|
||||
const presets = [
|
||||
{ id: "a", label: "A", supportText: "sa" },
|
||||
{ id: "b", label: "B", supportText: "sb" },
|
||||
];
|
||||
const customId = "00000000-0000-4000-8000-000000000099";
|
||||
const merged = mergePresetMethodsWithCustom(
|
||||
presets,
|
||||
["b", customId, "a"],
|
||||
{
|
||||
[customId]: { label: "Custom", supportText: "cx" },
|
||||
},
|
||||
);
|
||||
expect(merged.map((m) => m.id)).toEqual(["a", "b", customId]);
|
||||
expect(merged[2]).toEqual({
|
||||
id: customId,
|
||||
label: "Custom",
|
||||
supportText: "cx",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { moveFacetSelectionIdToFront } from "../../lib/create/methodCardSelectionOrder";
|
||||
|
||||
describe("moveFacetSelectionIdToFront", () => {
|
||||
it("places a new id at index 0", () => {
|
||||
expect(moveFacetSelectionIdToFront(["a", "b"], "c")).toEqual(["c", "a", "b"]);
|
||||
});
|
||||
|
||||
it("moves an existing id to index 0 without duplicating", () => {
|
||||
expect(moveFacetSelectionIdToFront(["a", "b", "c"], "b")).toEqual([
|
||||
"b",
|
||||
"a",
|
||||
"c",
|
||||
]);
|
||||
});
|
||||
|
||||
it("handles empty prior selection", () => {
|
||||
expect(moveFacetSelectionIdToFront([], "x")).toEqual(["x"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { CreateFlowState } from "../../app/(app)/create/types";
|
||||
import { removeMethodCardFromFacetSelection } from "../../lib/create/removeMethodCardFromFacetSelection";
|
||||
|
||||
const CUSTOM_A = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
|
||||
|
||||
describe("removeMethodCardFromFacetSelection", () => {
|
||||
it("returns {} when the card is not in the facet selection", () => {
|
||||
const state: CreateFlowState = {
|
||||
selectedCommunicationMethodIds: ["signal"],
|
||||
};
|
||||
expect(
|
||||
removeMethodCardFromFacetSelection(state, "communication", "loomio"),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it("removes a preset id, its details, and leaves other selections", () => {
|
||||
const state: CreateFlowState = {
|
||||
selectedCommunicationMethodIds: ["signal", "slack"],
|
||||
communicationMethodDetailsById: {
|
||||
signal: {} as never,
|
||||
slack: {} as never,
|
||||
},
|
||||
};
|
||||
const patch = removeMethodCardFromFacetSelection(
|
||||
state,
|
||||
"communication",
|
||||
"signal",
|
||||
);
|
||||
expect(patch.selectedCommunicationMethodIds).toEqual(["slack"]);
|
||||
expect(patch.communicationMethodDetailsById).toEqual({ slack: {} });
|
||||
});
|
||||
|
||||
it("removes a custom card id and clears meta + field blocks", () => {
|
||||
const state: CreateFlowState = {
|
||||
selectedCommunicationMethodIds: ["signal", CUSTOM_A],
|
||||
communicationMethodDetailsById: {
|
||||
signal: {} as never,
|
||||
[CUSTOM_A]: {} as never,
|
||||
},
|
||||
customMethodCardMetaById: {
|
||||
[CUSTOM_A]: { label: "P", supportText: "D" },
|
||||
},
|
||||
customMethodCardFieldBlocksById: {
|
||||
[CUSTOM_A]: [],
|
||||
},
|
||||
};
|
||||
const patch = removeMethodCardFromFacetSelection(
|
||||
state,
|
||||
"communication",
|
||||
CUSTOM_A,
|
||||
);
|
||||
expect(patch.selectedCommunicationMethodIds).toEqual(["signal"]);
|
||||
expect(patch.communicationMethodDetailsById).toEqual({ signal: {} });
|
||||
expect(patch.customMethodCardMetaById).toBeUndefined();
|
||||
expect(patch.customMethodCardFieldBlocksById).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -15,6 +15,19 @@ describe("stripCustomRuleSelectionFields", () => {
|
||||
selectedConflictManagementIds: ["z"],
|
||||
methodSectionsPinCommitted: { communication: true },
|
||||
coreValueDetailsByChipId: { "1": { meaning: "", signals: "" } },
|
||||
customMethodCardMetaById: {
|
||||
x: { label: "Custom", supportText: "S" },
|
||||
},
|
||||
customMethodCardFieldBlocksById: {
|
||||
x: [
|
||||
{
|
||||
kind: "text",
|
||||
id: "f1",
|
||||
blockTitle: "T",
|
||||
placeholderText: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
sections: [{ categoryName: "Communication", entries: [] }],
|
||||
};
|
||||
const out = stripCustomRuleSelectionFields(prev);
|
||||
@@ -28,5 +41,7 @@ describe("stripCustomRuleSelectionFields", () => {
|
||||
expect(out.selectedConflictManagementIds).toBeUndefined();
|
||||
expect(out.methodSectionsPinCommitted).toBeUndefined();
|
||||
expect(out.coreValueDetailsByChipId).toBeUndefined();
|
||||
expect(out.customMethodCardMetaById).toBeUndefined();
|
||||
expect(out.customMethodCardFieldBlocksById).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user