Component cleanup
This commit is contained in:
+5
-5
@@ -3,9 +3,9 @@ import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
} from "../utils/componentTestSuite";
|
||||
import CommunityRuleDocument from "../../app/components/sections/CommunityRuleDocument";
|
||||
import CommunityRule from "../../app/components/type/CommunityRule";
|
||||
|
||||
type Props = React.ComponentProps<typeof CommunityRuleDocument>;
|
||||
type Props = React.ComponentProps<typeof CommunityRule>;
|
||||
|
||||
const sampleSections = [
|
||||
{
|
||||
@@ -20,8 +20,8 @@ const sampleSections = [
|
||||
];
|
||||
|
||||
const config: ComponentTestSuiteConfig<Props> = {
|
||||
component: CommunityRuleDocument,
|
||||
name: "CommunityRuleDocument",
|
||||
component: CommunityRule,
|
||||
name: "CommunityRule",
|
||||
props: {
|
||||
sections: sampleSections,
|
||||
} as Props,
|
||||
@@ -32,6 +32,6 @@ const config: ComponentTestSuiteConfig<Props> = {
|
||||
},
|
||||
};
|
||||
|
||||
describe("CommunityRuleDocument", () => {
|
||||
describe("CommunityRule", () => {
|
||||
componentTestSuite<Props>(config);
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from "react";
|
||||
import ContextMenu from "../../app/components/modals/ContextMenu/ContextMenu";
|
||||
import ContextMenuItem from "../../app/components/modals/ContextMenuItem";
|
||||
import { componentTestSuite } from "../utils/componentTestSuite";
|
||||
|
||||
type ContextMenuProps = React.ComponentProps<typeof ContextMenu>;
|
||||
|
||||
componentTestSuite<ContextMenuProps>({
|
||||
component: ContextMenu,
|
||||
name: "ContextMenu",
|
||||
props: {
|
||||
children: <ContextMenuItem>Item</ContextMenuItem>,
|
||||
} as ContextMenuProps,
|
||||
requiredProps: [],
|
||||
primaryRole: "menu",
|
||||
testCases: {
|
||||
renders: true,
|
||||
accessibility: true,
|
||||
keyboardNavigation: false,
|
||||
disabledState: false,
|
||||
errorState: false,
|
||||
},
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import ContextMenuItem from "../../app/components/modals/ContextMenuItem";
|
||||
import { componentTestSuite } from "../utils/componentTestSuite";
|
||||
|
||||
type ContextMenuItemProps = React.ComponentProps<typeof ContextMenuItem>;
|
||||
|
||||
componentTestSuite<ContextMenuItemProps>({
|
||||
component: ContextMenuItem,
|
||||
name: "ContextMenuItem",
|
||||
props: {
|
||||
children: "Item",
|
||||
} as ContextMenuItemProps,
|
||||
requiredProps: [],
|
||||
primaryRole: "menuitem",
|
||||
testCases: {
|
||||
renders: true,
|
||||
accessibility: false,
|
||||
keyboardNavigation: true,
|
||||
disabledState: true,
|
||||
errorState: false,
|
||||
},
|
||||
states: {
|
||||
disabledProps: { disabled: true },
|
||||
},
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import CreateFlowFooter from "../../app/components/utility/CreateFlowFooter";
|
||||
import CreateFlowFooter from "../../app/components/navigation/CreateFlowFooter";
|
||||
import Button from "../../app/components/buttons/Button";
|
||||
import {
|
||||
componentTestSuite,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { describe, it, expect, vi } from "vitest";
|
||||
import { renderWithProviders as render, screen } from "../utils/test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import CreateFlowTopNav from "../../app/components/utility/CreateFlowTopNav";
|
||||
import CreateFlowTopNav from "../../app/components/navigation/CreateFlowTopNav";
|
||||
import {
|
||||
componentTestSuite,
|
||||
ComponentTestSuiteConfig,
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -77,7 +77,7 @@ describe("FinalReviewScreen", () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard with fallback title when context has no name", () => {
|
||||
it("renders Rule with fallback title when context has no name", () => {
|
||||
render(<FinalReviewScreen />);
|
||||
expect(screen.getByText(FALLBACK_CARD_TITLE)).toBeInTheDocument();
|
||||
});
|
||||
@@ -89,7 +89,7 @@ describe("FinalReviewScreen", () => {
|
||||
replaceState({
|
||||
title: "Oak Park Commons",
|
||||
summary:
|
||||
"Leftover template or one-line summary — must not appear as the RuleCard description.",
|
||||
"Leftover template or one-line summary — must not appear as the Rule description.",
|
||||
});
|
||||
}, [replaceState]);
|
||||
return <FinalReviewScreen />;
|
||||
@@ -100,7 +100,7 @@ describe("FinalReviewScreen", () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard title from create flow state", async () => {
|
||||
it("renders Rule title from create flow state", async () => {
|
||||
render(
|
||||
<FinalReviewWithFlowState
|
||||
title="Oak Park Commons"
|
||||
@@ -115,7 +115,7 @@ describe("FinalReviewScreen", () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard as a button (card is interactive)", () => {
|
||||
it("renders Rule as a button (card is interactive)", () => {
|
||||
render(<FinalReviewScreen />);
|
||||
const buttons = screen.getAllByRole("button");
|
||||
expect(buttons.length).toBeGreaterThanOrEqual(1);
|
||||
@@ -124,7 +124,7 @@ describe("FinalReviewScreen", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("renders expanded RuleCard with category labels", () => {
|
||||
it("renders expanded Rule with category labels", () => {
|
||||
render(<FinalReviewScreen />);
|
||||
expect(screen.getByText("Values")).toBeInTheDocument();
|
||||
expect(screen.getByText("Communication")).toBeInTheDocument();
|
||||
@@ -143,7 +143,7 @@ describe("FinalReviewScreen", () => {
|
||||
|
||||
/**
|
||||
* Seeds a Customize-from-template style state (method ids + core-value
|
||||
* snapshot) and asserts the final-review RuleCard renders the resolved
|
||||
* snapshot) and asserts the final-review Rule renders the resolved
|
||||
* labels — the fix for "preselected chips don't register on final review".
|
||||
*/
|
||||
function FinalReviewWithCustomizeSelections() {
|
||||
|
||||
@@ -1,26 +1,112 @@
|
||||
import { describe } from "vitest";
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import Icon from "../../app/components/cards/Icon";
|
||||
import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
} from "../utils/componentTestSuite";
|
||||
import { Icon } from "../../app/components/asset";
|
||||
|
||||
type Props = React.ComponentProps<typeof Icon>;
|
||||
type IconProps = React.ComponentProps<typeof Icon>;
|
||||
|
||||
const config: ComponentTestSuiteConfig<Props> = {
|
||||
const baseProps: IconProps = {
|
||||
icon: <div data-testid="test-icon">Icon</div>,
|
||||
title: "Worker's cooperatives",
|
||||
description:
|
||||
"Employee-owned businesses often need to clarify how power is shared",
|
||||
};
|
||||
|
||||
const config: ComponentTestSuiteConfig<IconProps> = {
|
||||
component: Icon,
|
||||
name: "Icon",
|
||||
props: {
|
||||
name: "exclamation",
|
||||
size: 24,
|
||||
} as Props,
|
||||
requiredProps: ["name"],
|
||||
props: baseProps,
|
||||
requiredProps: ["icon", "title", "description"],
|
||||
optionalProps: {
|
||||
className: "custom-class",
|
||||
onClick: vi.fn(),
|
||||
},
|
||||
primaryRole: "button",
|
||||
testCases: {
|
||||
renders: true,
|
||||
accessibility: true,
|
||||
keyboardNavigation: true,
|
||||
disabledState: false,
|
||||
errorState: false,
|
||||
},
|
||||
};
|
||||
|
||||
describe("Icon", () => {
|
||||
componentTestSuite<Props>(config);
|
||||
componentTestSuite<IconProps>(config);
|
||||
|
||||
// Pure presentational; no provider context needed.
|
||||
describe("Icon (behavioral tests)", () => {
|
||||
it("calls onClick when clicked", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<Icon
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
onClick={handleClick}
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.click(card);
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("calls onClick when Enter key is pressed", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<Icon
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
onClick={handleClick}
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.keyDown(card, { key: "Enter" });
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("calls onClick when Space key is pressed", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<Icon
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
onClick={handleClick}
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.keyDown(card, { key: " " });
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders icon, title, and description", () => {
|
||||
render(
|
||||
<Icon
|
||||
icon={<div data-testid="icon">Icon</div>}
|
||||
title="Worker's cooperatives"
|
||||
description="Employee-owned businesses"
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId("icon")).toBeInTheDocument();
|
||||
expect(screen.getByText("Worker's cooperatives")).toBeInTheDocument();
|
||||
expect(screen.getByText("Employee-owned businesses")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("has proper ARIA label", () => {
|
||||
render(
|
||||
<Icon
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveAttribute("aria-label", "Test Title: Test Description");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import React from "react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom/vitest";
|
||||
import IconCard from "../../app/components/cards/IconCard";
|
||||
import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
} from "../utils/componentTestSuite";
|
||||
|
||||
type IconCardProps = React.ComponentProps<typeof IconCard>;
|
||||
|
||||
const baseProps: IconCardProps = {
|
||||
icon: <div data-testid="test-icon">Icon</div>,
|
||||
title: "Worker's cooperatives",
|
||||
description:
|
||||
"Employee-owned businesses often need to clarify how power is shared",
|
||||
};
|
||||
|
||||
const config: ComponentTestSuiteConfig<IconCardProps> = {
|
||||
component: IconCard,
|
||||
name: "IconCard",
|
||||
props: baseProps,
|
||||
requiredProps: ["icon", "title", "description"],
|
||||
optionalProps: {
|
||||
className: "custom-class",
|
||||
onClick: vi.fn(),
|
||||
},
|
||||
primaryRole: "button",
|
||||
testCases: {
|
||||
renders: true,
|
||||
accessibility: true,
|
||||
keyboardNavigation: true,
|
||||
disabledState: false,
|
||||
errorState: false,
|
||||
},
|
||||
};
|
||||
|
||||
componentTestSuite<IconCardProps>(config);
|
||||
|
||||
// Pure presentational; no provider context needed.
|
||||
describe("IconCard (behavioral tests)", () => {
|
||||
it("calls onClick when clicked", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
onClick={handleClick}
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.click(card);
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("calls onClick when Enter key is pressed", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
onClick={handleClick}
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.keyDown(card, { key: "Enter" });
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("calls onClick when Space key is pressed", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
onClick={handleClick}
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.keyDown(card, { key: " " });
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("renders icon, title, and description", () => {
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div data-testid="icon">Icon</div>}
|
||||
title="Worker's cooperatives"
|
||||
description="Employee-owned businesses"
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId("icon")).toBeInTheDocument();
|
||||
expect(screen.getByText("Worker's cooperatives")).toBeInTheDocument();
|
||||
expect(screen.getByText("Employee-owned businesses")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("has proper ARIA label", () => {
|
||||
render(
|
||||
<IconCard
|
||||
icon={<div>Icon</div>}
|
||||
title="Test Title"
|
||||
description="Test Description"
|
||||
/>,
|
||||
);
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveAttribute("aria-label", "Test Title: Test Description");
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ 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";
|
||||
import InfoMessageBox from "../../app/components/controls/InfoMessageBox";
|
||||
|
||||
describe("InfoMessageBox", () => {
|
||||
const items = [
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { renderWithProviders as render } from "../utils/test-utils";
|
||||
import InputLabel from "../../app/components/utility/InputLabel";
|
||||
import InputLabel from "../../app/components/type/InputLabel";
|
||||
import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import Logo from "../../app/components/asset/logo";
|
||||
import Logo from "../../app/components/asset/Logo";
|
||||
import {
|
||||
componentTestSuite,
|
||||
ComponentTestSuiteConfig,
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
} from "../utils/componentTestSuite";
|
||||
import ModalFooter from "../../app/components/utility/ModalFooter";
|
||||
import ModalFooter from "../../app/components/modals/ModalFooter";
|
||||
|
||||
type Props = React.ComponentProps<typeof ModalFooter>;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
} from "../utils/componentTestSuite";
|
||||
import ModalHeader from "../../app/components/utility/ModalHeader";
|
||||
import ModalHeader from "../../app/components/modals/ModalHeader";
|
||||
|
||||
type Props = React.ComponentProps<typeof ModalHeader>;
|
||||
|
||||
|
||||
@@ -39,12 +39,12 @@ describe("CommunityReviewScreen", () => {
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard with title fallback when no community name is set", () => {
|
||||
it("renders Rule with title fallback when no community name is set", () => {
|
||||
render(<CommunityReviewScreen />);
|
||||
expect(screen.getByText("Mutual Aid Mondays")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("omits the RuleCard description when the user has not entered community context", () => {
|
||||
it("omits the Rule description when the user has not entered community context", () => {
|
||||
render(<CommunityReviewScreen />);
|
||||
expect(
|
||||
screen.queryByText(
|
||||
@@ -53,7 +53,7 @@ describe("CommunityReviewScreen", () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RuleCard as a button (card is interactive)", () => {
|
||||
it("renders Rule as a button (card is interactive)", () => {
|
||||
render(<CommunityReviewScreen />);
|
||||
const buttons = screen.getAllByRole("button");
|
||||
expect(buttons.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { describe } from "vitest";
|
||||
import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
} from "../utils/componentTestSuite";
|
||||
import Section from "../../app/components/type/Section";
|
||||
import TextBlock from "../../app/components/type/TextBlock";
|
||||
|
||||
type Props = React.ComponentProps<typeof Section>;
|
||||
|
||||
const config: ComponentTestSuiteConfig<Props> = {
|
||||
component: Section,
|
||||
name: "Section",
|
||||
props: {
|
||||
categoryName: "Decision making",
|
||||
showRail: true,
|
||||
children: (
|
||||
<TextBlock title="How proposals pass" body="Important decisions require alignment." />
|
||||
),
|
||||
} as Props,
|
||||
requiredProps: ["categoryName", "children"],
|
||||
testCases: {
|
||||
renders: true,
|
||||
accessibility: true,
|
||||
},
|
||||
};
|
||||
|
||||
describe("Section", () => {
|
||||
componentTestSuite<Props>(config);
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import SectionHeader from "../../app/components/sections/SectionHeader";
|
||||
import SectionHeader from "../../app/components/type/SectionHeader";
|
||||
import { componentTestSuite } from "../utils/componentTestSuite";
|
||||
|
||||
type SectionHeaderProps = React.ComponentProps<typeof SectionHeader>;
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { describe } from "vitest";
|
||||
import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
} from "../utils/componentTestSuite";
|
||||
import Separator from "../../app/components/utility/Separator";
|
||||
|
||||
type Props = React.ComponentProps<typeof Separator>;
|
||||
|
||||
const config: ComponentTestSuiteConfig<Props> = {
|
||||
component: Separator,
|
||||
name: "Separator",
|
||||
props: {} as Props,
|
||||
testCases: {
|
||||
renders: true,
|
||||
accessibility: true,
|
||||
},
|
||||
};
|
||||
|
||||
describe("Separator", () => {
|
||||
componentTestSuite<Props>(config);
|
||||
});
|
||||
@@ -41,8 +41,8 @@ const config: ComponentTestSuiteConfig<Props> = {
|
||||
primaryRole: "button",
|
||||
testCases: {
|
||||
renders: true,
|
||||
// RuleCard contains nested interactive elements (chips inside a clickable card)
|
||||
// which trigger axe's "nested-interactive" rule. Tracked by RuleCard itself.
|
||||
// Rule contains nested interactive elements (chips inside a clickable card)
|
||||
// which trigger axe's "nested-interactive" rule. Tracked by Rule itself.
|
||||
accessibility: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe } from "vitest";
|
||||
import {
|
||||
componentTestSuite,
|
||||
type ComponentTestSuiteConfig,
|
||||
} from "../utils/componentTestSuite";
|
||||
import TextBlock from "../../app/components/type/TextBlock";
|
||||
|
||||
type Props = React.ComponentProps<typeof TextBlock>;
|
||||
|
||||
const config: ComponentTestSuiteConfig<Props> = {
|
||||
component: TextBlock,
|
||||
name: "TextBlock",
|
||||
props: {
|
||||
title: "Policy title",
|
||||
body: "Supporting text for the policy.",
|
||||
} as Props,
|
||||
requiredProps: ["title"],
|
||||
testCases: {
|
||||
renders: true,
|
||||
accessibility: true,
|
||||
},
|
||||
};
|
||||
|
||||
describe("TextBlock", () => {
|
||||
componentTestSuite<Props>(config);
|
||||
});
|
||||
@@ -3,7 +3,7 @@ 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 Top from "../../app/components/navigation/Top";
|
||||
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";
|
||||
@@ -23,13 +23,13 @@ vi.mock("next/navigation", () => ({
|
||||
usePathname: () => "/",
|
||||
}));
|
||||
|
||||
type TopNavProps = React.ComponentProps<typeof TopNav>;
|
||||
type TopProps = React.ComponentProps<typeof Top>;
|
||||
|
||||
// Test folderTop=false variant (standard header)
|
||||
componentTestSuite<TopNavProps>({
|
||||
component: TopNav,
|
||||
name: "TopNav (folderTop=false)",
|
||||
props: { folderTop: false } as TopNavProps,
|
||||
componentTestSuite<TopProps>({
|
||||
component: Top,
|
||||
name: "Top (folderTop=false)",
|
||||
props: { folderTop: false } as TopProps,
|
||||
requiredProps: [],
|
||||
primaryRole: "banner",
|
||||
testCases: {
|
||||
@@ -43,10 +43,10 @@ componentTestSuite<TopNavProps>({
|
||||
|
||||
// Test folderTop=true variant (home header)
|
||||
// Note: Accessibility test may fail due to Next.js Script component behavior in test environment
|
||||
componentTestSuite<TopNavProps>({
|
||||
component: TopNav,
|
||||
name: "TopNav (folderTop=true)",
|
||||
props: { folderTop: true } as TopNavProps,
|
||||
componentTestSuite<TopProps>({
|
||||
component: Top,
|
||||
name: "Top (folderTop=true)",
|
||||
props: { folderTop: true } as TopProps,
|
||||
requiredProps: [],
|
||||
primaryRole: "banner",
|
||||
testCases: {
|
||||
@@ -58,7 +58,7 @@ componentTestSuite<TopNavProps>({
|
||||
},
|
||||
});
|
||||
|
||||
describe('TopNav "Create rule" button', () => {
|
||||
describe('Top "Create rule" button', () => {
|
||||
beforeEach(() => {
|
||||
pushMock.mockReset();
|
||||
window.localStorage.clear();
|
||||
@@ -71,7 +71,7 @@ describe('TopNav "Create rule" button', () => {
|
||||
* 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.
|
||||
* handleCreateRuleClick in Top.container.tsx for the contract.
|
||||
*/
|
||||
it("clears anonymous draft + core-value-details localStorage before routing to /create", async () => {
|
||||
window.localStorage.setItem(
|
||||
@@ -83,9 +83,9 @@ describe('TopNav "Create rule" button', () => {
|
||||
JSON.stringify({ "1": { meaning: "m", signals: "s" } }),
|
||||
);
|
||||
|
||||
renderWithProviders(<TopNav folderTop={false} />);
|
||||
renderWithProviders(<Top folderTop={false} />);
|
||||
|
||||
// TopNav renders the Create Rule button at three breakpoints (xs/sm/md);
|
||||
// Top 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,
|
||||
@@ -22,10 +22,10 @@ test.describe("Critical User Journeys", () => {
|
||||
await learnButton.click();
|
||||
}
|
||||
|
||||
// 4. User scrolls to numbered cards section
|
||||
// Note: SectionHeader shows "How CommunityRule works" on mobile, "How CommunityRule helps" on desktop
|
||||
// 4. User scrolls to CardSteps section (home)
|
||||
// Note: mobile title is one line; lg+ uses stacked "How / CommunityRule / helps" from home copy
|
||||
const howItWorksHeading = page.locator(
|
||||
'h2:has-text("How CommunityRule works"), h2:has-text("How CommunityRule helps")',
|
||||
'h2:has-text("How CommunityRule works"), h2:has-text("How"), h2:has-text("CommunityRule"), h2:has-text("helps")',
|
||||
);
|
||||
await expect(howItWorksHeading).toBeVisible();
|
||||
|
||||
@@ -129,7 +129,7 @@ test.describe("Critical User Journeys", () => {
|
||||
featureSection.locator("text=Conflict resolution"),
|
||||
).toBeVisible();
|
||||
|
||||
// Check feature links - MiniCard components render as <a> tags with href="#..."
|
||||
// Check feature links - Mini tiles render as <a> tags with href="#..."
|
||||
// There are 4 feature cards + 1 "Learn more" link = 5 total links
|
||||
// We check for the specific feature card links
|
||||
await expect(
|
||||
|
||||
@@ -26,16 +26,15 @@ describe("Create flow decision-approaches page", () => {
|
||||
test("renders sidebar description with add link", () => {
|
||||
render(<DecisionApproachesScreen />);
|
||||
|
||||
const description = screen.getByText((content, element) => {
|
||||
if (element?.tagName !== "P") return false;
|
||||
const text = element.textContent ?? "";
|
||||
return (
|
||||
text.includes("Select as many as you need") &&
|
||||
text.includes("add") &&
|
||||
text.includes("new decision making approaches")
|
||||
);
|
||||
const addControl = screen.getByRole("button", {
|
||||
name: /^add$/,
|
||||
});
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(addControl).toBeInTheDocument();
|
||||
|
||||
const description = addControl.parentElement;
|
||||
expect(description).not.toBeNull();
|
||||
expect(description?.textContent).toMatch(/Select as many as you need/);
|
||||
expect(description?.textContent).toMatch(/new decision making approaches/);
|
||||
});
|
||||
|
||||
test("renders message box with title and checkboxes", () => {
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("Page", () => {
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
// Wait for dynamically imported components to load
|
||||
// Check numbered cards section (using getAllByText since there are multiple instances)
|
||||
// Check CardSteps section (using getAllByText since there are multiple instances)
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getAllByText("How CommunityRule works").length,
|
||||
@@ -70,10 +70,10 @@ describe("Page", () => {
|
||||
).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("renders numbered cards with correct data", async () => {
|
||||
test("renders CardSteps section with correct data", async () => {
|
||||
render(<Page />);
|
||||
|
||||
// Wait for dynamically imported NumberedCards component to load
|
||||
// Wait for dynamically imported CardSteps component to load
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getAllByText("How CommunityRule works").length,
|
||||
@@ -140,7 +140,7 @@ describe("Page", () => {
|
||||
|
||||
// Wait for dynamically imported components to load
|
||||
// LogoWall - should be present (even if just the component structure)
|
||||
// NumberedCards
|
||||
// CardSteps
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getAllByText("How CommunityRule works").length,
|
||||
@@ -192,7 +192,7 @@ describe("Page", () => {
|
||||
).length,
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
// Wait for dynamically imported NumberedCards component
|
||||
// Wait for dynamically imported CardSteps component
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getAllByText(
|
||||
@@ -239,12 +239,12 @@ describe("Page", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("renders numbered card items with correct content", async () => {
|
||||
test("renders CardSteps step tiles with correct content", async () => {
|
||||
render(<Page />);
|
||||
|
||||
// Wait for dynamically imported NumberedCards component
|
||||
// Wait for dynamically imported CardSteps component
|
||||
await waitFor(() => {
|
||||
// Check all three numbered card items (using getAllByText since there are multiple instances)
|
||||
// Three Step tiles inside CardSteps
|
||||
expect(
|
||||
screen.getAllByText("Document how your community makes decisions")
|
||||
.length,
|
||||
|
||||
@@ -70,7 +70,7 @@ describe("Page Flow Integration", () => {
|
||||
expect(screen.getByAltText("Mutual Aid CO")).toBeInTheDocument();
|
||||
expect(screen.getByAltText("CU Boulder")).toBeInTheDocument();
|
||||
|
||||
// Numbered Cards section - wait for dynamically imported component
|
||||
// CardSteps section — wait for dynamically imported component
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole("heading", { name: /How CommunityRule works/ }),
|
||||
@@ -153,10 +153,10 @@ describe("Page Flow Integration", () => {
|
||||
expect(ctaButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("numbered cards display with correct icons and colors", async () => {
|
||||
test("CardSteps section shows step tiles with expected icon/color props", async () => {
|
||||
render(<Page />);
|
||||
|
||||
// Wait for dynamically imported NumberedCards component
|
||||
// Wait for dynamically imported CardSteps component
|
||||
await waitFor(() => {
|
||||
const cards = screen.getAllByText(
|
||||
/Document how your community|Build an operating manual|Get a link to your manual/,
|
||||
@@ -189,11 +189,10 @@ describe("Page Flow Integration", () => {
|
||||
const seeAll = screen.getByRole("link", { name: "See all templates" });
|
||||
expect(seeAll).toHaveAttribute("href", "/templates");
|
||||
|
||||
// Check that create rule button is present
|
||||
const createButton = screen.getByRole("button", {
|
||||
name: "Create CommunityRule",
|
||||
const seeHowButton = screen.getByRole("button", {
|
||||
name: "See how it works",
|
||||
});
|
||||
expect(createButton).toBeInTheDocument();
|
||||
expect(seeHowButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("ask organizer section has proper call-to-action", () => {
|
||||
|
||||
@@ -57,7 +57,7 @@ describe("User Journey Integration", () => {
|
||||
const learnButton = learnButtons[0];
|
||||
await user.click(learnButton);
|
||||
|
||||
// Wait for dynamically imported NumberedCards component
|
||||
// Wait for dynamically imported CardSteps component
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("How CommunityRule works")).toBeInTheDocument();
|
||||
});
|
||||
@@ -77,14 +77,14 @@ describe("User Journey Integration", () => {
|
||||
expect(screen.getByText("Petition")).toBeInTheDocument();
|
||||
|
||||
// User clicks on a governance type to create a rule
|
||||
const createButtons = screen.getAllByRole("button", {
|
||||
name: "Create CommunityRule",
|
||||
const seeHowButtons = screen.getAllByRole("button", {
|
||||
name: "See how it works",
|
||||
});
|
||||
expect(createButtons.length).toBeGreaterThan(0);
|
||||
expect(seeHowButtons.length).toBeGreaterThan(0);
|
||||
|
||||
await user.click(createButtons[0]);
|
||||
await user.click(seeHowButtons[0]);
|
||||
// Button should remain interactive
|
||||
expect(createButtons[0]).toBeInTheDocument();
|
||||
expect(seeHowButtons[0]).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("user navigates through the application using header navigation", async () => {
|
||||
@@ -127,10 +127,10 @@ describe("User Journey Integration", () => {
|
||||
expect(askLink).toHaveAttribute("href", "#contact");
|
||||
});
|
||||
|
||||
test("user explores the process through numbered cards", async () => {
|
||||
test("user explores the process through CardSteps", async () => {
|
||||
render(<Page />);
|
||||
|
||||
// Wait for dynamically imported NumberedCards component
|
||||
// Wait for dynamically imported CardSteps component
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByText("Document how your community makes decisions"),
|
||||
@@ -278,10 +278,10 @@ describe("User Journey Integration", () => {
|
||||
).toBeInTheDocument();
|
||||
|
||||
// 6. User can take action
|
||||
const createButtons = screen.getAllByRole("button", {
|
||||
name: "Create CommunityRule",
|
||||
const seeHowButtons = screen.getAllByRole("button", {
|
||||
name: "See how it works",
|
||||
});
|
||||
expect(createButtons.length).toBeGreaterThan(0);
|
||||
expect(seeHowButtons.length).toBeGreaterThan(0);
|
||||
|
||||
// 7. User can get help if needed
|
||||
expect(screen.getByText("Still have questions?")).toBeInTheDocument();
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
} from "../utils/test-utils";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||
import CardStack from "../../app/components/utility/CardStack";
|
||||
import CardStack from "../../app/components/cards/CardStack";
|
||||
|
||||
const SAMPLE_CARDS = [
|
||||
{
|
||||
|
||||
@@ -4,14 +4,14 @@ import {
|
||||
cleanup,
|
||||
} from "../utils/test-utils";
|
||||
import { describe, test, expect, afterEach } from "vitest";
|
||||
import NumberedCards from "../../app/components/sections/NumberedCards";
|
||||
import CardSteps from "../../app/components/sections/CardSteps";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe("NumberedCards Component", () => {
|
||||
const mockCards = [
|
||||
describe("CardSteps Component", () => {
|
||||
const mockSteps = [
|
||||
{
|
||||
text: "Define your community values",
|
||||
iconShape: "circle",
|
||||
@@ -29,23 +29,20 @@ describe("NumberedCards Component", () => {
|
||||
},
|
||||
];
|
||||
|
||||
test("renders with title, subtitle, and cards", () => {
|
||||
test("renders with title, subtitle, and step cards", () => {
|
||||
render(
|
||||
<NumberedCards
|
||||
<CardSteps
|
||||
title="How CommunityRule helps"
|
||||
subtitle="Build better communities step by step"
|
||||
cards={mockCards}
|
||||
steps={mockSteps}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Check for the heading (it contains both mobile and desktop versions)
|
||||
expect(screen.getByRole("heading")).toBeInTheDocument();
|
||||
// Check for the subtitle text
|
||||
expect(
|
||||
screen.getByText("Build better communities step by step"),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Check for card content
|
||||
expect(
|
||||
screen.getByText("Define your community values"),
|
||||
).toBeInTheDocument();
|
||||
@@ -59,55 +56,51 @@ describe("NumberedCards Component", () => {
|
||||
|
||||
test("renders SectionHeader component", () => {
|
||||
render(
|
||||
<NumberedCards
|
||||
<CardSteps
|
||||
title="Test Title"
|
||||
subtitle="Test Subtitle"
|
||||
cards={mockCards}
|
||||
steps={mockSteps}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Check for the heading (it contains both mobile and desktop versions)
|
||||
expect(screen.getByRole("heading")).toBeInTheDocument();
|
||||
// Check for the subtitle text
|
||||
expect(screen.getByText("Test Subtitle")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders NumberCard components with correct props", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
|
||||
test("renders Step tiles with correct props", () => {
|
||||
render(<CardSteps title="Test" subtitle="Test" steps={mockSteps} />);
|
||||
|
||||
// Check that NumberCard components receive correct props
|
||||
expect(screen.getByText("1")).toBeInTheDocument(); // First card number
|
||||
expect(screen.getByText("2")).toBeInTheDocument(); // Second card number
|
||||
expect(screen.getByText("3")).toBeInTheDocument(); // Third card number
|
||||
expect(screen.getByText("1")).toBeInTheDocument();
|
||||
expect(screen.getByText("2")).toBeInTheDocument();
|
||||
expect(screen.getByText("3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders call-to-action buttons", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
|
||||
test("renders call-to-action button", () => {
|
||||
render(<CardSteps title="Test" subtitle="Test" steps={mockSteps} />);
|
||||
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Create CommunityRule" }),
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("button", { name: "See how it works" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("applies responsive button visibility", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
|
||||
test("renders stacked desktop heading lines when provided", () => {
|
||||
const { container } = render(
|
||||
<CardSteps
|
||||
title="Mobile line"
|
||||
subtitle="Test"
|
||||
steps={mockSteps}
|
||||
headingDesktopLines={["How", "CommunityRule", "helps"]}
|
||||
/>,
|
||||
);
|
||||
|
||||
const createButton = screen.getByRole("button", {
|
||||
name: "Create CommunityRule",
|
||||
});
|
||||
const seeHowButton = screen.getByRole("button", {
|
||||
name: "See how it works",
|
||||
});
|
||||
|
||||
expect(createButton.closest("div")).toHaveClass("block", "lg:hidden");
|
||||
expect(seeHowButton.closest("div")).toHaveClass("hidden", "lg:block");
|
||||
const h2 = container.querySelector("h2");
|
||||
expect(h2?.textContent).toContain("Mobile line");
|
||||
expect(h2?.textContent).toContain("CommunityRule");
|
||||
expect(h2?.textContent).toContain("helps");
|
||||
});
|
||||
|
||||
test("renders with design tokens", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
|
||||
render(<CardSteps title="Test" subtitle="Test" steps={mockSteps} />);
|
||||
|
||||
const section = document.querySelector("section");
|
||||
expect(section).toHaveClass(
|
||||
@@ -117,7 +110,7 @@ describe("NumberedCards Component", () => {
|
||||
});
|
||||
|
||||
test("applies responsive grid layout", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
|
||||
render(<CardSteps title="Test" subtitle="Test" steps={mockSteps} />);
|
||||
|
||||
const cardsContainer = document.querySelector(
|
||||
'[class*="grid grid-cols-1"]',
|
||||
@@ -127,10 +120,10 @@ describe("NumberedCards Component", () => {
|
||||
|
||||
test("renders schema markup", () => {
|
||||
render(
|
||||
<NumberedCards
|
||||
<CardSteps
|
||||
title="Test Title"
|
||||
subtitle="Test Description"
|
||||
cards={mockCards}
|
||||
steps={mockSteps}
|
||||
/>,
|
||||
);
|
||||
|
||||
@@ -145,40 +138,37 @@ describe("NumberedCards Component", () => {
|
||||
});
|
||||
|
||||
test("has proper semantic structure", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
|
||||
render(<CardSteps title="Test" subtitle="Test" steps={mockSteps} />);
|
||||
|
||||
const section = document.querySelector("section");
|
||||
expect(section).toBeInTheDocument();
|
||||
|
||||
// Check for proper heading structure
|
||||
const headings = screen.getAllByRole("heading");
|
||||
expect(headings.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("handles empty cards array", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={[]} />);
|
||||
test("handles empty steps array", () => {
|
||||
render(<CardSteps title="Test" subtitle="Test" steps={[]} />);
|
||||
|
||||
// Should still render the structure
|
||||
const section = document.querySelector("section");
|
||||
expect(section).toBeInTheDocument();
|
||||
|
||||
// Should render buttons even without cards
|
||||
expect(
|
||||
screen.getByRole("button", { name: "Create CommunityRule" }),
|
||||
screen.getByRole("button", { name: "See how it works" }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("applies responsive text alignment", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
|
||||
test("centers call-to-action region", () => {
|
||||
render(<CardSteps title="Test" subtitle="Test" steps={mockSteps} />);
|
||||
|
||||
const buttonContainer = screen
|
||||
.getByRole("button", { name: "Create CommunityRule" })
|
||||
const buttonRegion = screen
|
||||
.getByRole("button", { name: "See how it works" })
|
||||
.closest("div");
|
||||
expect(buttonContainer).toHaveClass("block", "lg:hidden");
|
||||
expect(buttonRegion).toHaveClass("text-center");
|
||||
});
|
||||
|
||||
test("renders with proper spacing", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
|
||||
render(<CardSteps title="Test" subtitle="Test" steps={mockSteps} />);
|
||||
|
||||
const section = document.querySelector("section");
|
||||
expect(section).toHaveClass(
|
||||
@@ -188,7 +178,7 @@ describe("NumberedCards Component", () => {
|
||||
});
|
||||
|
||||
test("applies max-width constraint", () => {
|
||||
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
|
||||
render(<CardSteps title="Test" subtitle="Test" steps={mockSteps} />);
|
||||
|
||||
const container = document.querySelector(
|
||||
'[class*="max-w-[var(--spacing-measures-max-width-lg)]"]',
|
||||
@@ -4,16 +4,16 @@ import {
|
||||
fireEvent,
|
||||
} from "../utils/test-utils";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import RuleCard from "../../app/components/cards/RuleCard";
|
||||
import Rule from "../../app/components/cards/Rule";
|
||||
|
||||
describe("RuleCard Component", () => {
|
||||
describe("Rule Component", () => {
|
||||
const defaultProps = {
|
||||
title: "Test Rule",
|
||||
description: "This is a test rule description",
|
||||
};
|
||||
|
||||
it("renders rule card with all required information", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
render(<Rule {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Test Rule")).toBeInTheDocument();
|
||||
expect(
|
||||
@@ -23,14 +23,14 @@ describe("RuleCard Component", () => {
|
||||
|
||||
it("renders with custom className", () => {
|
||||
const customClass = "custom-rule-card";
|
||||
render(<RuleCard {...defaultProps} className={customClass} />);
|
||||
render(<Rule {...defaultProps} className={customClass} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass(customClass);
|
||||
});
|
||||
|
||||
it("applies default background color", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
render(<Rule {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass("bg-[var(--color-community-teal-100)]");
|
||||
@@ -38,7 +38,7 @@ describe("RuleCard Component", () => {
|
||||
|
||||
it("applies custom background color", () => {
|
||||
const customBg = "bg-blue-100";
|
||||
render(<RuleCard {...defaultProps} backgroundColor={customBg} />);
|
||||
render(<Rule {...defaultProps} backgroundColor={customBg} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass(customBg);
|
||||
@@ -46,14 +46,14 @@ describe("RuleCard Component", () => {
|
||||
|
||||
it("renders with icon when provided", () => {
|
||||
const Icon = () => <span data-testid="icon">🚀</span>;
|
||||
render(<RuleCard {...defaultProps} icon={<Icon />} />);
|
||||
render(<Rule {...defaultProps} icon={<Icon />} />);
|
||||
|
||||
expect(screen.getByTestId("icon")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles click events", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<RuleCard {...defaultProps} onClick={handleClick} />);
|
||||
render(<Rule {...defaultProps} onClick={handleClick} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.click(card);
|
||||
@@ -63,7 +63,7 @@ describe("RuleCard Component", () => {
|
||||
|
||||
it("handles keyboard events", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<RuleCard {...defaultProps} onClick={handleClick} />);
|
||||
render(<Rule {...defaultProps} onClick={handleClick} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
|
||||
@@ -77,7 +77,7 @@ describe("RuleCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies hover effects correctly", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
render(<Rule {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass(
|
||||
@@ -87,7 +87,7 @@ describe("RuleCard Component", () => {
|
||||
});
|
||||
|
||||
it("renders with proper accessibility attributes", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
render(<Rule {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveAttribute(
|
||||
@@ -98,7 +98,7 @@ describe("RuleCard Component", () => {
|
||||
});
|
||||
|
||||
it("handles empty description gracefully", () => {
|
||||
render(<RuleCard {...defaultProps} description="" />);
|
||||
render(<Rule {...defaultProps} description="" />);
|
||||
|
||||
expect(screen.getByText("Test Rule")).toBeInTheDocument();
|
||||
expect(
|
||||
@@ -107,14 +107,14 @@ describe("RuleCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies proper sizing for expanded states", () => {
|
||||
render(<RuleCard {...defaultProps} expanded={true} size="L" />);
|
||||
render(<Rule {...defaultProps} expanded={true} size="L" />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass("w-[568px]");
|
||||
});
|
||||
|
||||
it("applies proper accessibility attributes", () => {
|
||||
render(<RuleCard {...defaultProps} expanded={true} />);
|
||||
render(<Rule {...defaultProps} expanded={true} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveAttribute("aria-expanded", "true");
|
||||
@@ -122,13 +122,13 @@ describe("RuleCard Component", () => {
|
||||
});
|
||||
|
||||
it("renders without icon when not provided", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
render(<Rule {...defaultProps} />);
|
||||
|
||||
expect(screen.queryByTestId("icon")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies proper border and shadow classes", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
render(<Rule {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass("shadow-[0px_0px_48px_0px_rgba(0,0,0,0.1)]");
|
||||
@@ -136,7 +136,7 @@ describe("RuleCard Component", () => {
|
||||
});
|
||||
|
||||
it("maintains proper heading structure", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
render(<Rule {...defaultProps} />);
|
||||
|
||||
const heading = screen.getByRole("heading", { level: 3 });
|
||||
expect(heading).toHaveTextContent("Test Rule");
|
||||
@@ -144,7 +144,7 @@ describe("RuleCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies proper font classes for title", () => {
|
||||
render(<RuleCard {...defaultProps} size="L" />);
|
||||
render(<Rule {...defaultProps} size="L" />);
|
||||
|
||||
const heading = screen.getByRole("heading", { level: 3 });
|
||||
// Check for responsive font classes - at 1440px+ it should have font-bricolage-grotesque and font-extrabold
|
||||
@@ -164,7 +164,7 @@ describe("RuleCard Component", () => {
|
||||
},
|
||||
];
|
||||
render(
|
||||
<RuleCard {...defaultProps} expanded={true} categories={categories} />,
|
||||
<Rule {...defaultProps} expanded={true} categories={categories} />,
|
||||
);
|
||||
|
||||
expect(screen.getByText("Values")).toBeInTheDocument();
|
||||
@@ -172,7 +172,7 @@ describe("RuleCard Component", () => {
|
||||
|
||||
it("renders with logo URL", () => {
|
||||
render(
|
||||
<RuleCard
|
||||
<Rule
|
||||
{...defaultProps}
|
||||
logoUrl="http://localhost:3845/assets/test.png"
|
||||
logoAlt="Test Logo"
|
||||
@@ -184,7 +184,7 @@ describe("RuleCard Component", () => {
|
||||
});
|
||||
|
||||
it("renders with community initials fallback", () => {
|
||||
render(<RuleCard {...defaultProps} communityInitials="CE" />);
|
||||
render(<Rule {...defaultProps} communityInitials="CE" />);
|
||||
|
||||
expect(screen.getByText("CE")).toBeInTheDocument();
|
||||
});
|
||||
@@ -179,7 +179,7 @@ describe("RuleStack Component", () => {
|
||||
expect(grid).toHaveClass("min-[768px]:grid", "min-[768px]:grid-cols-2");
|
||||
});
|
||||
|
||||
test("renders RuleCard components with catalog surface colors", async () => {
|
||||
test("renders Rule components with catalog surface colors", async () => {
|
||||
render(<RuleStack />);
|
||||
await waitForRuleStackCards();
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
fireEvent,
|
||||
} from "../utils/test-utils";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import Card from "../../app/components/cards/Card";
|
||||
import Selection from "../../app/components/cards/Selection";
|
||||
|
||||
describe("Card Component", () => {
|
||||
describe("Selection Component", () => {
|
||||
const defaultProps = {
|
||||
label: "Label",
|
||||
supportText: "Support text here",
|
||||
@@ -14,26 +14,26 @@ describe("Card Component", () => {
|
||||
};
|
||||
|
||||
it("renders label and supportText", () => {
|
||||
render(<Card {...defaultProps} />);
|
||||
render(<Selection {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Label")).toBeInTheDocument();
|
||||
expect(screen.getByText("Support text here")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders RECOMMENDED pill when recommended is true", () => {
|
||||
render(<Card {...defaultProps} recommended={true} />);
|
||||
render(<Selection {...defaultProps} recommended={true} />);
|
||||
|
||||
expect(screen.getByText("RECOMMENDED")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render RECOMMENDED pill when recommended is false", () => {
|
||||
render(<Card {...defaultProps} recommended={false} />);
|
||||
render(<Selection {...defaultProps} recommended={false} />);
|
||||
|
||||
expect(screen.queryByText("RECOMMENDED")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders SELECTED pill and inset dashed outline when selected is true", () => {
|
||||
render(<Card {...defaultProps} selected={true} />);
|
||||
render(<Selection {...defaultProps} selected={true} />);
|
||||
|
||||
expect(screen.getByText("SELECTED")).toBeInTheDocument();
|
||||
const card = screen.getByRole("button");
|
||||
@@ -41,14 +41,14 @@ describe("Card Component", () => {
|
||||
});
|
||||
|
||||
it("applies horizontal layout by default", () => {
|
||||
render(<Card {...defaultProps} />);
|
||||
render(<Selection {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Label")).toBeInTheDocument();
|
||||
expect(screen.getByText("Support text here")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies vertical layout when orientation is vertical", () => {
|
||||
render(<Card {...defaultProps} orientation="vertical" />);
|
||||
render(<Selection {...defaultProps} orientation="vertical" />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass("flex-row");
|
||||
@@ -56,7 +56,7 @@ describe("Card Component", () => {
|
||||
|
||||
it("handles click events", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<Card {...defaultProps} onClick={handleClick} />);
|
||||
render(<Selection {...defaultProps} onClick={handleClick} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.click(card);
|
||||
@@ -66,7 +66,7 @@ describe("Card Component", () => {
|
||||
|
||||
it("handles keyboard events", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<Card {...defaultProps} onClick={handleClick} />);
|
||||
render(<Selection {...defaultProps} onClick={handleClick} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
|
||||
@@ -79,14 +79,14 @@ describe("Card Component", () => {
|
||||
|
||||
it("renders with custom className", () => {
|
||||
const customClass = "custom-card";
|
||||
render(<Card {...defaultProps} className={customClass} />);
|
||||
render(<Selection {...defaultProps} className={customClass} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass(customClass);
|
||||
});
|
||||
|
||||
it("renders with proper accessibility attributes", () => {
|
||||
render(<Card {...defaultProps} />);
|
||||
render(<Selection {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveAttribute("aria-label", "Label: Support text here");
|
||||
@@ -94,7 +94,7 @@ describe("Card Component", () => {
|
||||
});
|
||||
|
||||
it("renders without supportText", () => {
|
||||
render(<Card label="Label only" orientation="horizontal" />);
|
||||
render(<Selection label="Label only" orientation="horizontal" />);
|
||||
|
||||
expect(screen.getByText("Label only")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button")).toHaveAttribute(
|
||||
@@ -1,41 +1,41 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import NumberCard from "../../app/components/cards/NumberCard";
|
||||
import Step from "../../app/components/cards/Step";
|
||||
|
||||
// Pure presentational; no provider context needed.
|
||||
describe("NumberCard Component", () => {
|
||||
describe("Step Component", () => {
|
||||
const defaultProps = {
|
||||
number: 1,
|
||||
text: "Test Card Text",
|
||||
};
|
||||
|
||||
it("renders number card with all required information", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
it("renders step with all required information", () => {
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Card Text")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with different numbers", () => {
|
||||
const { rerender } = render(<NumberCard {...defaultProps} number={42} />);
|
||||
const { rerender } = render(<Step {...defaultProps} number={42} />);
|
||||
expect(screen.getByText("42")).toBeInTheDocument();
|
||||
|
||||
rerender(<NumberCard {...defaultProps} number={999} />);
|
||||
rerender(<Step {...defaultProps} number={999} />);
|
||||
expect(screen.getByText("999")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with different text content", () => {
|
||||
const { rerender } = render(
|
||||
<NumberCard {...defaultProps} text="Different Text" />,
|
||||
<Step {...defaultProps} text="Different Text" />,
|
||||
);
|
||||
expect(screen.getByText("Different Text")).toBeInTheDocument();
|
||||
|
||||
rerender(<NumberCard {...defaultProps} text="Another Text" />);
|
||||
rerender(<Step {...defaultProps} text="Another Text" />);
|
||||
expect(screen.getByText("Another Text")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies proper responsive layout classes when size is not specified", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -53,7 +53,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies proper responsive spacing when size is not specified", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -62,7 +62,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies proper responsive gap when size is not specified", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -71,7 +71,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies proper responsive height when size is not specified", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -80,7 +80,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies proper background and shadow", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -92,7 +92,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies proper border radius", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -101,14 +101,14 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("renders section number in correct position for responsive mode", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const numberElement = screen.getByText("1");
|
||||
expect(numberElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders text content in correct position for responsive mode", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const textElement = screen.getByText("Test Card Text");
|
||||
expect(textElement).toBeInTheDocument();
|
||||
@@ -125,14 +125,14 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies proper font classes to text", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const textElement = screen.getByText("Test Card Text");
|
||||
expect(textElement).toHaveClass("font-bricolage-grotesque");
|
||||
});
|
||||
|
||||
it("applies proper text sizing for responsive mode", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const textElement = screen.getByText("Test Card Text");
|
||||
expect(textElement).toHaveClass(
|
||||
@@ -147,7 +147,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies proper text color", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const textElement = screen.getByText("Test Card Text");
|
||||
expect(textElement).toHaveClass("text-[#141414]");
|
||||
@@ -155,14 +155,14 @@ describe("NumberCard Component", () => {
|
||||
|
||||
it("handles long text content gracefully", () => {
|
||||
const longText =
|
||||
"This is a very long text that should wrap properly and not break the layout of the number card component";
|
||||
render(<NumberCard {...defaultProps} text={longText} />);
|
||||
"This is a very long text that should wrap properly and not break the layout of the Step component";
|
||||
render(<Step {...defaultProps} text={longText} />);
|
||||
|
||||
expect(screen.getByText(longText)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("maintains proper responsive behavior when size is not specified", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -192,7 +192,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("renders with proper flex layout", () => {
|
||||
render(<NumberCard {...defaultProps} />);
|
||||
render(<Step {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -201,7 +201,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies Small size variant correctly", () => {
|
||||
render(<NumberCard {...defaultProps} size="small" />);
|
||||
render(<Step {...defaultProps} size="small" />);
|
||||
|
||||
// For Small size, text is directly in card div (no wrapper), so use closest("div")
|
||||
const card = screen.getByText("Test Card Text").closest("div");
|
||||
@@ -220,7 +220,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies Medium size variant correctly", () => {
|
||||
render(<NumberCard {...defaultProps} size="medium" />);
|
||||
render(<Step {...defaultProps} size="medium" />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -238,7 +238,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies Large size variant correctly", () => {
|
||||
render(<NumberCard {...defaultProps} size="large" />);
|
||||
render(<Step {...defaultProps} size="large" />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -258,7 +258,7 @@ describe("NumberCard Component", () => {
|
||||
});
|
||||
|
||||
it("applies XLarge size variant correctly", () => {
|
||||
render(<NumberCard {...defaultProps} size="xlarge" />);
|
||||
render(<Step {...defaultProps} size="xlarge" />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
@@ -209,6 +209,39 @@ describe("parseDocumentSectionsForDisplay", () => {
|
||||
};
|
||||
expect(parseDocumentSectionsForDisplay(doc)).toEqual(doc.sections);
|
||||
});
|
||||
|
||||
it("accepts entries with labeled blocks and empty body", () => {
|
||||
const doc = {
|
||||
sections: [
|
||||
{
|
||||
categoryName: "Membership",
|
||||
entries: [
|
||||
{
|
||||
title: "Open membership",
|
||||
body: "",
|
||||
blocks: [
|
||||
{ label: "Eligibility", body: "Anyone may join." },
|
||||
{ label: "Process", body: "Sign the sheet." },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(parseDocumentSectionsForDisplay(doc)).toEqual(doc.sections);
|
||||
});
|
||||
|
||||
it("still parses entries with empty body and no blocks", () => {
|
||||
const doc = {
|
||||
sections: [
|
||||
{
|
||||
categoryName: "Values",
|
||||
entries: [{ title: "Consensus", body: "" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(parseDocumentSectionsForDisplay(doc)).toEqual(doc.sections);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseSectionsFromCreateFlowState", () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
} from "../../lib/create/templateReviewMapping";
|
||||
|
||||
describe("templateReviewMapping", () => {
|
||||
it("maps body sections to RuleCard categories", () => {
|
||||
it("maps body sections to Rule categories", () => {
|
||||
const body = {
|
||||
sections: [
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CreateFlowProvider } from "../../app/(app)/create/context/CreateFlowCon
|
||||
import messages from "../../messages/en/index";
|
||||
|
||||
/**
|
||||
* Custom render function: MessagesProvider, AuthModalProvider (TopNav login), CreateFlowProvider.
|
||||
* Custom render function: MessagesProvider, AuthModalProvider (`Top` login), CreateFlowProvider.
|
||||
*/
|
||||
export function renderWithProviders(
|
||||
ui: ReactElement,
|
||||
|
||||
Reference in New Issue
Block a user