Files
syndarix/frontend/tests/features/issues/components/BulkActions.test.tsx
Felipe Cardoso 5c35702caf test(frontend): comprehensive test coverage improvements and bug fixes
- 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>
2025-12-31 19:53:41 +01:00

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