Template navigation and review/complete cleanup

This commit is contained in:
adilallo
2026-04-20 19:00:30 -06:00
parent 707d08642c
commit 2438c6f707
15 changed files with 836 additions and 355 deletions
@@ -0,0 +1,127 @@
import { describe, expect, it } from "vitest";
import { applyFinalReviewChipEditPatch } from "../../lib/create/applyFinalReviewChipEditPatch";
import type { CreateFlowState } from "../../app/(app)/create/types";
import type { FinalReviewChipEditPatch } from "../../app/(app)/create/components/FinalReviewChipEditModal";
describe("applyFinalReviewChipEditPatch", () => {
it("creates the coreValueDetailsByChipId record when missing", () => {
const patch: FinalReviewChipEditPatch = {
groupKey: "coreValues",
overrideKey: "accessibility",
value: { meaning: "Be welcoming.", signals: "Captions on videos." },
};
const result = applyFinalReviewChipEditPatch({}, patch);
expect(result).toEqual({
coreValueDetailsByChipId: {
accessibility: {
meaning: "Be welcoming.",
signals: "Captions on videos.",
},
},
});
});
it("merges into the existing record without dropping siblings", () => {
const state: CreateFlowState = {
communicationMethodDetailsById: {
signal: {
corePrinciple: "Stay async-first.",
logisticsAdmin: "Daily check-ins.",
codeOfConduct: "Be kind.",
},
},
};
const patch: FinalReviewChipEditPatch = {
groupKey: "communication",
overrideKey: "in-person-meetings",
value: {
corePrinciple: "Meet weekly.",
logisticsAdmin: "Hybrid format.",
codeOfConduct: "Listen actively.",
},
};
const result = applyFinalReviewChipEditPatch(state, patch);
expect(result.communicationMethodDetailsById).toEqual({
signal: {
corePrinciple: "Stay async-first.",
logisticsAdmin: "Daily check-ins.",
codeOfConduct: "Be kind.",
},
"in-person-meetings": {
corePrinciple: "Meet weekly.",
logisticsAdmin: "Hybrid format.",
codeOfConduct: "Listen actively.",
},
});
});
it("overwrites the same key when the user re-saves it", () => {
const state: CreateFlowState = {
membershipMethodDetailsById: {
"open-access": {
eligibility: "Anyone",
joiningProcess: "Sign up",
expectations: "Old expectations",
},
},
};
const patch: FinalReviewChipEditPatch = {
groupKey: "membership",
overrideKey: "open-access",
value: {
eligibility: "Anyone over 18",
joiningProcess: "Sign up + intro call",
expectations: "New expectations",
},
};
const result = applyFinalReviewChipEditPatch(state, patch);
expect(result.membershipMethodDetailsById?.["open-access"]).toEqual({
eligibility: "Anyone over 18",
joiningProcess: "Sign up + intro call",
expectations: "New expectations",
});
});
it("routes decisionApproaches to its dedicated state field", () => {
const patch: FinalReviewChipEditPatch = {
groupKey: "decisionApproaches",
overrideKey: "lazy-consensus",
value: {
corePrinciple: "Silence implies assent.",
applicableScope: ["budget"],
selectedApplicableScope: ["budget"],
stepByStepInstructions: "Propose. Wait 72h.",
consensusLevel: 0.66,
objectionsDeadlocks: "Escalate to vote.",
},
};
const result = applyFinalReviewChipEditPatch({}, patch);
expect(Object.keys(result)).toEqual(["decisionApproachDetailsById"]);
});
it("routes conflictManagement to its dedicated state field", () => {
const patch: FinalReviewChipEditPatch = {
groupKey: "conflictManagement",
overrideKey: "peer-mediation",
value: {
corePrinciple: "Restore trust.",
applicableScope: ["interpersonal"],
selectedApplicableScope: ["interpersonal"],
processProtocol: "Pair the parties with a neutral facilitator.",
restorationFallbacks: "Council escalation.",
},
};
const result = applyFinalReviewChipEditPatch({}, patch);
expect(Object.keys(result)).toEqual(["conflictManagementDetailsById"]);
});
});
+14
View File
@@ -112,6 +112,20 @@ describe("createFlowStateSchema", () => {
expect(r.success).toBe(true);
});
it("accepts templateReviewBackSlug", () => {
const r = createFlowStateSchema.safeParse({
templateReviewBackSlug: "mutual-aid-mondays",
});
expect(r.success).toBe(true);
});
it("accepts templateReviewEntryFromCreateFlow", () => {
const r = createFlowStateSchema.safeParse({
templateReviewEntryFromCreateFlow: true,
});
expect(r.success).toBe(true);
});
it("rejects core value detail strings that are too long", () => {
const r = createFlowStateSchema.safeParse({
coreValueDetailsByChipId: {
+31
View File
@@ -1,10 +1,12 @@
import { describe, it, expect } from "vitest";
import {
FLOW_STEP_ORDER,
buildTemplateReviewHref,
getNextStep,
getPreviousStep,
isValidStep,
getStepIndex,
resolveCreateFlowBackTarget,
} from "../../app/(app)/create/utils/flowSteps";
describe("flowSteps", () => {
@@ -72,4 +74,33 @@ describe("flowSteps", () => {
expect(getNextStep("review", opts)).toBe("core-values");
expect(getPreviousStep("communication-methods", opts)).toBe("core-values");
});
it("resolveCreateFlowBackTarget returns template review when use-without slug is set on confirm-stakeholders", () => {
expect(
resolveCreateFlowBackTarget(
"confirm-stakeholders",
undefined,
"mutual-aid-mondays",
),
).toEqual({ kind: "templateReview", slug: "mutual-aid-mondays" });
});
it("resolveCreateFlowBackTarget falls back to linear previous when slug is absent", () => {
expect(
resolveCreateFlowBackTarget("confirm-stakeholders", undefined, undefined),
).toEqual({ kind: "step", step: "conflict-management" });
});
it("resolveCreateFlowBackTarget ignores whitespace-only slug", () => {
expect(
resolveCreateFlowBackTarget("confirm-stakeholders", undefined, " "),
).toEqual({ kind: "step", step: "conflict-management" });
});
it("buildTemplateReviewHref encodes slug and optional fromFlow query", () => {
expect(buildTemplateReviewHref("a/b")).toBe("/create/review-template/a%2Fb");
expect(buildTemplateReviewHref("mutual-aid", { fromCreateWizard: true })).toBe(
"/create/review-template/mutual-aid?fromFlow=1",
);
});
});