Simplify and standardize testing structure

This commit is contained in:
adilallo
2026-01-28 14:04:04 -07:00
parent e7a31789e3
commit 7ea724a8d9
95 changed files with 1534 additions and 15485 deletions
-298
View File
@@ -1,298 +0,0 @@
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();
// The description text might not be rendered or might be different
// Just verify the component renders without error
expect(
screen.getByRole("heading", { name: "Need help organizing?" }),
).toBeInTheDocument();
// Button renders as a link when href is provided
expect(
screen.getByRole("link", {
name: "Contact an organizer - Contact an organizer for help",
}),
).toBeInTheDocument();
});
test("renders with default button text", () => {
render(<AskOrganizer title="Test" subtitle="Test" description="Test" />);
// Button renders as a link when href is provided
expect(
screen.getByRole("link", {
name: "Ask an organizer - Contact an organizer for help",
}),
).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();
// Description might not be rendered if not provided to ContentLockup
// Just verify the component renders without error
expect(
screen.getByRole("heading", { name: "Ask Title" }),
).toBeInTheDocument();
});
test("renders button with correct props", () => {
render(
<AskOrganizer
title="Test"
subtitle="Test"
buttonText="Custom Button"
buttonHref="/custom"
/>,
);
const button = screen.getByRole("link", {
name: "Custom Button - Contact an organizer for help",
});
expect(button).toHaveAttribute("href", "/custom");
expect(button).toHaveClass("xl:!px-[var(--spacing-scale-020)]");
});
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("link", {
name: "Ask an organizer - Contact an organizer for help",
});
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("link", {
name: "Ask an organizer - Contact an organizer for help",
});
await user.click(button);
// Verify gtag was called with the expected event
expect(gtagSpy).toHaveBeenCalledWith(
"event",
"contact_button_click",
expect.objectContaining({
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("link", {
name: "Custom Button - Contact an organizer for help",
});
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("link", {
name: "Ask an organizer - Contact an organizer for help",
});
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 (as link when href is provided)
expect(
screen.getByRole("link", {
name: "Ask an organizer - Contact an organizer for help",
}),
).toBeInTheDocument();
});
test("applies responsive button container alignment", () => {
render(<AskOrganizer title="Test" subtitle="Test" variant="centered" />);
// Button renders as a link when href is provided
const buttonContainer = screen
.getByRole("link", {
name: "Ask an organizer - Contact an organizer for help",
})
.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)]");
});
});
-396
View File
@@ -1,396 +0,0 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import React from "react";
import BlogPostPage from "../../app/blog/[slug]/page";
// Mock Next.js components
vi.mock("next/navigation", () => ({
notFound: vi.fn(),
}));
vi.mock("next/link", () => {
return {
default: ({ children, href, ...props }) => (
<a href={href} {...props}>
{children}
</a>
),
};
});
// Mock next/dynamic to return components synchronously in tests
vi.mock("next/dynamic", () => {
return {
default: (importFn, options) => {
// In tests, resolve the dynamic import immediately and return the component
let Component = null;
importFn().then((mod) => {
Component = mod.default || mod;
});
// Return a synchronous wrapper that uses the mocked component
return (props) => {
// Use the mocked RelatedArticles component directly
if (Component) {
return <Component {...props} />;
}
// Fallback: return the loading placeholder if component not ready
return options?.loading ? options.loading() : null;
};
},
};
});
// Mock content processing
vi.mock("../../lib/content", () => ({
getBlogPostBySlug: vi.fn(),
getAllBlogPosts: vi.fn(),
}));
// Mock components
vi.mock("../../app/components/ContentBanner", () => {
return {
default: ({ post }) => (
<div data-testid="content-banner">
<h1>{post.frontmatter.title}</h1>
<p>{post.frontmatter.description}</p>
</div>
),
};
});
vi.mock("../../app/components/RelatedArticles", () => {
return {
default: ({ relatedPosts }) => (
<div data-testid="related-articles">
<h2>Related Articles</h2>
{relatedPosts.map((post) => (
<div key={post.slug} data-testid={`related-${post.slug}`}>
{post.frontmatter.title}
</div>
))}
</div>
),
};
});
vi.mock("../../app/components/AskOrganizer", () => {
return {
default: ({ title, subtitle, buttonText }) => (
<div data-testid="ask-organizer">
<h2>{title}</h2>
<p>{subtitle}</p>
<button>{buttonText}</button>
</div>
),
};
});
// Mock asset utils
vi.mock("../../lib/assetUtils", () => ({
getAssetPath: vi.fn((asset) => `/assets/${asset}`),
ASSETS: {
CONTENT_SHAPE_1: "Content_Shape_1.svg",
CONTENT_SHAPE_2: "Content_Shape_2.svg",
},
}));
// Mock blog post data
const mockPost = {
slug: "test-article",
frontmatter: {
title: "Test Article Title",
description: "This is a test article description",
author: "Test Author",
date: "2025-04-15",
},
htmlContent:
"<p>This is the article content with <strong>bold text</strong> and <em>italic text</em>.</p>",
};
const mockRelatedPosts = [
{
slug: "related-1",
frontmatter: {
title: "Related Article 1",
description: "First related article",
author: "Test Author",
date: "2025-04-10",
},
},
{
slug: "related-2",
frontmatter: {
title: "Related Article 2",
description: "Second related article",
author: "Test Author",
date: "2025-04-12",
},
},
];
describe("BlogPostPage", () => {
beforeEach(async () => {
// Reset mocks
vi.clearAllMocks();
// Mock the content functions
const { getBlogPostBySlug, getAllBlogPosts } =
await import("../../lib/content");
vi.mocked(getBlogPostBySlug).mockReturnValue(mockPost);
vi.mocked(getAllBlogPosts).mockReturnValue([mockPost, ...mockRelatedPosts]);
});
it("renders the blog post page with correct structure", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
// Check main container (it's a div, not main)
const mainContainer = document.querySelector("div.min-h-screen");
expect(mainContainer).toBeInTheDocument();
expect(mainContainer).toHaveClass(
"min-h-screen",
"relative",
"overflow-hidden",
);
// Background color is applied via inline style from frontmatter hex
expect(mainContainer).toHaveStyle({ backgroundColor: expect.any(String) });
});
it("renders the content banner", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
expect(screen.getByTestId("content-banner")).toBeInTheDocument();
expect(screen.getByText("Test Article Title")).toBeInTheDocument();
expect(
screen.getByText("This is a test article description"),
).toBeInTheDocument();
});
it("renders the article content", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
const article = document.querySelector("article");
expect(article).toBeInTheDocument();
expect(article).toHaveClass(
"p-[var(--spacing-scale-024)]",
"sm:py-[var(--spacing-scale-032)]",
);
// Check content is rendered
expect(screen.getByText(/This is the article content/)).toBeInTheDocument();
expect(screen.getByText("bold text")).toBeInTheDocument();
expect(screen.getByText("italic text")).toBeInTheDocument();
});
it("renders the related articles section", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
// Wait for dynamically imported RelatedArticles component to load
await waitFor(() => {
expect(screen.getByTestId("related-articles")).toBeInTheDocument();
});
expect(screen.getByText("Related Articles")).toBeInTheDocument();
expect(screen.getByTestId("related-related-1")).toBeInTheDocument();
expect(screen.getByTestId("related-related-2")).toBeInTheDocument();
});
it("renders the ask organizer section", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
expect(screen.getByTestId("ask-organizer")).toBeInTheDocument();
expect(screen.getByText("Still have questions?")).toBeInTheDocument();
expect(
screen.getByText("Get answers from an experienced organizer"),
).toBeInTheDocument();
expect(screen.getByText("Ask an organizer")).toBeInTheDocument();
});
it("renders decorative shapes", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
// Check for decorative shapes
const shapes = screen.getAllByAltText("");
expect(shapes).toHaveLength(2);
// Check shape sources
expect(shapes[0]).toHaveAttribute("src", "/assets/Content_Shape_1.svg");
expect(shapes[1]).toHaveAttribute("src", "/assets/Content_Shape_2.svg");
});
it("applies correct styling to article content", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
const contentDiv = screen
.getByText(/This is the article content/)
.closest("div.post-body");
expect(contentDiv).toHaveClass("post-body");
expect(contentDiv).toHaveClass("-mt-[var(--spacing-scale-048)]");
expect(contentDiv).toHaveClass(
"text-[var(--color-content-inverse-primary)]",
);
expect(contentDiv).toHaveClass("text-[16px]");
expect(contentDiv).toHaveClass("leading-[24px]");
});
it("applies responsive text sizing", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
const contentDiv = screen
.getByText(/This is the article content/)
.closest("div.post-body");
expect(contentDiv).toHaveClass("sm:text-[18px]");
expect(contentDiv).toHaveClass("sm:leading-[130%]");
expect(contentDiv).toHaveClass("lg:text-[24px]");
expect(contentDiv).toHaveClass("lg:leading-[32px]");
expect(contentDiv).toHaveClass("xl:text-[32px]");
expect(contentDiv).toHaveClass("xl:leading-[40px]");
});
it("applies responsive max-width constraints", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
const contentDiv = screen
.getByText(/This is the article content/)
.closest("div.post-body");
expect(contentDiv).toHaveClass("sm:mx-auto");
expect(contentDiv).toHaveClass("sm:max-w-[390px]");
expect(contentDiv).toHaveClass("md:max-w-[472px]");
expect(contentDiv).toHaveClass("lg:max-w-[700px]");
expect(contentDiv).toHaveClass("xl:max-w-[904px]");
});
it("includes structured data scripts", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
// Check for script elements using querySelector since RTL ignores them
const scripts = document.querySelectorAll(
'script[type="application/ld+json"]',
);
expect(scripts).toHaveLength(2);
// Check that scripts have the correct type and content
scripts.forEach((script) => {
expect(script).toHaveAttribute("type", "application/ld+json");
expect(script.innerHTML).toBeTruthy();
});
});
it("handles missing post gracefully", async () => {
const { getBlogPostBySlug } = await import("../../lib/content");
vi.mocked(getBlogPostBySlug).mockReturnValue(null);
// The component should throw an error when post is null
// This happens because notFound() is called
await expect(
BlogPostPage({ params: { slug: "non-existent" } }),
).rejects.toThrow();
});
it("filters out current post from related articles", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
// Wait for dynamically imported RelatedArticles component to load
await waitFor(() => {
expect(screen.getByTestId("related-articles")).toBeInTheDocument();
});
// Current post should not appear in related articles
expect(
screen.queryByTestId("related-test-article"),
).not.toBeInTheDocument();
// Other related posts should appear
expect(screen.getByTestId("related-related-1")).toBeInTheDocument();
expect(screen.getByTestId("related-related-2")).toBeInTheDocument();
});
it("applies correct positioning to decorative shapes", async () => {
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "test-article" },
});
render(BlogPostPageComponent);
const shapes = screen.getAllByAltText("");
// First shape (right side)
const rightShape = shapes[0].closest("div");
expect(rightShape).toHaveClass(
"hidden",
"md:block",
"absolute",
"top-1/4",
"right-0",
"pointer-events-none",
"z-10",
);
// Second shape (left side)
const leftShape = shapes[1].closest("div");
expect(leftShape).toHaveClass(
"hidden",
"md:block",
"absolute",
"top-1/2",
"left-0",
"pointer-events-none",
"z-10",
);
});
it("handles malformed post data gracefully", async () => {
const malformedPost = {
slug: "malformed",
frontmatter: {
title: "Malformed Post",
description: "A malformed post for testing",
author: "Test Author",
date: "2025-01-15",
},
htmlContent: "<p>Content</p>",
};
const { getBlogPostBySlug } = await import("../../lib/content");
vi.mocked(getBlogPostBySlug).mockReturnValue(malformedPost);
const BlogPostPageComponent = await BlogPostPage({
params: { slug: "malformed" },
});
render(BlogPostPageComponent);
expect(screen.getByText("Malformed Post")).toBeInTheDocument();
expect(screen.getByText("Content")).toBeInTheDocument();
});
});
-160
View File
@@ -1,160 +0,0 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import Button from "../../app/components/Button";
describe("Button Component", () => {
it("renders button with default props", () => {
render(<Button>Click me</Button>);
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");
});
it("renders with custom className", () => {
const customClass = "custom-button-class";
render(<Button className={customClass}>Custom Button</Button>);
const button = screen.getByRole("button");
expect(button).toHaveClass(customClass);
});
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]");
});
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");
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)]");
});
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");
});
it("applies proper accessibility attributes", () => {
render(<Button ariaLabel="Custom label">Button</Button>);
const button = screen.getByRole("button", { name: /custom label/i });
expect(button).toHaveAttribute("aria-label", "Custom label");
});
it("applies hover effects correctly", () => {
render(<Button>Hover Button</Button>);
const button = screen.getByRole("button");
expect(button).toHaveClass("hover:scale-[1.02]", "transition-all");
});
it("applies focus styles correctly", () => {
render(<Button>Focus Button</Button>);
const button = screen.getByRole("button");
expect(button).toHaveClass("focus:outline-none", "focus:ring-1");
});
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" target="_blank" rel="noopener">
External Link
</Button>,
);
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();
});
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");
});
});
-166
View File
@@ -1,166 +0,0 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { expect, test, describe, vi } from "vitest";
import Checkbox from "../../app/components/Checkbox";
describe("Checkbox Component", () => {
test("renders with default props", () => {
render(<Checkbox />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toBeInTheDocument();
expect(checkbox).toHaveAttribute("aria-checked", "false");
});
test("renders with label", () => {
render(<Checkbox label="Test checkbox" />);
expect(screen.getByText("Test checkbox")).toBeInTheDocument();
});
test("renders as checked when checked prop is true", () => {
render(<Checkbox checked={true} />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toHaveAttribute("aria-checked", "true");
});
test("renders as unchecked when checked prop is false", () => {
render(<Checkbox checked={false} />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toHaveAttribute("aria-checked", "false");
});
test("calls onChange when clicked", () => {
const handleChange = vi.fn();
render(<Checkbox onChange={handleChange} />);
const checkbox = screen.getByRole("checkbox");
fireEvent.click(checkbox);
expect(handleChange).toHaveBeenCalledWith({
checked: true,
value: undefined,
event: expect.any(Object),
});
});
test("calls onChange when toggled from checked to unchecked", () => {
const handleChange = vi.fn();
render(<Checkbox checked={true} onChange={handleChange} />);
const checkbox = screen.getByRole("checkbox");
fireEvent.click(checkbox);
expect(handleChange).toHaveBeenCalledWith({
checked: false,
value: undefined,
event: expect.any(Object),
});
});
test("handles keyboard navigation", () => {
const handleChange = vi.fn();
render(<Checkbox onChange={handleChange} />);
const checkbox = screen.getByRole("checkbox");
// Test Space key
fireEvent.keyDown(checkbox, { key: " " });
expect(handleChange).toHaveBeenCalledWith({
checked: true,
value: undefined,
event: expect.any(Object),
});
// Test Enter key
fireEvent.keyDown(checkbox, { key: "Enter" });
expect(handleChange).toHaveBeenCalledTimes(2);
});
test("does not call onChange when disabled", () => {
const handleChange = vi.fn();
render(<Checkbox disabled={true} onChange={handleChange} />);
const checkbox = screen.getByRole("checkbox");
fireEvent.click(checkbox);
expect(handleChange).not.toHaveBeenCalled();
});
test("applies disabled attributes when disabled", () => {
render(<Checkbox disabled={true} />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toHaveAttribute("aria-disabled", "true");
expect(checkbox).toHaveAttribute("tabIndex", "-1");
});
test("applies correct tabIndex when not disabled", () => {
render(<Checkbox />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toHaveAttribute("tabIndex", "0");
});
test("renders with standard mode by default", () => {
render(<Checkbox />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toBeInTheDocument();
});
test("renders with inverse mode", () => {
render(<Checkbox mode="inverse" />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toBeInTheDocument();
});
test("applies custom className", () => {
render(<Checkbox className="custom-class" />);
const label = screen.getByRole("checkbox").closest("label");
expect(label).toHaveClass("custom-class");
});
test("passes through additional props", () => {
render(<Checkbox id="test-checkbox" name="test" value="test-value" />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toHaveAttribute("id", "test-checkbox");
});
test("renders hidden native input for form compatibility", () => {
render(<Checkbox name="test" value="test-value" checked={true} />);
const hiddenInput = screen.getByDisplayValue("test-value");
expect(hiddenInput).toBeInTheDocument();
expect(hiddenInput).toHaveAttribute("type", "checkbox");
expect(hiddenInput).toHaveAttribute("name", "test");
expect(hiddenInput).toBeChecked();
});
test("applies aria-label when provided", () => {
render(<Checkbox ariaLabel="Custom label" />);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toHaveAttribute("aria-label", "Custom label");
});
test("prevents default on mouse down", () => {
render(<Checkbox />);
const label = screen.getByRole("checkbox").closest("label");
const mouseDownEvent = new MouseEvent("mousedown", { bubbles: true });
const preventDefaultSpy = vi.spyOn(mouseDownEvent, "preventDefault");
fireEvent(label, mouseDownEvent);
expect(preventDefaultSpy).toHaveBeenCalled();
});
test("renders checkmark SVG when checked", () => {
render(<Checkbox checked={true} />);
const svg = screen.getByRole("checkbox").querySelector("svg");
expect(svg).toBeInTheDocument();
expect(svg).toHaveAttribute("aria-hidden", "true");
expect(svg).toHaveAttribute("focusable", "false");
});
test("does not render checkmark SVG when unchecked", () => {
render(<Checkbox checked={false} />);
const svg = screen.getByRole("checkbox").querySelector("svg");
expect(svg).toBeInTheDocument();
// SVG should be present but checkmark should be transparent
const path = svg.querySelector("polyline");
expect(path).toHaveAttribute("stroke", "transparent");
});
});
-287
View File
@@ -1,287 +0,0 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import ContentBanner from "../../app/components/ContentBanner";
// Mock Next.js components
vi.mock("next/link", () => {
return {
default: ({ children, href, ...props }) => (
<a href={href} {...props}>
{children}
</a>
),
};
});
// Mock asset utils
vi.mock("../../lib/assetUtils", () => ({
getAssetPath: vi.fn((asset) => `/assets/${asset}`),
ASSETS: {
CONTENT_BANNER_1: "Content_Banner_1.svg",
CONTENT_BANNER_2: "Content_Banner_2.svg",
},
}));
// Mock blog post data
const mockPost = {
slug: "test-article",
frontmatter: {
title: "Test Article Title",
description: "This is a test article description",
author: "Test Author",
date: "2025-04-15",
thumbnail: {
horizontal: "test-article-horizontal.svg",
},
banner: {
horizontal: "test-article-banner.svg",
},
},
};
describe("ContentBanner", () => {
it("renders the banner with correct structure", () => {
render(<ContentBanner post={mockPost} />);
// Check that the banner container exists - it's the first div with the specific classes
const banner = document.querySelector(
"div[class*='pt-[var(--measures-spacing-016)]']",
);
expect(banner).toBeInTheDocument();
expect(banner).toHaveClass(
"pt-[var(--measures-spacing-016)]",
"md:pt-[var(--measures-spacing-008)]",
"lg:pt-[50px]",
"xl:pt-[112px]",
"h-[275px]",
"sm:h-[326px]",
"md:h-[224px]",
"lg:h-[358.4px]",
"xl:h-[504px]",
"relative",
"w-full",
"sm:overflow-hidden",
);
});
it("displays the background image correctly", () => {
render(<ContentBanner post={mockPost} />);
// Check for background div with correct styling
const backgroundDiv = document.querySelector(
"div[style*='background-image']",
);
expect(backgroundDiv).toBeInTheDocument();
expect(backgroundDiv).toHaveClass(
"absolute",
"inset-0",
"w-full",
"h-full",
"bg-cover",
"bg-no-repeat",
"aspect-[320/225.5]",
);
});
it("shows banner image at md breakpoint and above", () => {
render(<ContentBanner post={mockPost} />);
// Check for the md+ background div with banner image
const mdBackgroundDiv = document.querySelector(
"div[style*='test-article-banner.svg']",
);
expect(mdBackgroundDiv).toBeInTheDocument();
expect(mdBackgroundDiv).toHaveClass("hidden", "md:block");
});
it("displays the article title", () => {
render(<ContentBanner post={mockPost} />);
expect(screen.getByText("Test Article Title")).toBeInTheDocument();
});
it("displays the article description", () => {
render(<ContentBanner post={mockPost} />);
expect(
screen.getByText("This is a test article description"),
).toBeInTheDocument();
});
it("displays the author and date metadata", () => {
render(<ContentBanner post={mockPost} />);
expect(screen.getByText("Test Author")).toBeInTheDocument();
expect(screen.getByText("April 2025")).toBeInTheDocument();
});
it("applies correct styling classes", () => {
render(<ContentBanner post={mockPost} />);
// Check the content container div
const contentContainer = document.querySelector(
"div[class*='relative z-10']",
);
expect(contentContainer).toBeInTheDocument();
expect(contentContainer).toHaveClass(
"relative",
"z-10",
"h-full",
"flex",
"flex-col",
);
});
it("applies correct text styling", () => {
render(<ContentBanner post={mockPost} />);
const title = screen.getByText("Test Article Title");
expect(title).toHaveClass(
"font-bricolage",
"font-medium",
"text-[18px]",
"leading-[120%]",
"text-[var(--color-content-inverse-brand-royal)]",
);
const description = screen.getByText("This is a test article description");
expect(description).toHaveClass(
"font-inter",
"font-normal",
"text-[12px]",
"leading-[16px]",
"text-[var(--color-content-inverse-brand-royal)]",
);
});
it("applies correct metadata styling", () => {
render(<ContentBanner post={mockPost} />);
const author = screen.getByText("Test Author");
expect(author).toHaveClass(
"font-inter",
"font-normal",
"text-[10px]",
"leading-[14px]",
"text-[var(--color-content-inverse-brand-royal)]",
);
const date = screen.getByText("April 2025");
expect(date).toHaveClass(
"font-inter",
"font-normal",
"text-[10px]",
"leading-[14px]",
"text-[var(--color-content-inverse-brand-royal)]",
);
});
it("has proper spacing between elements", () => {
render(<ContentBanner post={mockPost} />);
// Check the ContentContainer spacing
const contentContainer = document.querySelector(
"div[class*='relative z-20']",
);
expect(contentContainer).toHaveClass("gap-[var(--measures-spacing-012)]");
});
it("has proper outer container padding", () => {
render(<ContentBanner post={mockPost} />);
const outerContainer = document.querySelector(
"div[class*='pt-[var(--measures-spacing-016)]']",
);
expect(outerContainer).toHaveClass(
"pt-[var(--measures-spacing-016)]",
"md:pt-[var(--measures-spacing-008)]",
"lg:pt-[50px]",
"xl:pt-[112px]",
);
});
it("handles missing post data gracefully", () => {
const incompletePost = {
slug: "incomplete",
frontmatter: {
title: "Incomplete Post",
// Missing other fields
},
};
render(<ContentBanner post={incompletePost} />);
expect(screen.getByText("Incomplete Post")).toBeInTheDocument();
});
it("falls back to thumbnail.horizontal when banner.horizontal is missing", () => {
const postWithoutBanner = {
...mockPost,
frontmatter: {
...mockPost.frontmatter,
banner: undefined,
},
};
render(<ContentBanner post={postWithoutBanner} />);
// Should use thumbnail.horizontal for md+ breakpoint
const mdBackgroundDiv = document.querySelector(
"div[style*='test-article-horizontal.svg'][class*='md:block']",
);
expect(mdBackgroundDiv).toBeInTheDocument();
expect(mdBackgroundDiv).toHaveClass("hidden", "md:block");
});
it("falls back to default banner when no images are provided", () => {
const postWithoutImages = {
...mockPost,
frontmatter: {
...mockPost.frontmatter,
thumbnail: undefined,
banner: undefined,
},
};
render(<ContentBanner post={postWithoutImages} />);
// Should use default banner for md+ breakpoint
const mdBackgroundDiv = document.querySelector(
"div[style*='Content_Banner_2.svg']",
);
expect(mdBackgroundDiv).toBeInTheDocument();
expect(mdBackgroundDiv).toHaveClass("hidden", "md:block");
});
it("applies responsive text sizing", () => {
render(<ContentBanner post={mockPost} />);
const title = screen.getByText("Test Article Title");
expect(title).toHaveClass(
"sm:text-[24px]",
"md:text-[32px]",
"lg:text-[44px]",
"xl:text-[64px]",
);
const description = screen.getByText("This is a test article description");
expect(description).toHaveClass(
"sm:text-[14px]",
"md:text-[14px]",
"lg:text-[18px]",
"xl:text-[24px]",
);
});
it("has proper accessibility attributes", () => {
render(<ContentBanner post={mockPost} />);
// Check that the component renders without accessibility errors
const banner = document.querySelector("div");
expect(banner).toBeInTheDocument();
// Check that the icon has proper alt text
const icon = screen.getByAltText("Icon for Test Article Title");
expect(icon).toBeInTheDocument();
});
});
-321
View File
@@ -1,321 +0,0 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { expect, describe, it, vi, beforeEach } from "vitest";
import { axe, toHaveNoViolations } from "jest-axe";
import ContextMenu from "../../app/components/ContextMenu";
import ContextMenuItem from "../../app/components/ContextMenuItem";
import ContextMenuSection from "../../app/components/ContextMenuSection";
import ContextMenuDivider from "../../app/components/ContextMenuDivider";
expect.extend(toHaveNoViolations);
describe("ContextMenu Component", () => {
const defaultProps = {
children: "Context Menu Content",
};
describe("Rendering", () => {
it("renders with default props", () => {
render(<ContextMenu {...defaultProps} />);
expect(screen.getByText("Context Menu Content")).toBeInTheDocument();
});
it("renders with custom className", () => {
render(<ContextMenu {...defaultProps} className="custom-class" />);
const menu = screen.getByText("Context Menu Content").closest("div");
expect(menu).toHaveClass("custom-class");
});
it("applies correct base styles", () => {
render(<ContextMenu {...defaultProps} />);
const menu = screen.getByText("Context Menu Content").closest("div");
expect(menu).toHaveClass(
"bg-black",
"border",
"rounded-[var(--measures-radius-medium)]",
"shadow-lg",
"p-[4px]",
);
});
it("has solid black background", () => {
render(<ContextMenu {...defaultProps} />);
const menu = screen.getByText("Context Menu Content").closest("div");
expect(menu).toHaveStyle({ backgroundColor: "#000000" });
});
});
describe("Accessibility", () => {
it("has no accessibility violations", async () => {
const { container } = render(
<ContextMenu {...defaultProps}>
<ContextMenuItem onClick={vi.fn()}>Menu Item</ContextMenuItem>
</ContextMenu>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("has proper role", () => {
render(<ContextMenu {...defaultProps} />);
const menu = screen.getByText("Context Menu Content").closest("div");
expect(menu).toHaveAttribute("role", "menu");
});
});
});
describe("ContextMenuItem Component", () => {
const defaultProps = {
children: "Menu Item",
onClick: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
});
describe("Rendering", () => {
it("renders with default props", () => {
render(<ContextMenuItem {...defaultProps} />);
expect(screen.getByText("Menu Item")).toBeInTheDocument();
});
it("renders as selected when selected prop is true", () => {
render(<ContextMenuItem {...defaultProps} selected={true} />);
const item = screen.getByRole("menuitem");
expect(item).toHaveClass(
"bg-[var(--color-surface-default-secondary)]",
"rounded-[var(--measures-radius-small)]",
);
});
it("renders with submenu arrow when hasSubmenu prop is true", () => {
render(<ContextMenuItem {...defaultProps} hasSubmenu={true} />);
// Check for the right-pointing chevron SVG
const item = screen.getByRole("menuitem");
const svg = item.querySelector("svg:last-child");
expect(svg).toBeInTheDocument();
});
it("renders with checkmark when selected prop is true", () => {
render(<ContextMenuItem {...defaultProps} selected={true} />);
// Check for the checkmark SVG
const item = screen.getByRole("menuitem");
const svg = item.querySelector("svg:first-child");
expect(svg).toBeInTheDocument();
});
it("applies correct size styles", () => {
render(<ContextMenuItem {...defaultProps} size="small" />);
const item = screen.getByRole("menuitem");
expect(item).toHaveClass("text-[10px]", "leading-[14px]");
});
it("applies medium size styles", () => {
render(<ContextMenuItem {...defaultProps} size="medium" />);
const item = screen.getByRole("menuitem");
expect(item).toHaveClass("text-[14px]", "leading-[20px]");
});
it("applies large size styles", () => {
render(<ContextMenuItem {...defaultProps} size="large" />);
const item = screen.getByRole("menuitem");
expect(item).toHaveClass("text-[16px]", "leading-[24px]");
});
});
describe("Interaction", () => {
it("calls onClick when clicked", async () => {
const user = userEvent.setup();
render(<ContextMenuItem {...defaultProps} />);
const item = screen.getByText("Menu Item");
await user.click(item);
expect(defaultProps.onClick).toHaveBeenCalledTimes(1);
});
it("does not call onClick when disabled", async () => {
const user = userEvent.setup();
render(<ContextMenuItem {...defaultProps} disabled={true} />);
const item = screen.getByText("Menu Item");
await user.click(item);
expect(defaultProps.onClick).not.toHaveBeenCalled();
});
it("has hover effects", () => {
render(<ContextMenuItem {...defaultProps} />);
const item = screen.getByRole("menuitem");
expect(item).toHaveClass(
"hover:!bg-[var(--color-surface-default-secondary)]",
);
});
});
describe("Accessibility", () => {
it("has no accessibility violations", async () => {
const { container } = render(
<ContextMenu>
<ContextMenuItem {...defaultProps} />
</ContextMenu>,
);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("has proper role", () => {
render(<ContextMenuItem {...defaultProps} />);
const item = screen.getByRole("menuitem");
expect(item).toBeInTheDocument();
});
});
describe("Styling", () => {
it("applies correct text color", () => {
render(<ContextMenuItem {...defaultProps} />);
const item = screen.getByRole("menuitem");
expect(item).toHaveClass(
"text-[var(--color-content-default-brand-primary)]",
);
});
it("applies correct padding", () => {
render(<ContextMenuItem {...defaultProps} />);
const item = screen.getByRole("menuitem");
expect(item).toHaveClass("px-[8px]", "py-[4px]");
});
it("applies correct gap between checkmark and text", () => {
render(<ContextMenuItem {...defaultProps} selected={true} />);
const item = screen.getByText("Menu Item").closest("div");
expect(item).toHaveClass("gap-[8px]");
});
});
});
describe("ContextMenuSection Component", () => {
const defaultProps = {
title: "Section Title",
children: "Section Content",
};
describe("Rendering", () => {
it("renders with title and children", () => {
render(<ContextMenuSection {...defaultProps} />);
expect(screen.getByText("Section Title")).toBeInTheDocument();
expect(screen.getByText("Section Content")).toBeInTheDocument();
});
it("renders without title when not provided", () => {
render(<ContextMenuSection>Section Content</ContextMenuSection>);
expect(screen.getByText("Section Content")).toBeInTheDocument();
expect(screen.queryByText("Section Title")).not.toBeInTheDocument();
});
it("applies correct title styling", () => {
render(<ContextMenuSection {...defaultProps} />);
const title = screen.getByText("Section Title");
expect(title).toHaveClass(
"text-[var(--color-content-default-primary)]",
"font-medium",
);
});
});
describe("Accessibility", () => {
it("has no accessibility violations", async () => {
const { container } = render(<ContextMenuSection {...defaultProps} />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
});
describe("ContextMenuDivider Component", () => {
describe("Rendering", () => {
it("renders divider", () => {
render(<ContextMenuDivider />);
const divider = screen.getByRole("separator");
expect(divider).toBeInTheDocument();
});
it("applies correct styling", () => {
render(<ContextMenuDivider />);
const divider = screen.getByRole("separator");
expect(divider).toHaveClass(
"border-t",
"border-[var(--color-border-default-tertiary)]",
"my-1",
);
});
});
describe("Accessibility", () => {
it("has no accessibility violations", async () => {
const { container } = render(<ContextMenuDivider />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
});
describe("ContextMenu Components Integration", () => {
const TestMenu = () => (
<ContextMenu>
<ContextMenuSection title="First Section">
<ContextMenuItem onClick={vi.fn()}>Item 1</ContextMenuItem>
<ContextMenuItem onClick={vi.fn()} selected={true}>
Item 2
</ContextMenuItem>
</ContextMenuSection>
<ContextMenuDivider />
<ContextMenuSection title="Second Section">
<ContextMenuItem onClick={vi.fn()} hasSubmenu={true}>
Item 3
</ContextMenuItem>
</ContextMenuSection>
</ContextMenu>
);
it("renders all components together", () => {
render(<TestMenu />);
expect(screen.getByText("First Section")).toBeInTheDocument();
expect(screen.getByText("Item 1")).toBeInTheDocument();
expect(screen.getByText("Item 2")).toBeInTheDocument();
expect(screen.getByText("Second Section")).toBeInTheDocument();
expect(screen.getByText("Item 3")).toBeInTheDocument();
expect(screen.getByRole("separator")).toBeInTheDocument();
});
it("has no accessibility violations when integrated", async () => {
const { container } = render(<TestMenu />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
-144
View File
@@ -1,144 +0,0 @@
import { render, screen, cleanup } from "@testing-library/react";
import { 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();
});
});
-285
View File
@@ -1,285 +0,0 @@
import { describe, test, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import Footer from "../../app/components/Footer";
describe("Footer", () => {
test("renders footer with correct structure", () => {
render(<Footer />);
const footers = screen.getAllByRole("contentinfo");
expect(footers.length).toBeGreaterThan(0);
const footer = footers[0];
expect(footer).toBeInTheDocument();
expect(footer).toHaveClass("bg-[var(--color-surface-default-primary)]");
expect(footer).toHaveClass("w-full");
});
test("renders schema markup for organization information", () => {
render(<Footer />);
const script = document.querySelector('script[type="application/ld+json"]');
expect(script).toBeInTheDocument();
const schemaData = JSON.parse(script.textContent);
expect(schemaData["@type"]).toBe("Organization");
expect(schemaData.name).toBe("Media Economies Design Lab");
expect(schemaData.email).toBe("medlab@colorado.edu");
expect(schemaData.url).toBe("https://communityrule.com");
expect(schemaData.sameAs).toContain(
"https://bsky.app/profile/medlabboulder",
);
expect(schemaData.sameAs).toContain("https://gitlab.com/medlabboulder");
});
test("renders organization name and contact information", () => {
render(<Footer />);
expect(
screen.getAllByText("Media Economies Design Lab").length,
).toBeGreaterThan(0);
const emailLinks = screen.getAllByRole("link", {
name: "medlab@colorado.edu",
});
expect(emailLinks.length).toBeGreaterThan(0);
const emailLink = emailLinks[0];
expect(emailLink).toBeInTheDocument();
expect(emailLink).toHaveAttribute("href", "mailto:medlab@colorado.edu");
});
test("renders social media links with correct accessibility", () => {
render(<Footer />);
// Check Bluesky link
const blueskyLinks = screen.getAllByRole("link", {
name: "Follow us on Bluesky",
});
expect(blueskyLinks.length).toBeGreaterThan(0);
const blueskyLink = blueskyLinks[0];
expect(blueskyLink).toBeInTheDocument();
expect(screen.getAllByText("medlabboulder").length).toBeGreaterThan(0);
// Check GitLab link
const gitlabLinks = screen.getAllByRole("link", {
name: "Follow us on GitLab",
});
expect(gitlabLinks.length).toBeGreaterThan(0);
const gitlabLink = gitlabLinks[0];
expect(gitlabLink).toBeInTheDocument();
// Check social media images
const blueskyImages = screen.getAllByAltText("Bluesky");
expect(blueskyImages.length).toBeGreaterThan(0);
const blueskyImage = blueskyImages[0];
expect(blueskyImage).toBeInTheDocument();
expect(blueskyImage).toHaveAttribute("src", "/assets/Bluesky_Logo.svg");
const gitlabImages = screen.getAllByAltText("GitLab");
expect(gitlabImages.length).toBeGreaterThan(0);
const gitlabImage = gitlabImages[0];
expect(gitlabImage).toBeInTheDocument();
expect(gitlabImage).toHaveAttribute("src", "/assets/GitLab_Icon.png");
});
test("renders navigation links", () => {
render(<Footer />);
expect(
screen.getAllByRole("link", { name: "Use cases" }).length,
).toBeGreaterThan(0);
expect(
screen.getAllByRole("link", { name: "Learn" }).length,
).toBeGreaterThan(0);
expect(
screen.getAllByRole("link", { name: "About" }).length,
).toBeGreaterThan(0);
});
test("renders legal links", () => {
render(<Footer />);
expect(
screen.getAllByRole("link", { name: "Privacy Policy" }).length,
).toBeGreaterThan(0);
expect(
screen.getAllByRole("link", { name: "Terms of Service" }).length,
).toBeGreaterThan(0);
expect(
screen.getAllByRole("link", { name: "Cookies Settings" }).length,
).toBeGreaterThan(0);
});
test("renders copyright information", () => {
render(<Footer />);
expect(screen.getAllByText("© All right reserved").length).toBeGreaterThan(
0,
);
});
test("renders responsive logo configurations", () => {
render(<Footer />);
// Check that logo containers exist for different breakpoints
const logoContainers = document.querySelectorAll(
'[class*="block sm:hidden"], [class*="hidden sm:block lg:hidden"], [class*="hidden lg:block"]',
);
expect(logoContainers.length).toBeGreaterThan(0);
});
test("has correct CSS classes for responsive design", () => {
render(<Footer />);
const footers = screen.getAllByRole("contentinfo");
expect(footers.length).toBeGreaterThan(0);
const footer = footers[0];
const mainContainer = footer.querySelector("div");
expect(mainContainer).toHaveClass("flex");
expect(mainContainer).toHaveClass("flex-col");
expect(mainContainer).toHaveClass("items-start");
expect(mainContainer).toHaveClass("mx-auto");
});
test("renders separator component", () => {
render(<Footer />);
// The Separator component should be rendered (it uses a div with border, not hr)
const separator = document.querySelector(
".bg-\\[var\\(--border-color-default-secondary\\)\\]",
);
expect(separator).toBeInTheDocument();
});
test("social media links have hover and focus states", () => {
render(<Footer />);
const blueskyLinks = screen.getAllByRole("link", {
name: "Follow us on Bluesky",
});
expect(blueskyLinks.length).toBeGreaterThan(0);
expect(blueskyLinks[0]).toHaveClass("hover:opacity-80");
expect(blueskyLinks[0]).toHaveClass("active:opacity-60");
expect(blueskyLinks[0]).toHaveClass("focus:opacity-80");
expect(blueskyLinks[0]).toHaveClass("transition-opacity");
const gitlabLinks = screen.getAllByRole("link", {
name: "Follow us on GitLab",
});
expect(gitlabLinks.length).toBeGreaterThan(0);
expect(gitlabLinks[0]).toHaveClass("hover:opacity-80");
expect(gitlabLinks[0]).toHaveClass("active:opacity-60");
expect(gitlabLinks[0]).toHaveClass("focus:opacity-80");
expect(gitlabLinks[0]).toHaveClass("transition-opacity");
});
test("navigation links have hover and focus states", () => {
render(<Footer />);
const useCasesLinks = screen.getAllByRole("link", { name: "Use cases" });
expect(useCasesLinks.length).toBeGreaterThan(0);
expect(useCasesLinks[0]).toHaveClass("hover:opacity-80");
expect(useCasesLinks[0]).toHaveClass("active:opacity-60");
expect(useCasesLinks[0]).toHaveClass("focus:opacity-80");
expect(useCasesLinks[0]).toHaveClass("transition-opacity");
});
test("legal links have hover and focus states", () => {
render(<Footer />);
const privacyLinks = screen.getAllByRole("link", {
name: "Privacy Policy",
});
expect(privacyLinks.length).toBeGreaterThan(0);
expect(privacyLinks[0]).toHaveClass("hover:opacity-80");
expect(privacyLinks[0]).toHaveClass("active:opacity-60");
expect(privacyLinks[0]).toHaveClass("focus:opacity-80");
expect(privacyLinks[0]).toHaveClass("transition-opacity");
});
test("email link has hover and focus states", () => {
render(<Footer />);
const emailLinks = screen.getAllByRole("link", {
name: "medlab@colorado.edu",
});
expect(emailLinks.length).toBeGreaterThan(0);
expect(emailLinks[0]).toHaveClass("hover:opacity-80");
expect(emailLinks[0]).toHaveClass("active:opacity-60");
expect(emailLinks[0]).toHaveClass("focus:opacity-80");
expect(emailLinks[0]).toHaveClass("transition-opacity");
});
test("social media images have hover effects", () => {
render(<Footer />);
const blueskyImages = screen.getAllByAltText("Bluesky");
expect(blueskyImages.length).toBeGreaterThan(0);
expect(blueskyImages[0]).toHaveClass("group-hover:scale-110");
expect(blueskyImages[0]).toHaveClass("transition-transform");
const gitlabImages = screen.getAllByAltText("GitLab");
expect(gitlabImages.length).toBeGreaterThan(0);
expect(gitlabImages[0]).toHaveClass("group-hover:scale-110");
expect(gitlabImages[0]).toHaveClass("transition-transform");
expect(gitlabImages[0]).toHaveClass("grayscale");
});
test("renders multiple instances of navigation links for responsive design", () => {
render(<Footer />);
// Should have navigation links in the footer
const useCasesLinks = screen.getAllByText("Use cases");
const learnLinks = screen.getAllByText("Learn");
const aboutLinks = screen.getAllByText("About");
expect(useCasesLinks.length).toBeGreaterThan(0);
expect(learnLinks.length).toBeGreaterThan(0);
expect(aboutLinks.length).toBeGreaterThan(0);
});
test("has proper focus management for accessibility", () => {
render(<Footer />);
// Get specific links that should have focus management
const emailLinks = screen.getAllByRole("link", {
name: "medlab@colorado.edu",
});
const blueskyLinks = screen.getAllByRole("link", {
name: "Follow us on Bluesky",
});
const gitlabLinks = screen.getAllByRole("link", {
name: "Follow us on GitLab",
});
// Use the first instance of each social media link
const blueskyLink = blueskyLinks[0];
const gitlabLink = gitlabLinks[0];
// Check email links (multiple due to responsive design)
emailLinks.forEach((emailLink) => {
expect(emailLink).toHaveClass("focus:outline-none");
expect(emailLink).toHaveClass("focus:ring-2");
expect(emailLink).toHaveClass("focus:ring-offset-2");
expect(emailLink).toHaveClass(
"focus:ring-[var(--color-content-default-primary)]",
);
expect(emailLink).toHaveClass(
"focus:ring-offset-[var(--color-surface-default-primary)]",
);
});
// Check social media links
[blueskyLink, gitlabLink].forEach((link) => {
expect(link).toHaveClass("focus:outline-none");
expect(link).toHaveClass("focus:ring-2");
expect(link).toHaveClass("focus:ring-offset-2");
expect(link).toHaveClass(
"focus:ring-[var(--color-content-default-primary)]",
);
expect(link).toHaveClass(
"focus:ring-offset-[var(--color-surface-default-primary)]",
);
});
});
});
-334
View File
@@ -1,334 +0,0 @@
import { describe, test, expect, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import Header, {
navigationItems,
avatarImages,
logoConfig,
} from "../../app/components/Header.js";
describe("Header", () => {
beforeEach(() => {
// Clear any existing rendered content
document.body.innerHTML = "";
});
describe("Accessibility and Landmarks", () => {
test("renders header with correct structure and accessibility attributes", () => {
const { container } = render(<Header />);
// Check main header structure - use container to scope the search
const header = container.querySelector(
'[role="banner"][aria-label="Main navigation header"]',
);
expect(header).toBeInTheDocument();
expect(header).toHaveAttribute("aria-label", "Main navigation header");
// Check navigation - use container to scope the search
const nav = container.querySelector(
'[role="navigation"][aria-label="Main navigation"]',
);
expect(nav).toBeInTheDocument();
expect(nav).toHaveAttribute("aria-label", "Main navigation");
});
test("renders all navigation items with proper accessibility", () => {
render(<Header />);
// Check all navigation items have proper aria-labels - use menuitem role since they're in a menubar
expect(
screen.getAllByRole("menuitem", { name: "Navigate to Use cases page" })
.length,
).toBeGreaterThan(0);
expect(
screen.getAllByRole("menuitem", { name: "Navigate to Learn page" })
.length,
).toBeGreaterThan(0);
expect(
screen.getAllByRole("menuitem", { name: "Navigate to About page" })
.length,
).toBeGreaterThan(0);
});
});
describe("Schema Markup", () => {
test("renders schema markup for site navigation", () => {
render(<Header />);
const script = document.querySelector(
'script[type="application/ld+json"]',
);
expect(script).toBeInTheDocument();
const schemaData = JSON.parse(script.textContent);
expect(schemaData["@type"]).toBe("WebSite");
expect(schemaData.name).toBe("CommunityRule");
expect(schemaData.url).toBe("https://communityrule.com");
expect(schemaData.potentialAction["@type"]).toBe("SearchAction");
});
});
describe("Configuration Data", () => {
test("navigationItems has correct structure and count", () => {
expect(navigationItems).toHaveLength(3);
expect(navigationItems[0]).toEqual({
href: "#",
text: "Use cases",
extraPadding: true,
});
expect(navigationItems[1]).toEqual({
href: "/learn",
text: "Learn",
});
expect(navigationItems[2]).toEqual({
href: "#",
text: "About",
});
});
test("avatarImages has correct structure and count", () => {
expect(avatarImages).toHaveLength(3);
expect(avatarImages[0]).toEqual({
src: "/assets/Avatar_1.png",
alt: "Avatar 1",
});
expect(avatarImages[1]).toEqual({
src: "/assets/Avatar_2.png",
alt: "Avatar 2",
});
expect(avatarImages[2]).toEqual({
src: "/assets/Avatar_3.png",
alt: "Avatar 3",
});
});
test("logoConfig has correct structure and count", () => {
expect(logoConfig).toHaveLength(5);
// Check first config (xs)
expect(logoConfig[0]).toEqual({
breakpoint: "block sm:hidden",
size: "header",
showText: false,
});
// Check last config (xl+)
expect(logoConfig[4]).toEqual({
breakpoint: "hidden xl:block",
size: "headerXl",
showText: true,
});
});
});
describe("Logo Configuration", () => {
test("renders correct number of logo variants", () => {
render(<Header />);
const logoWrappers = screen.getAllByTestId("logo-wrapper");
expect(logoWrappers).toHaveLength(logoConfig.length);
});
test("logo wrappers include expected breakpoint classes", () => {
render(<Header />);
const logoWrappers = screen.getAllByTestId("logo-wrapper");
// Check first logo variant (xs only)
expect(logoWrappers[0]).toHaveClass("block", "sm:hidden");
// Check second logo variant (sm only)
expect(logoWrappers[1]).toHaveClass("hidden", "sm:block", "md:hidden");
// Check third logo variant (md only)
expect(logoWrappers[2]).toHaveClass("hidden", "md:block", "lg:hidden");
// Check fourth logo variant (lg only)
expect(logoWrappers[3]).toHaveClass("hidden", "lg:block", "xl:hidden");
// Check fifth logo variant (xl+)
expect(logoWrappers[4]).toHaveClass("hidden", "xl:block");
});
});
describe("Navigation Structure", () => {
test("renders all breakpoint-specific navigation containers", () => {
render(<Header />);
expect(screen.getByTestId("nav-xs")).toBeInTheDocument();
expect(screen.getByTestId("nav-sm")).toBeInTheDocument();
expect(screen.getByTestId("nav-md")).toBeInTheDocument();
expect(screen.getByTestId("nav-lg")).toBeInTheDocument();
expect(screen.getByTestId("nav-xl")).toBeInTheDocument();
});
test("navigation containers use expected breakpoint classes", () => {
render(<Header />);
// XSmall navigation
const navXs = screen.getByTestId("nav-xs");
expect(navXs).toHaveClass("block", "sm:hidden");
// Small navigation
const navSm = screen.getByTestId("nav-sm");
expect(navSm).toHaveClass("hidden", "sm:block", "md:hidden");
// Medium navigation
const navMd = screen.getByTestId("nav-md");
expect(navMd).toHaveClass("hidden", "md:block", "lg:hidden");
// Large navigation
const navLg = screen.getByTestId("nav-lg");
expect(navLg).toHaveClass("hidden", "lg:block", "xl:hidden");
// XLarge navigation
const navXl = screen.getByTestId("nav-xl");
expect(navXl).toHaveClass("hidden", "xl:block");
});
test("renders navigation items with correct text and links", () => {
render(<Header />);
// Check navigation items
expect(screen.getAllByText("Use cases").length).toBeGreaterThan(0);
expect(screen.getAllByText("Learn").length).toBeGreaterThan(0);
expect(screen.getAllByText("About").length).toBeGreaterThan(0);
});
test("renders multiple instances of navigation items for responsive design", () => {
render(<Header />);
// Should have multiple instances of navigation items for different breakpoints
const useCasesLinks = screen.getAllByText("Use cases");
const learnLinks = screen.getAllByText("Learn");
const aboutLinks = screen.getAllByText("About");
expect(useCasesLinks.length).toBeGreaterThan(1);
expect(learnLinks.length).toBeGreaterThan(1);
expect(aboutLinks.length).toBeGreaterThan(1);
});
});
describe("Authentication Structure", () => {
test("renders all breakpoint-specific auth containers", () => {
render(<Header />);
expect(screen.getByTestId("auth-xs")).toBeInTheDocument();
expect(screen.getByTestId("auth-sm")).toBeInTheDocument();
expect(screen.getByTestId("auth-md")).toBeInTheDocument();
expect(screen.getByTestId("auth-lg")).toBeInTheDocument();
expect(screen.getByTestId("auth-xl")).toBeInTheDocument();
});
test("auth containers use expected breakpoint classes", () => {
render(<Header />);
// XSmall auth
const authXs = screen.getByTestId("auth-xs");
expect(authXs).toHaveClass("block", "sm:hidden");
// Small auth
const authSm = screen.getByTestId("auth-sm");
expect(authSm).toHaveClass("hidden", "sm:block", "md:hidden");
// Medium auth
const authMd = screen.getByTestId("auth-md");
expect(authMd).toHaveClass("hidden", "md:block", "lg:hidden");
// Large auth
const authLg = screen.getByTestId("auth-lg");
expect(authLg).toHaveClass("hidden", "lg:block", "xl:hidden");
// XLarge auth
const authXl = screen.getByTestId("auth-xl");
expect(authXl).toHaveClass("hidden", "xl:block");
});
test("renders login button with correct accessibility", () => {
render(<Header />);
const loginLinks = screen.getAllByRole("menuitem", {
name: "Log in to your account",
});
expect(loginLinks.length).toBeGreaterThan(0);
expect(screen.getAllByText("Log in").length).toBeGreaterThan(0);
});
test("renders multiple login buttons for responsive design", () => {
render(<Header />);
// Should have multiple login buttons for different breakpoints
const loginButtons = screen.getAllByText("Log in");
expect(loginButtons.length).toBeGreaterThan(1);
});
test("renders create rule button with avatar decoration", () => {
render(<Header />);
const createRuleButtons = screen.getAllByRole("button", {
name: "Create a new rule with avatar decoration",
});
expect(createRuleButtons.length).toBeGreaterThan(0);
expect(screen.getAllByText("Create rule").length).toBeGreaterThan(0);
});
test("renders multiple create rule buttons for responsive design", () => {
render(<Header />);
// Should have multiple create rule buttons for different breakpoints
const createRuleButtons = screen.getAllByText("Create rule");
expect(createRuleButtons.length).toBeGreaterThan(1);
});
});
describe("Avatar Images", () => {
test("renders avatar images with correct attributes", () => {
render(<Header />);
const avatars = screen.getAllByRole("img");
expect(avatars.length).toBeGreaterThan(0);
// Check for avatar images
const avatarImages = avatars.filter(
(img) =>
img.alt === "Avatar 1" ||
img.alt === "Avatar 2" ||
img.alt === "Avatar 3",
);
expect(avatarImages.length).toBeGreaterThan(0);
});
});
describe("Sticky Header Behavior", () => {
test("applies sticky positioning classes", () => {
const { container } = render(<Header />);
const header = container.querySelector(
'[role="banner"][aria-label="Main navigation header"]',
);
expect(header).toHaveClass("sticky", "top-0", "z-50");
});
});
describe("CSS Classes and Styling", () => {
test("has correct CSS classes for styling", () => {
const { container } = render(<Header />);
const header = container.querySelector(
'[role="banner"][aria-label="Main navigation header"]',
);
expect(header).toHaveClass("bg-[var(--color-surface-default-primary)]");
expect(header).toHaveClass("w-full");
expect(header).toHaveClass("border-b");
expect(header).toHaveClass(
"border-[var(--border-color-default-tertiary)]",
);
const nav = container.querySelector(
'[role="navigation"][aria-label="Main navigation"]',
);
expect(nav).toHaveClass("flex");
expect(nav).toHaveClass("items-center");
expect(nav).toHaveClass("justify-between");
});
});
});
-141
View File
@@ -1,141 +0,0 @@
import { render, screen, cleanup } from "@testing-library/react";
import { 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();
// Button component renders multiple versions for different screen sizes
// Use getAllByRole to handle multiple buttons with same text
const buttons = screen.getAllByRole("button", { name: "Get Started" });
expect(buttons.length).toBeGreaterThan(0);
});
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");
// Find the div with md:flex-1 class
const contentLockup = document.querySelector('[class*="md:flex-1"]');
expect(contentLockup).toBeInTheDocument();
});
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();
// Button component renders multiple versions for different screen sizes
const buttons = screen.getAllByRole("button", { name: "Test CTA" });
expect(buttons.length).toBeGreaterThan(0);
});
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 = document.querySelector("section");
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();
});
});
-271
View File
@@ -1,271 +0,0 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { expect, test, describe, vi } from "vitest";
import Input from "../../app/components/Input";
describe("Input Component", () => {
test("renders with default props", () => {
render(<Input />);
const input = screen.getByRole("textbox");
expect(input).toBeInTheDocument();
expect(input).toHaveAttribute("type", "text");
});
test("renders with label", () => {
render(<Input label="Test input" />);
expect(screen.getByText("Test input")).toBeInTheDocument();
expect(screen.getByLabelText("Test input")).toBeInTheDocument();
});
test("renders with placeholder", () => {
render(<Input placeholder="Enter text..." />);
const input = screen.getByPlaceholderText("Enter text...");
expect(input).toBeInTheDocument();
});
test("renders with value", () => {
render(<Input value="test value" />);
const input = screen.getByDisplayValue("test value");
expect(input).toBeInTheDocument();
});
test("calls onChange when text is entered", () => {
const handleChange = vi.fn();
render(<Input onChange={handleChange} />);
const input = screen.getByRole("textbox");
fireEvent.change(input, { target: { value: "new text" } });
expect(handleChange).toHaveBeenCalledWith(expect.any(Object));
});
test("calls onFocus when focused", () => {
const handleFocus = vi.fn();
render(<Input onFocus={handleFocus} />);
const input = screen.getByRole("textbox");
fireEvent.focus(input);
expect(handleFocus).toHaveBeenCalledWith(expect.any(Object));
});
test("calls onBlur when blurred", () => {
const handleBlur = vi.fn();
render(<Input onBlur={handleBlur} />);
const input = screen.getByRole("textbox");
fireEvent.blur(input);
expect(handleBlur).toHaveBeenCalledWith(expect.any(Object));
});
test("does not call onChange when disabled", () => {
const handleChange = vi.fn();
render(<Input disabled={true} onChange={handleChange} />);
const input = screen.getByRole("textbox");
fireEvent.change(input, { target: { value: "new text" } });
expect(handleChange).not.toHaveBeenCalled();
});
test("does not call onFocus when disabled", () => {
const handleFocus = vi.fn();
render(<Input disabled={true} onFocus={handleFocus} />);
const input = screen.getByRole("textbox");
fireEvent.focus(input);
expect(handleFocus).not.toHaveBeenCalled();
});
test("does not call onBlur when disabled", () => {
const handleBlur = vi.fn();
render(<Input disabled={true} onBlur={handleBlur} />);
const input = screen.getByRole("textbox");
fireEvent.blur(input);
expect(handleBlur).not.toHaveBeenCalled();
});
test("applies disabled attributes when disabled", () => {
render(<Input disabled={true} />);
const input = screen.getByRole("textbox");
expect(input).toBeDisabled();
});
test("applies correct size classes", () => {
const { rerender } = render(<Input size="small" />);
let input = screen.getByRole("textbox");
expect(input).toHaveClass("h-[32px]");
rerender(<Input size="medium" />);
input = screen.getByRole("textbox");
expect(input).toHaveClass("h-[36px]");
rerender(<Input size="large" />);
input = screen.getByRole("textbox");
expect(input).toHaveClass("h-[40px]");
});
test("applies correct label variant classes", () => {
const { rerender } = render(<Input label="Test" labelVariant="default" />);
let container = screen.getByRole("textbox").closest("div").parentElement;
expect(container).toHaveClass("flex-col");
rerender(<Input label="Test" labelVariant="horizontal" />);
container = screen.getByRole("textbox").closest("div").parentElement;
expect(container).toHaveClass("flex", "items-center");
});
test("applies error state classes", () => {
render(<Input error={true} />);
const input = screen.getByRole("textbox");
expect(input).toHaveClass(
"border-[var(--color-border-default-utility-negative)]",
);
});
test("applies disabled state classes", () => {
render(<Input disabled={true} />);
const input = screen.getByRole("textbox");
expect(input).toHaveClass("cursor-not-allowed");
expect(input).toHaveClass("bg-[var(--color-content-default-secondary)]");
});
test("applies focus state classes", () => {
render(<Input state="focus" />);
const input = screen.getByRole("textbox");
expect(input).toHaveClass(
"border-[var(--color-border-default-utility-info)]",
);
expect(input).toHaveClass("shadow-[0_0_5px_3px_#3281F8]");
});
test("applies hover state classes", () => {
render(<Input state="hover" />);
const input = screen.getByRole("textbox");
expect(input).toHaveClass("border-[var(--color-border-default-tertiary)]");
expect(input).toHaveClass(
"shadow-[0_0_0_2px_var(--color-border-default-tertiary)]",
);
});
test("applies active state classes", () => {
render(<Input state="active" />);
const input = screen.getByRole("textbox");
expect(input).toHaveClass("border-[var(--color-border-default-tertiary)]");
});
test("applies default state classes", () => {
render(<Input state="default" />);
const input = screen.getByRole("textbox");
expect(input).toHaveClass("border-[var(--color-border-default-tertiary)]");
expect(input).toHaveClass(
"hover:shadow-[0_0_0_2px_var(--color-border-default-tertiary)]",
);
});
test("applies custom className", () => {
render(<Input className="custom-class" />);
const input = screen.getByRole("textbox");
expect(input).toHaveClass("custom-class");
});
test("passes through additional props", () => {
render(<Input id="test-input" name="test" type="email" />);
const input = screen.getByRole("textbox");
expect(input).toHaveAttribute("id", "test-input");
expect(input).toHaveAttribute("name", "test");
expect(input).toHaveAttribute("type", "email");
});
test("generates unique ID when not provided", () => {
render(<Input label="Test" />);
const input = screen.getByRole("textbox");
const label = screen.getByText("Test");
expect(input).toHaveAttribute("id");
expect(label).toHaveAttribute("for", input.id);
});
test("uses provided ID when given", () => {
render(<Input id="custom-id" label="Test" />);
const input = screen.getByRole("textbox");
const label = screen.getByText("Test");
expect(input).toHaveAttribute("id", "custom-id");
expect(label).toHaveAttribute("for", "custom-id");
});
test("applies correct border radius style", () => {
const { rerender } = render(<Input size="small" />);
let input = screen.getByRole("textbox");
expect(input).toHaveStyle("border-radius: var(--measures-radius-small)");
rerender(<Input size="medium" />);
input = screen.getByRole("textbox");
expect(input).toHaveStyle("border-radius: var(--measures-radius-medium)");
rerender(<Input size="large" />);
input = screen.getByRole("textbox");
expect(input).toHaveStyle("border-radius: var(--measures-radius-large)");
});
test("applies opacity wrapper when disabled", () => {
render(<Input disabled={true} />);
const wrapper = screen.getByRole("textbox").closest("div");
expect(wrapper).toHaveClass("opacity-40");
});
test("does not apply opacity wrapper when not disabled", () => {
render(<Input disabled={false} />);
const wrapper = screen.getByRole("textbox").closest("div");
expect(wrapper).not.toHaveClass("opacity-40");
});
test("applies correct label styling", () => {
render(<Input label="Test label" size="small" />);
const label = screen.getByText("Test label");
expect(label).toHaveClass("text-[12px]");
expect(label).toHaveClass("leading-[14px]");
expect(label).toHaveClass("font-medium");
expect(label).toHaveClass("text-[var(--color-content-default-secondary)]");
});
test("applies correct input text styling for different sizes", () => {
const { rerender } = render(<Input size="small" />);
let input = screen.getByRole("textbox");
expect(input).toHaveClass("text-[10px]");
rerender(<Input size="medium" />);
input = screen.getByRole("textbox");
expect(input).toHaveClass("text-[14px]");
expect(input).toHaveClass("leading-[20px]");
rerender(<Input size="large" />);
input = screen.getByRole("textbox");
expect(input).toHaveClass("text-[16px]");
expect(input).toHaveClass("leading-[24px]");
});
test("handles keyboard navigation", () => {
const handleFocus = vi.fn();
render(<Input onFocus={handleFocus} />);
const input = screen.getByRole("textbox");
fireEvent.keyDown(input, { key: "Tab" });
fireEvent.focus(input);
expect(handleFocus).toHaveBeenCalled();
});
test("forwards ref correctly", () => {
const ref = React.createRef();
render(<Input ref={ref} />);
expect(ref.current).toBeInstanceOf(HTMLInputElement);
});
test("is memoized", () => {
expect(Input.$$typeof).toBe(Symbol.for("react.memo"));
});
});
-128
View File
@@ -1,128 +0,0 @@
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 logoDiv = screen.getByRole("link").querySelector("div");
expect(logoDiv).toHaveClass("h-[20.85px]");
rerender(<Logo size="headerLg" />);
logoDiv = screen.getByRole("link").querySelector("div");
expect(logoDiv).toHaveClass("h-[28px]");
rerender(<Logo size="footer" />);
logoDiv = screen.getByRole("link").querySelector("div");
expect(logoDiv).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 logoDiv = screen.getByRole("link").querySelector("div");
expect(logoDiv).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("href", "/");
});
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();
});
});
});
-283
View File
@@ -1,283 +0,0 @@
import { describe, test, expect } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import Page from "../../app/page";
describe("Page", () => {
test("renders all main sections", async () => {
render(<Page />);
// Check that all main sections are rendered (using getAllByText since there are multiple instances)
expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0);
expect(screen.getAllByText("with clarity").length).toBeGreaterThan(0);
expect(
screen.getAllByText(
"Help your community make important decisions in a way that reflects its unique values.",
).length,
).toBeGreaterThan(0);
// Wait for dynamically imported components to load
// Check numbered cards section (using getAllByText since there are multiple instances)
await waitFor(() => {
expect(
screen.getAllByText("How CommunityRule works").length,
).toBeGreaterThan(0);
});
expect(
screen.getAllByText(
"Here's a quick overview of the process, from start to finish.",
).length,
).toBeGreaterThan(0);
// Check feature grid section (using getAllByText since there are multiple instances)
expect(
screen.getAllByText("We've got your back, every step of the way").length,
).toBeGreaterThan(0);
expect(
screen.getAllByText(
"Use our toolkit to improve, document, and evolve your organization.",
).length,
).toBeGreaterThan(0);
// Check ask organizer section (using getAllByText since there are multiple instances)
expect(screen.getAllByText("Still have questions?").length).toBeGreaterThan(
0,
);
expect(
screen.getAllByText("Get answers from an experienced organizer").length,
).toBeGreaterThan(0);
});
test("renders hero banner with correct data", () => {
render(<Page />);
// Check hero banner content (using getAllByText since there are multiple instances)
expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0);
expect(screen.getAllByText("with clarity").length).toBeGreaterThan(0);
expect(
screen.getAllByText(
"Help your community make important decisions in a way that reflects its unique values.",
).length,
).toBeGreaterThan(0);
expect(
screen.getAllByText("Learn how CommunityRule works").length,
).toBeGreaterThan(0);
});
test("renders numbered cards with correct data", async () => {
render(<Page />);
// Wait for dynamically imported NumberedCards component to load
await waitFor(() => {
expect(
screen.getAllByText("How CommunityRule works").length,
).toBeGreaterThan(0);
});
expect(
screen.getAllByText(
"Here's a quick overview of the process, from start to finish.",
).length,
).toBeGreaterThan(0);
// Check individual card content (using getAllByText since there are multiple instances)
expect(
screen.getAllByText("Document how your community makes decisions").length,
).toBeGreaterThan(0);
expect(
screen.getAllByText(
"Build an operating manual for a successful community",
).length,
).toBeGreaterThan(0);
expect(
screen.getAllByText(
"Get a link to your manual for your group to review and evolve",
).length,
).toBeGreaterThan(0);
});
test("renders feature grid with correct data", async () => {
render(<Page />);
// Wait for dynamically imported FeatureGrid component to load
await waitFor(() => {
expect(
screen.getAllByText("We've got your back, every step of the way")
.length,
).toBeGreaterThan(0);
});
expect(
screen.getAllByText(
"Use our toolkit to improve, document, and evolve your organization.",
).length,
).toBeGreaterThan(0);
});
test("renders ask organizer section with correct data", () => {
render(<Page />);
// Check ask organizer content (using getAllByText since there are multiple instances)
expect(screen.getAllByText("Still have questions?").length).toBeGreaterThan(
0,
);
expect(
screen.getAllByText("Get answers from an experienced organizer").length,
).toBeGreaterThan(0);
expect(screen.getAllByText("Ask an organizer").length).toBeGreaterThan(0);
});
test("renders all component sections", async () => {
render(<Page />);
// Check that all major components are present by looking for their content
// HeroBanner
expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0);
// Wait for dynamically imported components to load
// LogoWall - should be present (even if just the component structure)
// NumberedCards
await waitFor(() => {
expect(
screen.getAllByText("How CommunityRule works").length,
).toBeGreaterThan(0);
});
// RuleStack - should be present
// FeatureGrid
await waitFor(() => {
expect(
screen.getAllByText("We've got your back, every step of the way")
.length,
).toBeGreaterThan(0);
});
// QuoteBlock - should be present
// AskOrganizer
expect(screen.getAllByText("Still have questions?").length).toBeGreaterThan(
0,
);
});
test("has correct page structure", () => {
render(<Page />);
const mainContainer = screen.getAllByText("Collaborate")[0].closest("div");
expect(mainContainer).toBeInTheDocument();
});
test("renders call-to-action elements", () => {
render(<Page />);
// Check CTA button in hero banner
expect(
screen.getAllByText("Learn how CommunityRule works").length,
).toBeGreaterThan(0);
// Check CTA button in ask organizer section
expect(screen.getAllByText("Ask an organizer").length).toBeGreaterThan(0);
});
test("renders descriptive text content", async () => {
render(<Page />);
// Check main description (using getAllByText since there are multiple instances)
expect(
screen.getAllByText(
"Help your community make important decisions in a way that reflects its unique values.",
).length,
).toBeGreaterThan(0);
// Wait for dynamically imported NumberedCards component
await waitFor(() => {
expect(
screen.getAllByText(
"Here's a quick overview of the process, from start to finish.",
).length,
).toBeGreaterThan(0);
});
// Wait for dynamically imported FeatureGrid component
await waitFor(() => {
expect(
screen.getAllByText(
"Use our toolkit to improve, document, and evolve your organization.",
).length,
).toBeGreaterThan(0);
});
// Check ask organizer description (using getAllByText since there are multiple instances)
expect(
screen.getAllByText("Get answers from an experienced organizer").length,
).toBeGreaterThan(0);
});
test("renders section titles correctly", async () => {
render(<Page />);
// Check all section titles (using getAllByText since there are multiple instances)
expect(screen.getAllByText("Collaborate").length).toBeGreaterThan(0);
// Wait for dynamically imported components
await waitFor(() => {
expect(
screen.getAllByText("How CommunityRule works").length,
).toBeGreaterThan(0);
});
await waitFor(() => {
expect(
screen.getAllByText("We've got your back, every step of the way")
.length,
).toBeGreaterThan(0);
});
expect(screen.getAllByText("Still have questions?").length).toBeGreaterThan(
0,
);
});
test("renders numbered card items with correct content", async () => {
render(<Page />);
// Wait for dynamically imported NumberedCards component
await waitFor(() => {
// Check all three numbered card items (using getAllByText since there are multiple instances)
expect(
screen.getAllByText("Document how your community makes decisions")
.length,
).toBeGreaterThan(0);
});
expect(
screen.getAllByText(
"Build an operating manual for a successful community",
).length,
).toBeGreaterThan(0);
expect(
screen.getAllByText(
"Get a link to your manual for your group to review and evolve",
).length,
).toBeGreaterThan(0);
});
test("renders subtitle content correctly", async () => {
render(<Page />);
// Check subtitles (using getAllByText since there are multiple instances)
expect(screen.getAllByText("with clarity").length).toBeGreaterThan(0);
// Wait for dynamically imported components
await waitFor(() => {
expect(
screen.getAllByText(
"Here's a quick overview of the process, from start to finish.",
).length,
).toBeGreaterThan(0);
});
await waitFor(() => {
expect(
screen.getAllByText(
"Use our toolkit to improve, document, and evolve your organization.",
).length,
).toBeGreaterThan(0);
});
expect(
screen.getAllByText("Get answers from an experienced organizer").length,
).toBeGreaterThan(0);
});
});
-248
View File
@@ -1,248 +0,0 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi } from "vitest";
import RadioButton from "../../app/components/RadioButton";
describe("RadioButton", () => {
it("renders with default props", () => {
render(<RadioButton />);
const radioButton = screen.getByRole("radio");
expect(radioButton).toBeInTheDocument();
expect(radioButton).toHaveAttribute("aria-checked", "false");
});
it("renders with label", () => {
render(<RadioButton label="Test Radio" />);
expect(screen.getByText("Test Radio")).toBeInTheDocument();
});
it("shows checked state", () => {
render(<RadioButton checked={true} label="Checked Radio" />);
const radioButton = screen.getByRole("radio");
expect(radioButton).toHaveAttribute("aria-checked", "true");
});
it("calls onChange when clicked", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioButton
checked={false}
onChange={handleChange}
label="Test Radio"
/>,
);
const radioButton = screen.getByRole("radio");
await user.click(radioButton);
expect(handleChange).toHaveBeenCalledWith({
checked: true,
value: undefined,
});
});
it("calls onChange with value when clicked", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioButton
checked={false}
value="test-value"
onChange={handleChange}
label="Test Radio"
/>,
);
const radioButton = screen.getByRole("radio");
await user.click(radioButton);
expect(handleChange).toHaveBeenCalledWith({
checked: true,
value: "test-value",
});
});
it("does not call onChange when clicking already checked radio button", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioButton checked={true} onChange={handleChange} label="Test Radio" />,
);
const radioButton = screen.getByRole("radio");
await user.click(radioButton);
// Radio buttons should not be unchecked by clicking them again
expect(handleChange).not.toHaveBeenCalled();
});
it("handles keyboard activation", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioButton
checked={false}
onChange={handleChange}
label="Test Radio"
/>,
);
const radioButton = screen.getByRole("radio");
radioButton.focus();
await user.keyboard(" ");
expect(handleChange).toHaveBeenCalledWith({
checked: true,
value: undefined,
});
});
it("handles Enter key activation", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioButton
checked={false}
onChange={handleChange}
label="Test Radio"
/>,
);
const radioButton = screen.getByRole("radio");
await user.click(radioButton); // Focus the element first
await user.keyboard("{Enter}");
expect(handleChange).toHaveBeenCalledWith({
checked: true,
value: undefined,
});
});
it("applies standard mode classes", () => {
render(<RadioButton mode="standard" label="Standard Radio" />);
const radioButton = screen.getByRole("radio");
expect(radioButton).toHaveClass(
"outline-[var(--color-border-default-tertiary)]",
);
});
it("applies inverse mode classes", () => {
render(<RadioButton mode="inverse" label="Inverse Radio" />);
const radioButton = screen.getByRole("radio");
expect(radioButton).toHaveClass(
"outline-[var(--color-border-inverse-primary)]",
);
});
it("applies focus state classes", () => {
render(<RadioButton state="focus" label="Focus Radio" />);
const radioButton = screen.getByRole("radio");
expect(radioButton).toHaveClass("focus:outline");
});
it("applies hover state classes", () => {
render(<RadioButton state="hover" label="Hover Radio" />);
const radioButton = screen.getByRole("radio");
expect(radioButton).toHaveClass("hover:outline");
});
it("renders hidden input for form submission", () => {
render(
<RadioButton
name="test-radio"
value="test-value"
checked={true}
label="Test Radio"
/>,
);
const hiddenInput = screen.getByDisplayValue("test-value");
expect(hiddenInput).toBeInTheDocument();
expect(hiddenInput).toHaveAttribute("type", "radio");
expect(hiddenInput).toHaveAttribute("name", "test-radio");
expect(hiddenInput).toBeChecked();
});
it("applies custom className", () => {
render(<RadioButton className="custom-class" label="Custom Radio" />);
const label = screen.getByText("Custom Radio").closest("label");
expect(label).toHaveClass("custom-class");
});
it("generates unique ID when not provided", () => {
render(<RadioButton label="Radio 1" />);
render(<RadioButton label="Radio 2" />);
const radioButtons = screen.getAllByRole("radio");
expect(radioButtons[0]).toHaveAttribute("id");
expect(radioButtons[1]).toHaveAttribute("id");
expect(radioButtons[0].id).not.toBe(radioButtons[1].id);
});
it("uses provided ID", () => {
render(<RadioButton id="custom-id" label="Custom ID Radio" />);
const radioButton = screen.getByRole("radio");
expect(radioButton).toHaveAttribute("id", "custom-id");
});
it("associates label with radio button for accessibility", () => {
render(<RadioButton label="Accessible Radio" />);
const radioButton = screen.getByRole("radio");
const labelId = radioButton.getAttribute("aria-labelledby");
expect(labelId).toBeTruthy();
const labelElement = document.getElementById(labelId);
expect(labelElement).toHaveTextContent("Accessible Radio");
});
it("uses aria-label when provided", () => {
render(<RadioButton ariaLabel="Custom Aria Label" />);
const radioButton = screen.getByRole("radio");
expect(radioButton).toHaveAttribute("aria-label", "Custom Aria Label");
});
it("shows dot indicator when checked", () => {
render(
<RadioButton checked={true} mode="standard" label="Checked Radio" />,
);
const dot = screen.getByRole("radio").querySelector("div");
expect(dot).toHaveClass("w-[16px]", "h-[16px]", "rounded-full");
});
it("hides dot indicator when unchecked", () => {
render(
<RadioButton checked={false} mode="standard" label="Unchecked Radio" />,
);
const dot = screen.getByRole("radio").querySelector("div");
// Check if the dot has transparent background or no background color set
const computedStyle = window.getComputedStyle(dot);
const backgroundColor = computedStyle.backgroundColor;
// The dot should either be transparent or have no background color
expect(
backgroundColor === "transparent" ||
backgroundColor === "rgba(0, 0, 0, 0)" ||
backgroundColor === "",
).toBe(true);
});
});
-240
View File
@@ -1,240 +0,0 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi } from "vitest";
import RadioGroup from "../../app/components/RadioGroup";
describe("RadioGroup", () => {
const defaultOptions = [
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
];
it("renders with default props", () => {
render(<RadioGroup options={defaultOptions} />);
const radioGroup = screen.getByRole("radiogroup");
expect(radioGroup).toBeInTheDocument();
const radioButtons = screen.getAllByRole("radio");
expect(radioButtons).toHaveLength(3);
});
it("renders all options", () => {
render(<RadioGroup options={defaultOptions} />);
expect(screen.getByText("Option 1")).toBeInTheDocument();
expect(screen.getByText("Option 2")).toBeInTheDocument();
expect(screen.getByText("Option 3")).toBeInTheDocument();
});
it("shows selected option", () => {
render(<RadioGroup options={defaultOptions} value="option2" />);
const radioButtons = screen.getAllByRole("radio");
expect(radioButtons[0]).toHaveAttribute("aria-checked", "false");
expect(radioButtons[1]).toHaveAttribute("aria-checked", "true");
expect(radioButtons[2]).toHaveAttribute("aria-checked", "false");
});
it("calls onChange when option is selected", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioGroup
options={defaultOptions}
value="option1"
onChange={handleChange}
/>,
);
const option2 = screen.getByText("Option 2").closest("label");
await user.click(option2);
expect(handleChange).toHaveBeenCalledWith({ value: "option2" });
});
it("updates selection when different option is clicked", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioGroup
options={defaultOptions}
value="option1"
onChange={handleChange}
/>,
);
// Click option 3
const option3 = screen.getByText("Option 3").closest("label");
await user.click(option3);
expect(handleChange).toHaveBeenCalledWith({ value: "option3" });
});
it("handles keyboard navigation", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioGroup
options={defaultOptions}
value="option1"
onChange={handleChange}
/>,
);
const radioButtons = screen.getAllByRole("radio");
radioButtons[1].focus();
await user.keyboard(" ");
expect(handleChange).toHaveBeenCalledWith({ value: "option2" });
});
it("handles Enter key activation", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioGroup
options={defaultOptions}
value="option1"
onChange={handleChange}
/>,
);
const radioButtons = screen.getAllByRole("radio");
await user.click(radioButtons[2]);
await user.keyboard("{Enter}");
expect(handleChange).toHaveBeenCalledWith({ value: "option3" });
});
it("applies standard mode to all radio buttons", () => {
render(<RadioGroup options={defaultOptions} mode="standard" />);
const radioButtons = screen.getAllByRole("radio");
radioButtons.forEach((button) => {
expect(button).toHaveClass(
"outline-[var(--color-border-default-tertiary)]",
);
});
});
it("applies inverse mode to all radio buttons", () => {
render(<RadioGroup options={defaultOptions} mode="inverse" />);
const radioButtons = screen.getAllByRole("radio");
radioButtons.forEach((button) => {
expect(button).toHaveClass(
"outline-[var(--color-border-inverse-primary)]",
);
});
});
it("applies state to all radio buttons", () => {
render(<RadioGroup options={defaultOptions} state="focus" />);
const radioButtons = screen.getAllByRole("radio");
radioButtons.forEach((button) => {
expect(button).toHaveClass("focus:outline");
});
});
it("generates unique group name when not provided", () => {
render(<RadioGroup options={defaultOptions} />);
render(<RadioGroup options={defaultOptions} />);
const hiddenInputs = screen.getAllByRole("radio", { hidden: true });
const names = hiddenInputs.map((input) => input.getAttribute("name"));
// Should have unique names
const uniqueNames = new Set(names);
expect(uniqueNames.size).toBeGreaterThan(1);
});
it("uses provided name for all radio buttons", () => {
render(<RadioGroup options={defaultOptions} name="test-group" />);
const hiddenInputs = screen.getAllByDisplayValue("option1");
hiddenInputs.forEach((input) => {
expect(input).toHaveAttribute("name", "test-group");
});
});
it("applies custom className to container", () => {
render(<RadioGroup options={defaultOptions} className="custom-group" />);
const radioGroup = screen.getByRole("radiogroup");
expect(radioGroup).toHaveClass("custom-group");
});
it("passes aria-label to radiogroup", () => {
render(
<RadioGroup options={defaultOptions} aria-label="Test Radio Group" />,
);
const radioGroup = screen.getByRole("radiogroup");
expect(radioGroup).toHaveAttribute("aria-label", "Test Radio Group");
});
it("handles empty options array", () => {
render(<RadioGroup options={[]} />);
const radioGroup = screen.getByRole("radiogroup");
expect(radioGroup).toBeInTheDocument();
const radioButtons = screen.queryAllByRole("radio");
expect(radioButtons).toHaveLength(0);
});
it("handles options with ariaLabel", () => {
const optionsWithAria = [
{ value: "option1", label: "Option 1", ariaLabel: "First Option" },
{ value: "option2", label: "Option 2", ariaLabel: "Second Option" },
];
render(<RadioGroup options={optionsWithAria} />);
const radioButtons = screen.getAllByRole("radio");
expect(radioButtons[0]).toHaveAttribute("aria-label", "First Option");
expect(radioButtons[1]).toHaveAttribute("aria-label", "Second Option");
});
it("maintains selection state correctly", () => {
const { rerender } = render(
<RadioGroup options={defaultOptions} value="option1" />,
);
let radioButtons = screen.getAllByRole("radio");
expect(radioButtons[0]).toHaveAttribute("aria-checked", "true");
rerender(<RadioGroup options={defaultOptions} value="option3" />);
radioButtons = screen.getAllByRole("radio");
expect(radioButtons[0]).toHaveAttribute("aria-checked", "false");
expect(radioButtons[2]).toHaveAttribute("aria-checked", "true");
});
it("does not call onChange when clicking already selected option", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(
<RadioGroup
options={defaultOptions}
value="option2"
onChange={handleChange}
/>,
);
const option2 = screen.getByText("Option 2").closest("label");
await user.click(option2);
// Should not call onChange since it's already selected
expect(handleChange).not.toHaveBeenCalled();
});
});
-395
View File
@@ -1,395 +0,0 @@
import { describe, expect, vi, beforeEach, it } from "vitest";
import { render, screen } from "@testing-library/react";
import RelatedArticles from "../../app/components/RelatedArticles";
// Mock Next.js components
vi.mock("next/link", () => {
return {
default: ({ children, href, ...props }) => (
<a href={href} {...props}>
{children}
</a>
),
};
});
// Mock ContentThumbnailTemplate
vi.mock("../../app/components/ContentThumbnailTemplate", () => {
return {
default: ({ post }) => (
<div data-testid={`thumbnail-${post.slug}`}>
<a href={`/blog/${post.slug}`}>
<h3>{post.frontmatter.title}</h3>
<p>{post.frontmatter.description}</p>
</a>
</div>
),
};
});
// Mock blog post data
const mockRelatedPosts = [
{
slug: "related-article-1",
frontmatter: {
title: "Related Article 1",
description: "This is the first related article",
author: "Test Author",
date: "2025-04-10",
},
},
{
slug: "related-article-2",
frontmatter: {
title: "Related Article 2",
description: "This is the second related article",
author: "Test Author",
date: "2025-04-12",
},
},
{
slug: "related-article-3",
frontmatter: {
title: "Related Article 3",
description: "This is the third related article",
author: "Test Author",
date: "2025-04-14",
},
},
];
describe("RelatedArticles", () => {
beforeEach(() => {
// Mock window.innerWidth for responsive tests
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: 1024, // Desktop width
});
});
it("renders the section with correct structure", () => {
render(
<RelatedArticles
relatedPosts={mockRelatedPosts}
currentPostSlug="current-article"
/>,
);
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
expect(section).toHaveClass(
"py-[var(--spacing-scale-032)]",
"lg:py-[var(--spacing-scale-064)]",
);
});
it("displays the section heading", () => {
render(
<RelatedArticles
relatedPosts={mockRelatedPosts}
currentPostSlug="current-article"
/>,
);
const heading = screen.getByRole("heading", { level: 2 });
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent("Related Articles");
expect(heading).toHaveClass(
"text-[32px]",
"lg:text-[44px]",
"leading-[110%]",
"font-medium",
"text-[var(--color-content-inverse-primary)]",
"text-center",
);
});
it("renders all related articles", () => {
render(
<RelatedArticles
relatedPosts={mockRelatedPosts}
currentPostSlug="current-article"
/>,
);
expect(
screen.getByTestId("thumbnail-related-article-1"),
).toBeInTheDocument();
expect(
screen.getByTestId("thumbnail-related-article-2"),
).toBeInTheDocument();
expect(
screen.getByTestId("thumbnail-related-article-3"),
).toBeInTheDocument();
});
it("filters out the current post from related articles", () => {
const postsWithCurrent = [
...mockRelatedPosts,
{
slug: "current-article",
frontmatter: {
title: "Current Article",
description: "This is the current article",
author: "Test Author",
date: "2025-04-15",
},
},
];
render(
<RelatedArticles
relatedPosts={postsWithCurrent}
currentPostSlug="current-article"
/>,
);
// Should not render the current article
expect(
screen.queryByTestId("thumbnail-current-article"),
).not.toBeInTheDocument();
// Should still render the other related articles
expect(
screen.getByTestId("thumbnail-related-article-1"),
).toBeInTheDocument();
expect(
screen.getByTestId("thumbnail-related-article-2"),
).toBeInTheDocument();
expect(
screen.getByTestId("thumbnail-related-article-3"),
).toBeInTheDocument();
});
it("renders nothing when no related posts", () => {
const { container } = render(
<RelatedArticles relatedPosts={[]} currentPostSlug="current-article" />,
);
expect(container.firstChild).toBeNull();
});
it("renders nothing when all posts are filtered out", () => {
const currentPostOnly = [
{
slug: "current-article",
frontmatter: {
title: "Current Article",
description: "This is the current article",
author: "Test Author",
date: "2025-04-15",
},
},
];
const { container } = render(
<RelatedArticles
relatedPosts={currentPostOnly}
currentPostSlug="current-article"
/>,
);
expect(container.firstChild).toBeNull();
});
it("has correct container styling", () => {
render(
<RelatedArticles
relatedPosts={mockRelatedPosts}
currentPostSlug="current-article"
/>,
);
const container = document.querySelector("section > div");
expect(container).toHaveClass(
"flex",
"flex-col",
"gap-[var(--spacing-scale-032)]",
"lg:gap-[51px]",
);
});
it("has correct articles container styling", () => {
render(
<RelatedArticles
relatedPosts={mockRelatedPosts}
currentPostSlug="current-article"
/>,
);
const articlesContainer = document.querySelector("section > div > div");
expect(articlesContainer).toHaveClass(
"flex",
"justify-center",
"overflow-hidden",
);
});
it("applies correct responsive behavior for desktop", () => {
// Set desktop width (must be > 1024px to be desktop, since lg breakpoint is 1024px)
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: 1200,
});
render(
<RelatedArticles
relatedPosts={mockRelatedPosts}
currentPostSlug="current-article"
/>,
);
const carouselContainer = document.querySelector(
"section > div > div > div",
);
expect(carouselContainer).toHaveClass(
"overflow-x-auto",
"scrollbar-hide",
"cursor-grab",
"active:cursor-grabbing",
);
});
it("applies correct responsive behavior for mobile", () => {
// Set mobile width
Object.defineProperty(window, "innerWidth", {
writable: true,
configurable: true,
value: 768,
});
render(
<RelatedArticles
relatedPosts={mockRelatedPosts}
currentPostSlug="current-article"
/>,
);
const carouselContainer = document.querySelector(
"section > div > div > div",
);
expect(carouselContainer).toHaveClass(
"transition-transform",
"duration-500",
"ease-in-out",
);
});
it("handles single related article", () => {
const singlePost = [mockRelatedPosts[0]];
render(
<RelatedArticles
relatedPosts={singlePost}
currentPostSlug="current-article"
/>,
);
expect(
screen.getByTestId("thumbnail-related-article-1"),
).toBeInTheDocument();
expect(
screen.queryByTestId("thumbnail-related-article-2"),
).not.toBeInTheDocument();
expect(
screen.queryByTestId("thumbnail-related-article-3"),
).not.toBeInTheDocument();
});
it("handles two related articles", () => {
const twoPosts = mockRelatedPosts.slice(0, 2);
render(
<RelatedArticles
relatedPosts={twoPosts}
currentPostSlug="current-article"
/>,
);
expect(
screen.getByTestId("thumbnail-related-article-1"),
).toBeInTheDocument();
expect(
screen.getByTestId("thumbnail-related-article-2"),
).toBeInTheDocument();
expect(
screen.queryByTestId("thumbnail-related-article-3"),
).not.toBeInTheDocument();
});
it("has proper accessibility attributes", () => {
render(
<RelatedArticles
relatedPosts={mockRelatedPosts}
currentPostSlug="current-article"
/>,
);
const section = document.querySelector("section");
expect(section).toBeInTheDocument();
});
it("applies correct gap between articles", () => {
render(
<RelatedArticles
relatedPosts={mockRelatedPosts}
currentPostSlug="current-article"
/>,
);
const carouselContainer = document.querySelector(
"section > div > div > div",
);
expect(carouselContainer).toHaveClass("gap-0");
});
it("handles missing currentPostSlug gracefully", () => {
render(<RelatedArticles relatedPosts={mockRelatedPosts} />);
// Should still render all articles
expect(
screen.getByTestId("thumbnail-related-article-1"),
).toBeInTheDocument();
expect(
screen.getByTestId("thumbnail-related-article-2"),
).toBeInTheDocument();
expect(
screen.getByTestId("thumbnail-related-article-3"),
).toBeInTheDocument();
});
it("handles malformed post data gracefully", () => {
const malformedPosts = [
{
slug: "malformed-1",
frontmatter: {
title: "Malformed Post 1",
description: "Test description",
author: "Test Author",
date: "2025-04-15",
},
},
{
slug: "malformed-2",
frontmatter: {
title: "Malformed Post 2",
description: "Test description",
author: "Test Author",
date: "2025-04-15",
},
},
];
render(
<RelatedArticles
relatedPosts={malformedPosts}
currentPostSlug="current-article"
/>,
);
expect(screen.getByTestId("thumbnail-malformed-1")).toBeInTheDocument();
expect(screen.getByTestId("thumbnail-malformed-2")).toBeInTheDocument();
});
});
-198
View File
@@ -1,198 +0,0 @@
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)]",
);
});
});
-401
View File
@@ -1,401 +0,0 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { expect, describe, it, vi } from "vitest";
import { axe, toHaveNoViolations } from "jest-axe";
import Select from "../../app/components/Select";
expect.extend(toHaveNoViolations);
describe("Select Component", () => {
const defaultProps = {
label: "Test Select",
placeholder: "Select an option",
options: [
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
],
};
describe("Rendering", () => {
it("renders with default props", () => {
render(<Select {...defaultProps} />);
expect(screen.getByText("Test Select")).toBeInTheDocument();
expect(screen.getByText("Select an option")).toBeInTheDocument();
});
it("renders without label when not provided", () => {
render(
<Select
placeholder="Select an option"
options={defaultProps.options}
/>,
);
expect(screen.queryByText("Test Select")).not.toBeInTheDocument();
expect(screen.getByText("Select an option")).toBeInTheDocument();
});
it("renders with horizontal label variant", () => {
render(<Select {...defaultProps} labelVariant="horizontal" />);
const container = screen.getByText("Test Select").closest("div");
expect(container).toHaveClass("flex", "items-center");
});
it("renders with default label variant", () => {
render(<Select {...defaultProps} labelVariant="default" />);
const container = screen.getByText("Test Select").closest("div");
expect(container).toHaveClass("flex", "flex-col");
});
});
describe("Size Variants", () => {
it("renders small size correctly", () => {
render(<Select {...defaultProps} size="small" />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass("h-[32px]");
});
it("renders medium size correctly", () => {
render(<Select {...defaultProps} size="medium" />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass("h-[36px]");
});
it("renders large size correctly", () => {
render(<Select {...defaultProps} size="large" />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass("h-[40px]");
});
it("applies correct height for small horizontal label", () => {
render(
<Select {...defaultProps} size="small" labelVariant="horizontal" />,
);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass("h-[30px]");
});
it("applies correct height for small default label", () => {
render(<Select {...defaultProps} size="small" labelVariant="default" />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass("h-[32px]");
});
});
describe("State Variants", () => {
it("renders default state", () => {
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass(
"border-[var(--color-border-default-tertiary)]",
);
});
it("renders hover state", () => {
render(<Select {...defaultProps} state="hover" />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass(
"shadow-[0_0_0_2px_var(--color-border-default-tertiary)]",
);
});
it("renders focus state", () => {
render(<Select {...defaultProps} state="focus" />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass(
"border-[var(--color-border-default-utility-info)]",
);
expect(selectButton).toHaveClass("shadow-[0_0_5px_3px_#3281F8]");
});
it("renders error state", () => {
render(<Select {...defaultProps} error={true} />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass(
"border-[var(--color-border-default-utility-negative)]",
);
});
it("renders disabled state", () => {
render(<Select {...defaultProps} disabled={true} />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveClass("cursor-not-allowed");
expect(selectButton).toHaveClass("opacity-40");
});
});
describe("Interaction", () => {
it("opens dropdown when clicked", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
expect(screen.getByText("Option 2")).toBeInTheDocument();
expect(screen.getByText("Option 3")).toBeInTheDocument();
});
});
it("closes dropdown when clicked again", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
await user.click(selectButton);
await waitFor(() => {
expect(screen.queryByText("Option 1")).not.toBeInTheDocument();
});
});
it("selects an option when clicked", async () => {
const user = userEvent.setup();
const onChange = vi.fn();
render(<Select {...defaultProps} onChange={onChange} />);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
await user.click(screen.getByText("Option 1"));
expect(onChange).toHaveBeenCalledWith({
target: {
value: "option1",
text: "Option 1",
},
});
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
it("closes dropdown when option is selected", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
await user.click(screen.getByText("Option 1"));
await waitFor(() => {
expect(screen.queryByText("Option 2")).not.toBeInTheDocument();
});
});
it("does not open when disabled", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} disabled={true} />);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
expect(screen.queryByText("Option 1")).not.toBeInTheDocument();
});
});
describe("Keyboard Navigation", () => {
it("opens dropdown with Enter key", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
selectButton.focus();
await user.keyboard("{Enter}");
await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
});
it("opens dropdown with Space key", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
selectButton.focus();
await user.keyboard(" ");
await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
});
it("closes dropdown with Escape key", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
await user.keyboard("{Escape}");
await waitFor(() => {
expect(screen.queryByText("Option 1")).not.toBeInTheDocument();
});
});
it("does not respond to keyboard when disabled", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} disabled={true} />);
const selectButton = screen.getByRole("button");
selectButton.focus();
await user.keyboard("{Enter}");
expect(screen.queryByText("Option 1")).not.toBeInTheDocument();
});
});
describe("Click Outside", () => {
it("closes dropdown when clicking outside", async () => {
const user = userEvent.setup();
render(
<div>
<Select {...defaultProps} />
<div data-testid="outside">Outside element</div>
</div>,
);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
await user.click(screen.getByTestId("outside"));
await waitFor(() => {
expect(screen.queryByText("Option 1")).not.toBeInTheDocument();
});
});
});
describe("Value Display", () => {
it("shows placeholder when no value selected", () => {
render(<Select {...defaultProps} />);
expect(screen.getByText("Select an option")).toBeInTheDocument();
});
it("shows selected value when option is selected", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
await waitFor(() => {
expect(screen.getByText("Option 1")).toBeInTheDocument();
});
await user.click(screen.getByText("Option 1"));
expect(screen.getByText("Option 1")).toBeInTheDocument();
expect(screen.queryByText("Select an option")).not.toBeInTheDocument();
});
it("shows selected value when value prop is provided", () => {
render(<Select {...defaultProps} value="option2" />);
expect(screen.getByText("Option 2")).toBeInTheDocument();
});
});
describe("Accessibility", () => {
it("has no accessibility violations", async () => {
const { container } = render(<Select {...defaultProps} />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
it("has proper ARIA attributes", () => {
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
expect(selectButton).toHaveAttribute("aria-expanded", "false");
expect(selectButton).toHaveAttribute("aria-haspopup", "listbox");
});
it("updates aria-expanded when dropdown opens", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
await waitFor(() => {
expect(selectButton).toHaveAttribute("aria-expanded", "true");
});
});
it("associates label with select button", () => {
render(<Select {...defaultProps} />);
const label = screen.getByText("Test Select");
const selectButton = screen.getByRole("button");
expect(label).toHaveAttribute("for", selectButton.id);
});
});
describe("Focus Behavior", () => {
it("enters focus state when tabbed to", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
await user.tab();
expect(selectButton).toHaveFocus();
expect(selectButton).toHaveClass(
"focus-visible:border-[var(--color-border-default-utility-info)]",
);
});
it("does not enter focus state when clicked", async () => {
const user = userEvent.setup();
render(<Select {...defaultProps} />);
const selectButton = screen.getByRole("button");
await user.click(selectButton);
expect(selectButton).toHaveFocus();
// Focus state should not be applied on click, only on keyboard navigation
});
});
});
-184
View File
@@ -1,184 +0,0 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import Switch from "../../app/components/Switch";
describe("Switch Component", () => {
it("renders with default props", () => {
render(<Switch />);
const switchButton = screen.getByRole("switch");
expect(switchButton).toBeInTheDocument();
expect(switchButton).toHaveAttribute("aria-checked", "false");
});
it("renders with custom props", () => {
const handleChange = vi.fn();
render(
<Switch
checked={true}
onChange={handleChange}
label="Test Switch"
state="focus"
/>,
);
const switchButton = screen.getByRole("switch");
expect(switchButton).toHaveAttribute("aria-checked", "true");
expect(screen.getByText("Test Switch")).toBeInTheDocument();
});
it("handles checked prop correctly", () => {
const { rerender } = render(<Switch checked={false} />);
let switchButton = screen.getByRole("switch");
expect(switchButton).toHaveAttribute("aria-checked", "false");
rerender(<Switch checked={true} />);
switchButton = screen.getByRole("switch");
expect(switchButton).toHaveAttribute("aria-checked", "true");
});
it("handles state prop correctly", () => {
const { rerender } = render(<Switch state="default" />);
let switchButton = screen.getByRole("switch");
expect(switchButton).not.toHaveClass("shadow-[0_0_5px_1px_#3281F8]");
rerender(<Switch state="focus" />);
switchButton = screen.getByRole("switch");
expect(switchButton).toHaveClass("shadow-[0_0_5px_3px_#3281F8]");
});
it("calls onChange when clicked", () => {
const handleChange = vi.fn();
render(<Switch onChange={handleChange} />);
const switchButton = screen.getByRole("switch");
fireEvent.click(switchButton);
expect(handleChange).toHaveBeenCalledTimes(1);
});
it("calls onFocus when focused", () => {
const handleFocus = vi.fn();
render(<Switch onFocus={handleFocus} />);
const switchButton = screen.getByRole("switch");
fireEvent.focus(switchButton);
expect(handleFocus).toHaveBeenCalledTimes(1);
});
it("calls onBlur when blurred", () => {
const handleBlur = vi.fn();
render(<Switch onBlur={handleBlur} />);
const switchButton = screen.getByRole("switch");
fireEvent.blur(switchButton);
expect(handleBlur).toHaveBeenCalledTimes(1);
});
it("handles keyboard events correctly", () => {
const handleChange = vi.fn();
render(<Switch onChange={handleChange} />);
const switchButton = screen.getByRole("switch");
// Test Enter key
fireEvent.keyDown(switchButton, { key: "Enter" });
expect(handleChange).toHaveBeenCalledTimes(1);
// Test Space key
fireEvent.keyDown(switchButton, { key: " " });
expect(handleChange).toHaveBeenCalledTimes(2);
// Test other key (should not trigger)
fireEvent.keyDown(switchButton, { key: "Tab" });
expect(handleChange).toHaveBeenCalledTimes(2);
});
it("applies correct classes for different states", () => {
const { rerender } = render(<Switch checked={false} />);
let switchButton = screen.getByRole("switch");
expect(switchButton).toHaveClass("cursor-pointer");
rerender(<Switch checked={true} />);
switchButton = screen.getByRole("switch");
expect(switchButton).toHaveClass("cursor-pointer");
});
it("applies correct track styles based on checked state", () => {
const { rerender } = render(<Switch checked={false} />);
let switchButton = screen.getByRole("switch");
let track = switchButton.querySelector("div");
expect(track).toHaveClass("bg-[var(--color-surface-default-tertiary)]");
rerender(<Switch checked={true} />);
switchButton = screen.getByRole("switch");
track = switchButton.querySelector("div");
expect(track).toHaveClass("bg-[var(--color-surface-inverse-tertiary)]");
switchButton = screen.getByRole("switch");
track = switchButton.querySelector("div");
expect(track).toHaveClass("bg-[var(--color-surface-inverse-tertiary)]");
});
it("applies correct focus styles", () => {
const { rerender } = render(<Switch state="default" />);
let switchButton = screen.getByRole("switch");
expect(switchButton).not.toHaveClass("shadow-[0_0_5px_1px_#3281F8]");
rerender(<Switch state="focus" />);
switchButton = screen.getByRole("switch");
expect(switchButton).toHaveClass("shadow-[0_0_5px_3px_#3281F8]");
});
it("applies correct base classes", () => {
render(<Switch />);
const switchButton = screen.getByRole("switch");
expect(switchButton).toHaveClass(
"relative",
"inline-flex",
"items-center",
"cursor-pointer",
"transition-all",
"duration-200",
"focus:outline-none",
"focus-visible:shadow-[0_0_5px_3px_#3281F8]",
);
});
it("forwards ref correctly", () => {
const ref = React.createRef();
render(<Switch ref={ref} />);
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
});
it("applies custom className", () => {
render(<Switch className="custom-class" />);
const switchButton = screen.getByRole("switch");
expect(switchButton).toHaveClass("custom-class");
});
it("renders label when provided", () => {
render(<Switch label="Test Label" />);
expect(screen.getByText("Test Label")).toBeInTheDocument();
});
it("does not render label when not provided", () => {
render(<Switch />);
expect(screen.queryByText("Switch label")).not.toBeInTheDocument();
// Should have aria-label for accessibility
const switchButton = screen.getByRole("switch");
expect(switchButton).toHaveAttribute("aria-label", "Toggle switch");
});
it("applies correct label styles", () => {
render(<Switch label="Test Label" />);
const label = screen.getByText("Test Label");
expect(label).toHaveClass(
"ml-[var(--measures-spacing-008)]",
"font-inter",
"font-normal",
"text-[14px]",
"leading-[20px]",
"text-[var(--color-content-default-primary)]",
);
});
});
-203
View File
@@ -1,203 +0,0 @@
import { expect, test, describe, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import TextArea from "../../app/components/TextArea";
describe("TextArea", () => {
test("renders with default props", () => {
render(<TextArea />);
const textarea = screen.getByRole("textbox");
expect(textarea).toBeInTheDocument();
});
test("renders with label", () => {
render(<TextArea label="Test Label" />);
expect(screen.getByText("Test Label")).toBeInTheDocument();
expect(screen.getByLabelText("Test Label")).toBeInTheDocument();
});
test("renders with placeholder", () => {
render(<TextArea placeholder="Enter text..." />);
expect(screen.getByPlaceholderText("Enter text...")).toBeInTheDocument();
});
test("renders with value", () => {
render(<TextArea value="Test value" />);
const textarea = screen.getByRole("textbox");
expect(textarea).toHaveValue("Test value");
});
test("renders with different sizes", () => {
const { rerender } = render(<TextArea size="small" label="Small" />);
let textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass("h-[60px]");
rerender(<TextArea size="medium" label="Medium" />);
textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass("h-[100px]");
rerender(<TextArea size="large" label="Large" />);
textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass("h-[150px]");
});
test("renders with horizontal label variant", () => {
render(<TextArea labelVariant="horizontal" label="Horizontal Label" />);
const container = screen.getByRole("textbox").closest("div").parentElement;
expect(container).toHaveClass("flex", "items-center", "gap-[12px]");
});
test("renders with default label variant", () => {
render(<TextArea labelVariant="default" label="Default Label" />);
const container = screen.getByRole("textbox").closest("div").parentElement;
expect(container).toHaveClass("flex", "flex-col");
});
test("applies disabled state", () => {
render(<TextArea disabled />);
const textarea = screen.getByRole("textbox");
expect(textarea).toBeDisabled();
});
test("applies error state", () => {
render(<TextArea error />);
const textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass(
"border-[var(--color-border-default-utility-negative)]",
);
});
test("applies different states", () => {
const { rerender } = render(<TextArea state="active" />);
let textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass(
"border-[var(--color-border-default-tertiary)]",
);
rerender(<TextArea state="hover" />);
textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass(
"shadow-[0_0_0_2px_var(--color-border-default-tertiary)]",
);
rerender(<TextArea state="focus" />);
textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass(
"border-[var(--color-border-default-utility-info)]",
"shadow-[0_0_5px_3px_#3281F8]",
);
});
test("calls onChange when text changes", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(<TextArea onChange={handleChange} />);
const textarea = screen.getByRole("textbox");
await user.type(textarea, "test");
expect(handleChange).toHaveBeenCalledTimes(4);
});
test("calls onFocus when focused", async () => {
const user = userEvent.setup();
const handleFocus = vi.fn();
render(<TextArea onFocus={handleFocus} />);
const textarea = screen.getByRole("textbox");
await user.click(textarea);
expect(handleFocus).toHaveBeenCalled();
});
test("calls onBlur when blurred", async () => {
const user = userEvent.setup();
const handleBlur = vi.fn();
render(<TextArea onBlur={handleBlur} />);
const textarea = screen.getByRole("textbox");
await user.click(textarea);
await user.tab();
expect(handleBlur).toHaveBeenCalled();
});
test("does not call onChange when disabled", async () => {
const user = userEvent.setup();
const handleChange = vi.fn();
render(<TextArea disabled onChange={handleChange} />);
const textarea = screen.getByRole("textbox");
await user.type(textarea, "test");
expect(handleChange).not.toHaveBeenCalled();
});
test("applies custom className", () => {
render(<TextArea className="custom-class" />);
const textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass("custom-class");
});
test("forwards ref", () => {
const ref = vi.fn();
render(<TextArea ref={ref} />);
expect(ref).toHaveBeenCalled();
});
test("applies correct height for small horizontal label", () => {
render(
<TextArea
size="small"
labelVariant="horizontal"
label="Small Horizontal"
/>,
);
const textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass("h-[60px]");
});
test("applies correct height for medium horizontal label", () => {
render(
<TextArea
size="medium"
labelVariant="horizontal"
label="Medium Horizontal"
/>,
);
const textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass("h-[110px]");
});
test("applies correct border radius for different sizes", () => {
const { rerender } = render(<TextArea size="small" />);
let textarea = screen.getByRole("textbox");
expect(textarea).toHaveStyle({
borderRadius: "var(--measures-radius-xsmall)",
});
rerender(<TextArea size="medium" />);
textarea = screen.getByRole("textbox");
expect(textarea).toHaveStyle({
borderRadius: "var(--measures-radius-xsmall)",
});
rerender(<TextArea size="large" />);
textarea = screen.getByRole("textbox");
expect(textarea).toHaveStyle({
borderRadius: "var(--measures-radius-small)",
});
});
test("applies correct text color", () => {
render(<TextArea />);
const textarea = screen.getByRole("textbox");
expect(textarea).toHaveClass("text-[var(--color-content-default-primary)]");
});
test("applies correct label color", () => {
render(<TextArea label="Test Label" />);
const label = screen.getByText("Test Label");
expect(label).toHaveClass("text-[var(--color-content-default-secondary)]");
});
});
-195
View File
@@ -1,195 +0,0 @@
import { expect, test, describe, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import Toggle from "../../app/components/Toggle";
describe("Toggle Component", () => {
test("renders with default props", () => {
render(<Toggle label="Test Toggle" />);
const toggle = screen.getByRole("switch");
const label = screen.getByText("Test Toggle");
expect(toggle).toBeInTheDocument();
expect(label).toBeInTheDocument();
expect(toggle).toHaveAttribute("type", "button");
});
test("renders with custom props", () => {
render(
<Toggle
label="Custom Toggle"
checked={true}
disabled={true}
className="custom-class"
/>,
);
const toggle = screen.getByRole("switch");
expect(toggle).toBeInTheDocument();
expect(toggle).toHaveAttribute("aria-checked", "true");
expect(toggle).toHaveAttribute("disabled");
});
test("handles checked state", () => {
const { rerender } = render(<Toggle label="Test Toggle" checked={false} />);
let toggle = screen.getByRole("switch");
expect(toggle).toHaveAttribute("aria-checked", "false");
rerender(<Toggle label="Test Toggle" checked={true} />);
toggle = screen.getByRole("switch");
expect(toggle).toHaveAttribute("aria-checked", "true");
});
test("handles disabled state", () => {
render(<Toggle label="Test Toggle" disabled={true} />);
const toggle = screen.getByRole("switch");
expect(toggle).toHaveAttribute("disabled");
expect(toggle).toHaveClass("cursor-not-allowed");
});
test("handles state prop", () => {
const { rerender } = render(<Toggle label="Test Toggle" state="focus" />);
let toggle = screen.getByRole("switch");
expect(toggle).toHaveClass("shadow-[0_0_5px_1px_#3281F8]");
rerender(<Toggle label="Test Toggle" state="default" />);
toggle = screen.getByRole("switch");
expect(toggle).not.toHaveClass("shadow-[0_0_5px_1px_#3281F8]");
});
test("handles showIcon and icon props", () => {
render(<Toggle label="Test Toggle" showIcon={true} icon="I" />);
const toggle = screen.getByRole("switch");
expect(toggle).toHaveTextContent("I");
});
test("handles showText and text props", () => {
render(<Toggle label="Test Toggle" showText={true} text="Toggle" />);
const toggle = screen.getByRole("switch");
expect(toggle).toHaveTextContent("Toggle");
});
test("handles both icon and text", () => {
render(
<Toggle
label="Test Toggle"
showIcon={true}
showText={true}
icon="I"
text="Toggle"
/>,
);
const toggle = screen.getByRole("switch");
expect(toggle).toHaveTextContent("I");
expect(toggle).toHaveTextContent("Toggle");
});
test("calls onChange when clicked", () => {
const handleChange = vi.fn();
render(<Toggle label="Test Toggle" onChange={handleChange} />);
const toggle = screen.getByRole("switch");
fireEvent.click(toggle);
expect(handleChange).toHaveBeenCalledTimes(1);
});
test("does not call onChange when disabled", () => {
const handleChange = vi.fn();
render(
<Toggle label="Test Toggle" disabled={true} onChange={handleChange} />,
);
const toggle = screen.getByRole("switch");
fireEvent.click(toggle);
expect(handleChange).not.toHaveBeenCalled();
});
test("applies correct classes for different states", () => {
const { rerender } = render(<Toggle label="Test Toggle" checked={false} />);
let toggle = screen.getByRole("switch");
expect(toggle).toHaveClass("bg-[var(--color-surface-default-primary)]");
rerender(<Toggle label="Test Toggle" checked={true} />);
toggle = screen.getByRole("switch");
expect(toggle).toHaveClass("bg-[var(--color-magenta-magenta100)]");
rerender(<Toggle label="Test Toggle" disabled={true} />);
toggle = screen.getByRole("switch");
expect(toggle).toHaveClass("bg-[var(--color-surface-default-tertiary)]");
});
test("applies hover classes when not checked", () => {
render(<Toggle label="Test Toggle" checked={false} />);
const toggle = screen.getByRole("switch");
expect(toggle).toHaveClass(
"hover:!bg-[var(--color-surface-default-secondary)]",
);
});
test("does not apply hover classes when checked", () => {
render(<Toggle label="Test Toggle" checked={true} />);
const toggle = screen.getByRole("switch");
expect(toggle).not.toHaveClass(
"hover:!bg-[var(--color-surface-default-secondary)]",
);
});
test("applies focus-visible classes", () => {
render(<Toggle label="Test Toggle" />);
const toggle = screen.getByRole("switch");
expect(toggle).toHaveClass("focus-visible:shadow-[0_0_5px_1px_#3281F8]");
});
test("applies correct size classes", () => {
render(<Toggle label="Test Toggle" />);
const toggle = screen.getByRole("switch");
expect(toggle).toHaveClass("h-[var(--measures-sizing-032)]");
expect(toggle).toHaveClass("px-[16px]");
expect(toggle).toHaveClass("py-[8px]");
expect(toggle).toHaveClass("gap-[4px]");
});
test("applies correct text classes", () => {
render(<Toggle label="Test Toggle" />);
const toggle = screen.getByRole("switch");
expect(toggle).toHaveClass("text-[12px]");
expect(toggle).toHaveClass("leading-[16px]");
});
test("applies correct label classes", () => {
render(<Toggle label="Test Toggle" />);
const label = screen.getByText("Test Toggle");
expect(label).toHaveClass("text-[12px]");
expect(label).toHaveClass("leading-[16px]");
expect(label).toHaveClass("text-[var(--color-content-default-secondary)]");
});
test("forwards ref correctly", () => {
const ref = vi.fn();
render(<Toggle label="Test Toggle" ref={ref} />);
expect(ref).toHaveBeenCalled();
});
test("applies custom className", () => {
render(<Toggle label="Test Toggle" className="custom-class" />);
const toggle = screen.getByRole("switch");
expect(toggle).toHaveClass("custom-class");
});
});
-213
View File
@@ -1,213 +0,0 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import ToggleGroup from "../../app/components/ToggleGroup";
describe("ToggleGroup Component", () => {
it("renders with default props", () => {
render(<ToggleGroup>Test Content</ToggleGroup>);
const toggleGroup = screen.getByRole("button");
expect(toggleGroup).toBeInTheDocument();
expect(toggleGroup).toHaveTextContent("Test Content");
});
it("renders with custom props", () => {
render(
<ToggleGroup position="middle" state="selected" showText={true}>
Custom Content
</ToggleGroup>,
);
const toggleGroup = screen.getByRole("button");
expect(toggleGroup).toBeInTheDocument();
expect(toggleGroup).toHaveTextContent("Custom Content");
});
it("handles position prop correctly", () => {
const { rerender } = render(
<ToggleGroup position="left">Left</ToggleGroup>,
);
let toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"rounded-l-[var(--measures-radius-medium)]",
"rounded-r-none",
);
rerender(<ToggleGroup position="middle">Middle</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass("rounded-none");
rerender(<ToggleGroup position="right">Right</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"rounded-r-[var(--measures-radius-medium)]",
"rounded-l-none",
);
});
it("handles state prop correctly", () => {
const { rerender } = render(
<ToggleGroup state="default">Default</ToggleGroup>,
);
let toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"bg-[var(--color-surface-default-primary)]",
);
rerender(<ToggleGroup state="hover">Hover</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass("bg-[var(--color-magenta-magenta100)]");
rerender(<ToggleGroup state="focus">Focus</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"bg-[var(--color-surface-default-primary)]",
"shadow-[0_0_5px_1px_#3281F8]",
);
rerender(<ToggleGroup state="selected">Selected</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"bg-[var(--color-magenta-magenta100)]",
"shadow-[inset_0_0_0_1px_var(--color-border-default-secondary)]",
);
});
it("handles showText prop correctly", () => {
const { rerender } = render(
<ToggleGroup showText={true}>Visible Text</ToggleGroup>,
);
let toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveTextContent("Visible Text");
rerender(<ToggleGroup showText={false}></ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveTextContent("☰");
});
it("calls onChange when clicked", () => {
const handleChange = vi.fn();
render(<ToggleGroup onChange={handleChange}>Clickable</ToggleGroup>);
const toggleGroup = screen.getByRole("button");
fireEvent.click(toggleGroup);
expect(handleChange).toHaveBeenCalledTimes(1);
});
it("calls onFocus when focused", () => {
const handleFocus = vi.fn();
render(<ToggleGroup onFocus={handleFocus}>Focusable</ToggleGroup>);
const toggleGroup = screen.getByRole("button");
fireEvent.focus(toggleGroup);
expect(handleFocus).toHaveBeenCalledTimes(1);
});
it("calls onBlur when blurred", () => {
const handleBlur = vi.fn();
render(<ToggleGroup onBlur={handleBlur}>Blurable</ToggleGroup>);
const toggleGroup = screen.getByRole("button");
fireEvent.blur(toggleGroup);
expect(handleBlur).toHaveBeenCalledTimes(1);
});
it("handles keyboard events correctly", () => {
const handleChange = vi.fn();
render(<ToggleGroup onChange={handleChange}>Keyboard</ToggleGroup>);
const toggleGroup = screen.getByRole("button");
// Test Enter key
fireEvent.keyDown(toggleGroup, { key: "Enter" });
expect(handleChange).toHaveBeenCalledTimes(1);
// Test Space key
fireEvent.keyDown(toggleGroup, { key: " " });
expect(handleChange).toHaveBeenCalledTimes(2);
// Test other key (should not trigger)
fireEvent.keyDown(toggleGroup, { key: "Escape" });
expect(handleChange).toHaveBeenCalledTimes(2);
});
it("applies correct classes for different states", () => {
const { rerender } = render(
<ToggleGroup state="default">Default</ToggleGroup>,
);
let toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"bg-[var(--color-surface-default-primary)]",
);
rerender(<ToggleGroup state="hover">Hover</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass("bg-[var(--color-magenta-magenta100)]");
rerender(<ToggleGroup state="focus">Focus</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass("shadow-[0_0_5px_1px_#3281F8]");
rerender(<ToggleGroup state="selected">Selected</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"bg-[var(--color-magenta-magenta100)]",
"shadow-[inset_0_0_0_1px_var(--color-border-default-secondary)]",
);
});
it("applies correct position classes", () => {
const { rerender } = render(
<ToggleGroup position="left">Left</ToggleGroup>,
);
let toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"rounded-l-[var(--measures-radius-medium)]",
"rounded-r-none",
);
rerender(<ToggleGroup position="middle">Middle</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass("rounded-none");
rerender(<ToggleGroup position="right">Right</ToggleGroup>);
toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"rounded-r-[var(--measures-radius-medium)]",
"rounded-l-none",
);
});
it("applies correct base classes", () => {
render(<ToggleGroup>Base</ToggleGroup>);
const toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass(
"py-[var(--measures-spacing-008)]",
"px-[var(--measures-spacing-008)]",
"gap-[var(--measures-spacing-008)]",
"font-inter",
"font-medium",
"text-[12px]",
"leading-[12px]",
"cursor-pointer",
"transition-all",
"duration-200",
"focus:outline-none",
"focus-visible:shadow-[0_0_5px_1px_#3281F8]",
"hover:bg-[var(--color-magenta-magenta100)]",
"flex",
"items-center",
"justify-center",
);
});
it("forwards ref correctly", () => {
const ref = React.createRef();
render(<ToggleGroup ref={ref}>Ref Test</ToggleGroup>);
expect(ref.current).toBeInstanceOf(HTMLButtonElement);
});
it("applies custom className", () => {
render(<ToggleGroup className="custom-class">Custom</ToggleGroup>);
const toggleGroup = screen.getByRole("button");
expect(toggleGroup).toHaveClass("custom-class");
});
});