Implement use cases page

This commit is contained in:
adilallo
2026-05-17 21:41:54 -06:00
parent b6b9b63608
commit 450da4d8ab
78 changed files with 1870 additions and 118 deletions
+13
View File
@@ -109,4 +109,17 @@ describe("Icon (behavioral tests)", () => {
const card = screen.getByRole("button");
expect(card).toHaveAttribute("aria-label", "Test Title: Test Description");
});
it("uses article semantics when interactive is false", () => {
render(
<Icon
interactive={false}
icon={<div>Icon</div>}
title="Static Title"
description="Static Description"
/>,
);
expect(screen.getByRole("article")).toBeInTheDocument();
expect(screen.queryByRole("button")).not.toBeInTheDocument();
});
});
+21 -2
View File
@@ -1,8 +1,11 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import RelatedArticles from "../../app/components/sections/RelatedArticles";
import type { BlogPost } from "../../lib/content";
import {
renderWithProviders as render,
screen,
} from "../utils/test-utils";
vi.mock("next/link", () => ({
default: ({
@@ -63,7 +66,7 @@ const mockPosts: BlogPost[] = [
},
];
// Pure presentational; no provider context needed (mocked thumbnail + useIsMobile).
// MessagesProvider required — container uses useMessages().
describe("RelatedArticles", () => {
it("renders without crashing", () => {
render(
@@ -86,4 +89,20 @@ describe("RelatedArticles", () => {
expect(screen.queryByTestId("thumbnail-article-1")).not.toBeInTheDocument();
expect(screen.getByTestId("thumbnail-article-2")).toBeInTheDocument();
});
it("useCases variant shows localized stacked title", () => {
render(
<RelatedArticles
relatedPosts={mockPosts}
currentPostSlug="current"
variant="useCases"
/>,
);
expect(
screen.getByRole("heading", {
level: 2,
name: /Tools to set your group up for success/,
}),
).toBeInTheDocument();
});
});
+31
View File
@@ -0,0 +1,31 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import CaseStudy from "../../../app/components/cards/CaseStudy";
describe("CaseStudy", () => {
it("renders tile container", () => {
const { container } = render(
<CaseStudy surface="lavender" imageAlt="Mutual Aid Colorado logo" />,
);
expect(container.querySelector('[data-figma-node="21993-32352"]')).toBeTruthy();
});
it("renders built-in raster when visual is omitted (neutral)", () => {
render(
<CaseStudy surface="neutral" imageAlt="Food Not Bombs logo" />,
);
expect(
screen.getByRole("img", { name: "Food Not Bombs logo" }),
).toHaveAttribute("src");
});
it("uses Mutual Aid vector on lavender surface", () => {
const { container } = render(
<CaseStudy surface="lavender" imageAlt="Mutual Aid Colorado logo" />,
);
expect(container.querySelector("img")?.getAttribute("src")).toContain(
"case-study-mutual-aid.svg",
);
});
});
+44
View File
@@ -0,0 +1,44 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import Groups from "../../../app/components/sections/Groups";
describe("Groups", () => {
it("renders a static icon tile grid", () => {
const { container } = render(
<Groups
title="Who is this for?"
items={[
{
icon: <span data-testid="ico-a">a</span>,
title: "One",
description: "First description text.",
},
{
icon: <span data-testid="ico-b">b</span>,
title: "Two",
description: "Second description text.",
},
{
icon: <span data-testid="ico-c">c</span>,
title: "Three",
description: "Third description text.",
},
{
icon: <span data-testid="ico-d">d</span>,
title: "Four",
description: "Fourth description text.",
},
]}
/>,
);
expect(
screen.getByRole("heading", { level: 2, name: "Who is this for?" }),
).toBeInTheDocument();
expect(screen.getAllByRole("article")).toHaveLength(4);
expect(screen.queryByRole("button")).not.toBeInTheDocument();
expect(
container.querySelector('[data-figma-node="22085-860411"]'),
).toBeTruthy();
});
});
@@ -0,0 +1,20 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import UseCasesOrgs from "../../../app/components/sections/UseCasesOrgs";
describe("UseCasesOrgs", () => {
it("renders children", () => {
const { container } = render(
<UseCasesOrgs>
<div>Child A</div>
<div>Child B</div>
</UseCasesOrgs>,
);
expect(screen.getByText("Child A")).toBeInTheDocument();
expect(screen.getByText("Child B")).toBeInTheDocument();
expect(
container.querySelector('[data-figma-node="21993-33687"]'),
).toBeTruthy();
});
});
+72
View File
@@ -0,0 +1,72 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import PageHeader from "../../../app/components/type/PageHeader";
describe("PageHeader", () => {
it("renders title and description", () => {
render(
<PageHeader
title="Test title"
description="Test description body."
ctaText="Go"
ctaHref="/create"
/>,
);
expect(
screen.getByRole("heading", { level: 1, name: "Test title" }),
).toBeInTheDocument();
expect(screen.getByText("Test description body.")).toBeInTheDocument();
expect(screen.getByRole("link", { name: "Go" })).toHaveAttribute(
"href",
"/create",
);
});
it("omits CTA when ctaText is absent", () => {
render(
<PageHeader title="Only title" description="Only description" />,
);
expect(screen.queryByRole("link")).not.toBeInTheDocument();
});
it("omits description when omitted and renders stacked centered title lines", () => {
render(
<PageHeader
title={["See how groups use", "CommunityRule"]}
headingAlign="center"
sectionMinimal
/>,
);
const heading = screen.getByRole("heading", { level: 1 });
expect(heading).toHaveTextContent(/See how groups useCommunityRule/);
expect(
heading.querySelectorAll("span.block"),
).toHaveLength(2);
expect(screen.queryByRole("paragraph")).not.toBeInTheDocument();
expect(screen.queryByRole("link")).not.toBeInTheDocument();
});
it("renders use-cases lg single-line title segments when singleLineTitleFromLg", () => {
render(
<PageHeader
title={["See how groups use", "CommunityRule"]}
headingAlign="center"
sectionMinimal
singleLineTitleFromLg
/>,
);
const heading = screen.getByRole("heading", { level: 1 });
expect(heading).toHaveTextContent(/See how groups use CommunityRule/);
expect(
heading.querySelectorAll("span.block.lg\\:inline"),
).toHaveLength(2);
expect(heading.closest("section")).toHaveAttribute(
"data-figma-node",
"21004-24825",
);
});
});
+32
View File
@@ -0,0 +1,32 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import TripleStep from "../../../app/components/type/TripleStep";
describe("TripleStep", () => {
it("renders heading, steps, and CTA", () => {
render(
<TripleStep
heading="Get organized"
steps={[
{ title: "Step one", body: "Body one." },
{ title: "Step two", body: "Body two." },
{ title: "Step three", body: "Body three." },
]}
ctaText="Create Rule"
ctaHref="/create"
/>,
);
expect(
screen.getByRole("heading", { level: 2, name: "Get organized" }),
).toBeInTheDocument();
expect(
document.querySelector('[data-figma-node="22084-859405"]'),
).toBeTruthy();
expect(screen.getByText("Step one")).toBeInTheDocument();
expect(screen.getByRole("link", { name: "Create Rule" })).toHaveAttribute(
"href",
"/create",
);
});
});
@@ -48,4 +48,45 @@ describe("TripleTextBlock", () => {
);
expect(screen.getByText("Only body.")).toBeInTheDocument();
});
it("useCases preset renders persistent section heading, column h3 titles, dual paragraphs, outline CTA", () => {
const { container } = render(
<TripleTextBlock
layoutPreset="useCases"
title="Why Horizontal groups need CommunityRule"
ctaText="Setup your community"
ctaHref="/create"
columns={[
{
title: "Share Leadership",
description: "First paragraph.",
descriptionSecondary: "Second paragraph.",
},
]}
/>,
);
expect(
container.querySelector('[data-figma-node="22085-860414"]'),
).toBeTruthy();
expect(
screen.getByRole("heading", {
level: 2,
name: "Why Horizontal groups need CommunityRule",
}),
).toBeInTheDocument();
expect(
screen.getByRole("heading", {
level: 3,
name: "Share Leadership",
}),
).toBeInTheDocument();
expect(screen.getByText("First paragraph.")).toBeInTheDocument();
expect(screen.getByText("Second paragraph.")).toBeInTheDocument();
expect(screen.getByRole("link", { name: "Setup your community" })).toHaveAttribute(
"href",
"/create",
);
});
});
+5 -1
View File
@@ -223,7 +223,7 @@ describe("QuoteBlock Component", () => {
).not.toBeInTheDocument();
});
test("statement variant renders dual paragraphs without attribution", () => {
test("statement variant uses one paragraph with responsive stack (Figma 21967-24638)", () => {
render(
<QuoteBlock
variant="statement"
@@ -237,10 +237,14 @@ describe("QuoteBlock Component", () => {
name: /first paragraph of the statement/i,
});
expect(region).toBeInTheDocument();
expect(region).toHaveAttribute("data-figma-node", "21967-24638");
expect(
screen.getByText("Second paragraph of the statement."),
).toBeInTheDocument();
expect(screen.queryByRole("cite")).not.toBeInTheDocument();
const heading = region.querySelector("#about-test-quote-content");
expect(heading?.querySelectorAll("span.block.lg\\:inline").length).toBe(2);
});
test("statement variant logs when quoteSecondary is missing", () => {
+26
View File
@@ -74,6 +74,25 @@ describe("RuleStack Component", () => {
expect(fetchMock.mock.calls.length).toBe(callsBefore);
});
test("uses translationNamespace for section heading copy", () => {
render(
<RuleStack
initialGridEntries={homeFeatured}
translationNamespace="pages.useCases.ruleStack"
/>,
);
const heading = screen.getByRole("heading", { level: 2 });
expect(heading.textContent).toMatch(
/Get Templates that help your community run smoothly/,
);
});
test("defaults to home rule stack heading copy when namespace omitted", () => {
render(<RuleStack initialGridEntries={homeFeatured} />);
const heading = screen.getByRole("heading", { level: 2 });
expect(heading.textContent).toMatch(/Popular templates/);
});
test("renders four featured governance template cards on the home row", async () => {
render(<RuleStack />);
await waitForRuleStackCards();
@@ -166,6 +185,7 @@ describe("RuleStack Component", () => {
await waitForRuleStackCards();
const section = document.querySelector("section");
expect(section).toHaveAttribute("data-figma-node", "22085-860413");
expect(section).toHaveClass("px-[20px]", "py-[32px]");
expect(section?.className).toMatch(/min-\[640px\]:px-\[32px\]/);
expect(section?.className).toMatch(/min-\[640px\]:py-\[48px\]/);
@@ -281,6 +301,12 @@ describe("RuleStack Component", () => {
expect(circlesIcon?.className).toMatch(
/min-\[640px\]:max-\[1023px\]:h-\[56px\]/,
);
expect(circlesIcon?.className).toMatch(
/min-\[1024px\]:max-\[1439px\]:w-\[90px\]/,
);
expect(circlesIcon?.className).toMatch(
/min-\[1024px\]:max-\[1439px\]:h-\[90px\]/,
);
expect(circlesIcon?.className).toMatch(/min-\[1440px\]:w-\[90px\]/);
expect(circlesIcon?.className).toMatch(/min-\[1440px\]:h-\[90px\]/);
});