template-saas-frontend-v2/src/__tests__/components/goals/GoalProgressBar.test.tsx
Adrian Flores Cortes 9bd1aba33d [SPRINT-3] feat: Add WCAG improvements and 160 unit tests
## ST-3.1 WCAG Accessibility (5 SP)
- Replace div onClick with semantic buttons
- Add aria-label to interactive icons
- Add aria-hidden to decorative icons
- Add focus:ring-2 for visible focus states
- Add role attributes to modals, trees, progressbars
- Add proper form labels with htmlFor

Files modified: NotificationDrawer, DashboardLayout, PermissionsMatrix,
NetworkTree, GoalProgressBar, AuditFilters, NotificationBell,
NotificationItem, RoleCard, RoleForm

## ST-3.2 Unit Tests (5 SP)
- Add 160 new unit tests (target was 42)
- Hooks: useAuth (18), useRbac (18), useGoals (15), useMlm (19), usePortfolio (24)
- Components: RoleForm (15), GoalProgressBar (28), RankBadge (23)
- All tests use AAA pattern with proper mocking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 20:27:34 -06:00

270 lines
8.5 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { GoalProgressBar } from '@/components/goals/GoalProgressBar';
describe('GoalProgressBar', () => {
describe('progress calculation', () => {
it('should render correct percentage', () => {
render(<GoalProgressBar currentValue={50} targetValue={100} />);
expect(screen.getByText('50.0%')).toBeInTheDocument();
});
it('should calculate percentage correctly', () => {
render(<GoalProgressBar currentValue={75} targetValue={100} />);
expect(screen.getByText('75.0%')).toBeInTheDocument();
});
it('should cap percentage at 100%', () => {
render(<GoalProgressBar currentValue={150} targetValue={100} />);
expect(screen.getByText('100.0%')).toBeInTheDocument();
});
it('should handle zero target value', () => {
render(<GoalProgressBar currentValue={50} targetValue={0} />);
expect(screen.getByText('0.0%')).toBeInTheDocument();
});
it('should handle zero current value', () => {
render(<GoalProgressBar currentValue={0} targetValue={100} />);
expect(screen.getByText('0.0%')).toBeInTheDocument();
});
});
describe('value display', () => {
it('should display current and target values', () => {
render(<GoalProgressBar currentValue={50} targetValue={100} />);
expect(screen.getByText(/50.*\/.*100/)).toBeInTheDocument();
});
it('should display unit when provided', () => {
render(<GoalProgressBar currentValue={5000} targetValue={10000} unit="USD" />);
expect(screen.getByText(/5,000.*\/.*10,000 USD/)).toBeInTheDocument();
});
it('should hide values when showValue is false', () => {
render(<GoalProgressBar currentValue={50} targetValue={100} showValue={false} />);
expect(screen.queryByText(/50.*\/.*100/)).not.toBeInTheDocument();
});
it('should hide percentage when showPercentage is false', () => {
render(<GoalProgressBar currentValue={50} targetValue={100} showPercentage={false} />);
expect(screen.queryByText('50.0%')).not.toBeInTheDocument();
});
it('should format large numbers with locale', () => {
render(<GoalProgressBar currentValue={1000000} targetValue={2000000} />);
expect(screen.getByText(/1,000,000.*\/.*2,000,000/)).toBeInTheDocument();
});
});
describe('color logic', () => {
it('should show green color when progress >= 100%', () => {
const { container } = render(<GoalProgressBar currentValue={100} targetValue={100} />);
const progressFill = container.querySelector('.bg-green-500');
expect(progressFill).toBeInTheDocument();
});
it('should show green-400 color when progress >= 80%', () => {
const { container } = render(<GoalProgressBar currentValue={85} targetValue={100} />);
const progressFill = container.querySelector('.bg-green-400');
expect(progressFill).toBeInTheDocument();
});
it('should show yellow color when progress >= 50%', () => {
const { container } = render(<GoalProgressBar currentValue={60} targetValue={100} />);
const progressFill = container.querySelector('.bg-yellow-500');
expect(progressFill).toBeInTheDocument();
});
it('should show orange color when progress >= 25%', () => {
const { container } = render(<GoalProgressBar currentValue={30} targetValue={100} />);
const progressFill = container.querySelector('.bg-orange-500');
expect(progressFill).toBeInTheDocument();
});
it('should show red color when progress < 25%', () => {
const { container } = render(<GoalProgressBar currentValue={10} targetValue={100} />);
const progressFill = container.querySelector('.bg-red-500');
expect(progressFill).toBeInTheDocument();
});
});
describe('size variants', () => {
it('should render small size', () => {
const { container } = render(
<GoalProgressBar currentValue={50} targetValue={100} size="sm" />
);
const progressBar = container.querySelector('.h-2');
expect(progressBar).toBeInTheDocument();
});
it('should render medium size (default)', () => {
const { container } = render(
<GoalProgressBar currentValue={50} targetValue={100} size="md" />
);
const progressBar = container.querySelector('.h-3');
expect(progressBar).toBeInTheDocument();
});
it('should render large size', () => {
const { container } = render(
<GoalProgressBar currentValue={50} targetValue={100} size="lg" />
);
const progressBar = container.querySelector('.h-4');
expect(progressBar).toBeInTheDocument();
});
});
describe('milestones', () => {
it('should render milestones when provided', () => {
const milestones = [
{ percentage: 25 },
{ percentage: 50 },
{ percentage: 75 },
];
const { container } = render(
<GoalProgressBar
currentValue={60}
targetValue={100}
milestones={milestones}
/>
);
// Should have milestone markers
const milestoneMarkers = container.querySelectorAll('.rounded-full.border-2');
expect(milestoneMarkers.length).toBeGreaterThanOrEqual(3);
});
it('should mark achieved milestones as green', () => {
const milestones = [
{ percentage: 25 },
{ percentage: 50 },
{ percentage: 75 },
];
const { container } = render(
<GoalProgressBar
currentValue={60}
targetValue={100}
milestones={milestones}
/>
);
// 25% and 50% milestones should be achieved (green)
const achievedMilestones = container.querySelectorAll('.bg-green-500.border-green-600');
expect(achievedMilestones.length).toBe(2);
});
it('should show milestone labels for large size', () => {
const milestones = [
{ percentage: 25 },
{ percentage: 50 },
{ percentage: 75 },
];
render(
<GoalProgressBar
currentValue={60}
targetValue={100}
milestones={milestones}
size="lg"
/>
);
expect(screen.getByText('25%')).toBeInTheDocument();
expect(screen.getByText('50%')).toBeInTheDocument();
expect(screen.getByText('75%')).toBeInTheDocument();
});
it('should not show milestone labels for non-large sizes', () => {
const milestones = [
{ percentage: 50 },
];
render(
<GoalProgressBar
currentValue={60}
targetValue={100}
milestones={milestones}
size="sm"
/>
);
// The percentage label in the stats row is different from milestone labels
// Milestone labels are only shown for size="lg"
const allText = screen.queryAllByText('50%');
// Should not have additional milestone label for small size
expect(allText.length).toBe(0);
});
});
describe('custom className', () => {
it('should apply custom className', () => {
const { container } = render(
<GoalProgressBar
currentValue={50}
targetValue={100}
className="my-custom-class"
/>
);
const wrapper = container.firstChild;
expect(wrapper).toHaveClass('my-custom-class');
});
});
describe('edge cases', () => {
it('should handle decimal values', () => {
render(<GoalProgressBar currentValue={33.33} targetValue={100} />);
expect(screen.getByText('33.3%')).toBeInTheDocument();
});
it('should handle very small progress', () => {
render(<GoalProgressBar currentValue={1} targetValue={1000} />);
expect(screen.getByText('0.1%')).toBeInTheDocument();
});
it('should handle exact boundary at 80%', () => {
const { container } = render(<GoalProgressBar currentValue={80} targetValue={100} />);
const progressFill = container.querySelector('.bg-green-400');
expect(progressFill).toBeInTheDocument();
});
it('should handle exact boundary at 50%', () => {
const { container } = render(<GoalProgressBar currentValue={50} targetValue={100} />);
const progressFill = container.querySelector('.bg-yellow-500');
expect(progressFill).toBeInTheDocument();
});
it('should handle exact boundary at 25%', () => {
const { container } = render(<GoalProgressBar currentValue={25} targetValue={100} />);
const progressFill = container.querySelector('.bg-orange-500');
expect(progressFill).toBeInTheDocument();
});
});
});