forked from cardosofelipe/fast-next-template
Add comprehensive test coverage for dashboard components: - Dashboard.test.tsx: Main component integration tests - WelcomeHeader.test.tsx: User greeting and time-based messages - DashboardQuickStats.test.tsx: Stats cards rendering and links - RecentProjects.test.tsx: Project cards grid and navigation - PendingApprovals.test.tsx: Approval items and actions - EmptyState.test.tsx: New user onboarding experience 46 tests covering rendering, interactions, and edge cases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
124 lines
4.0 KiB
TypeScript
124 lines
4.0 KiB
TypeScript
/**
|
|
* PendingApprovals Component Tests
|
|
*/
|
|
|
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
import { PendingApprovals } from '@/components/dashboard/PendingApprovals';
|
|
import type { PendingApproval } from '@/lib/api/hooks/useDashboard';
|
|
|
|
// Mock next-intl navigation
|
|
jest.mock('@/lib/i18n/routing', () => ({
|
|
Link: ({ children, href }: { children: React.ReactNode; href: string }) => (
|
|
<a href={href}>{children}</a>
|
|
),
|
|
}));
|
|
|
|
describe('PendingApprovals', () => {
|
|
const mockApprovals: PendingApproval[] = [
|
|
{
|
|
id: 'approval-1',
|
|
type: 'sprint_boundary',
|
|
title: 'Sprint 3 Completion',
|
|
description: 'Review sprint deliverables',
|
|
projectId: 'proj-1',
|
|
projectName: 'E-Commerce Platform',
|
|
requestedBy: 'Product Owner Agent',
|
|
requestedAt: new Date().toISOString(),
|
|
priority: 'high',
|
|
},
|
|
{
|
|
id: 'approval-2',
|
|
type: 'code_review',
|
|
title: 'PR #123 Review',
|
|
description: 'Authentication module changes',
|
|
projectId: 'proj-2',
|
|
projectName: 'Banking App',
|
|
requestedBy: 'Developer Agent',
|
|
requestedAt: new Date().toISOString(),
|
|
priority: 'medium',
|
|
},
|
|
];
|
|
|
|
it('renders approval items', () => {
|
|
render(<PendingApprovals approvals={mockApprovals} />);
|
|
|
|
expect(screen.getByText('Sprint 3 Completion')).toBeInTheDocument();
|
|
expect(screen.getByText('PR #123 Review')).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays section header with count', () => {
|
|
render(<PendingApprovals approvals={mockApprovals} />);
|
|
|
|
expect(screen.getByText('Pending Approvals')).toBeInTheDocument();
|
|
expect(screen.getByText('2')).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays approval descriptions', () => {
|
|
render(<PendingApprovals approvals={mockApprovals} />);
|
|
|
|
expect(screen.getByText('Review sprint deliverables')).toBeInTheDocument();
|
|
expect(screen.getByText('Authentication module changes')).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays project names with links', () => {
|
|
render(<PendingApprovals approvals={mockApprovals} />);
|
|
|
|
const projectLink = screen.getByRole('link', { name: 'E-Commerce Platform' });
|
|
expect(projectLink).toHaveAttribute('href', '/projects/proj-1');
|
|
});
|
|
|
|
it('displays requestor information', () => {
|
|
render(<PendingApprovals approvals={mockApprovals} />);
|
|
|
|
expect(screen.getByText(/Product Owner Agent/)).toBeInTheDocument();
|
|
expect(screen.getByText(/Developer Agent/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('displays priority badges', () => {
|
|
render(<PendingApprovals approvals={mockApprovals} />);
|
|
|
|
expect(screen.getByText('High')).toBeInTheDocument();
|
|
expect(screen.getByText('Medium')).toBeInTheDocument();
|
|
});
|
|
|
|
it('calls onApprove when Approve button clicked', () => {
|
|
const onApprove = jest.fn();
|
|
render(<PendingApprovals approvals={mockApprovals} onApprove={onApprove} />);
|
|
|
|
const approveButtons = screen.getAllByRole('button', { name: /Approve/i });
|
|
fireEvent.click(approveButtons[0]);
|
|
|
|
expect(onApprove).toHaveBeenCalledWith(mockApprovals[0]);
|
|
});
|
|
|
|
it('calls onReject when Reject button clicked', () => {
|
|
const onReject = jest.fn();
|
|
render(<PendingApprovals approvals={mockApprovals} onReject={onReject} />);
|
|
|
|
const rejectButtons = screen.getAllByRole('button', { name: /Reject/i });
|
|
fireEvent.click(rejectButtons[0]);
|
|
|
|
expect(onReject).toHaveBeenCalledWith(mockApprovals[0]);
|
|
});
|
|
|
|
it('does not render when no approvals and not loading', () => {
|
|
const { container } = render(<PendingApprovals approvals={[]} />);
|
|
|
|
expect(container.firstChild).toBeNull();
|
|
});
|
|
|
|
it('shows loading skeletons when isLoading is true', () => {
|
|
render(<PendingApprovals isLoading />);
|
|
|
|
expect(screen.getByText('Pending Approvals')).toBeInTheDocument();
|
|
});
|
|
|
|
it('applies custom className', () => {
|
|
const { container } = render(
|
|
<PendingApprovals approvals={mockApprovals} className="custom-class" />
|
|
);
|
|
|
|
expect(container.firstChild).toHaveClass('custom-class');
|
|
});
|
|
});
|