Testing Framwork #17
@@ -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)]");
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user