Cleanup, add tests and storybook
This commit is contained in:
@@ -40,7 +40,8 @@ const WebVitalsDashboardContainer = memo(() => {
|
|||||||
|
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
import("web-vitals").then((webVitals) => {
|
import("web-vitals").then((webVitals) => {
|
||||||
const { getCLS, getFID, getFCP, getLCP, getTTFB } = webVitals as {
|
// web-vitals v4 typings don't expose legacy get* names the same way; runtime bundle still provides them for this dashboard.
|
||||||
|
const { getCLS, getFID, getFCP, getLCP, getTTFB } = webVitals as unknown as {
|
||||||
getCLS: (
|
getCLS: (
|
||||||
_fn: (_m: { value: number; rating: string }) => void,
|
_fn: (_m: { value: number; rating: string }) => void,
|
||||||
) => void;
|
) => void;
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import CardsPage from "../../app/create/cards/page";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Create Flow/Cards",
|
||||||
|
component: CardsPage,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Communication / card selection step with modals and responsive layout.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Desktop = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "desktop" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mobile = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "mobile1" },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import CompletedPage from "../../app/create/completed/page";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Create Flow/Completed",
|
||||||
|
component: CompletedPage,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Completed flow: teal background, inverse HeaderLockup, CommunityRule document, optional bottom toast.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="min-h-screen bg-[var(--color-teal-teal50,#c9fef9)] flex flex-col items-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Desktop = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "desktop" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mobile = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "mobile1" },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import ConfirmStakeholdersPage from "../../app/create/confirm-stakeholders/page";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Create Flow/Confirm stakeholders",
|
||||||
|
component: ConfirmStakeholdersPage,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Stacked lockup + MultiSelect; draft congratulations banner; before final review.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Desktop = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "desktop" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mobile = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "mobile1" },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import FinalReviewPage from "../../app/create/final-review/page";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Create Flow/Final review",
|
||||||
|
component: FinalReviewPage,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Pre-finalize review: HeaderLockup + expanded RuleCard sections.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Desktop = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "desktop" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mobile = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "mobile1" },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import InformationalPage from "../../app/create/informational/page";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Create Flow/Informational",
|
||||||
|
component: InformationalPage,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Create flow entry: HeaderLockup + NumberedList. Responsive L/M and M/S at 640px.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Desktop = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "desktop" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mobile = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "mobile1" },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import RightRailPage from "../../app/create/right-rail/page";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Create Flow/Right rail",
|
||||||
|
component: RightRailPage,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Decision-making sidebar layout with CardStack and supporting content.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Desktop = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "desktop" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mobile = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "mobile1" },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import SelectPage from "../../app/create/select/page";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Create Flow/Select",
|
||||||
|
component: SelectPage,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Multi-select template: two columns at 640px+, stacked below. MultiSelect with add → custom chip.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Desktop = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "desktop" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mobile = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "mobile1" },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import TextPage from "../../app/create/text/page";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Create Flow/Text",
|
||||||
|
component: TextPage,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Community name step: HeaderLockup + TextInput. Responsive sizing at 640px.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Desktop = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "desktop" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mobile = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "mobile1" },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import UploadPage from "../../app/create/upload/page";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Pages/Create Flow/Upload",
|
||||||
|
component: UploadPage,
|
||||||
|
parameters: {
|
||||||
|
layout: "fullscreen",
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Upload step: HeaderLockup + Upload control. Centered lockup at 640px+.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Desktop = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "desktop" },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Mobile = {
|
||||||
|
parameters: {
|
||||||
|
viewport: { defaultViewport: "mobile1" },
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import DecisionMakingSidebar from "../../app/components/utility/DecisionMakingSidebar";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Components/Utility/DecisionMakingSidebar",
|
||||||
|
component: DecisionMakingSidebar,
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
backgrounds: { default: "dark" },
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"HeaderLockup + InfoMessageBox for decision-making step sidebars.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="bg-black p-8 max-w-lg w-full">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const messageItems = [
|
||||||
|
{ id: "c1", label: "Consensus" },
|
||||||
|
{ id: "c2", label: "Majority vote" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Default = {
|
||||||
|
args: {
|
||||||
|
title: "How does your group make decisions?",
|
||||||
|
description:
|
||||||
|
"Choose the approaches that best match how your community operates.",
|
||||||
|
messageBoxTitle: "Common approaches",
|
||||||
|
messageBoxItems: messageItems,
|
||||||
|
size: "L",
|
||||||
|
justification: "left",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Medium = {
|
||||||
|
args: {
|
||||||
|
title: "Decision-making",
|
||||||
|
description: "Short description.",
|
||||||
|
messageBoxTitle: "Pick any",
|
||||||
|
messageBoxItems: [{ id: "x", label: "Single method" }],
|
||||||
|
size: "M",
|
||||||
|
justification: "left",
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import InfoMessageBox from "../../app/components/utility/InfoMessageBox";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Components/Utility/InfoMessageBox",
|
||||||
|
component: InfoMessageBox,
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
backgrounds: { default: "dark" },
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
"Message region with optional exclamation icon and CheckboxGroup items.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="bg-black p-8 max-w-md w-full">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
tags: ["autodocs"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const sampleItems = [
|
||||||
|
{ id: "1", label: "First option" },
|
||||||
|
{ id: "2", label: "Second option" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Default = {
|
||||||
|
args: {
|
||||||
|
title: "Before you continue",
|
||||||
|
items: sampleItems,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SingleItem = {
|
||||||
|
args: {
|
||||||
|
title: "Select one",
|
||||||
|
items: [{ id: "a", label: "Only choice" }],
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
import "@testing-library/jest-dom/vitest";
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import ConfirmStakeholdersPage from "../../app/create/confirm-stakeholders/page";
|
||||||
|
|
||||||
|
describe("ConfirmStakeholdersPage", () => {
|
||||||
|
it("renders title and description", () => {
|
||||||
|
render(<ConfirmStakeholdersPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", {
|
||||||
|
name: /Do other stakeholders need to be involved/i,
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
/Adding people at this step will invite them to see your proposed CommunityRule/i,
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders Add stakeholder control", () => {
|
||||||
|
render(<ConfirmStakeholdersPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("button", { name: "Add stakeholder" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows draft toast and can dismiss it", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
render(<ConfirmStakeholdersPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByText(/Congratulations! You've drafted your CommunityRule!/i),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
await user.click(screen.getByRole("button", { name: "Close alert" }));
|
||||||
|
expect(
|
||||||
|
screen.queryByText(/Congratulations! You've drafted your CommunityRule!/i),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -37,6 +37,10 @@ const mockPost: BlogPost = {
|
|||||||
author: "Test Author",
|
author: "Test Author",
|
||||||
date: "2025-04-15",
|
date: "2025-04-15",
|
||||||
},
|
},
|
||||||
|
content: "",
|
||||||
|
htmlContent: "",
|
||||||
|
filePath: "test-article.md",
|
||||||
|
lastModified: new Date("2025-04-15"),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("ContentBanner", () => {
|
describe("ContentBanner", () => {
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import DecisionMakingSidebar from "../../app/components/utility/DecisionMakingSidebar";
|
||||||
|
|
||||||
|
describe("DecisionMakingSidebar", () => {
|
||||||
|
const messageBoxItems = [{ id: "1", label: "Consensus" }];
|
||||||
|
|
||||||
|
it("renders title and description", () => {
|
||||||
|
render(
|
||||||
|
<DecisionMakingSidebar
|
||||||
|
title="How are decisions made?"
|
||||||
|
description="Pick approaches for your group."
|
||||||
|
messageBoxTitle="Select methods"
|
||||||
|
messageBoxItems={messageBoxItems}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", { name: "How are decisions made?" }),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText("Pick approaches for your group."),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders InfoMessageBox section", () => {
|
||||||
|
render(
|
||||||
|
<DecisionMakingSidebar
|
||||||
|
title="Decisions"
|
||||||
|
description="Desc"
|
||||||
|
messageBoxTitle="Select methods"
|
||||||
|
messageBoxItems={messageBoxItems}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(screen.getByText("Select methods")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Consensus")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
import "@testing-library/jest-dom/vitest";
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { describe, it, expect, vi } from "vitest";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import InfoMessageBox from "../../app/components/utility/InfoMessageBox";
|
||||||
|
|
||||||
|
describe("InfoMessageBox", () => {
|
||||||
|
const items = [
|
||||||
|
{ id: "a", label: "Option A" },
|
||||||
|
{ id: "b", label: "Option B" },
|
||||||
|
];
|
||||||
|
|
||||||
|
it("renders title and item labels", () => {
|
||||||
|
render(
|
||||||
|
<InfoMessageBox title="Important" items={items} />,
|
||||||
|
);
|
||||||
|
expect(screen.getByRole("region", { name: "Important" })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Important")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Option A")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Option B")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("calls onCheckboxChange when toggling", async () => {
|
||||||
|
const u = userEvent.setup();
|
||||||
|
const onCheckboxChange = vi.fn();
|
||||||
|
render(
|
||||||
|
<InfoMessageBox
|
||||||
|
title="Pick one"
|
||||||
|
items={[{ id: "x", label: "Choice X" }]}
|
||||||
|
onCheckboxChange={onCheckboxChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
const checkbox = screen.getByRole("checkbox", { name: /Choice X/i });
|
||||||
|
await u.click(checkbox);
|
||||||
|
expect(onCheckboxChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import InformationalPage from "../../app/create/informational/page";
|
||||||
|
|
||||||
|
describe("InformationalPage", () => {
|
||||||
|
it("renders without crashing", () => {
|
||||||
|
render(<InformationalPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", {
|
||||||
|
name: "How CommunityRule helps groups like yours",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders lockup description", () => {
|
||||||
|
render(<InformationalPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
/This flow will give you recommendations to improve your community/i,
|
||||||
|
),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders first numbered list item title", () => {
|
||||||
|
render(<InformationalPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByText("Tell us about your organization"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -43,6 +43,10 @@ const mockPosts: BlogPost[] = [
|
|||||||
author: "Author",
|
author: "Author",
|
||||||
date: "2025-04-10",
|
date: "2025-04-10",
|
||||||
},
|
},
|
||||||
|
content: "",
|
||||||
|
htmlContent: "",
|
||||||
|
filePath: "article-1.md",
|
||||||
|
lastModified: new Date("2025-04-10"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: "article-2",
|
slug: "article-2",
|
||||||
@@ -52,6 +56,10 @@ const mockPosts: BlogPost[] = [
|
|||||||
author: "Author",
|
author: "Author",
|
||||||
date: "2025-04-11",
|
date: "2025-04-11",
|
||||||
},
|
},
|
||||||
|
content: "",
|
||||||
|
htmlContent: "",
|
||||||
|
filePath: "article-2.md",
|
||||||
|
lastModified: new Date("2025-04-11"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import React from "react";
|
|
||||||
import { describe, it, expect } from "vitest";
|
import { describe, it, expect } from "vitest";
|
||||||
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
import "@testing-library/jest-dom/vitest";
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import SelectPage from "../../app/create/select/page";
|
||||||
|
|
||||||
|
describe("SelectPage", () => {
|
||||||
|
it("renders HeaderLockup title", () => {
|
||||||
|
render(<SelectPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", {
|
||||||
|
name: "What is your community called?",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders MultiSelect add control", () => {
|
||||||
|
render(<SelectPage />);
|
||||||
|
const addButtons = screen.getAllByRole("button", {
|
||||||
|
name: "Add organization type",
|
||||||
|
});
|
||||||
|
expect(addButtons.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders preset chip labels", () => {
|
||||||
|
render(<SelectPage />);
|
||||||
|
expect(screen.getByText("1 member")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("Non-profit")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import TextPage from "../../app/create/text/page";
|
||||||
|
|
||||||
|
describe("TextPage", () => {
|
||||||
|
it("renders main heading", () => {
|
||||||
|
render(<TextPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", {
|
||||||
|
name: "What is your community called?",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders description and text field", () => {
|
||||||
|
render(<TextPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByText("This will be the name of your community"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByPlaceholderText("Enter your community name"),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
|
import UploadPage from "../../app/create/upload/page";
|
||||||
|
|
||||||
|
describe("UploadPage", () => {
|
||||||
|
it("renders HeaderLockup", () => {
|
||||||
|
render(<UploadPage />);
|
||||||
|
expect(
|
||||||
|
screen.getByRole("heading", {
|
||||||
|
name: "How should conflicts be resolved?",
|
||||||
|
}),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders Upload control and helper copy", () => {
|
||||||
|
render(<UploadPage />);
|
||||||
|
expect(screen.getByRole("button", { name: "Upload" })).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(/Add images, PDFs, and other files to the policy/i),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import {
|
||||||
|
FLOW_STEP_ORDER,
|
||||||
|
getNextStep,
|
||||||
|
getPreviousStep,
|
||||||
|
isValidStep,
|
||||||
|
getStepIndex,
|
||||||
|
} from "../../app/create/utils/flowSteps";
|
||||||
|
|
||||||
|
describe("flowSteps", () => {
|
||||||
|
it("places confirm-stakeholders immediately before final-review", () => {
|
||||||
|
const i = FLOW_STEP_ORDER.indexOf("confirm-stakeholders");
|
||||||
|
const j = FLOW_STEP_ORDER.indexOf("final-review");
|
||||||
|
expect(i).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(j).toBe(i + 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getNextStep returns next step in order", () => {
|
||||||
|
expect(getNextStep("right-rail")).toBe("confirm-stakeholders");
|
||||||
|
expect(getNextStep("confirm-stakeholders")).toBe("final-review");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getNextStep returns null for last step or invalid", () => {
|
||||||
|
expect(getNextStep("completed")).toBeNull();
|
||||||
|
expect(getNextStep(null)).toBeNull();
|
||||||
|
// @ts-expect-error — exercise invalid step id at runtime
|
||||||
|
expect(getNextStep("not-a-step")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getPreviousStep returns prior step or null", () => {
|
||||||
|
expect(getPreviousStep("final-review")).toBe("confirm-stakeholders");
|
||||||
|
expect(getPreviousStep("informational")).toBeNull();
|
||||||
|
expect(getPreviousStep(null)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("isValidStep reflects FLOW_STEP_ORDER membership", () => {
|
||||||
|
expect(isValidStep("select")).toBe(true);
|
||||||
|
expect(isValidStep("confirm-stakeholders")).toBe(true);
|
||||||
|
expect(isValidStep("nope")).toBe(false);
|
||||||
|
expect(isValidStep(null)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("getStepIndex matches position in FLOW_STEP_ORDER", () => {
|
||||||
|
expect(getStepIndex("informational")).toBe(0);
|
||||||
|
expect(getStepIndex("completed")).toBe(FLOW_STEP_ORDER.length - 1);
|
||||||
|
// @ts-expect-error — invalid step id
|
||||||
|
expect(getStepIndex("bogus")).toBe(-1);
|
||||||
|
});
|
||||||
|
});
|
||||||
+4
-3
@@ -1,5 +1,5 @@
|
|||||||
import "@testing-library/jest-dom/vitest";
|
import "@testing-library/jest-dom/vitest";
|
||||||
import type React from "react";
|
import React from "react";
|
||||||
import { afterAll, afterEach, beforeAll, vi } from "vitest";
|
import { afterAll, afterEach, beforeAll, vi } from "vitest";
|
||||||
import { cleanup } from "@testing-library/react";
|
import { cleanup } from "@testing-library/react";
|
||||||
import { server } from "./tests/msw/server";
|
import { server } from "./tests/msw/server";
|
||||||
@@ -18,8 +18,9 @@ vi.mock("next/dynamic", () => {
|
|||||||
) => {
|
) => {
|
||||||
// In tests, return a component that immediately resolves and renders
|
// In tests, return a component that immediately resolves and renders
|
||||||
return function DynamicComponent(props: Record<string, unknown>) {
|
return function DynamicComponent(props: Record<string, unknown>) {
|
||||||
const [Component, setComponent] =
|
const [Component, setComponent] = React.useState(
|
||||||
React.useState<React.ComponentType | null>(null);
|
null as React.ComponentType | null,
|
||||||
|
);
|
||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = React.useState(true);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user