Files
fast-next-template/frontend/tests/components/admin/StatCard.test.tsx
Felipe Cardoso 67860c68e3 Add admin hooks, components, and tests for statistics, navigation, and access control
- Introduced `useAdminStats`, `useAdminUsers`, and `useAdminOrganizations` hooks for admin data fetching with React Query.
- Added `AdminSidebar`, `Breadcrumbs`, and related navigation components for the admin section.
- Implemented comprehensive unit and integration tests for admin components.
- Created E2E tests for admin access control, navigation, and dashboard functionality.
- Updated exports to include new admin components.
2025-11-06 00:35:11 +01:00

325 lines
9.6 KiB
TypeScript

/**
* Tests for StatCard Component
* Verifies stat display, loading states, and trend indicators
*/
import { render, screen } from '@testing-library/react';
import { StatCard } from '@/components/admin/StatCard';
import { Users, Activity, Building2, FileText } from 'lucide-react';
describe('StatCard', () => {
const defaultProps = {
title: 'Total Users',
value: 1234,
icon: Users,
};
describe('Rendering', () => {
it('renders stat card with test id', () => {
render(<StatCard {...defaultProps} />);
expect(screen.getByTestId('stat-card')).toBeInTheDocument();
});
it('renders title correctly', () => {
render(<StatCard {...defaultProps} />);
expect(screen.getByTestId('stat-title')).toHaveTextContent('Total Users');
});
it('renders numeric value correctly', () => {
render(<StatCard {...defaultProps} />);
expect(screen.getByTestId('stat-value')).toHaveTextContent('1234');
});
it('renders string value correctly', () => {
render(<StatCard {...defaultProps} value="Active" />);
expect(screen.getByTestId('stat-value')).toHaveTextContent('Active');
});
it('renders icon', () => {
const { container } = render(<StatCard {...defaultProps} />);
// Icon should be rendered (lucide icons render as SVG)
const svg = container.querySelector('svg');
expect(svg).toBeInTheDocument();
});
it('renders description when provided', () => {
render(
<StatCard {...defaultProps} description="Total registered users" />
);
expect(screen.getByTestId('stat-description')).toHaveTextContent(
'Total registered users'
);
});
it('does not render description when not provided', () => {
render(<StatCard {...defaultProps} />);
expect(screen.queryByTestId('stat-description')).not.toBeInTheDocument();
});
});
describe('Loading State', () => {
it('applies loading class when loading', () => {
render(<StatCard {...defaultProps} loading />);
const card = screen.getByTestId('stat-card');
expect(card).toHaveClass('animate-pulse');
});
it('shows skeleton for value when loading', () => {
render(<StatCard {...defaultProps} loading />);
// Value should not be visible
expect(screen.queryByTestId('stat-value')).not.toBeInTheDocument();
// Skeleton placeholder should be present
const card = screen.getByTestId('stat-card');
const skeleton = card.querySelector('.bg-muted.rounded');
expect(skeleton).toBeInTheDocument();
});
it('hides description when loading', () => {
render(
<StatCard
{...defaultProps}
description="Test description"
loading
/>
);
expect(screen.queryByTestId('stat-description')).not.toBeInTheDocument();
});
it('hides trend when loading', () => {
render(
<StatCard
{...defaultProps}
trend={{ value: 10, label: 'vs last month', isPositive: true }}
loading
/>
);
expect(screen.queryByTestId('stat-trend')).not.toBeInTheDocument();
});
it('applies muted styles to icon when loading', () => {
const { container } = render(<StatCard {...defaultProps} loading />);
const icon = container.querySelector('svg');
expect(icon).toHaveClass('text-muted-foreground');
});
});
describe('Trend Indicator', () => {
it('renders positive trend correctly', () => {
render(
<StatCard
{...defaultProps}
trend={{ value: 12.5, label: 'vs last month', isPositive: true }}
/>
);
const trend = screen.getByTestId('stat-trend');
expect(trend).toBeInTheDocument();
expect(trend).toHaveTextContent('↑');
expect(trend).toHaveTextContent('12.5%');
expect(trend).toHaveTextContent('vs last month');
expect(trend).toHaveClass('text-green-600');
});
it('renders negative trend correctly', () => {
render(
<StatCard
{...defaultProps}
trend={{ value: 8.3, label: 'vs last week', isPositive: false }}
/>
);
const trend = screen.getByTestId('stat-trend');
expect(trend).toBeInTheDocument();
expect(trend).toHaveTextContent('↓');
expect(trend).toHaveTextContent('8.3%');
expect(trend).toHaveTextContent('vs last week');
expect(trend).toHaveClass('text-red-600');
});
it('handles negative trend values with absolute value', () => {
render(
<StatCard
{...defaultProps}
trend={{ value: -5.0, label: 'vs last month', isPositive: false }}
/>
);
const trend = screen.getByTestId('stat-trend');
// Should display absolute value
expect(trend).toHaveTextContent('5%');
expect(trend).not.toHaveTextContent('-5%');
});
it('does not render trend when not provided', () => {
render(<StatCard {...defaultProps} />);
expect(screen.queryByTestId('stat-trend')).not.toBeInTheDocument();
});
});
describe('Icon Variations', () => {
it('renders Users icon', () => {
const { container } = render(<StatCard {...defaultProps} icon={Users} />);
const svg = container.querySelector('svg');
expect(svg).toBeInTheDocument();
});
it('renders Activity icon', () => {
const { container } = render(
<StatCard {...defaultProps} icon={Activity} />
);
const svg = container.querySelector('svg');
expect(svg).toBeInTheDocument();
});
it('renders Building2 icon', () => {
const { container } = render(
<StatCard {...defaultProps} icon={Building2} />
);
const svg = container.querySelector('svg');
expect(svg).toBeInTheDocument();
});
it('renders FileText icon', () => {
const { container } = render(
<StatCard {...defaultProps} icon={FileText} />
);
const svg = container.querySelector('svg');
expect(svg).toBeInTheDocument();
});
});
describe('Styling', () => {
it('applies custom className', () => {
render(<StatCard {...defaultProps} className="custom-class" />);
const card = screen.getByTestId('stat-card');
expect(card).toHaveClass('custom-class');
});
it('applies default card styles', () => {
render(<StatCard {...defaultProps} />);
const card = screen.getByTestId('stat-card');
expect(card).toHaveClass('rounded-lg');
expect(card).toHaveClass('border');
expect(card).toHaveClass('bg-card');
expect(card).toHaveClass('p-6');
expect(card).toHaveClass('shadow-sm');
});
it('applies primary color to icon by default', () => {
const { container } = render(<StatCard {...defaultProps} />);
const icon = container.querySelector('svg');
expect(icon).toHaveClass('text-primary');
});
it('applies correct icon background', () => {
const { container } = render(<StatCard {...defaultProps} />);
const iconWrapper = container.querySelector('.rounded-full');
expect(iconWrapper).toHaveClass('bg-primary/10');
});
it('applies muted styles when loading', () => {
const { container } = render(<StatCard {...defaultProps} loading />);
const iconWrapper = container.querySelector('.rounded-full');
expect(iconWrapper).toHaveClass('bg-muted');
});
});
describe('Complex Scenarios', () => {
it('renders all props together', () => {
render(
<StatCard
title="Active Users"
value={856}
icon={Activity}
description="Currently online"
trend={{ value: 15.2, label: 'vs yesterday', isPositive: true }}
className="custom-stat"
/>
);
expect(screen.getByTestId('stat-title')).toHaveTextContent('Active Users');
expect(screen.getByTestId('stat-value')).toHaveTextContent('856');
expect(screen.getByTestId('stat-description')).toHaveTextContent(
'Currently online'
);
expect(screen.getByTestId('stat-trend')).toHaveTextContent('↑');
expect(screen.getByTestId('stat-card')).toHaveClass('custom-stat');
});
it('handles zero value', () => {
render(<StatCard {...defaultProps} value={0} />);
expect(screen.getByTestId('stat-value')).toHaveTextContent('0');
});
it('handles very large numbers', () => {
render(<StatCard {...defaultProps} value={1234567890} />);
expect(screen.getByTestId('stat-value')).toHaveTextContent('1234567890');
});
it('handles formatted string values', () => {
render(<StatCard {...defaultProps} value="1,234" />);
expect(screen.getByTestId('stat-value')).toHaveTextContent('1,234');
});
it('handles percentage string values', () => {
render(<StatCard {...defaultProps} value="98.5%" />);
expect(screen.getByTestId('stat-value')).toHaveTextContent('98.5%');
});
});
describe('Accessibility', () => {
it('renders semantic HTML structure', () => {
render(<StatCard {...defaultProps} />);
const card = screen.getByTestId('stat-card');
expect(card.tagName).toBe('DIV');
});
it('maintains readable text contrast', () => {
render(<StatCard {...defaultProps} />);
const title = screen.getByTestId('stat-title');
expect(title).toHaveClass('text-muted-foreground');
const value = screen.getByTestId('stat-value');
expect(value).toHaveClass('font-bold');
});
it('renders description with appropriate text size', () => {
render(
<StatCard {...defaultProps} description="Test description" />
);
const description = screen.getByTestId('stat-description');
expect(description).toHaveClass('text-xs');
});
});
});