forked from cardosofelipe/fast-next-template
- Raise coverage thresholds to 90% statements/lines/functions, 85% branches - Add comprehensive tests for ProjectDashboard, ProjectWizard, and all wizard steps - Add tests for issue management: IssueDetailPanel, BulkActions, IssueFilters - Expand IssueTable tests with keyboard navigation, dropdown menu, edge cases - Add useIssues hook tests covering all mutations and optimistic updates - Expand eventStore tests with selector hooks and additional scenarios - Expand useProjectEvents tests with error recovery, ping events, edge cases - Add PriorityBadge, StatusBadge, SyncStatusIndicator fallback branch tests - Add constants.test.ts for comprehensive constant validation Bug fixes: - Fix false positive rollback test to properly verify onMutate context setup - Replace deprecated substr() with substring() in mock helpers - Fix type errors: ProjectComplexity, ClientMode enum values - Fix unused imports and variables across test files - Fix @ts-expect-error directives and method override signatures 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
158 lines
5.5 KiB
TypeScript
158 lines
5.5 KiB
TypeScript
/**
|
|
* BulkActions Component Tests
|
|
*
|
|
* Comprehensive tests for the bulk actions toolbar component.
|
|
*/
|
|
|
|
import { render, screen } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { BulkActions } from '@/features/issues/components/BulkActions';
|
|
|
|
describe('BulkActions', () => {
|
|
const defaultProps = {
|
|
selectedCount: 3,
|
|
onChangeStatus: jest.fn(),
|
|
onAssign: jest.fn(),
|
|
onAddLabels: jest.fn(),
|
|
onDelete: jest.fn(),
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('Visibility', () => {
|
|
it('renders when selectedCount > 0', () => {
|
|
render(<BulkActions {...defaultProps} />);
|
|
expect(screen.getByRole('toolbar')).toBeInTheDocument();
|
|
});
|
|
|
|
it('does not render when selectedCount is 0', () => {
|
|
render(<BulkActions {...defaultProps} selectedCount={0} />);
|
|
expect(screen.queryByRole('toolbar')).not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Selected count display', () => {
|
|
it('displays the selected count', () => {
|
|
render(<BulkActions {...defaultProps} selectedCount={5} />);
|
|
expect(screen.getByText('5 selected')).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays singular count correctly', () => {
|
|
render(<BulkActions {...defaultProps} selectedCount={1} />);
|
|
expect(screen.getByText('1 selected')).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays large count correctly', () => {
|
|
render(<BulkActions {...defaultProps} selectedCount={100} />);
|
|
expect(screen.getByText('100 selected')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Action buttons', () => {
|
|
it('renders Change Status button', () => {
|
|
render(<BulkActions {...defaultProps} />);
|
|
expect(screen.getByRole('button', { name: 'Change Status' })).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders Assign button', () => {
|
|
render(<BulkActions {...defaultProps} />);
|
|
expect(screen.getByRole('button', { name: 'Assign' })).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders Add Labels button', () => {
|
|
render(<BulkActions {...defaultProps} />);
|
|
expect(screen.getByRole('button', { name: 'Add Labels' })).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders Delete button', () => {
|
|
render(<BulkActions {...defaultProps} />);
|
|
expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Button callbacks', () => {
|
|
it('calls onChangeStatus when Change Status is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
render(<BulkActions {...defaultProps} />);
|
|
|
|
await user.click(screen.getByRole('button', { name: 'Change Status' }));
|
|
expect(defaultProps.onChangeStatus).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('calls onAssign when Assign is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
render(<BulkActions {...defaultProps} />);
|
|
|
|
await user.click(screen.getByRole('button', { name: 'Assign' }));
|
|
expect(defaultProps.onAssign).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('calls onAddLabels when Add Labels is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
render(<BulkActions {...defaultProps} />);
|
|
|
|
await user.click(screen.getByRole('button', { name: 'Add Labels' }));
|
|
expect(defaultProps.onAddLabels).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('calls onDelete when Delete is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
render(<BulkActions {...defaultProps} />);
|
|
|
|
await user.click(screen.getByRole('button', { name: /delete/i }));
|
|
expect(defaultProps.onDelete).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('Accessibility', () => {
|
|
it('has accessible toolbar role', () => {
|
|
render(<BulkActions {...defaultProps} />);
|
|
const toolbar = screen.getByRole('toolbar');
|
|
expect(toolbar).toHaveAttribute('aria-label', 'Bulk actions for selected issues');
|
|
});
|
|
|
|
it('delete icon has aria-hidden', () => {
|
|
render(<BulkActions {...defaultProps} />);
|
|
const icons = document.querySelectorAll('[aria-hidden="true"]');
|
|
expect(icons.length).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('Styling', () => {
|
|
it('applies custom className', () => {
|
|
render(<BulkActions {...defaultProps} className="custom-toolbar-class" />);
|
|
expect(screen.getByRole('toolbar')).toHaveClass('custom-toolbar-class');
|
|
});
|
|
|
|
it('delete button has destructive styling', () => {
|
|
render(<BulkActions {...defaultProps} />);
|
|
const deleteButton = screen.getByRole('button', { name: /delete/i });
|
|
expect(deleteButton).toHaveClass('text-destructive');
|
|
});
|
|
});
|
|
|
|
describe('Edge cases', () => {
|
|
it('handles rapid clicks on all buttons', async () => {
|
|
const user = userEvent.setup();
|
|
render(<BulkActions {...defaultProps} />);
|
|
|
|
await user.click(screen.getByRole('button', { name: 'Change Status' }));
|
|
await user.click(screen.getByRole('button', { name: 'Assign' }));
|
|
await user.click(screen.getByRole('button', { name: 'Add Labels' }));
|
|
await user.click(screen.getByRole('button', { name: /delete/i }));
|
|
|
|
expect(defaultProps.onChangeStatus).toHaveBeenCalledTimes(1);
|
|
expect(defaultProps.onAssign).toHaveBeenCalledTimes(1);
|
|
expect(defaultProps.onAddLabels).toHaveBeenCalledTimes(1);
|
|
expect(defaultProps.onDelete).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('works with very large selected count', () => {
|
|
render(<BulkActions {...defaultProps} selectedCount={999999} />);
|
|
expect(screen.getByText('999999 selected')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|