Files
syndarix/frontend/tests/components/layout/ProjectSwitcher.test.tsx
Felipe Cardoso a4c91cb8c3 refactor(frontend): clean up code by consolidating multi-line JSX into single lines where feasible
- Refactored JSX elements to improve readability by collapsing multi-line props and attributes into single lines if their length permits.
- Improved consistency in component imports by grouping and consolidating them.
- No functional changes, purely restructuring for clarity and maintainability.
2026-01-01 11:46:57 +01:00

237 lines
7.5 KiB
TypeScript

/**
* Tests for ProjectSwitcher Component
* Verifies project selection, navigation, and accessibility
*/
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ProjectSwitcher, ProjectSelect } from '@/components/layout/ProjectSwitcher';
import { mockUseRouter } from 'next-intl/navigation';
// Mock useRouter
const mockPush = jest.fn();
describe('ProjectSwitcher', () => {
const mockProjects = [
{ id: '1', slug: 'project-one', name: 'Project One' },
{ id: '2', slug: 'project-two', name: 'Project Two' },
{ id: '3', slug: 'project-three', name: 'Project Three' },
];
beforeEach(() => {
jest.clearAllMocks();
mockUseRouter.mockReturnValue({
push: mockPush,
replace: jest.fn(),
prefetch: jest.fn(),
back: jest.fn(),
forward: jest.fn(),
refresh: jest.fn(),
});
});
describe('Rendering', () => {
it('renders create project button when no projects', () => {
render(<ProjectSwitcher projects={[]} />);
expect(screen.getByTestId('create-project-button')).toBeInTheDocument();
expect(screen.getByText('Create Project')).toBeInTheDocument();
});
it('renders project switcher trigger when projects exist', () => {
render(<ProjectSwitcher projects={mockProjects} />);
expect(screen.getByTestId('project-switcher-trigger')).toBeInTheDocument();
});
it('displays current project name', () => {
render(<ProjectSwitcher projects={mockProjects} currentProject={mockProjects[0]} />);
expect(screen.getByText('Project One')).toBeInTheDocument();
});
it('displays placeholder when no current project', () => {
render(<ProjectSwitcher projects={mockProjects} />);
expect(screen.getByText('Select Project')).toBeInTheDocument();
});
});
describe('Dropdown Menu', () => {
it('opens dropdown when trigger is clicked', async () => {
const user = userEvent.setup();
render(<ProjectSwitcher projects={mockProjects} />);
const trigger = screen.getByTestId('project-switcher-trigger');
await user.click(trigger);
await waitFor(() => {
expect(screen.getByTestId('project-switcher-menu')).toBeInTheDocument();
});
});
it('displays all projects in dropdown', async () => {
const user = userEvent.setup();
render(<ProjectSwitcher projects={mockProjects} />);
const trigger = screen.getByTestId('project-switcher-trigger');
await user.click(trigger);
await waitFor(() => {
expect(screen.getByTestId('project-option-project-one')).toBeInTheDocument();
expect(screen.getByTestId('project-option-project-two')).toBeInTheDocument();
expect(screen.getByTestId('project-option-project-three')).toBeInTheDocument();
});
});
it('shows current indicator on selected project', async () => {
const user = userEvent.setup();
render(<ProjectSwitcher projects={mockProjects} currentProject={mockProjects[0]} />);
const trigger = screen.getByTestId('project-switcher-trigger');
await user.click(trigger);
await waitFor(() => {
const currentOption = screen.getByTestId('project-option-project-one');
expect(currentOption).toHaveTextContent('Current');
});
});
it('includes create new project option', async () => {
const user = userEvent.setup();
render(<ProjectSwitcher projects={mockProjects} />);
const trigger = screen.getByTestId('project-switcher-trigger');
await user.click(trigger);
await waitFor(() => {
expect(screen.getByTestId('create-project-option')).toBeInTheDocument();
expect(screen.getByText('Create New Project')).toBeInTheDocument();
});
});
});
describe('Navigation', () => {
it('navigates to project when option is selected', async () => {
const user = userEvent.setup();
render(<ProjectSwitcher projects={mockProjects} />);
const trigger = screen.getByTestId('project-switcher-trigger');
await user.click(trigger);
const projectOption = await screen.findByTestId('project-option-project-two');
await user.click(projectOption);
expect(mockPush).toHaveBeenCalledWith('/projects/project-two');
});
it('calls onProjectChange callback when provided', async () => {
const user = userEvent.setup();
const mockOnChange = jest.fn();
render(<ProjectSwitcher projects={mockProjects} onProjectChange={mockOnChange} />);
const trigger = screen.getByTestId('project-switcher-trigger');
await user.click(trigger);
const projectOption = await screen.findByTestId('project-option-project-two');
await user.click(projectOption);
expect(mockOnChange).toHaveBeenCalledWith('project-two');
expect(mockPush).not.toHaveBeenCalled();
});
it('navigates to create project page', async () => {
const user = userEvent.setup();
render(<ProjectSwitcher projects={mockProjects} />);
const trigger = screen.getByTestId('project-switcher-trigger');
await user.click(trigger);
const createOption = await screen.findByTestId('create-project-option');
await user.click(createOption);
expect(mockPush).toHaveBeenCalledWith('/projects/new');
});
it('navigates from empty state button', async () => {
const user = userEvent.setup();
render(<ProjectSwitcher projects={[]} />);
const createButton = screen.getByTestId('create-project-button');
await user.click(createButton);
expect(mockPush).toHaveBeenCalledWith('/projects/new');
});
});
describe('Accessibility', () => {
it('has accessible label on trigger', () => {
render(<ProjectSwitcher projects={mockProjects} currentProject={mockProjects[0]} />);
const trigger = screen.getByTestId('project-switcher-trigger');
expect(trigger).toHaveAttribute('aria-label', 'Switch project, current: Project One');
});
it('has accessible label when no current project', () => {
render(<ProjectSwitcher projects={mockProjects} />);
const trigger = screen.getByTestId('project-switcher-trigger');
expect(trigger).toHaveAttribute('aria-label', 'Select project');
});
});
});
describe('ProjectSelect', () => {
const mockProjects = [
{ id: '1', slug: 'project-one', name: 'Project One' },
{ id: '2', slug: 'project-two', name: 'Project Two' },
];
describe('Rendering', () => {
it('renders select component', () => {
render(<ProjectSelect projects={mockProjects} onValueChange={jest.fn()} />);
expect(screen.getByTestId('project-select')).toBeInTheDocument();
});
it('displays placeholder', () => {
render(
<ProjectSelect
projects={mockProjects}
onValueChange={jest.fn()}
placeholder="Choose a project"
/>
);
expect(screen.getByText('Choose a project')).toBeInTheDocument();
});
it('has combobox role', () => {
render(<ProjectSelect projects={mockProjects} onValueChange={jest.fn()} />);
expect(screen.getByRole('combobox')).toBeInTheDocument();
});
it('applies custom className', () => {
render(
<ProjectSelect projects={mockProjects} onValueChange={jest.fn()} className="custom-class" />
);
const select = screen.getByTestId('project-select');
expect(select).toHaveClass('custom-class');
});
});
// Note: Selection interaction tests are skipped because Radix UI Select
// doesn't properly open in JSDOM environment. The component is tested
// through E2E tests instead.
});