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>
212 lines
5.7 KiB
TypeScript
212 lines
5.7 KiB
TypeScript
/**
|
|
* Dashboard Component Tests
|
|
*/
|
|
|
|
import { render, screen } from '@testing-library/react';
|
|
import { Dashboard } from '@/components/dashboard/Dashboard';
|
|
import { useAuth } from '@/lib/auth/AuthContext';
|
|
import { useDashboard } from '@/lib/api/hooks/useDashboard';
|
|
import { useProjectEvents } from '@/lib/hooks/useProjectEvents';
|
|
import { useProjectEventsFromStore } from '@/lib/stores/eventStore';
|
|
|
|
// Mock dependencies
|
|
jest.mock('@/lib/auth/AuthContext', () => ({
|
|
useAuth: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('@/lib/api/hooks/useDashboard', () => ({
|
|
useDashboard: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('@/lib/hooks/useProjectEvents', () => ({
|
|
useProjectEvents: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('@/lib/stores/eventStore', () => ({
|
|
useProjectEventsFromStore: jest.fn(),
|
|
}));
|
|
|
|
// Mock next-intl navigation
|
|
jest.mock('@/lib/i18n/routing', () => ({
|
|
Link: ({ children, href }: { children: React.ReactNode; href: string }) => (
|
|
<a href={href}>{children}</a>
|
|
),
|
|
}));
|
|
|
|
// Mock sonner
|
|
jest.mock('sonner', () => ({
|
|
toast: {
|
|
success: jest.fn(),
|
|
info: jest.fn(),
|
|
error: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
const mockUseAuth = useAuth as jest.MockedFunction<typeof useAuth>;
|
|
const mockUseDashboard = useDashboard as jest.MockedFunction<typeof useDashboard>;
|
|
const mockUseProjectEvents = useProjectEvents as jest.MockedFunction<typeof useProjectEvents>;
|
|
const mockUseProjectEventsFromStore = useProjectEventsFromStore as jest.MockedFunction<
|
|
typeof useProjectEventsFromStore
|
|
>;
|
|
|
|
describe('Dashboard', () => {
|
|
const mockUser = {
|
|
id: '1',
|
|
email: 'test@example.com',
|
|
first_name: 'Test',
|
|
is_active: true,
|
|
is_superuser: false,
|
|
created_at: '',
|
|
};
|
|
|
|
const mockDashboardData = {
|
|
stats: {
|
|
activeProjects: 3,
|
|
runningAgents: 8,
|
|
openIssues: 24,
|
|
pendingApprovals: 2,
|
|
},
|
|
recentProjects: [
|
|
{
|
|
id: 'proj-1',
|
|
name: 'Test Project',
|
|
description: 'Test description',
|
|
status: 'active' as const,
|
|
autonomy_level: 'milestone' as const,
|
|
created_at: '2025-01-01T00:00:00Z',
|
|
owner_id: 'user-1',
|
|
progress: 50,
|
|
openIssues: 5,
|
|
activeAgents: 2,
|
|
lastActivity: '5 min ago',
|
|
},
|
|
],
|
|
pendingApprovals: [
|
|
{
|
|
id: 'approval-1',
|
|
type: 'sprint_boundary' as const,
|
|
title: 'Sprint Review',
|
|
description: 'Review sprint',
|
|
projectId: 'proj-1',
|
|
projectName: 'Test Project',
|
|
requestedBy: 'Agent',
|
|
requestedAt: new Date().toISOString(),
|
|
priority: 'high' as const,
|
|
},
|
|
],
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
mockUseAuth.mockReturnValue({
|
|
user: mockUser,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
error: null,
|
|
login: jest.fn(),
|
|
logout: jest.fn(),
|
|
clearError: jest.fn(),
|
|
checkAuth: jest.fn(),
|
|
});
|
|
|
|
mockUseDashboard.mockReturnValue({
|
|
data: mockDashboardData,
|
|
isLoading: false,
|
|
error: null,
|
|
isError: false,
|
|
isPending: false,
|
|
isSuccess: true,
|
|
status: 'success',
|
|
} as ReturnType<typeof useDashboard>);
|
|
|
|
mockUseProjectEvents.mockReturnValue({
|
|
connectionState: 'connected',
|
|
events: [],
|
|
isConnected: true,
|
|
error: null,
|
|
retryCount: 0,
|
|
reconnect: jest.fn(),
|
|
disconnect: jest.fn(),
|
|
clearEvents: jest.fn(),
|
|
});
|
|
|
|
mockUseProjectEventsFromStore.mockReturnValue([]);
|
|
});
|
|
|
|
it('renders welcome header', () => {
|
|
render(<Dashboard />);
|
|
|
|
// User first name appears in welcome message
|
|
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument();
|
|
});
|
|
|
|
it('renders quick stats', () => {
|
|
render(<Dashboard />);
|
|
|
|
expect(screen.getByText('Active Projects')).toBeInTheDocument();
|
|
expect(screen.getByText('Running Agents')).toBeInTheDocument();
|
|
expect(screen.getByText('Open Issues')).toBeInTheDocument();
|
|
// "Pending Approvals" appears in both stats and approvals section
|
|
const pendingApprovalsTexts = screen.getAllByText('Pending Approvals');
|
|
expect(pendingApprovalsTexts.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('renders recent projects section', () => {
|
|
render(<Dashboard />);
|
|
|
|
expect(screen.getByText('Recent Projects')).toBeInTheDocument();
|
|
// Use getAllByText since project name appears in multiple places
|
|
const projectNames = screen.getAllByText('Test Project');
|
|
expect(projectNames.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('renders pending approvals section when approvals exist', () => {
|
|
render(<Dashboard />);
|
|
|
|
// Check for pending approvals header
|
|
const approvalHeaders = screen.getAllByText('Pending Approvals');
|
|
expect(approvalHeaders.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('shows empty state when no projects', () => {
|
|
mockUseDashboard.mockReturnValue({
|
|
data: { ...mockDashboardData, recentProjects: [] },
|
|
isLoading: false,
|
|
error: null,
|
|
isError: false,
|
|
isPending: false,
|
|
isSuccess: true,
|
|
status: 'success',
|
|
} as unknown as ReturnType<typeof useDashboard>);
|
|
|
|
render(<Dashboard />);
|
|
|
|
expect(screen.getByText(/Welcome to Syndarix/)).toBeInTheDocument();
|
|
expect(screen.getByText(/Create Your First Project/)).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows loading state', () => {
|
|
mockUseDashboard.mockReturnValue({
|
|
data: undefined,
|
|
isLoading: true,
|
|
error: null,
|
|
isError: false,
|
|
isPending: true,
|
|
isSuccess: false,
|
|
status: 'pending',
|
|
} as ReturnType<typeof useDashboard>);
|
|
|
|
render(<Dashboard />);
|
|
|
|
// Should show skeleton loading states
|
|
expect(screen.getByText('Recent Projects')).toBeInTheDocument();
|
|
});
|
|
|
|
it('applies custom className', () => {
|
|
const { container } = render(<Dashboard className="custom-class" />);
|
|
|
|
expect(container.firstChild).toHaveClass('custom-class');
|
|
});
|
|
});
|