// @vitest-environment happy-dom
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { LoadingSpinner } from './LoadingSpinner.js';
import { LoadingSkeleton } from './LoadingSkeleton.js';
import { EmptyState } from './EmptyState.js';
import { PageHeader } from './PageHeader.js';
import { ErrorPage } from './ErrorPage.js';
import { NotFoundPage } from './NotFoundPage.js';
describe('LoadingSpinner', () => {
it('renders with default size', () => {
render();
const status = screen.getByRole('status');
expect(status).toBeDefined();
expect(status.className).toContain('w-8 h-8');
});
it('renders with small size', () => {
render();
const status = screen.getByRole('status');
expect(status.className).toContain('w-4 h-4');
});
it('renders with large size', () => {
render();
const status = screen.getByRole('status');
expect(status.className).toContain('w-12 h-12');
});
it('applies custom className', () => {
render();
const status = screen.getByRole('status');
expect(status.className).toContain('mt-4');
});
it('renders SVG spinner element', () => {
render();
const svg = screen.getByRole('status').querySelector('svg');
expect(svg).toBeDefined();
expect(svg!.classList.contains('animate-spin')).toBe(true);
});
});
describe('LoadingSkeleton', () => {
it('renders default 3 rows', () => {
render();
const container = screen.getByRole('status');
const rows = container.querySelectorAll('.animate-pulse');
expect(rows.length).toBe(3);
});
it('renders custom number of rows', () => {
render();
const container = screen.getByRole('status');
const rows = container.querySelectorAll('.animate-pulse');
expect(rows.length).toBe(5);
});
it('applies custom className', () => {
render();
const container = screen.getByRole('status');
expect(container.className).toContain('my-8');
});
it('renders pulse-animated skeleton rows', () => {
render();
const row = screen.getByRole('status').querySelector('.animate-pulse');
expect(row).toBeDefined();
expect(row!.classList.contains('rounded')).toBe(true);
});
});
describe('EmptyState', () => {
it('renders title and description', () => {
render();
expect(screen.getByText('No items')).toBeDefined();
expect(screen.getByText('Create your first item.')).toBeDefined();
});
it('renders icon when provided', () => {
render(
X}
/>
);
expect(screen.getByTestId('icon')).toBeDefined();
});
it('does not render icon container when not provided', () => {
const { container } = render();
const iconWrapper = container.querySelector('.w-16.h-16');
expect(iconWrapper).toBeNull();
});
it('renders action button and handles click', () => {
const onClick = vi.fn();
render(
);
const button = screen.getByText('Create');
expect(button).toBeDefined();
fireEvent.click(button);
expect(onClick).toHaveBeenCalledOnce();
});
it('does not render action button when not provided', () => {
const { container } = render();
const buttons = container.querySelectorAll('button');
expect(buttons.length).toBe(0);
});
it('renders with theme-aware structure', () => {
const { container } = render();
const heading = container.querySelector('h3');
expect(heading).toBeDefined();
expect(heading!.textContent).toBe('Test');
const desc = container.querySelector('p');
expect(desc).toBeDefined();
expect(desc!.textContent).toBe('Desc');
});
});
describe('PageHeader', () => {
it('renders title', () => {
render();
expect(screen.getByText('Dashboard')).toBeDefined();
});
it('renders breadcrumbs', () => {
render(
);
expect(screen.getByText('Home')).toBeDefined();
expect(screen.getByLabelText('Breadcrumb')).toBeDefined();
});
it('renders breadcrumb links with href', () => {
render(
);
const link = screen.getByText('Home');
expect(link.tagName).toBe('A');
expect(link.getAttribute('href')).toBe('/');
});
it('renders breadcrumb text without href', () => {
render();
const text = screen.getByText('Current');
expect(text.tagName).toBe('SPAN');
});
it('renders actions', () => {
render(Action} />);
expect(screen.getByTestId('action-btn')).toBeDefined();
});
it('does not render breadcrumb nav when empty', () => {
const { container } = render();
const nav = container.querySelector('nav');
expect(nav).toBeNull();
});
});
describe('ErrorPage', () => {
it('renders with default props', () => {
render();
expect(screen.getByText('Something went wrong')).toBeDefined();
expect(screen.getByText('An unexpected error occurred. Please try again.')).toBeDefined();
});
it('renders custom title and message', () => {
render();
expect(screen.getByText('Server Error')).toBeDefined();
expect(screen.getByText('The server is down.')).toBeDefined();
});
it('renders retry button and handles click', () => {
const onRetry = vi.fn();
render();
const button = screen.getByText('Try Again');
expect(button).toBeDefined();
fireEvent.click(button);
expect(onRetry).toHaveBeenCalledOnce();
});
it('does not render retry button when not provided', () => {
const { container } = render();
const buttons = container.querySelectorAll('button');
expect(buttons.length).toBe(0);
});
it('renders error icon and semantic structure', () => {
const { container } = render();
const iconContainer = container.querySelector('.w-16');
expect(iconContainer).toBeDefined();
const svg = iconContainer!.querySelector('svg');
expect(svg).toBeDefined();
const heading = container.querySelector('h2');
expect(heading).toBeDefined();
expect(heading!.textContent).toBe('Something went wrong');
});
});
describe('NotFoundPage', () => {
it('renders with default props', () => {
render();
expect(screen.getByText('404')).toBeDefined();
expect(screen.getByText('Page not found')).toBeDefined();
});
it('renders custom status code', () => {
render();
expect(screen.getByText('403')).toBeDefined();
expect(screen.getByText('Forbidden')).toBeDefined();
});
it('renders back button with onClick', () => {
const onBack = vi.fn();
render();
const button = screen.getByText('Go Back');
fireEvent.click(button);
expect(onBack).toHaveBeenCalledOnce();
});
it('renders back link with href', () => {
render();
const link = screen.getByText('Go Home');
expect(link.tagName).toBe('A');
expect(link.getAttribute('href')).toBe('/');
});
it('does not render button when neither onBack nor backHref provided', () => {
const { container } = render();
const buttons = container.querySelectorAll('button');
const links = container.querySelectorAll('a');
expect(buttons.length).toBe(0);
expect(links.length).toBe(0);
});
it('custom backLabel is used', () => {
render( {}} backLabel="Return" />);
expect(screen.getByText('Return')).toBeDefined();
});
});