Files
fast-next-template/frontend/tests/components/layout/AppBreadcrumbs.test.tsx
Felipe Cardoso 6e645835dc feat(frontend): Implement navigation and layout (#44)
Implements the main navigation and layout structure:

- Sidebar component with collapsible navigation and keyboard shortcut
- AppHeader with project switcher and user menu
- AppBreadcrumbs with auto-generation from pathname
- ProjectSwitcher dropdown for quick project navigation
- UserMenu with profile, settings, and logout
- AppLayout component combining all layout elements

Features:
- Responsive design (mobile sidebar sheet, desktop sidebar)
- Keyboard navigation (Cmd/Ctrl+B to toggle sidebar)
- Dark mode support
- WCAG AA accessible (ARIA labels, focus management)

All 125 tests passing. Follows design system guidelines.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 01:35:39 +01:00

180 lines
5.4 KiB
TypeScript

/**
* Tests for AppBreadcrumbs Component
* Verifies breadcrumb generation, navigation, and accessibility
*/
import { render, screen } from '@testing-library/react';
import { AppBreadcrumbs } from '@/components/layout/AppBreadcrumbs';
import { mockUsePathname } from 'next-intl/navigation';
describe('AppBreadcrumbs', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUsePathname.mockReturnValue('/projects');
});
describe('Rendering', () => {
it('renders breadcrumb navigation', () => {
render(<AppBreadcrumbs />);
expect(screen.getByTestId('breadcrumbs')).toBeInTheDocument();
});
it('renders with correct aria-label', () => {
render(<AppBreadcrumbs />);
const nav = screen.getByTestId('breadcrumbs');
expect(nav).toHaveAttribute('aria-label', 'Breadcrumb');
});
it('renders home icon by default', () => {
render(<AppBreadcrumbs />);
expect(screen.getByTestId('breadcrumb-home')).toBeInTheDocument();
});
it('hides home icon when showHome is false', () => {
render(<AppBreadcrumbs showHome={false} />);
expect(screen.queryByTestId('breadcrumb-home')).not.toBeInTheDocument();
});
});
describe('Auto-generated Breadcrumbs', () => {
it('generates breadcrumb from pathname', () => {
mockUsePathname.mockReturnValue('/projects');
render(<AppBreadcrumbs />);
expect(screen.getByTestId('breadcrumb-projects')).toBeInTheDocument();
});
it('generates nested breadcrumbs', () => {
mockUsePathname.mockReturnValue('/projects/my-project/issues');
render(<AppBreadcrumbs />);
expect(screen.getByTestId('breadcrumb-projects')).toBeInTheDocument();
expect(screen.getByTestId('breadcrumb-my-project')).toBeInTheDocument();
expect(screen.getByTestId('breadcrumb-issues')).toBeInTheDocument();
});
it('uses label mappings for known paths', () => {
mockUsePathname.mockReturnValue('/admin/agent-types');
render(<AppBreadcrumbs />);
expect(screen.getByText('Admin')).toBeInTheDocument();
expect(screen.getByText('Agent Types')).toBeInTheDocument();
});
it('uses segment as label for unknown paths', () => {
mockUsePathname.mockReturnValue('/custom-path');
render(<AppBreadcrumbs />);
expect(screen.getByText('custom-path')).toBeInTheDocument();
});
});
describe('Custom Breadcrumbs', () => {
it('uses provided items instead of auto-generation', () => {
mockUsePathname.mockReturnValue('/some/path');
const items = [
{ label: 'Custom', href: '/custom', current: false },
{ label: 'Path', href: '/custom/path', current: true },
];
render(<AppBreadcrumbs items={items} />);
expect(screen.getByTestId('breadcrumb-custom')).toBeInTheDocument();
expect(screen.getByTestId('breadcrumb-path')).toBeInTheDocument();
expect(screen.queryByText('some')).not.toBeInTheDocument();
});
});
describe('Active State', () => {
it('marks last item as current page', () => {
mockUsePathname.mockReturnValue('/projects/issues');
render(<AppBreadcrumbs />);
const issuesItem = screen.getByTestId('breadcrumb-issues');
expect(issuesItem).toHaveAttribute('aria-current', 'page');
});
it('renders non-current items as links', () => {
mockUsePathname.mockReturnValue('/projects/issues');
render(<AppBreadcrumbs />);
const projectsLink = screen.getByTestId('breadcrumb-projects');
expect(projectsLink.tagName).toBe('A');
expect(projectsLink).toHaveAttribute('href', '/projects');
});
it('renders current item as span (not a link)', () => {
mockUsePathname.mockReturnValue('/projects');
render(<AppBreadcrumbs />);
const projectsItem = screen.getByTestId('breadcrumb-projects');
expect(projectsItem.tagName).toBe('SPAN');
});
});
describe('Separators', () => {
it('renders chevron separators between items', () => {
mockUsePathname.mockReturnValue('/projects/issues');
render(<AppBreadcrumbs />);
// There should be separators: home -> projects -> issues
const separators = screen.getAllByRole('listitem');
expect(separators.length).toBeGreaterThanOrEqual(2);
});
});
describe('Empty State', () => {
it('returns null when no breadcrumbs', () => {
mockUsePathname.mockReturnValue('/');
const { container } = render(<AppBreadcrumbs showHome={false} />);
expect(container).toBeEmptyDOMElement();
});
});
describe('Accessibility', () => {
it('home link has accessible label', () => {
render(<AppBreadcrumbs />);
const homeLink = screen.getByTestId('breadcrumb-home');
expect(homeLink).toHaveAttribute('aria-label', 'Home');
});
it('links have focus-visible styling', () => {
mockUsePathname.mockReturnValue('/projects/issues');
render(<AppBreadcrumbs />);
const projectsLink = screen.getByTestId('breadcrumb-projects');
expect(projectsLink).toHaveClass('focus-visible:ring-2');
});
it('navigation has proper landmark role', () => {
render(<AppBreadcrumbs />);
const nav = screen.getByRole('navigation');
expect(nav).toBeInTheDocument();
});
});
describe('Custom className', () => {
it('applies custom className', () => {
render(<AppBreadcrumbs className="custom-class" />);
const nav = screen.getByTestId('breadcrumbs');
expect(nav).toHaveClass('custom-class');
});
});
});