/**
* Tests for AppLayout and related components
* Verifies layout structure, responsive behavior, and component integration
*/
import { render, screen } from '@testing-library/react';
import { AppLayout, PageContainer, PageHeader } from '@/components/layout/AppLayout';
import { useAuth } from '@/lib/auth/AuthContext';
import { useLogout } from '@/lib/api/hooks/useAuth';
import { mockUsePathname } from 'next-intl/navigation';
import type { User } from '@/lib/stores/authStore';
// Mock dependencies
jest.mock('@/lib/auth/AuthContext', () => ({
useAuth: jest.fn(),
AuthProvider: ({ children }: { children: React.ReactNode }) => <>{children}>,
}));
jest.mock('@/lib/api/hooks/useAuth', () => ({
useLogout: jest.fn(),
}));
jest.mock('@/components/theme', () => ({
ThemeToggle: () =>
Theme Toggle
,
}));
jest.mock('@/components/i18n', () => ({
LocaleSwitcher: () => Locale Switcher
,
}));
// Helper to create mock user
function createMockUser(overrides: Partial = {}): User {
return {
id: 'user-123',
email: 'test@example.com',
first_name: 'Test',
last_name: 'User',
phone_number: null,
is_active: true,
is_superuser: false,
created_at: new Date().toISOString(),
updated_at: null,
...overrides,
};
}
describe('AppLayout', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUsePathname.mockReturnValue('/projects');
(useAuth as unknown as jest.Mock).mockReturnValue({
user: createMockUser(),
});
(useLogout as jest.Mock).mockReturnValue({
mutate: jest.fn(),
isPending: false,
});
});
describe('Rendering', () => {
it('renders layout container', () => {
render(
Content
);
expect(screen.getByTestId('app-layout')).toBeInTheDocument();
});
it('renders children', () => {
render(
Test Content
);
expect(screen.getByTestId('test-content')).toBeInTheDocument();
});
it('renders header', () => {
render(
Content
);
expect(screen.getByTestId('app-header')).toBeInTheDocument();
});
it('renders sidebar', () => {
render(
Content
);
expect(screen.getByTestId('sidebar')).toBeInTheDocument();
});
it('renders breadcrumbs', () => {
render(
Content
);
expect(screen.getByTestId('breadcrumbs')).toBeInTheDocument();
});
it('renders main content area', () => {
render(
Content
);
const main = screen.getByRole('main');
expect(main).toBeInTheDocument();
expect(main).toHaveAttribute('id', 'main-content');
});
});
describe('Configuration Options', () => {
it('hides sidebar when hideSidebar is true', () => {
render(
Content
);
expect(screen.queryByTestId('sidebar')).not.toBeInTheDocument();
});
it('hides breadcrumbs when hideBreadcrumbs is true', () => {
render(
Content
);
expect(screen.queryByTestId('breadcrumbs')).not.toBeInTheDocument();
});
it('passes custom breadcrumbs to AppBreadcrumbs', () => {
const customBreadcrumbs = [
{ label: 'Custom', href: '/custom', current: true },
];
render(
Content
);
expect(screen.getByTestId('breadcrumb-custom')).toBeInTheDocument();
});
it('passes project slug to sidebar', () => {
const currentProject = { id: '1', slug: 'test-project', name: 'Test' };
render(
Content
);
// Sidebar should show project-specific navigation
expect(screen.getByTestId('nav-dashboard')).toBeInTheDocument();
});
});
describe('Custom ClassNames', () => {
it('applies className to main content', () => {
render(
Content
);
const main = screen.getByRole('main');
expect(main).toHaveClass('custom-main');
});
it('applies wrapperClassName to outer container', () => {
render(
Content
);
const layout = screen.getByTestId('app-layout');
expect(layout).toHaveClass('custom-wrapper');
});
});
describe('Accessibility', () => {
it('main content has tabIndex for skip link support', () => {
render(
Content
);
const main = screen.getByRole('main');
expect(main).toHaveAttribute('tabIndex', '-1');
});
it('layout has min-h-screen for full viewport', () => {
render(
Content
);
const layout = screen.getByTestId('app-layout');
expect(layout).toHaveClass('min-h-screen');
});
});
});
describe('PageContainer', () => {
describe('Rendering', () => {
it('renders container', () => {
render(
Content
);
expect(screen.getByTestId('page-container')).toBeInTheDocument();
});
it('renders children', () => {
render(
Test Content
);
expect(screen.getByTestId('test-content')).toBeInTheDocument();
});
});
describe('Max Width', () => {
it('defaults to max-w-6xl', () => {
render(
Content
);
const container = screen.getByTestId('page-container');
expect(container).toHaveClass('max-w-6xl');
});
it('applies custom max width', () => {
render(
Content
);
const container = screen.getByTestId('page-container');
expect(container).toHaveClass('max-w-md');
});
it('applies full width', () => {
render(
Content
);
const container = screen.getByTestId('page-container');
expect(container).toHaveClass('max-w-full');
});
});
describe('Styling', () => {
it('has container and centering classes', () => {
render(
Content
);
const container = screen.getByTestId('page-container');
expect(container).toHaveClass('container', 'mx-auto');
});
it('has responsive padding', () => {
render(
Content
);
const container = screen.getByTestId('page-container');
expect(container).toHaveClass('px-4', 'py-6', 'lg:px-6', 'lg:py-8');
});
it('applies custom className', () => {
render(
Content
);
const container = screen.getByTestId('page-container');
expect(container).toHaveClass('custom-class');
});
});
});
describe('PageHeader', () => {
describe('Rendering', () => {
it('renders page header', () => {
render();
expect(screen.getByTestId('page-header')).toBeInTheDocument();
});
it('renders title', () => {
render();
expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent('Test Title');
});
it('renders description when provided', () => {
render();
expect(screen.getByText('Test description')).toBeInTheDocument();
});
it('does not render description when not provided', () => {
render();
const header = screen.getByTestId('page-header');
expect(header.querySelectorAll('p')).toHaveLength(0);
});
it('renders actions when provided', () => {
render(
Action}
/>
);
expect(screen.getByTestId('action-button')).toBeInTheDocument();
});
});
describe('Styling', () => {
it('has responsive flex layout', () => {
render();
const header = screen.getByTestId('page-header');
expect(header).toHaveClass('flex', 'flex-col', 'sm:flex-row');
});
it('title has responsive text size', () => {
render();
const title = screen.getByRole('heading', { level: 1 });
expect(title).toHaveClass('text-2xl', 'sm:text-3xl');
});
it('description has muted styling', () => {
render();
const description = screen.getByText('Description');
expect(description).toHaveClass('text-muted-foreground');
});
it('applies custom className', () => {
render();
const header = screen.getByTestId('page-header');
expect(header).toHaveClass('custom-class');
});
});
});