Tighten final-review screen
This commit is contained in:
@@ -39,4 +39,70 @@ describe("CoreValuesSelectScreen", () => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// The "Add value" → custom-chip → modal flow uses a `customPending`
|
||||
// session: dismissing the modal must drop the brand-new chip entirely
|
||||
// (not just unselect it), because the user never confirmed it via
|
||||
// the modal's Add Value button. Clicking Add Value keeps the chip
|
||||
// as a selected entry. These two tests pin both halves of the
|
||||
// contract so the screens stay in sync with the create-flow draft.
|
||||
describe("custom chip — confirmed vs dismissed", () => {
|
||||
// Use a label guaranteed to NOT collide with any preset value
|
||||
// (we'd otherwise get two matching chips and false positives).
|
||||
const CUSTOM_LABEL = "ZZTopBespokeValue";
|
||||
|
||||
async function addCustomChipNamed(label: string) {
|
||||
fireEvent.click(screen.getByRole("button", { name: "Add value" }));
|
||||
const input = await screen.findByPlaceholderText("Type to add");
|
||||
fireEvent.change(input, { target: { value: label } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
|
||||
return screen.findByRole("dialog");
|
||||
}
|
||||
|
||||
/**
|
||||
* The label can also appear in the modal header while the modal
|
||||
* is open, and as the chip's "Remove" button aria-label. Scope to
|
||||
* chip-row buttons by excluding both.
|
||||
*/
|
||||
function countCustomChips(label: string) {
|
||||
return screen
|
||||
.queryAllByRole("button", { name: label })
|
||||
.filter(
|
||||
(el) =>
|
||||
!el.closest('[role="dialog"]') &&
|
||||
(el.getAttribute("aria-label") ?? "") !== `Remove ${label}`,
|
||||
).length;
|
||||
}
|
||||
|
||||
it("removes the custom chip when its modal is dismissed without Add Value", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
await addCustomChipNamed(CUSTOM_LABEL);
|
||||
expect(countCustomChips(CUSTOM_LABEL)).toBe(1);
|
||||
|
||||
fireEvent.keyDown(document, { key: "Escape" });
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
// Chip must be gone — not just unselected. If it were merely
|
||||
// unselected the chip button would still render. Wrap in waitFor
|
||||
// because the chip removal flushes through `updateState` →
|
||||
// `useEffect` → `setCoreValueOptions` and isn't synchronous with
|
||||
// the dialog close.
|
||||
await waitFor(() => {
|
||||
expect(countCustomChips(CUSTOM_LABEL)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the custom chip selected when Add Value is clicked", async () => {
|
||||
renderWithProviders(<CoreValuesSelectScreen />);
|
||||
const dialog = await addCustomChipNamed(CUSTOM_LABEL);
|
||||
fireEvent.click(
|
||||
within(dialog).getByRole("button", { name: "Add Value" }),
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
expect(countCustomChips(CUSTOM_LABEL)).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,20 +35,21 @@ function FinalReviewWithStateProbe({
|
||||
}
|
||||
|
||||
const FALLBACK_CARD_TITLE = "Your community";
|
||||
const FALLBACK_CARD_DESCRIPTION_SNIPPET =
|
||||
"Add a short description of your community";
|
||||
|
||||
function FinalReviewWithFlowState({
|
||||
title,
|
||||
summary,
|
||||
communityContext,
|
||||
}: {
|
||||
title: string;
|
||||
summary?: string;
|
||||
communityContext?: string;
|
||||
}) {
|
||||
const { replaceState } = useCreateFlow();
|
||||
useLayoutEffect(() => {
|
||||
replaceState({ title, ...(summary !== undefined ? { summary } : {}) });
|
||||
}, [replaceState, title, summary]);
|
||||
replaceState({
|
||||
title,
|
||||
...(communityContext !== undefined ? { communityContext } : {}),
|
||||
});
|
||||
}, [replaceState, title, communityContext]);
|
||||
return <FinalReviewScreen />;
|
||||
}
|
||||
|
||||
@@ -81,16 +82,30 @@ describe("FinalReviewScreen", () => {
|
||||
expect(screen.getByText(FALLBACK_CARD_TITLE)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard with fallback description when context has no summary", () => {
|
||||
render(<FinalReviewScreen />);
|
||||
it("does not use summary as the card body when community context is empty", () => {
|
||||
function TitleAndSummaryHarness() {
|
||||
const { replaceState } = useCreateFlow();
|
||||
useLayoutEffect(() => {
|
||||
replaceState({
|
||||
title: "Oak Park Commons",
|
||||
summary:
|
||||
"Leftover template or one-line summary — must not appear as the RuleCard description.",
|
||||
});
|
||||
}, [replaceState]);
|
||||
return <FinalReviewScreen />;
|
||||
}
|
||||
render(<TitleAndSummaryHarness />);
|
||||
expect(
|
||||
screen.getByText(new RegExp(FALLBACK_CARD_DESCRIPTION_SNIPPET, "i")),
|
||||
).toBeInTheDocument();
|
||||
screen.queryByText(/Leftover template or one-line summary/i),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard title from create flow state", async () => {
|
||||
render(
|
||||
<FinalReviewWithFlowState title="Oak Park Commons" summary="Local mutual aid." />,
|
||||
<FinalReviewWithFlowState
|
||||
title="Oak Park Commons"
|
||||
communityContext="Local mutual aid."
|
||||
/>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Oak Park Commons")).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user