Add custom intervention modals

This commit is contained in:
adilallo
2026-05-01 22:05:05 -06:00
parent 58d0e33500
commit dee2dd800e
67 changed files with 3480 additions and 197 deletions
+19
View File
@@ -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"],
+19
View File
@@ -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",
+34
View File
@@ -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,
+18
View File
@@ -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();
});
});