// @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(); }); });