- Add root biome.json that extends packages/config/biome.json - Add explicit ignore patterns for dist/, build/, .next/, .turbo/, etc. - Make a11y lints warnings instead of errors (non-blocking) - Remove invalid biome-ignore comment from Section.tsx - Apply formatting to all source files (155 files checked, 129 fixed) This fixes the CI format check that was failing with 2064+ errors because Biome was checking generated files in dist/ and .turbo/. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
325 lines
12 KiB
TypeScript
325 lines
12 KiB
TypeScript
import { render, screen } from '@testing-library/react';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { Header } from './Header';
|
|
|
|
describe('Header', () => {
|
|
describe('Rendering', () => {
|
|
it('renders a header element', () => {
|
|
render(<Header data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header).toBeInTheDocument();
|
|
expect(header.tagName).toBe('HEADER');
|
|
});
|
|
|
|
it('renders without title or actions', () => {
|
|
render(<Header data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header).toBeInTheDocument();
|
|
expect(screen.queryByTestId('header-title')).not.toBeInTheDocument();
|
|
expect(screen.queryByTestId('header-actions')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Title', () => {
|
|
it('renders title when provided', () => {
|
|
render(<Header title="TPMJS Registry" />);
|
|
expect(screen.getByText('TPMJS Registry')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders title in title container', () => {
|
|
render(<Header title="My App" data-testid="header" />);
|
|
const titleContainer = screen.getByTestId('header-title');
|
|
expect(titleContainer).toBeInTheDocument();
|
|
expect(titleContainer.textContent).toBe('My App');
|
|
});
|
|
|
|
it('title container has correct base classes', () => {
|
|
render(<Header title="App" />);
|
|
const titleContainer = screen.getByTestId('header-title');
|
|
expect(titleContainer.className).toContain('flex');
|
|
expect(titleContainer.className).toContain('items-center');
|
|
expect(titleContainer.className).toContain('font-semibold');
|
|
expect(titleContainer.className).toContain('text-foreground');
|
|
});
|
|
|
|
it('renders ReactNode as title', () => {
|
|
render(<Header title={<div data-testid="custom-title">Custom</div>} />);
|
|
expect(screen.getByTestId('custom-title')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Actions', () => {
|
|
it('renders actions when provided', () => {
|
|
render(
|
|
<Header
|
|
actions={
|
|
<button type="button" data-testid="action-btn">
|
|
Sign In
|
|
</button>
|
|
}
|
|
/>
|
|
);
|
|
expect(screen.getByTestId('action-btn')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders actions in actions container', () => {
|
|
render(<Header actions="Actions content" data-testid="header" />);
|
|
const actionsContainer = screen.getByTestId('header-actions');
|
|
expect(actionsContainer).toBeInTheDocument();
|
|
expect(actionsContainer.textContent).toBe('Actions content');
|
|
});
|
|
|
|
it('actions container has correct base classes', () => {
|
|
render(<Header actions="Actions" />);
|
|
const actionsContainer = screen.getByTestId('header-actions');
|
|
expect(actionsContainer.className).toContain('flex');
|
|
expect(actionsContainer.className).toContain('items-center');
|
|
});
|
|
|
|
it('renders ReactNode as actions', () => {
|
|
render(<Header actions={<div data-testid="custom-actions">Custom Actions</div>} />);
|
|
expect(screen.getByTestId('custom-actions')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Children', () => {
|
|
it('renders children when provided', () => {
|
|
render(<Header>Header content</Header>);
|
|
expect(screen.getByText('Header content')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders children in children container', () => {
|
|
render(<Header data-testid="header">Center content</Header>);
|
|
const childrenContainer = screen.getByTestId('header-children');
|
|
expect(childrenContainer).toBeInTheDocument();
|
|
expect(childrenContainer.textContent).toBe('Center content');
|
|
});
|
|
|
|
it('children container has centering classes', () => {
|
|
render(<Header>Content</Header>);
|
|
const childrenContainer = screen.getByTestId('header-children');
|
|
expect(childrenContainer.className).toContain('flex-1');
|
|
expect(childrenContainer.className).toContain('flex');
|
|
expect(childrenContainer.className).toContain('items-center');
|
|
expect(childrenContainer.className).toContain('justify-center');
|
|
});
|
|
|
|
it('renders ReactNode as children', () => {
|
|
render(
|
|
<Header>
|
|
<nav data-testid="custom-nav">Navigation</nav>
|
|
</Header>
|
|
);
|
|
expect(screen.getByTestId('custom-nav')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Combined Content', () => {
|
|
it('renders title and actions together', () => {
|
|
render(<Header title="Title" actions="Actions" />);
|
|
expect(screen.getByText('Title')).toBeInTheDocument();
|
|
expect(screen.getByText('Actions')).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders title, children, and actions together', () => {
|
|
render(
|
|
<Header title="Title" actions="Actions">
|
|
Center
|
|
</Header>
|
|
);
|
|
expect(screen.getByText('Title')).toBeInTheDocument();
|
|
expect(screen.getByText('Center')).toBeInTheDocument();
|
|
expect(screen.getByText('Actions')).toBeInTheDocument();
|
|
});
|
|
|
|
it('maintains correct layout with all content', () => {
|
|
render(
|
|
<Header title="Left" actions="Right" data-testid="header">
|
|
Center
|
|
</Header>
|
|
);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('justify-between');
|
|
});
|
|
});
|
|
|
|
describe('Size Variants', () => {
|
|
it('applies small size to header', () => {
|
|
render(<Header size="sm" data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('h-12');
|
|
expect(header.className).toContain('px-4');
|
|
});
|
|
|
|
it('applies medium size to header', () => {
|
|
render(<Header size="md" data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('h-16');
|
|
expect(header.className).toContain('px-6');
|
|
});
|
|
|
|
it('applies large size to header', () => {
|
|
render(<Header size="lg" data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('h-20');
|
|
expect(header.className).toContain('px-8');
|
|
});
|
|
|
|
it('uses md size by default', () => {
|
|
render(<Header data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('h-16');
|
|
expect(header.className).toContain('px-6');
|
|
});
|
|
|
|
it('applies size to title', () => {
|
|
render(<Header title="Title" size="lg" />);
|
|
const titleContainer = screen.getByTestId('header-title');
|
|
expect(titleContainer.className).toContain('text-xl');
|
|
expect(titleContainer.className).toContain('gap-4');
|
|
});
|
|
|
|
it('applies size to actions', () => {
|
|
render(<Header actions="Actions" size="sm" />);
|
|
const actionsContainer = screen.getByTestId('header-actions');
|
|
expect(actionsContainer.className).toContain('gap-2');
|
|
});
|
|
});
|
|
|
|
describe('Sticky Positioning', () => {
|
|
it('applies sticky classes when sticky is true', () => {
|
|
render(<Header sticky={true} data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('sticky');
|
|
expect(header.className).toContain('top-0');
|
|
expect(header.className).toContain('z-50');
|
|
});
|
|
|
|
it('does not apply sticky classes when sticky is false', () => {
|
|
render(<Header sticky={false} data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).not.toContain('sticky');
|
|
expect(header.className).not.toContain('top-0');
|
|
expect(header.className).not.toContain('z-50');
|
|
});
|
|
|
|
it('is not sticky by default', () => {
|
|
render(<Header data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).not.toContain('sticky');
|
|
});
|
|
});
|
|
|
|
describe('HTML Attributes', () => {
|
|
it('passes through id attribute', () => {
|
|
render(<Header id="header-id" data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header).toHaveAttribute('id', 'header-id');
|
|
});
|
|
|
|
it('passes through data attributes', () => {
|
|
render(<Header data-custom="test" data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header).toHaveAttribute('data-custom', 'test');
|
|
});
|
|
|
|
it('passes through aria attributes', () => {
|
|
render(<Header aria-label="Main header" data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header).toHaveAttribute('aria-label', 'Main header');
|
|
});
|
|
});
|
|
|
|
describe('Custom className', () => {
|
|
it('merges custom className with variant classes', () => {
|
|
render(<Header className="custom-class" data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('custom-class');
|
|
expect(header.className).toContain('flex');
|
|
expect(header.className).toContain('bg-surface');
|
|
});
|
|
});
|
|
|
|
describe('Ref Forwarding', () => {
|
|
it('forwards ref to header element', () => {
|
|
let ref: HTMLElement | null = null;
|
|
render(
|
|
<Header
|
|
ref={(el) => {
|
|
ref = el;
|
|
}}
|
|
/>
|
|
);
|
|
expect(ref).toBeInstanceOf(HTMLElement);
|
|
expect(ref!.tagName).toBe('HEADER');
|
|
});
|
|
});
|
|
|
|
describe('Base Classes', () => {
|
|
it('always includes base classes', () => {
|
|
render(<Header data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('flex');
|
|
expect(header.className).toContain('items-center');
|
|
expect(header.className).toContain('justify-between');
|
|
expect(header.className).toContain('w-full');
|
|
expect(header.className).toContain('bg-surface');
|
|
expect(header.className).toContain('border-b');
|
|
expect(header.className).toContain('border-border');
|
|
});
|
|
});
|
|
|
|
describe('Compound Scenarios', () => {
|
|
it('works correctly with large size and sticky', () => {
|
|
render(<Header title="App" actions="Sign In" size="lg" sticky={true} data-testid="header" />);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('h-20');
|
|
expect(header.className).toContain('px-8');
|
|
expect(header.className).toContain('sticky');
|
|
expect(header.className).toContain('top-0');
|
|
expect(header.className).toContain('z-50');
|
|
});
|
|
|
|
it('works correctly with all content and custom className', () => {
|
|
render(
|
|
<Header
|
|
title="Title"
|
|
actions="Actions"
|
|
className="my-header"
|
|
size="sm"
|
|
data-testid="header"
|
|
>
|
|
Center
|
|
</Header>
|
|
);
|
|
const header = screen.getByTestId('header');
|
|
expect(header.className).toContain('my-header');
|
|
expect(header.className).toContain('h-12');
|
|
expect(screen.getByText('Title')).toBeInTheDocument();
|
|
expect(screen.getByText('Center')).toBeInTheDocument();
|
|
expect(screen.getByText('Actions')).toBeInTheDocument();
|
|
});
|
|
|
|
it('works with complex ReactNode content', () => {
|
|
render(
|
|
<Header
|
|
title={
|
|
<div className="logo">
|
|
<img src="logo.png" alt="Logo" />
|
|
<span>TPMJS</span>
|
|
</div>
|
|
}
|
|
actions={
|
|
<div className="nav">
|
|
<button type="button">Docs</button>
|
|
<button type="button">GitHub</button>
|
|
</div>
|
|
}
|
|
/>
|
|
);
|
|
expect(screen.getByText('TPMJS')).toBeInTheDocument();
|
|
expect(screen.getByText('Docs')).toBeInTheDocument();
|
|
expect(screen.getByText('GitHub')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|