Testing Framwork #17

Merged
an.di merged 83 commits from adilallo/enhancement/TestingFramework2 into main 2025-09-03 18:50:40 +00:00
7 changed files with 1340 additions and 0 deletions
Showing only changes of commit c8d19089a7 - Show all commits
+266
View File
@@ -0,0 +1,266 @@
import { render, screen, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, describe, test, expect, afterEach } from "vitest";
import AskOrganizer from "../../app/components/AskOrganizer";
afterEach(() => {
cleanup();
});
describe("AskOrganizer Component", () => {
test("renders with all props", () => {
render(
<AskOrganizer
title="Need help organizing?"
subtitle="Get expert guidance"
description="Our organizers can help you build better communities"
buttonText="Contact an organizer"
buttonHref="/contact"
/>
);
expect(
screen.getByRole("heading", { name: "Need help organizing?" })
).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Get expert guidance" })
).toBeInTheDocument();
expect(
screen.getByText("Our organizers can help you build better communities")
).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Contact an organizer" })
).toBeInTheDocument();
});
test("renders with default button text", () => {
render(<AskOrganizer title="Test" subtitle="Test" description="Test" />);
expect(
screen.getByRole("button", { name: "Ask an organizer" })
).toBeInTheDocument();
});
test("renders with custom className", () => {
render(
<AskOrganizer title="Test" subtitle="Test" className="custom-class" />
);
const section = document.querySelector("section");
expect(section).toHaveClass("custom-class");
});
test("renders different variants", () => {
const { rerender } = render(
<AskOrganizer title="Test" subtitle="Test" variant="centered" />
);
// Centered variant should have center alignment
const container = screen
.getByRole("region")
.querySelector('[class*="text-center"]');
expect(container).toBeInTheDocument();
rerender(
<AskOrganizer title="Test" subtitle="Test" variant="left-aligned" />
);
// Left-aligned variant should have left alignment
const leftContainer = screen
.getByRole("region")
.querySelector('[class*="text-left"]');
expect(leftContainer).toBeInTheDocument();
});
test("renders ContentLockup with ask variant", () => {
render(
<AskOrganizer
title="Ask Title"
subtitle="Ask Subtitle"
description="Ask Description"
/>
);
expect(
screen.getByRole("heading", { name: "Ask Title" })
).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Ask Subtitle" })
).toBeInTheDocument();
expect(screen.getByText("Ask Description")).toBeInTheDocument();
});
test("renders button with correct props", () => {
render(
<AskOrganizer
title="Test"
subtitle="Test"
buttonText="Custom Button"
buttonHref="/custom"
/>
);
const button = screen.getByRole("button", { name: "Custom Button" });
expect(button).toHaveAttribute("href", "/custom");
expect(button).toHaveClass("size-large", "variant-default");
});
test("handles button click events", async () => {
const user = userEvent.setup();
const onContactClick = vi.fn();
render(
<AskOrganizer
title="Test"
subtitle="Test"
onContactClick={onContactClick}
/>
);
const button = screen.getByRole("button", { name: "Ask an organizer" });
await user.click(button);
expect(onContactClick).toHaveBeenCalledWith({
event: "contact_button_click",
component: "AskOrganizer",
variant: "centered",
buttonText: "Ask an organizer",
buttonHref: "#",
timestamp: expect.any(String),
});
});
test("applies analytics tracking", async () => {
const user = userEvent.setup();
const gtagSpy = vi.fn();
// Mock window.gtag
Object.defineProperty(window, "gtag", {
value: gtagSpy,
writable: true,
});
render(<AskOrganizer title="Test" subtitle="Test" />);
const button = screen.getByRole("button", { name: "Ask an organizer" });
await user.click(button);
expect(gtagSpy).toHaveBeenCalledWith("event", "contact_button_click", {
event_category: "engagement",
event_label: "ask_organizer",
value: 1,
});
});
test("renders with proper accessibility attributes", () => {
render(
<AskOrganizer title="Test" subtitle="Test" buttonText="Custom Button" />
);
const section = document.querySelector("section");
expect(section).toHaveAttribute(
"aria-labelledby",
"ask-organizer-headline"
);
expect(section).toHaveAttribute("tabIndex", "-1");
const button = screen.getByRole("button", { name: "Custom Button" });
expect(button).toHaveAttribute(
"aria-label",
"Custom Button - Contact an organizer for help"
);
});
test("renders with design tokens", () => {
render(<AskOrganizer title="Test" subtitle="Test" />);
const section = document.querySelector("section");
expect(section).toHaveClass(
"py-[var(--spacing-scale-032)]",
"px-[var(--spacing-scale-032)]"
);
});
test("applies responsive spacing", () => {
render(<AskOrganizer title="Test" subtitle="Test" />);
const section = document.querySelector("section");
expect(section).toHaveClass(
"md:py-[var(--spacing-scale-096)]",
"md:px-[var(--spacing-scale-064)]"
);
});
test("renders with proper semantic structure", () => {
render(<AskOrganizer title="Test" subtitle="Test" />);
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
// Check for proper heading structure
const headings = screen.getAllByRole("heading");
expect(headings).toHaveLength(2); // title and subtitle
});
test("applies variant-specific styling", () => {
const { rerender } = render(
<AskOrganizer title="Test" subtitle="Test" variant="compact" />
);
// Compact variant should have different padding
const section = screen.getByRole("region");
expect(section).toHaveClass(
"py-[var(--spacing-scale-016)]",
"px-[var(--spacing-scale-016)]"
);
rerender(
<AskOrganizer title="Test" subtitle="Test" variant="left-aligned" />
);
// Left-aligned variant should have left alignment
const container = section.querySelector('[class*="text-left"]');
expect(container).toBeInTheDocument();
});
test("renders button with custom styling", () => {
render(<AskOrganizer title="Test" subtitle="Test" />);
const button = screen.getByRole("button", { name: "Ask an organizer" });
expect(button).toHaveClass(
"xl:!px-[var(--spacing-scale-020)]",
"xl:!py-[var(--spacing-scale-012)]"
);
});
test("handles missing optional props gracefully", () => {
render(<AskOrganizer title="Test" />);
// Should still render the structure
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
// Should render default button
expect(
screen.getByRole("button", { name: "Ask an organizer" })
).toBeInTheDocument();
});
test("applies responsive button container alignment", () => {
render(<AskOrganizer title="Test" subtitle="Test" variant="centered" />);
const buttonContainer = screen
.getByRole("button", { name: "Ask an organizer" })
.closest("div");
expect(buttonContainer).toHaveClass("flex", "justify-center");
});
test("renders with proper content gap", () => {
render(<AskOrganizer title="Test" subtitle="Test" variant="compact" />);
const container = screen
.getByRole("region")
.querySelector('[class*="flex flex-col"]');
expect(container).toHaveClass("gap-[var(--spacing-scale-020)]");
});
});
+145
View File
@@ -0,0 +1,145 @@
import { render, screen, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, describe, test, expect, afterEach } from "vitest";
import FeatureGrid from "../../app/components/FeatureGrid";
afterEach(() => {
cleanup();
});
describe("FeatureGrid Component", () => {
test("renders with title and subtitle", () => {
render(
<FeatureGrid
title="Feature Tools"
subtitle="Everything you need to build better communities"
/>
);
expect(
screen.getByRole("heading", { name: "Feature Tools" })
).toBeInTheDocument();
expect(
screen.getByRole("heading", {
name: "Everything you need to build better communities",
})
).toBeInTheDocument();
});
test("renders with custom className", () => {
render(
<FeatureGrid title="Test" subtitle="Test" className="custom-class" />
);
const section = document.querySelector("section");
expect(section).toHaveClass("custom-class");
});
test("renders all four MiniCard components", () => {
render(<FeatureGrid title="Test" subtitle="Test" />);
// Check for all four MiniCard components
expect(
screen.getByRole("link", { name: "Decision-making support tools" })
).toBeInTheDocument();
expect(
screen.getByRole("link", { name: "Values alignment exercises" })
).toBeInTheDocument();
expect(
screen.getByRole("link", { name: "Membership guidance resources" })
).toBeInTheDocument();
expect(
screen.getByRole("link", { name: "Conflict resolution tools" })
).toBeInTheDocument();
});
test("renders ContentLockup with feature variant", () => {
render(<FeatureGrid title="Feature Title" subtitle="Feature Subtitle" />);
expect(
screen.getByRole("heading", { name: "Feature Title" })
).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Feature Subtitle" })
).toBeInTheDocument();
expect(
screen.getByRole("link", { name: "Learn more" })
).toBeInTheDocument();
});
test("has proper accessibility attributes", () => {
render(<FeatureGrid title="Test" subtitle="Test" />);
const section = document.querySelector("section");
expect(section).toHaveAttribute("aria-labelledby", "feature-grid-headline");
expect(section).toHaveAttribute("tabIndex", "-1");
const grid = screen.getByRole("grid");
expect(grid).toHaveAttribute("aria-label", "Feature tools and services");
});
test("renders with design tokens", () => {
render(<FeatureGrid title="Test" subtitle="Test" />);
const section = document.querySelector("section");
expect(section).toHaveClass("p-0", "lg:p-[var(--spacing-scale-064)]");
const container = section.querySelector('[class*="bg-[#171717]"]');
expect(container).toBeInTheDocument();
});
test("applies responsive grid layout", () => {
render(<FeatureGrid title="Test" subtitle="Test" />);
const grid = screen.getByRole("grid");
expect(grid).toHaveClass("grid", "grid-cols-2", "md:grid-cols-4");
});
test("renders MiniCard with correct props", () => {
render(<FeatureGrid title="Test" subtitle="Test" />);
// Check first MiniCard (Decision-making support)
const firstCard = screen.getByRole("link", {
name: "Decision-making support tools",
});
expect(firstCard).toHaveAttribute("href", "#decision-making");
// Check second MiniCard (Values alignment)
const secondCard = screen.getByRole("link", {
name: "Values alignment exercises",
});
expect(secondCard).toHaveAttribute("href", "#values-alignment");
});
test("renders with proper semantic structure", () => {
render(<FeatureGrid title="Test" subtitle="Test" />);
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
const grid = screen.getByRole("grid");
expect(grid).toBeInTheDocument();
});
test("handles missing optional props gracefully", () => {
render(<FeatureGrid />);
// Should still render the structure
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
// Should render default MiniCards
expect(
screen.getByRole("link", { name: "Decision-making support tools" })
).toBeInTheDocument();
});
test("applies focus-within styles", () => {
render(<FeatureGrid title="Test" subtitle="Test" />);
const container = document
.querySelector("section")
.querySelector('[class*="focus-within:ring-2"]');
expect(container).toBeInTheDocument();
});
});
+142
View File
@@ -0,0 +1,142 @@
import { render, screen, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, describe, test, expect, afterEach } from "vitest";
import HeroBanner from "../../app/components/HeroBanner";
afterEach(() => {
cleanup();
});
describe("HeroBanner Component", () => {
test("renders with all props", () => {
render(
<HeroBanner
title="Welcome to CommunityRule"
subtitle="Build better communities"
description="Create and manage community rules with ease"
ctaText="Get Started"
ctaHref="/signup"
/>
);
expect(
screen.getByRole("heading", { name: "Welcome to CommunityRule" })
).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Build better communities" })
).toBeInTheDocument();
expect(
screen.getByText("Create and manage community rules with ease")
).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Get Started" })
).toBeInTheDocument();
});
test("renders with minimal props", () => {
render(<HeroBanner title="Minimal" />);
expect(
screen.getByRole("heading", { name: "Minimal" })
).toBeInTheDocument();
expect(
screen.getByRole("img", { name: "Hero illustration" })
).toBeInTheDocument();
});
test("renders hero image", () => {
render(<HeroBanner title="Test" />);
const heroImage = screen.getByRole("img", { name: "Hero illustration" });
expect(heroImage).toBeInTheDocument();
expect(heroImage).toHaveAttribute("src", "assets/HeroImage.png");
});
test("applies correct CSS classes", () => {
render(<HeroBanner title="Test" />);
const section = document.querySelector("section");
expect(section).toHaveClass("bg-transparent");
const contentLockup = screen
.getByRole("heading", { name: "Test" })
.closest("div");
expect(contentLockup).toHaveClass("md:flex-1");
});
test("renders ContentLockup with correct props", () => {
render(
<HeroBanner
title="Test Title"
subtitle="Test Subtitle"
description="Test Description"
ctaText="Test CTA"
ctaHref="/test"
/>
);
// Check that ContentLockup receives the props correctly
expect(
screen.getByRole("heading", { name: "Test Title" })
).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Test Subtitle" })
).toBeInTheDocument();
expect(screen.getByText("Test Description")).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Test CTA" })
).toBeInTheDocument();
});
test("renders HeroDecor component", () => {
render(<HeroBanner title="Test" />);
// HeroDecor should be present (it's a decorative component)
const heroDecor = document.querySelector(
'[class*="pointer-events-none absolute z-0"]'
);
expect(heroDecor).toBeInTheDocument();
});
test("has proper semantic structure", () => {
render(<HeroBanner title="Test" />);
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
// Should have proper heading structure
const heading = screen.getByRole("heading", { name: "Test" });
expect(heading).toBeInTheDocument();
});
test("handles empty title gracefully", () => {
render(<HeroBanner title="" />);
// Should still render the structure even with empty title
const section = screen.getByRole("region");
expect(section).toBeInTheDocument();
});
test("applies responsive design classes", () => {
render(<HeroBanner title="Test" />);
const section = document.querySelector("section");
expect(section).toHaveClass(
"px-[var(--spacing-scale-008)]",
"sm:px-[var(--spacing-scale-010)]"
);
});
test("renders with design tokens", () => {
render(<HeroBanner title="Test" />);
const section = document.querySelector("section");
expect(section).toHaveClass("bg-transparent");
// Check for design token usage in the component structure
const container = section.querySelector(
'[class*="bg-[var(--color-surface-inverse-brand-primary)]"]'
);
expect(container).toBeInTheDocument();
});
});
+166
View File
@@ -0,0 +1,166 @@
import { render, screen, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, describe, test, expect, afterEach } from "vitest";
import LogoWall from "../../app/components/LogoWall";
afterEach(() => {
cleanup();
});
describe("LogoWall Component", () => {
test("renders with default logos", () => {
render(<LogoWall />);
// Check for default logos
expect(screen.getByAltText("Food Not Bombs")).toBeInTheDocument();
expect(screen.getByAltText("Start COOP")).toBeInTheDocument();
expect(screen.getByAltText("Metagov")).toBeInTheDocument();
expect(screen.getByAltText("Open Civics")).toBeInTheDocument();
expect(screen.getByAltText("Mutual Aid CO")).toBeInTheDocument();
expect(screen.getByAltText("CU Boulder")).toBeInTheDocument();
});
test("renders with custom logos", () => {
const customLogos = [
{
src: "assets/custom1.png",
alt: "Custom Logo 1",
size: "h-8",
order: "order-1",
},
{
src: "assets/custom2.png",
alt: "Custom Logo 2",
size: "h-10",
order: "order-2",
},
];
render(<LogoWall logos={customLogos} />);
expect(screen.getByAltText("Custom Logo 1")).toBeInTheDocument();
expect(screen.getByAltText("Custom Logo 2")).toBeInTheDocument();
expect(screen.queryByAltText("Food Not Bombs")).not.toBeInTheDocument();
});
test("renders section label", () => {
render(<LogoWall />);
expect(
screen.getByText("Trusted by leading cooperators")
).toBeInTheDocument();
});
test("applies correct CSS classes", () => {
render(<LogoWall />);
const section = document.querySelector("section");
expect(section).toHaveClass("p-[var(--spacing-scale-032)]");
});
test("renders with design tokens", () => {
render(<LogoWall />);
const section = document.querySelector("section");
expect(section).toHaveClass(
"p-[var(--spacing-scale-032)]",
"md:px-[var(--spacing-scale-024)]"
);
});
test("applies responsive grid layout", () => {
render(<LogoWall />);
const grid = document.querySelector(
'[class*="grid grid-cols-2 grid-rows-3"]'
);
expect(grid).toBeInTheDocument();
expect(grid).toHaveClass("sm:grid-cols-3", "sm:grid-rows-2", "md:flex");
});
test("renders logos with correct attributes", () => {
render(<LogoWall />);
const foodNotBombsLogo = screen.getByAltText("Food Not Bombs");
expect(foodNotBombsLogo).toHaveAttribute(
"src",
"assets/Section/Logo_FoodNotBombs.png"
);
expect(foodNotBombsLogo).toHaveClass("h-11", "lg:h-14", "xl:h-[70px]");
});
test("applies order classes for responsive layout", () => {
render(<LogoWall />);
const foodNotBombsContainer = screen
.getByAltText("Food Not Bombs")
.closest("div");
expect(foodNotBombsContainer).toHaveClass("order-1", "sm:order-4");
});
test("handles empty logos array", () => {
render(<LogoWall logos={[]} />);
// Should fall back to default logos
expect(screen.getByAltText("Food Not Bombs")).toBeInTheDocument();
});
test("applies hover effects", () => {
render(<LogoWall />);
const logoContainers = document.querySelectorAll(
'[class*="hover:opacity-100"]'
);
expect(logoContainers.length).toBeGreaterThan(0);
});
test("renders with proper semantic structure", () => {
render(<LogoWall />);
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
// Check for the label
const label = screen.getByText("Trusted by leading cooperators");
expect(label).toBeInTheDocument();
});
test("applies transition effects", () => {
render(<LogoWall />);
const logoContainers = document.querySelectorAll(
'[class*="transition-opacity duration-500"]'
);
expect(logoContainers.length).toBeGreaterThan(0);
});
test("renders with proper image optimization", () => {
render(<LogoWall />);
const logos = screen.getAllByRole("img");
logos.forEach((logo) => {
expect(logo).toHaveAttribute("unoptimized");
expect(logo).toHaveAttribute("sizes", "100vw");
});
});
test("prioritizes first two logos", () => {
render(<LogoWall />);
const logos = screen.getAllByRole("img");
const foodNotBombsLogo = logos.find((img) => img.alt === "Food Not Bombs");
const startCoopLogo = logos.find((img) => img.alt === "Start COOP");
expect(foodNotBombsLogo).toHaveAttribute("priority");
expect(startCoopLogo).toHaveAttribute("priority");
});
test("applies scale effect on hover", () => {
render(<LogoWall />);
const logos = screen.getAllByRole("img");
logos.forEach((logo) => {
expect(logo).toHaveClass("hover:scale-105");
});
});
});
+199
View File
@@ -0,0 +1,199 @@
import { render, screen, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, describe, test, expect, afterEach } from "vitest";
import NumberedCards from "../../app/components/NumberedCards";
afterEach(() => {
cleanup();
});
describe("NumberedCards Component", () => {
const mockCards = [
{
text: "Define your community values",
iconShape: "circle",
iconColor: "blue",
},
{
text: "Create decision-making processes",
iconShape: "square",
iconColor: "green",
},
{
text: "Establish communication channels",
iconShape: "triangle",
iconColor: "red",
},
];
test("renders with title, subtitle, and cards", () => {
render(
<NumberedCards
title="How CommunityRule helps"
subtitle="Build better communities step by step"
cards={mockCards}
/>
);
expect(
screen.getByRole("heading", { name: "How CommunityRule helps" })
).toBeInTheDocument();
expect(
screen.getByRole("heading", {
name: "Build better communities step by step",
})
).toBeInTheDocument();
// Check for card content
expect(
screen.getByText("Define your community values")
).toBeInTheDocument();
expect(
screen.getByText("Create decision-making processes")
).toBeInTheDocument();
expect(
screen.getByText("Establish communication channels")
).toBeInTheDocument();
});
test("renders SectionHeader component", () => {
render(
<NumberedCards
title="Test Title"
subtitle="Test Subtitle"
cards={mockCards}
/>
);
expect(
screen.getByRole("heading", { name: "Test Title" })
).toBeInTheDocument();
expect(
screen.getByRole("heading", { name: "Test Subtitle" })
).toBeInTheDocument();
});
test("renders NumberedCard components with correct props", () => {
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
// Check that NumberedCard 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
});
test("renders call-to-action buttons", () => {
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
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} />);
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");
});
test("renders with design tokens", () => {
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
const section = document.querySelector("section");
expect(section).toHaveClass(
"bg-transparent",
"py-[var(--spacing-scale-032)]"
);
});
test("applies responsive grid layout", () => {
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
const cardsContainer = document.querySelector(
'[class*="grid grid-cols-1"]'
);
expect(cardsContainer).toBeInTheDocument();
});
test("renders schema markup", () => {
render(
<NumberedCards
title="Test Title"
subtitle="Test Description"
cards={mockCards}
/>
);
const script = document.querySelector('script[type="application/ld+json"]');
expect(script).toBeInTheDocument();
const schemaData = JSON.parse(script.textContent);
expect(schemaData["@type"]).toBe("HowTo");
expect(schemaData.name).toBe("Test Title");
expect(schemaData.description).toBe("Test Description");
expect(schemaData.step).toHaveLength(3);
});
test("has proper semantic structure", () => {
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
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={[]} />);
// 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" })
).toBeInTheDocument();
});
test("applies responsive text alignment", () => {
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
const buttonContainer = screen
.getByRole("button", { name: "Create CommunityRule" })
.closest("div");
expect(buttonContainer).toHaveClass("block", "lg:hidden");
});
test("renders with proper spacing", () => {
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
const section = document.querySelector("section");
expect(section).toHaveClass(
"py-[var(--spacing-scale-032)]",
"sm:py-[var(--spacing-scale-048)]"
);
});
test("applies max-width constraint", () => {
render(<NumberedCards title="Test" subtitle="Test" cards={mockCards} />);
const container = document.querySelector(
'[class*="max-w-[var(--spacing-measures-max-width-lg)]"]'
);
expect(container).toBeInTheDocument();
});
});
+222
View File
@@ -0,0 +1,222 @@
import { render, screen, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, describe, test, expect, afterEach } from "vitest";
import QuoteBlock from "../../app/components/QuoteBlock";
afterEach(() => {
cleanup();
});
describe("QuoteBlock Component", () => {
test("renders with default props", () => {
render(<QuoteBlock />);
expect(
screen.getByText(/The rules of decision-making must be open/)
).toBeInTheDocument();
expect(screen.getByText("Jo Freeman")).toBeInTheDocument();
expect(
screen.getByText("The Tyranny of Structurelessness")
).toBeInTheDocument();
expect(screen.getByAltText("Portrait of Jo Freeman")).toBeInTheDocument();
});
test("renders with custom props", () => {
render(
<QuoteBlock
quote="Custom quote text"
author="Custom Author"
source="Custom Source"
/>
);
expect(screen.getByText("Custom quote text")).toBeInTheDocument();
expect(screen.getByText("Custom Author")).toBeInTheDocument();
expect(screen.getByText("Custom Source")).toBeInTheDocument();
});
test("renders with custom className", () => {
render(
<QuoteBlock
quote="Test quote"
author="Test Author"
className="custom-class"
/>
);
const section = document.querySelector("section");
expect(section).toHaveClass("custom-class");
});
test("renders different variants", () => {
const { rerender } = render(
<QuoteBlock quote="Test quote" author="Test Author" variant="compact" />
);
// Compact variant should have different styling
const section = screen.getByRole("region");
expect(section).toHaveClass(
"py-[var(--spacing-scale-032)]",
"px-[var(--spacing-scale-016)]"
);
rerender(
<QuoteBlock quote="Test quote" author="Test Author" variant="extended" />
);
// Extended variant should have different styling
expect(section).toHaveClass(
"py-[var(--spacing-scale-048)]",
"px-[var(--spacing-scale-024)]"
);
});
test("renders with custom ID", () => {
render(
<QuoteBlock
quote="Test quote"
author="Test Author"
id="custom-quote-id"
/>
);
const quoteElement = screen.getByText("Test quote");
expect(quoteElement).toBeInTheDocument();
});
test("handles image error gracefully", () => {
render(<QuoteBlock quote="Test quote" author="Test Author" />);
// Should render the quote and author
expect(screen.getByText("Test quote")).toBeInTheDocument();
expect(screen.getByText("Test Author")).toBeInTheDocument();
});
test("calls onError callback when image fails", () => {
const onError = vi.fn();
render(
<QuoteBlock quote="Test quote" author="Test Author" onError={onError} />
);
// Should render without errors
expect(screen.getByText("Test quote")).toBeInTheDocument();
});
test("renders with fallback avatar", () => {
render(<QuoteBlock quote="Test quote" author="Test Author" />);
// Should render the quote and author
expect(screen.getByText("Test quote")).toBeInTheDocument();
expect(screen.getByText("Test Author")).toBeInTheDocument();
});
test("renders decorative elements for standard variant", () => {
render(
<QuoteBlock quote="Test quote" author="Test Author" variant="standard" />
);
// Should render QuoteDecor for standard variant
const decor = document.querySelector(
'[class*="pointer-events-none absolute z-0"]'
);
expect(decor).toBeInTheDocument();
});
test("does not render decorative elements for compact variant", () => {
render(
<QuoteBlock quote="Test quote" author="Test Author" variant="compact" />
);
// Should not render QuoteDecor for compact variant
const decor = document.querySelector(
'[class*="pointer-events-none absolute z-0"]'
);
expect(decor).not.toBeInTheDocument();
});
test("renders with proper semantic structure", () => {
render(<QuoteBlock quote="Test quote" author="Test Author" />);
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
const blockquote = document.querySelector("blockquote");
expect(blockquote).toBeInTheDocument();
const cite = document.querySelector("cite");
expect(cite).toBeInTheDocument();
});
test("applies correct accessibility attributes", () => {
render(
<QuoteBlock quote="Test quote" author="Test Author" id="test-quote" />
);
const section = document.querySelector("section");
expect(section).toHaveAttribute("aria-labelledby", "test-quote-content");
const blockquote = document.querySelector("blockquote");
expect(blockquote).toHaveAttribute("aria-labelledby", "test-quote-author");
});
test("renders with design tokens", () => {
render(<QuoteBlock quote="Test quote" author="Test Author" />);
const section = document.querySelector("section");
expect(section).toHaveClass("md:py-[var(--spacing-scale-032)]");
const card = section.querySelector(
'[class*="bg-[var(--color-surface-default-brand-darker-accent)]"]'
);
expect(card).toBeInTheDocument();
});
test("handles missing required props", () => {
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
render(<QuoteBlock quote="" author="" />);
expect(consoleSpy).toHaveBeenCalledWith(
"QuoteBlock: Missing required props (quote or author)"
);
consoleSpy.mockRestore();
});
test("renders with proper quote styling", () => {
render(<QuoteBlock quote="Test quote" author="Test Author" />);
const quoteElement = screen.getByText("Test quote");
expect(quoteElement).toHaveClass("font-bricolage-grotesque", "font-normal");
});
test("renders with proper author styling", () => {
render(<QuoteBlock quote="Test quote" author="Test Author" />);
const authorElement = screen.getByText("Test Author");
expect(authorElement).toHaveClass("font-inter", "font-normal", "uppercase");
});
test("applies responsive text sizing", () => {
render(
<QuoteBlock quote="Test quote" author="Test Author" variant="standard" />
);
const quoteElement = screen.getByText("Test quote");
expect(quoteElement).toHaveClass(
"text-[18px]",
"md:text-[36px]",
"lg:text-[52px]"
);
});
test("renders without source when not provided", () => {
render(<QuoteBlock quote="Test quote" author="Test Author" source="" />);
expect(screen.getByText("Test quote")).toBeInTheDocument();
expect(screen.getByText("Test Author")).toBeInTheDocument();
expect(
screen.queryByText("The Tyranny of Structurelessness")
).not.toBeInTheDocument();
});
});
+200
View File
@@ -0,0 +1,200 @@
import { render, screen, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, describe, test, expect, afterEach } from "vitest";
import RuleStack from "../../app/components/RuleStack";
afterEach(() => {
cleanup();
});
describe("RuleStack Component", () => {
test("renders all four rule cards", () => {
render(<RuleStack />);
expect(screen.getByText("Consensus clusters")).toBeInTheDocument();
expect(screen.getByText("Consensus")).toBeInTheDocument();
expect(screen.getByText("Elected Board")).toBeInTheDocument();
expect(screen.getByText("Petition")).toBeInTheDocument();
});
test("renders with custom className", () => {
render(<RuleStack className="custom-class" />);
const section = document.querySelector("section");
expect(section).toHaveClass("custom-class");
});
test("renders rule card descriptions", () => {
render(<RuleStack />);
expect(
screen.getByText(/Units called Circles have the ability to decide/)
).toBeInTheDocument();
expect(
screen.getByText(/Decisions that affect the group collectively/)
).toBeInTheDocument();
expect(
screen.getByText(/An elected board determines policies/)
).toBeInTheDocument();
expect(
screen.getByText(/All participants can propose and vote/)
).toBeInTheDocument();
});
test("renders rule card icons", () => {
render(<RuleStack />);
expect(screen.getByAltText("Sociocracy")).toBeInTheDocument();
expect(screen.getByAltText("Consensus")).toBeInTheDocument();
expect(screen.getByAltText("Elected Board")).toBeInTheDocument();
expect(screen.getByAltText("Petition")).toBeInTheDocument();
});
test("renders call-to-action button", () => {
render(<RuleStack />);
expect(
screen.getByRole("button", { name: "See all templates" })
).toBeInTheDocument();
});
test("applies correct CSS classes", () => {
render(<RuleStack />);
const section = document.querySelector("section");
expect(section).toHaveClass("w-full", "bg-transparent");
});
test("renders with design tokens", () => {
render(<RuleStack />);
const section = document.querySelector("section");
expect(section).toHaveClass(
"py-[var(--spacing-scale-032)]",
"px-[var(--spacing-scale-020)]"
);
});
test("applies responsive grid layout", () => {
render(<RuleStack />);
const grid = document.querySelector('[class*="flex flex-col gap-[18px]"]');
expect(grid).toHaveClass("xmd:grid", "xmd:grid-cols-2");
});
test("renders RuleCard components with correct props", () => {
render(<RuleStack />);
// Check that RuleCard components receive correct props
const consensusClustersCard = screen
.getByText("Consensus clusters")
.closest('[class*="bg-[var(--color-surface-default-brand-lime)]"]');
expect(consensusClustersCard).toBeInTheDocument();
const consensusCard = screen
.getByText("Consensus")
.closest('[class*="bg-[var(--color-surface-default-brand-rust)]"]');
expect(consensusCard).toBeInTheDocument();
});
test("handles template click events", async () => {
const user = userEvent.setup();
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
render(<RuleStack />);
const consensusCard = screen.getByText("Consensus").closest("div");
await user.click(consensusCard);
expect(consoleSpy).toHaveBeenCalledWith("Consensus template clicked");
consoleSpy.mockRestore();
});
test("renders with proper semantic structure", () => {
render(<RuleStack />);
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
// Check for proper heading structure in cards
const headings = screen.getAllByRole("heading");
expect(headings).toHaveLength(4); // Four rule cards
});
test("applies responsive spacing", () => {
render(<RuleStack />);
const section = document.querySelector("section");
expect(section).toHaveClass(
"md:py-[var(--spacing-scale-048)]",
"lg:py-[var(--spacing-scale-064)]"
);
});
test("renders icons with correct attributes", () => {
render(<RuleStack />);
const sociocracyIcon = screen.getByAltText("Sociocracy");
expect(sociocracyIcon).toHaveAttribute("src", "assets/Icon_Sociocracy.svg");
expect(sociocracyIcon).toHaveClass(
"md:w-[56px]",
"md:h-[56px]",
"lg:w-[90px]",
"lg:h-[90px]"
);
});
test("applies different background colors to cards", () => {
render(<RuleStack />);
const cards = document.querySelectorAll(
'[class*="bg-[var(--color-surface-default-brand-"]'
);
expect(cards.length).toBeGreaterThan(0);
});
test("renders with proper button styling", () => {
render(<RuleStack />);
const button = screen.getByRole("button", { name: "See all templates" });
expect(button).toHaveClass("bg-transparent", "border-[1.5px]");
});
test("applies flex layout for button container", () => {
render(<RuleStack />);
const buttonContainer = screen
.getByRole("button", { name: "See all templates" })
.closest("div");
expect(buttonContainer).toHaveClass("flex", "justify-center");
});
test("handles analytics tracking", async () => {
const user = userEvent.setup();
const gtagSpy = vi.fn();
const analyticsSpy = vi.fn();
// Mock window.gtag and window.analytics
Object.defineProperty(window, "gtag", {
value: gtagSpy,
writable: true,
});
Object.defineProperty(window, "analytics", {
value: { track: analyticsSpy },
writable: true,
});
render(<RuleStack />);
const electedBoardCard = screen.getByText("Elected Board").closest("div");
await user.click(electedBoardCard);
expect(gtagSpy).toHaveBeenCalledWith("event", "template_click", {
template_name: "Elected Board",
});
expect(analyticsSpy).toHaveBeenCalledWith("Template Clicked", {
templateName: "Elected Board",
});
});
});