Add more unit testing
This commit is contained in:
+125
-77
@@ -1,112 +1,160 @@
|
||||
import { render, screen, cleanup } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { vi, describe, test, expect, afterEach } from "vitest";
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import Button from "../../app/components/Button";
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
describe("Button Component", () => {
|
||||
test("renders button with children", () => {
|
||||
it("renders button with default props", () => {
|
||||
render(<Button>Click me</Button>);
|
||||
const button = screen.getByRole("button", { name: "Click me" });
|
||||
|
||||
const button = screen.getByRole("button", { name: /click me/i });
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toHaveClass("bg-[var(--color-surface-inverse-primary)]");
|
||||
expect(button).toHaveAttribute("type", "button");
|
||||
});
|
||||
|
||||
test("handles click events", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onClick = vi.fn();
|
||||
render(<Button onClick={onClick}>Click me</Button>);
|
||||
it("renders with custom className", () => {
|
||||
const customClass = "custom-button-class";
|
||||
render(<Button className={customClass}>Custom Button</Button>);
|
||||
|
||||
const button = screen.getByRole("button", { name: "Click me" });
|
||||
await user.click(button);
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toHaveClass(customClass);
|
||||
});
|
||||
|
||||
test("renders as link when href is provided", () => {
|
||||
render(<Button href="/test">Link Button</Button>);
|
||||
const link = screen.getByRole("link", { name: "Link Button" });
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute("href", "/test");
|
||||
it("applies variant classes correctly", () => {
|
||||
const { rerender } = render(<Button variant="secondary">Secondary</Button>);
|
||||
let button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("bg-transparent");
|
||||
|
||||
rerender(<Button variant="primary">Primary</Button>);
|
||||
button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("bg-[var(--color-surface-default-primary)]");
|
||||
|
||||
rerender(<Button variant="outlined">Outlined</Button>);
|
||||
button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("bg-transparent", "border-[1.5px]");
|
||||
});
|
||||
|
||||
test("applies different variants", () => {
|
||||
const { rerender } = render(<Button variant="default">Default</Button>);
|
||||
let button = screen.getByRole("button", { name: "Default" });
|
||||
expect(button.className).toContain(
|
||||
"bg-[var(--color-surface-inverse-primary)]",
|
||||
);
|
||||
|
||||
rerender(<Button variant="secondary">Secondary</Button>);
|
||||
button = screen.getByRole("button", { name: "Secondary" });
|
||||
expect(button.className).toContain("bg-transparent");
|
||||
});
|
||||
|
||||
test("applies different sizes", () => {
|
||||
const { rerender } = render(<Button size="xsmall">Small</Button>);
|
||||
let button = screen.getByRole("button", { name: "Small" });
|
||||
expect(button.className).toContain("px-[var(--spacing-scale-006)]");
|
||||
it("applies size classes correctly", () => {
|
||||
const { rerender } = render(<Button size="small">Small</Button>);
|
||||
let button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("px-[var(--spacing-measures-spacing-008)]");
|
||||
|
||||
rerender(<Button size="large">Large</Button>);
|
||||
button = screen.getByRole("button", { name: "Large" });
|
||||
expect(button.className).toContain("px-[var(--spacing-scale-012)]");
|
||||
button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("px-[var(--spacing-scale-012)]");
|
||||
|
||||
rerender(<Button size="xlarge">XLarge</Button>);
|
||||
button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("px-[var(--spacing-scale-020)]");
|
||||
});
|
||||
|
||||
test("handles disabled state", () => {
|
||||
render(<Button disabled>Disabled</Button>);
|
||||
const button = screen.getByRole("button", { name: "Disabled" });
|
||||
it("renders as link when href is provided", () => {
|
||||
const href = "/test-page";
|
||||
render(<Button href={href}>Link Button</Button>);
|
||||
|
||||
const link = screen.getByRole("link", { name: /link button/i });
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute("href", href);
|
||||
});
|
||||
|
||||
it("renders as button when href is not provided", () => {
|
||||
render(<Button>Regular Button</Button>);
|
||||
|
||||
expect(screen.queryByRole("link")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("button")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles click events", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<Button onClick={handleClick}>Clickable</Button>);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("applies disabled state correctly", () => {
|
||||
render(<Button disabled>Disabled Button</Button>);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toBeDisabled();
|
||||
expect(button).toHaveClass(
|
||||
"disabled:opacity-50",
|
||||
"disabled:cursor-not-allowed"
|
||||
);
|
||||
expect(button).toHaveAttribute("aria-disabled", "true");
|
||||
expect(button).toHaveAttribute("tabindex", "-1");
|
||||
expect(button).toHaveAttribute("tabIndex", "-1");
|
||||
});
|
||||
|
||||
test("applies custom className", () => {
|
||||
render(<Button className="custom-class">Custom</Button>);
|
||||
const button = screen.getByRole("button", { name: "Custom" });
|
||||
expect(button.className).toContain("custom-class");
|
||||
});
|
||||
it("applies proper accessibility attributes", () => {
|
||||
render(<Button ariaLabel="Custom label">Button</Button>);
|
||||
|
||||
test("applies aria-label for accessibility", () => {
|
||||
render(<Button aria-label="Custom label">Button</Button>);
|
||||
const button = screen.getByRole("button", { name: "Custom label" });
|
||||
const button = screen.getByRole("button", { name: /custom label/i });
|
||||
expect(button).toHaveAttribute("aria-label", "Custom label");
|
||||
});
|
||||
|
||||
test("renders with design tokens", () => {
|
||||
render(<Button>Token Test</Button>);
|
||||
const button = screen.getByRole("button", { name: "Token Test" });
|
||||
it("applies hover effects correctly", () => {
|
||||
render(<Button>Hover Button</Button>);
|
||||
|
||||
// Check that design tokens are applied
|
||||
expect(button.className).toContain(
|
||||
"rounded-[var(--radius-measures-radius-full)]",
|
||||
);
|
||||
expect(button.className).toContain(
|
||||
"bg-[var(--color-surface-inverse-primary)]",
|
||||
);
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("hover:scale-[1.02]", "transition-all");
|
||||
});
|
||||
|
||||
test("handles keyboard navigation", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onClick = vi.fn();
|
||||
render(<Button onClick={onClick}>Keyboard</Button>);
|
||||
it("applies focus styles correctly", () => {
|
||||
render(<Button>Focus Button</Button>);
|
||||
|
||||
const button = screen.getByRole("button", { name: "Keyboard" });
|
||||
button.focus();
|
||||
expect(button).toHaveFocus();
|
||||
|
||||
await user.keyboard("{Enter}");
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("focus:outline-none", "focus:ring-1");
|
||||
});
|
||||
|
||||
test("does not render as link when disabled and href provided", () => {
|
||||
it("applies active styles correctly", () => {
|
||||
render(<Button>Active Button</Button>);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("active:scale-[0.98]");
|
||||
});
|
||||
|
||||
it("handles target and rel props for links", () => {
|
||||
render(
|
||||
<Button href="/test" disabled>
|
||||
Disabled Link
|
||||
</Button>,
|
||||
<Button href="/test" target="_blank" rel="noopener">
|
||||
External Link
|
||||
</Button>
|
||||
);
|
||||
const button = screen.getByRole("button", { name: "Disabled Link" });
|
||||
|
||||
const link = screen.getByRole("link");
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
expect(link).toHaveAttribute("rel", "noopener");
|
||||
});
|
||||
|
||||
it("forwards additional props", () => {
|
||||
render(<Button data-testid="test-button">Test Button</Button>);
|
||||
|
||||
const button = screen.getByTestId("test-button");
|
||||
expect(button).toBeInTheDocument();
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
it("applies proper font styles for different sizes", () => {
|
||||
const { rerender } = render(<Button size="xsmall">XSmall</Button>);
|
||||
let button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("text-[10px]", "leading-[12px]");
|
||||
|
||||
rerender(<Button size="xlarge">XLarge</Button>);
|
||||
button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("text-[24px]", "leading-[28px]");
|
||||
});
|
||||
|
||||
it("applies proper border radius", () => {
|
||||
render(<Button>Rounded Button</Button>);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toHaveClass("rounded-[var(--radius-measures-radius-full)]");
|
||||
});
|
||||
|
||||
it("maintains proper tab index when not disabled", () => {
|
||||
render(<Button>Tab Button</Button>);
|
||||
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toHaveAttribute("tabIndex", "0");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import Logo from "../../app/components/Logo";
|
||||
|
||||
describe("Logo Component", () => {
|
||||
it("renders the logo with default props", () => {
|
||||
render(<Logo />);
|
||||
|
||||
const logo = screen.getByRole("link", { name: /communityrule logo/i });
|
||||
expect(logo).toBeInTheDocument();
|
||||
expect(screen.getByText("CommunityRule")).toBeInTheDocument();
|
||||
expect(screen.getByAltText("CommunityRule Logo Icon")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with custom size variant", () => {
|
||||
const { rerender } = render(<Logo size="header" />);
|
||||
let logo = screen.getByRole("link");
|
||||
expect(logo).toHaveClass("h-[20.85px]");
|
||||
|
||||
rerender(<Logo size="headerLg" />);
|
||||
logo = screen.getByRole("link");
|
||||
expect(logo).toHaveClass("h-[28px]");
|
||||
|
||||
rerender(<Logo size="footer" />);
|
||||
logo = screen.getByRole("link");
|
||||
expect(logo).toHaveClass("h-[calc(40px*1.37)]");
|
||||
});
|
||||
|
||||
it("renders without text when showText is false", () => {
|
||||
render(<Logo showText={false} />);
|
||||
|
||||
expect(screen.queryByText("CommunityRule")).not.toBeInTheDocument();
|
||||
expect(screen.getByAltText("CommunityRule Logo Icon")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies proper hover effects", () => {
|
||||
render(<Logo />);
|
||||
|
||||
const logo = screen.getByRole("link");
|
||||
expect(logo).toHaveClass("hover:scale-[1.02]", "transition-all");
|
||||
});
|
||||
|
||||
it("applies proper accessibility attributes", () => {
|
||||
render(<Logo />);
|
||||
|
||||
const logo = screen.getByRole("link");
|
||||
expect(logo).toHaveAttribute("aria-label", "CommunityRule Logo");
|
||||
expect(logo).toHaveAttribute("role", "link");
|
||||
});
|
||||
|
||||
it("applies proper text styling for different sizes", () => {
|
||||
const { rerender } = render(<Logo size="homeHeaderMd" />);
|
||||
let textElement = screen.getByText("CommunityRule");
|
||||
expect(textElement).toHaveClass(
|
||||
"text-[var(--color-content-inverse-primary)]"
|
||||
);
|
||||
|
||||
rerender(<Logo size="header" />);
|
||||
textElement = screen.getByText("CommunityRule");
|
||||
expect(textElement).toHaveClass(
|
||||
"text-[var(--color-content-default-primary)]"
|
||||
);
|
||||
});
|
||||
|
||||
it("applies proper icon sizing for different variants", () => {
|
||||
const { rerender } = render(<Logo size="homeHeaderSm" />);
|
||||
let icon = screen.getByAltText("CommunityRule Logo Icon");
|
||||
expect(icon).toHaveClass("w-[14.39px]", "h-[14.39px]");
|
||||
|
||||
rerender(<Logo size="headerXl" />);
|
||||
icon = screen.getByAltText("CommunityRule Logo Icon");
|
||||
expect(icon).toHaveClass("w-[33.81px]", "h-[33.81px]");
|
||||
});
|
||||
|
||||
it("applies brightness filter for home header variants", () => {
|
||||
render(<Logo size="homeHeaderMd" />);
|
||||
|
||||
const icon = screen.getByAltText("CommunityRule Logo Icon");
|
||||
expect(icon).toHaveClass("filter", "brightness-0");
|
||||
});
|
||||
|
||||
it("maintains proper spacing when text is hidden", () => {
|
||||
render(<Logo showText={false} />);
|
||||
|
||||
const logo = screen.getByRole("link");
|
||||
// Should not have gap class when text is hidden
|
||||
expect(logo.className).not.toContain("gap-[8.28px]");
|
||||
});
|
||||
|
||||
it("applies proper font classes to text", () => {
|
||||
render(<Logo />);
|
||||
|
||||
const textElement = screen.getByText("CommunityRule");
|
||||
expect(textElement).toHaveClass("font-bricolage-grotesque", "font-normal");
|
||||
});
|
||||
|
||||
it("applies proper icon attributes", () => {
|
||||
render(<Logo />);
|
||||
|
||||
const icon = screen.getByAltText("CommunityRule Logo Icon");
|
||||
expect(icon).toHaveAttribute("src", "assets/Logo.svg");
|
||||
expect(icon).toHaveAttribute("aria-hidden", "true");
|
||||
});
|
||||
|
||||
it("handles all size variants correctly", () => {
|
||||
const sizes = [
|
||||
"default",
|
||||
"homeHeaderXsmall",
|
||||
"homeHeaderSm",
|
||||
"homeHeaderMd",
|
||||
"homeHeaderLg",
|
||||
"homeHeaderXl",
|
||||
"header",
|
||||
"headerMd",
|
||||
"headerLg",
|
||||
"headerXl",
|
||||
"footer",
|
||||
"footerLg",
|
||||
];
|
||||
|
||||
sizes.forEach((size) => {
|
||||
const { unmount } = render(<Logo size={size} />);
|
||||
const logo = screen.getByRole("link");
|
||||
expect(logo).toBeInTheDocument();
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,206 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import NumberedCard from "../../app/components/NumberedCard";
|
||||
|
||||
describe("NumberedCard Component", () => {
|
||||
const defaultProps = {
|
||||
number: 1,
|
||||
text: "Test Card Text",
|
||||
};
|
||||
|
||||
it("renders numbered card with all required information", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Card Text")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with different numbers", () => {
|
||||
const { rerender } = render(<NumberedCard {...defaultProps} number={42} />);
|
||||
expect(screen.getByText("42")).toBeInTheDocument();
|
||||
|
||||
rerender(<NumberedCard {...defaultProps} number={999} />);
|
||||
expect(screen.getByText("999")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with different text content", () => {
|
||||
const { rerender } = render(
|
||||
<NumberedCard {...defaultProps} text="Different Text" />
|
||||
);
|
||||
expect(screen.getByText("Different Text")).toBeInTheDocument();
|
||||
|
||||
rerender(<NumberedCard {...defaultProps} text="Another Text" />);
|
||||
expect(screen.getByText("Another Text")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies proper responsive layout classes", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass("flex", "flex-col", "sm:flex-row", "lg:flex-row");
|
||||
});
|
||||
|
||||
it("applies proper responsive spacing", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass("p-5", "sm:p-8", "lg:p-8");
|
||||
});
|
||||
|
||||
it("applies proper responsive gap", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass("gap-4", "sm:gap-8", "lg:gap-0");
|
||||
});
|
||||
|
||||
it("applies proper responsive height", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass("lg:h-[238px]");
|
||||
});
|
||||
|
||||
it("applies proper background and shadow", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass(
|
||||
"bg-[var(--color-surface-inverse-primary)]",
|
||||
"shadow-lg"
|
||||
);
|
||||
});
|
||||
|
||||
it("applies proper border radius", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass("rounded-[12px]");
|
||||
});
|
||||
|
||||
it("renders section number in correct position", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const numberElement = screen.getByText("1");
|
||||
expect(numberElement).toBeInTheDocument();
|
||||
|
||||
// Check that it's in a container with proper positioning
|
||||
const numberContainer = numberElement.closest("div");
|
||||
expect(numberContainer).toHaveClass(
|
||||
"absolute",
|
||||
"inset-0",
|
||||
"flex",
|
||||
"items-center",
|
||||
"justify-center"
|
||||
);
|
||||
});
|
||||
|
||||
it("renders text content in correct position", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const textElement = screen.getByText("Test Card Text");
|
||||
expect(textElement).toBeInTheDocument();
|
||||
|
||||
// Check that it's in a container with proper positioning
|
||||
const textContainer = textElement.closest("div");
|
||||
expect(textContainer).toHaveClass(
|
||||
"sm:flex-1",
|
||||
"lg:absolute",
|
||||
"lg:bottom-8",
|
||||
"lg:left-8",
|
||||
"lg:right-16"
|
||||
);
|
||||
});
|
||||
|
||||
it("applies proper font classes to text", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const textElement = screen.getByText("Test Card Text");
|
||||
expect(textElement).toHaveClass("font-bricolage-grotesque");
|
||||
});
|
||||
|
||||
it("applies proper text sizing", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const textElement = screen.getByText("Test Card Text");
|
||||
expect(textElement).toHaveClass(
|
||||
"text-[24px]",
|
||||
"sm:text-[24px]",
|
||||
"lg:text-[24px]",
|
||||
"xl:text-[32px]"
|
||||
);
|
||||
});
|
||||
|
||||
it("applies proper text color", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const textElement = screen.getByText("Test Card Text");
|
||||
expect(textElement).toHaveClass("text-[#141414]");
|
||||
});
|
||||
|
||||
it("handles long text content gracefully", () => {
|
||||
const longText =
|
||||
"This is a very long text that should wrap properly and not break the layout of the numbered card component";
|
||||
render(<NumberedCard {...defaultProps} text={longText} />);
|
||||
|
||||
expect(screen.getByText(longText)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("maintains proper responsive behavior", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
|
||||
// Mobile first approach
|
||||
expect(card).toHaveClass("flex-col", "gap-4", "p-5");
|
||||
|
||||
// Small breakpoint
|
||||
expect(card).toHaveClass(
|
||||
"sm:flex-row",
|
||||
"sm:gap-8",
|
||||
"sm:p-8",
|
||||
"sm:items-center"
|
||||
);
|
||||
|
||||
// Large breakpoint
|
||||
expect(card).toHaveClass(
|
||||
"lg:flex-row",
|
||||
"lg:gap-0",
|
||||
"lg:p-8",
|
||||
"lg:items-stretch",
|
||||
"lg:relative"
|
||||
);
|
||||
});
|
||||
|
||||
it("renders with proper flex layout", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass("flex");
|
||||
});
|
||||
|
||||
it("applies proper items alignment", () => {
|
||||
render(<NumberedCard {...defaultProps} />);
|
||||
|
||||
const card = screen
|
||||
.getByText("Test Card Text")
|
||||
.closest("div").parentElement;
|
||||
expect(card).toHaveClass("sm:items-center", "lg:items-stretch");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import RuleCard from "../../app/components/RuleCard";
|
||||
|
||||
describe("RuleCard Component", () => {
|
||||
const defaultProps = {
|
||||
title: "Test Rule",
|
||||
description: "This is a test rule description",
|
||||
};
|
||||
|
||||
it("renders rule card with all required information", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
expect(screen.getByText("Test Rule")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("This is a test rule description")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with custom className", () => {
|
||||
const customClass = "custom-rule-card";
|
||||
render(<RuleCard {...defaultProps} className={customClass} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass(customClass);
|
||||
});
|
||||
|
||||
it("applies default background color", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass("bg-[var(--color-community-teal-100)]");
|
||||
});
|
||||
|
||||
it("applies custom background color", () => {
|
||||
const customBg = "bg-blue-100";
|
||||
render(<RuleCard {...defaultProps} backgroundColor={customBg} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass(customBg);
|
||||
});
|
||||
|
||||
it("renders with icon when provided", () => {
|
||||
const Icon = () => <span data-testid="icon">🚀</span>;
|
||||
render(<RuleCard {...defaultProps} icon={<Icon />} />);
|
||||
|
||||
expect(screen.getByTestId("icon")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles click events", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<RuleCard {...defaultProps} onClick={handleClick} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
fireEvent.click(card);
|
||||
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("handles keyboard events", () => {
|
||||
const handleClick = vi.fn();
|
||||
render(<RuleCard {...defaultProps} onClick={handleClick} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
|
||||
// Test Enter key
|
||||
fireEvent.keyDown(card, { key: "Enter" });
|
||||
expect(handleClick).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Test Space key
|
||||
fireEvent.keyDown(card, { key: " " });
|
||||
expect(handleClick).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("applies hover effects correctly", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass(
|
||||
"hover:shadow-xl",
|
||||
"hover:scale-[1.02]",
|
||||
"transition-all"
|
||||
);
|
||||
});
|
||||
|
||||
it("renders with proper accessibility attributes", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveAttribute(
|
||||
"aria-label",
|
||||
"Learn more about Test Rule governance pattern"
|
||||
);
|
||||
expect(card).toHaveAttribute("tabIndex", "0");
|
||||
});
|
||||
|
||||
it("handles empty description gracefully", () => {
|
||||
render(<RuleCard {...defaultProps} description="" />);
|
||||
|
||||
expect(screen.getByText("Test Rule")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByText("This is a test rule description")
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies proper responsive sizing", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass("md:h-[210px]", "lg:h-[277px]");
|
||||
});
|
||||
|
||||
it("applies focus styles correctly", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass("focus:outline-none", "focus:ring-2");
|
||||
});
|
||||
|
||||
it("renders without icon when not provided", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
expect(screen.queryByTestId("icon")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies proper border and shadow classes", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
const card = screen.getByRole("button");
|
||||
expect(card).toHaveClass("shadow-lg", "backdrop-blur-sm");
|
||||
});
|
||||
|
||||
it("maintains proper heading structure", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
const heading = screen.getByRole("heading", { level: 3 });
|
||||
expect(heading).toHaveTextContent("Test Rule");
|
||||
expect(heading.tagName).toBe("H3");
|
||||
});
|
||||
|
||||
it("applies proper font classes", () => {
|
||||
render(<RuleCard {...defaultProps} />);
|
||||
|
||||
const heading = screen.getByRole("heading", { level: 3 });
|
||||
expect(heading).toHaveClass("font-space-grotesk", "font-bold");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,198 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import SectionHeader from "../../app/components/SectionHeader";
|
||||
|
||||
describe("SectionHeader Component", () => {
|
||||
it("renders section header with title", () => {
|
||||
render(<SectionHeader title="Test Section" />);
|
||||
|
||||
expect(screen.getByRole("heading", { level: 2 })).toBeInTheDocument();
|
||||
// Check for both mobile and desktop versions of the title
|
||||
expect(screen.getAllByText("Test Section")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("renders with subtitle when provided", () => {
|
||||
const subtitle = "This is a test subtitle";
|
||||
render(<SectionHeader title="Test Section" subtitle={subtitle} />);
|
||||
|
||||
expect(screen.getByText(subtitle)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders with titleLg when provided", () => {
|
||||
const titleLg = "Large Title for Desktop";
|
||||
render(<SectionHeader title="Test Section" titleLg={titleLg} />);
|
||||
|
||||
// Check for mobile title and desktop titleLg
|
||||
expect(screen.getByText("Test Section")).toBeInTheDocument();
|
||||
expect(screen.getByText(titleLg)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies variant classes correctly", () => {
|
||||
const { rerender } = render(
|
||||
<SectionHeader title="Default Header" variant="default" />
|
||||
);
|
||||
let titleContainer = screen
|
||||
.getByRole("heading", { level: 2 })
|
||||
.closest("div");
|
||||
expect(titleContainer).toHaveClass(
|
||||
"lg:w-[369px]",
|
||||
"lg:h-[var(--spacing-scale-120)]"
|
||||
);
|
||||
|
||||
rerender(<SectionHeader title="Multi-line Header" variant="multi-line" />);
|
||||
titleContainer = screen.getByRole("heading", { level: 2 }).closest("div");
|
||||
expect(titleContainer).toHaveClass(
|
||||
"lg:w-[50%]",
|
||||
"lg:h-[var(--spacing-scale-120)]"
|
||||
);
|
||||
});
|
||||
|
||||
it("renders responsive title spans", () => {
|
||||
render(<SectionHeader title="Test Section" />);
|
||||
|
||||
const mobileTitle = screen.getByText("Test Section", {
|
||||
selector: "span.block.lg\\:hidden",
|
||||
});
|
||||
const desktopTitle = screen.getByText("Test Section", {
|
||||
selector: "span.hidden.lg\\:block",
|
||||
});
|
||||
|
||||
expect(mobileTitle).toBeInTheDocument();
|
||||
expect(desktopTitle).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("uses titleLg for desktop when provided", () => {
|
||||
const titleLg = "Desktop Title";
|
||||
render(<SectionHeader title="Mobile Title" titleLg={titleLg} />);
|
||||
|
||||
const mobileTitle = screen.getByText("Mobile Title", {
|
||||
selector: "span.block.lg\\:hidden",
|
||||
});
|
||||
const desktopTitle = screen.getByText("Desktop Title", {
|
||||
selector: "span.hidden.lg\\:block",
|
||||
});
|
||||
|
||||
expect(mobileTitle).toBeInTheDocument();
|
||||
expect(desktopTitle).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("falls back to title for desktop when titleLg not provided", () => {
|
||||
render(<SectionHeader title="Test Section" />);
|
||||
|
||||
const mobileTitle = screen.getByText("Test Section", {
|
||||
selector: "span.block.lg\\:hidden",
|
||||
});
|
||||
const desktopTitle = screen.getByText("Test Section", {
|
||||
selector: "span.hidden.lg\\:block",
|
||||
});
|
||||
|
||||
expect(mobileTitle).toBeInTheDocument();
|
||||
expect(desktopTitle).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies proper responsive layout classes", () => {
|
||||
render(<SectionHeader title="Test Section" />);
|
||||
|
||||
const container = screen
|
||||
.getByRole("heading", { level: 2 })
|
||||
.closest("div").parentElement;
|
||||
expect(container).toHaveClass(
|
||||
"flex",
|
||||
"flex-col",
|
||||
"lg:flex-row",
|
||||
"lg:justify-between"
|
||||
);
|
||||
});
|
||||
|
||||
it("handles empty subtitle gracefully", () => {
|
||||
render(<SectionHeader title="Test Section" subtitle="" />);
|
||||
|
||||
expect(screen.getByRole("heading", { level: 2 })).toBeInTheDocument();
|
||||
// Empty subtitle should not cause issues - check that the paragraph element exists
|
||||
const subtitleContainer = screen
|
||||
.getByRole("heading", { level: 2 })
|
||||
.closest("div")
|
||||
.parentElement.querySelector("p");
|
||||
expect(subtitleContainer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("maintains proper heading structure", () => {
|
||||
render(<SectionHeader title="Test Section" />);
|
||||
|
||||
const heading = screen.getByRole("heading", { level: 2 });
|
||||
expect(heading).toHaveTextContent("Test Section");
|
||||
expect(heading.tagName).toBe("H2");
|
||||
});
|
||||
|
||||
it("applies proper font classes", () => {
|
||||
render(<SectionHeader title="Test Section" />);
|
||||
|
||||
const heading = screen.getByRole("heading", { level: 2 });
|
||||
expect(heading).toHaveClass("font-bricolage-grotesque", "font-bold");
|
||||
});
|
||||
|
||||
it("applies proper text sizing", () => {
|
||||
render(<SectionHeader title="Test Section" />);
|
||||
|
||||
const heading = screen.getByRole("heading", { level: 2 });
|
||||
expect(heading).toHaveClass(
|
||||
"text-[28px]",
|
||||
"sm:text-[32px]",
|
||||
"lg:text-[32px]",
|
||||
"xl:text-[40px]"
|
||||
);
|
||||
});
|
||||
|
||||
it("applies proper line heights", () => {
|
||||
render(<SectionHeader title="Test Section" />);
|
||||
|
||||
const heading = screen.getByRole("heading", { level: 2 });
|
||||
expect(heading).toHaveClass(
|
||||
"leading-[36px]",
|
||||
"sm:leading-[40px]",
|
||||
"lg:leading-[40px]",
|
||||
"xl:leading-[52px]"
|
||||
);
|
||||
});
|
||||
|
||||
it("applies proper text colors", () => {
|
||||
render(<SectionHeader title="Test Section" />);
|
||||
|
||||
const heading = screen.getByRole("heading", { level: 2 });
|
||||
expect(heading).toHaveClass("text-[var(--color-content-default-primary)]");
|
||||
});
|
||||
|
||||
it("applies proper subtitle styling", () => {
|
||||
const subtitle = "Test Subtitle";
|
||||
render(<SectionHeader title="Test Section" subtitle={subtitle} />);
|
||||
|
||||
const subtitleElement = screen.getByText(subtitle);
|
||||
expect(subtitleElement).toHaveClass("font-inter", "font-normal");
|
||||
});
|
||||
|
||||
it("applies proper subtitle text sizing", () => {
|
||||
const subtitle = "Test Subtitle";
|
||||
render(<SectionHeader title="Test Section" subtitle={subtitle} />);
|
||||
|
||||
const subtitleElement = screen.getByText(subtitle);
|
||||
expect(subtitleElement).toHaveClass(
|
||||
"text-[18px]",
|
||||
"sm:text-[18px]",
|
||||
"lg:text-[24px]",
|
||||
"xl:text-[32px]"
|
||||
);
|
||||
});
|
||||
|
||||
it("applies proper subtitle colors", () => {
|
||||
const subtitle = "Test Subtitle";
|
||||
render(<SectionHeader title="Test Section" subtitle={subtitle} />);
|
||||
|
||||
const subtitleElement = screen.getByText(subtitle);
|
||||
expect(subtitleElement).toHaveClass(
|
||||
"text-[#484848]",
|
||||
"sm:text-[var(--color-content-default-tertiary)]",
|
||||
"lg:text-[var(--color-content-default-tertiary)]",
|
||||
"xl:text-[var(--color-content-default-tertiary)]"
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user