diff --git a/app/(app)/create/components/FinalReviewChipEditModal.tsx b/app/(app)/create/components/FinalReviewChipEditModal.tsx
index 3d4421c..9443807 100644
--- a/app/(app)/create/components/FinalReviewChipEditModal.tsx
+++ b/app/(app)/create/components/FinalReviewChipEditModal.tsx
@@ -66,6 +66,7 @@ import { uploadCreateFlowFile } from "../../../../lib/create/uploadToServer";
import CustomMethodCardWizard from "./CustomMethodCardWizard";
import {
duplicateCoreValueChipInDraft,
+ MAX_SELECTED_CORE_VALUES,
removeCoreValueChipFromDraft,
} from "../../../../lib/create/coreValueChipFacet";
import {
@@ -650,7 +651,7 @@ export function FinalReviewChipEditModal({
if ((state.editingPublishedRuleId?.trim() ?? "") !== "") {
return;
}
- if ((state.selectedCoreValueIds ?? []).length >= 5) {
+ if ((state.selectedCoreValueIds ?? []).length >= MAX_SELECTED_CORE_VALUES) {
return;
}
if (
@@ -725,7 +726,7 @@ export function FinalReviewChipEditModal({
onCustomize: handleCustomize,
onDuplicate:
(state.editingPublishedRuleId?.trim() ?? "") !== "" ||
- (state.selectedCoreValueIds ?? []).length >= 5
+ (state.selectedCoreValueIds ?? []).length >= MAX_SELECTED_CORE_VALUES
? undefined
: handleDuplicateCoreValue,
});
@@ -775,8 +776,12 @@ export function FinalReviewChipEditModal({
return;
}
onInteract?.();
- const existingBlocks =
+ const persistedBlocks =
state.customMethodCardFieldBlocksById?.[target.overrideKey];
+ const existingBlocks =
+ draftFieldBlocks && draftFieldBlocks.length > 0
+ ? draftFieldBlocks
+ : persistedBlocks;
onSave({
groupKey: "coreValues",
overrideKey: target.overrideKey,
diff --git a/tests/components/FinalReviewPage.test.tsx b/tests/components/FinalReviewPage.test.tsx
index aea63f8..25b9237 100644
--- a/tests/components/FinalReviewPage.test.tsx
+++ b/tests/components/FinalReviewPage.test.tsx
@@ -10,6 +10,7 @@ import {
import "@testing-library/jest-dom/vitest";
import { FinalReviewScreen } from "../../app/(app)/create/screens/review/FinalReviewScreen";
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";
+import { clearCoreValueDetailsLocalStorage } from "../../app/(app)/create/utils/coreValueDetailsLocalStorage";
import type { CreateFlowState } from "../../app/(app)/create/types";
function catalogChipName(label: string) {
@@ -363,6 +364,173 @@ describe("FinalReviewScreen — chip detail modal", () => {
).not.toBeInTheDocument();
});
+ it("offers Duplicate with five values picked and hides it at six", async () => {
+ function CoreValuesHarness({ count }: { count: number }) {
+ const { replaceState } = useCreateFlow();
+ useLayoutEffect(() => {
+ const ids = Array.from({ length: count }, (_, i) => String(i + 1));
+ replaceState({
+ selectedCoreValueIds: ids,
+ coreValuesChipsSnapshot: ids.map((id, i) => ({
+ id,
+ label: `Value ${i + 1}`,
+ state: "selected" as const,
+ })),
+ });
+ }, [count, replaceState]);
+ return ;
+ }
+
+ const { unmount } = render();
+ fireEvent.click(
+ await screen.findByRole("button", { name: catalogChipName("Value 1") }),
+ );
+ fireEvent.click(
+ within(await screen.findByRole("dialog")).getByRole("button", {
+ name: /more options/i,
+ }),
+ );
+ expect(
+ await screen.findByRole("menuitem", { name: /^duplicate$/i }),
+ ).toBeInTheDocument();
+ unmount();
+
+ render();
+ fireEvent.click(
+ await screen.findByRole("button", { name: catalogChipName("Value 1") }),
+ );
+ fireEvent.click(
+ within(await screen.findByRole("dialog")).getByRole("button", {
+ name: /more options/i,
+ }),
+ );
+ await waitFor(() => {
+ expect(
+ screen.getByRole("menuitem", { name: /^customize$/i }),
+ ).toBeInTheDocument();
+ });
+ expect(
+ screen.queryByRole("menuitem", { name: /^duplicate$/i }),
+ ).not.toBeInTheDocument();
+ });
+
+ it("duplicates a fifth value into the remaining slot", async () => {
+ let latest: CreateFlowState = {};
+ render(
+ {
+ latest = s;
+ }}
+ initial={{
+ selectedCoreValueIds: ["1", "2", "3", "4", "5"],
+ coreValuesChipsSnapshot: ["1", "2", "3", "4", "5"].map((id, i) => ({
+ id,
+ label: `Value ${i + 1}`,
+ state: "selected" as const,
+ })),
+ }}
+ />,
+ );
+ fireEvent.click(
+ await screen.findByRole("button", { name: catalogChipName("Value 1") }),
+ );
+ fireEvent.click(
+ within(await screen.findByRole("dialog")).getByRole("button", {
+ name: /more options/i,
+ }),
+ );
+ fireEvent.click(
+ await screen.findByRole("menuitem", { name: /^duplicate$/i }),
+ );
+ await waitFor(() => {
+ expect(latest.selectedCoreValueIds).toHaveLength(6);
+ });
+ expect(latest.coreValuesChipsSnapshot?.map((row) => row.label)).toContain(
+ "Value 1 (copy)",
+ );
+ });
+
+ it("saves in-modal edits to extra value field blocks", async () => {
+ let latest: CreateFlowState = {};
+ let view: ReturnType | undefined;
+ try {
+ view = render(
+ {
+ latest = s;
+ }}
+ initial={{
+ selectedCoreValueIds: ["1"],
+ coreValuesChipsSnapshot: [
+ { id: "1", label: "Accessibility", state: "selected" },
+ ],
+ coreValueDetailsByChipId: {
+ "1": {
+ meaning: "Everyone can participate.",
+ signals: "Captions and ramps.",
+ },
+ },
+ customMethodCardFieldBlocksById: {
+ "1": [
+ {
+ kind: "text",
+ id: "extra-note",
+ blockTitle: "House note",
+ placeholderText: "Be kind.",
+ },
+ {
+ kind: "text",
+ id: "facet-meaning",
+ blockTitle: "What this value means",
+ placeholderText: "Everyone can participate.",
+ },
+ {
+ kind: "text",
+ id: "facet-signals",
+ blockTitle: "Signals",
+ placeholderText: "Captions and ramps.",
+ },
+ ],
+ },
+ }}
+ />,
+ );
+
+ fireEvent.click(
+ await screen.findByRole("button", {
+ name: catalogChipName("Accessibility"),
+ }),
+ );
+ const dialog = await screen.findByRole("dialog");
+ fireEvent.change(
+ within(dialog).getByRole("textbox", { name: "House note" }),
+ { target: { value: "Leave the ramp clear." } },
+ );
+ const save = within(dialog).getByRole("button", { name: "Save" });
+ await waitFor(() => {
+ expect(save).toBeEnabled();
+ });
+ fireEvent.click(save);
+
+ await waitFor(() => {
+ expect(
+ latest.customMethodCardFieldBlocksById?.["1"]?.map(
+ (block) => block.id,
+ ),
+ ).toEqual(["extra-note", "facet-meaning", "facet-signals"]);
+ });
+ const extra = latest.customMethodCardFieldBlocksById?.["1"]?.find(
+ (block) => block.id === "extra-note",
+ );
+ expect(extra?.kind === "text" ? extra.placeholderText : "").toBe(
+ "Leave the ramp clear.",
+ );
+ } finally {
+ view?.unmount();
+ clearCoreValueDetailsLocalStorage();
+ }
+ });
+
it("values Customize starts on the policy name step", async () => {
function CoreValuesHarness() {
const { replaceState } = useCreateFlow();