Template flow cleaned up
This commit is contained in:
@@ -100,3 +100,46 @@ describe("FinalReviewScreen", () => {
|
||||
expect(screen.getByText("Open Admission")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Seeds a Customize-from-template style state (method ids + core-value
|
||||
* snapshot) and asserts the final-review RuleCard renders the resolved
|
||||
* labels — the fix for "preselected chips don't register on final review".
|
||||
*/
|
||||
function FinalReviewWithCustomizeSelections() {
|
||||
const { replaceState } = useCreateFlow();
|
||||
useLayoutEffect(() => {
|
||||
replaceState({
|
||||
title: "Oak Park Commons",
|
||||
selectedCoreValueIds: ["1"],
|
||||
coreValuesChipsSnapshot: [
|
||||
{ id: "1", label: "Accessibility", state: "selected" },
|
||||
{ id: "2", label: "Accountability", state: "unselected" },
|
||||
],
|
||||
selectedCommunicationMethodIds: ["signal"],
|
||||
selectedMembershipMethodIds: ["open-access"],
|
||||
selectedDecisionApproachIds: ["lazy-consensus"],
|
||||
selectedConflictManagementIds: ["peer-mediation"],
|
||||
});
|
||||
}, [replaceState]);
|
||||
return <FinalReviewScreen />;
|
||||
}
|
||||
|
||||
describe("FinalReviewScreen — prefilled selections", () => {
|
||||
it("renders chips resolved from selection ids, not demo fallbacks", async () => {
|
||||
render(<FinalReviewWithCustomizeSelections />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Accessibility")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByText("Signal")).toBeInTheDocument();
|
||||
expect(screen.getByText("Open Access")).toBeInTheDocument();
|
||||
expect(screen.getByText("Lazy Consensus")).toBeInTheDocument();
|
||||
expect(screen.getByText("Peer Mediation")).toBeInTheDocument();
|
||||
|
||||
// Demo chips from `finalReview.json` must not leak through once the
|
||||
// user has real selections: "Open Admission" is shipped as fallback,
|
||||
// while the customize flow resolves to "Open Access".
|
||||
expect(screen.queryByText("Open Admission")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Consciousness")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||
import { beforeEach, describe, it, expect } from "vitest";
|
||||
import React, { useEffect } from "react";
|
||||
import {
|
||||
renderWithProviders as render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from "../utils/test-utils";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import { CommunityReviewScreen } from "../../app/(app)/create/screens/review/CommunityReviewScreen";
|
||||
import { useCreateFlow } from "../../app/(app)/create/context/CreateFlowContext";
|
||||
import { testRouter } from "../mocks/navigation";
|
||||
|
||||
describe("CommunityReviewScreen", () => {
|
||||
beforeEach(() => {
|
||||
testRouter.replace.mockReset();
|
||||
testRouter.push.mockReset();
|
||||
});
|
||||
|
||||
it("renders without crashing", () => {
|
||||
render(<CommunityReviewScreen />);
|
||||
expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
|
||||
@@ -27,18 +39,18 @@ describe("CommunityReviewScreen", () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard with title", () => {
|
||||
it("renders RuleCard with title fallback when no community name is set", () => {
|
||||
render(<CommunityReviewScreen />);
|
||||
expect(screen.getByText("Mutual Aid Mondays")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard with description", () => {
|
||||
it("omits the RuleCard description when the user has not entered community context", () => {
|
||||
render(<CommunityReviewScreen />);
|
||||
expect(
|
||||
screen.getByText(
|
||||
/Mutual Aid Monday is a grassroots community in Denver, founded in November 2020 by Kelsang Virya, dedicated to supporting neighbors experiencing homelessness./i,
|
||||
screen.queryByText(
|
||||
/Mutual Aid Monday is a grassroots community in Denver/i,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard as a button (card is interactive)", () => {
|
||||
@@ -50,3 +62,60 @@ describe("CommunityReviewScreen", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Seeds `pendingTemplateAction` into CreateFlowContext before the screen
|
||||
* under test mounts, so we can assert its mount-time redirect behavior.
|
||||
* Mirrors the flow `handleCustomizeTemplate` / `handleUseTemplateWithoutChanges`
|
||||
* create when the user picks a template before completing community stage.
|
||||
*/
|
||||
function ReviewWithPendingAction({
|
||||
mode,
|
||||
}: {
|
||||
mode: "customize" | "useWithoutChanges";
|
||||
}) {
|
||||
const { state, updateState } = useCreateFlow();
|
||||
const seededRef = React.useRef(false);
|
||||
useEffect(() => {
|
||||
if (seededRef.current) return;
|
||||
seededRef.current = true;
|
||||
updateState({
|
||||
title: "Neighborhood",
|
||||
pendingTemplateAction: { slug: "mutual-aid-mondays", mode },
|
||||
});
|
||||
}, [mode, updateState]);
|
||||
// Block the real screen from mounting until the seed landed — otherwise
|
||||
// its own `useEffect` reads an empty state on the first pass and bails.
|
||||
if (!state.pendingTemplateAction) return null;
|
||||
return <CommunityReviewScreen />;
|
||||
}
|
||||
|
||||
describe("CommunityReviewScreen — pendingTemplateAction redirect", () => {
|
||||
beforeEach(() => {
|
||||
testRouter.replace.mockReset();
|
||||
testRouter.push.mockReset();
|
||||
});
|
||||
|
||||
it("redirects to /create/core-values when mode === 'customize'", async () => {
|
||||
render(<ReviewWithPendingAction mode="customize" />);
|
||||
await waitFor(() => {
|
||||
expect(testRouter.replace).toHaveBeenCalledWith("/create/core-values");
|
||||
});
|
||||
expect(testRouter.push).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("redirects to /create/confirm-stakeholders when mode === 'useWithoutChanges'", async () => {
|
||||
render(<ReviewWithPendingAction mode="useWithoutChanges" />);
|
||||
await waitFor(() => {
|
||||
expect(testRouter.replace).toHaveBeenCalledWith(
|
||||
"/create/confirm-stakeholders",
|
||||
);
|
||||
});
|
||||
expect(testRouter.push).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not redirect when no pendingTemplateAction is set", () => {
|
||||
render(<CommunityReviewScreen />);
|
||||
expect(testRouter.replace).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import React from "react";
|
||||
import { vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import TopNav from "../../app/components/navigation/TopNav";
|
||||
import { renderWithProviders } from "../utils/test-utils";
|
||||
import { CREATE_FLOW_ANONYMOUS_KEY } from "../../app/(app)/create/utils/anonymousDraftStorage";
|
||||
import { CORE_VALUE_DETAILS_STORAGE_KEY } from "../../app/(app)/create/utils/coreValueDetailsLocalStorage";
|
||||
import { componentTestSuite } from "../utils/componentTestSuite";
|
||||
|
||||
// Mock next/navigation (TopNav uses useRouter for Create Rule button and usePathname for nav state)
|
||||
const { pushMock } = vi.hoisted(() => ({ pushMock: vi.fn() }));
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({
|
||||
push: vi.fn(),
|
||||
push: pushMock,
|
||||
replace: vi.fn(),
|
||||
prefetch: vi.fn(),
|
||||
back: vi.fn(),
|
||||
@@ -50,3 +57,45 @@ componentTestSuite<TopNavProps>({
|
||||
errorState: false,
|
||||
},
|
||||
});
|
||||
|
||||
describe('TopNav "Create rule" button', () => {
|
||||
beforeEach(() => {
|
||||
pushMock.mockReset();
|
||||
window.localStorage.clear();
|
||||
});
|
||||
afterEach(() => {
|
||||
window.localStorage.clear();
|
||||
});
|
||||
|
||||
/**
|
||||
* Guards against localStorage stickiness on the marketing homepage: hitting
|
||||
* the top-nav "Create rule" from anywhere outside `/create` must wipe the
|
||||
* in-flight anonymous draft so the wizard always starts fresh. See
|
||||
* handleCreateRuleClick in TopNav.container.tsx for the contract.
|
||||
*/
|
||||
it("clears anonymous draft + core-value-details localStorage before routing to /create", async () => {
|
||||
window.localStorage.setItem(
|
||||
CREATE_FLOW_ANONYMOUS_KEY,
|
||||
JSON.stringify({ title: "Stale community" }),
|
||||
);
|
||||
window.localStorage.setItem(
|
||||
CORE_VALUE_DETAILS_STORAGE_KEY,
|
||||
JSON.stringify({ "1": { meaning: "m", signals: "s" } }),
|
||||
);
|
||||
|
||||
renderWithProviders(<TopNav folderTop={false} />);
|
||||
|
||||
// TopNav renders the Create Rule button at three breakpoints (xs/sm/md);
|
||||
// any of them clicking the same handler is the point.
|
||||
const [btn] = screen.getAllByRole("button", {
|
||||
name: /create a new rule/i,
|
||||
});
|
||||
await userEvent.click(btn);
|
||||
|
||||
expect(window.localStorage.getItem(CREATE_FLOW_ANONYMOUS_KEY)).toBeNull();
|
||||
expect(
|
||||
window.localStorage.getItem(CORE_VALUE_DETAILS_STORAGE_KEY),
|
||||
).toBeNull();
|
||||
expect(pushMock).toHaveBeenCalledWith("/create");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user