// src/pages/admin/components/PasswordStrengthIndicator.test.tsx import React from 'react'; import { render, screen } from '@testing-library/react'; import { describe, it, expect, vi, type Mock } from 'vitest'; import { PasswordStrengthIndicator } from './PasswordStrengthIndicator'; import zxcvbn from 'zxcvbn'; // Mock the zxcvbn library to control its output for testing vi.mock('zxcvbn'); describe('PasswordStrengthIndicator', () => { it('should render 5 gray bars when no password is provided', () => { (zxcvbn as Mock).mockReturnValue({ score: -1, feedback: { warning: '', suggestions: [] } }); const { container } = render(); const bars = container.querySelectorAll('.h-1\\.5'); expect(bars).toHaveLength(5); bars.forEach((bar) => { expect(bar).toHaveClass('bg-gray-200'); }); expect(screen.queryByText(/Very Weak/i)).not.toBeInTheDocument(); }); it.each([ { score: 0, label: 'Very Weak', color: 'bg-red-500', bars: 1 }, { score: 1, label: 'Weak', color: 'bg-red-500', bars: 2 }, { score: 2, label: 'Fair', color: 'bg-orange-500', bars: 3 }, { score: 3, label: 'Good', color: 'bg-yellow-500', bars: 4 }, { score: 4, label: 'Strong', color: 'bg-green-500', bars: 5 }, ])('should render correctly for score $score ($label)', ({ score, label, color, bars }) => { (zxcvbn as Mock).mockReturnValue({ score, feedback: { warning: '', suggestions: [] } }); const { container } = render(); // Check the label expect(screen.getByText(label)).toBeInTheDocument(); // Check the bar colors const barElements = container.querySelectorAll('.h-1\\.5'); expect(barElements).toHaveLength(5); for (let i = 0; i < 5; i++) { if (i < bars) { expect(barElements[i]).toHaveClass(color); } else { expect(barElements[i]).toHaveClass('bg-gray-200'); } } }); it('should display a warning from zxcvbn', () => { (zxcvbn as Mock).mockReturnValue({ score: 1, feedback: { warning: 'This is a very common password', suggestions: [], }, }); render(); expect(screen.getByText(/this is a very common password/i)).toBeInTheDocument(); }); it('should display a suggestion from zxcvbn', () => { (zxcvbn as Mock).mockReturnValue({ score: 1, feedback: { warning: '', suggestions: ['Add another word or two'], }, }); render(); expect(screen.getByText(/add another word or two/i)).toBeInTheDocument(); }); it('should prioritize warning over suggestions', () => { (zxcvbn as Mock).mockReturnValue({ score: 1, feedback: { warning: 'A warning here', suggestions: ['A suggestion here'] }, }); render(); expect(screen.getByText(/a warning here/i)).toBeInTheDocument(); expect(screen.queryByText(/a suggestion here/i)).not.toBeInTheDocument(); }); it('should use default empty string if password prop is undefined', () => { (zxcvbn as Mock).mockReturnValue({ score: 0, feedback: { warning: '', suggestions: [] } }); const { container } = render(); const bars = container.querySelectorAll('.h-1\\.5'); expect(bars).toHaveLength(5); bars.forEach((bar) => { expect(bar).toHaveClass('bg-gray-200'); }); expect(screen.queryByText(/Very Weak/i)).not.toBeInTheDocument(); }); it('should handle out-of-range scores gracefully (defensive)', () => { // Mock a score that isn't 0-4 to hit default switch cases (zxcvbn as Mock).mockReturnValue({ score: 99, feedback: { warning: '', suggestions: [] } }); const { container } = render(); // Check bars - should hit default case in getBarColor which returns gray const bars = container.querySelectorAll('.h-1\\.5'); bars.forEach((bar) => { expect(bar).toHaveClass('bg-gray-200'); }); // Check label - should hit default case in getStrengthLabel which returns empty string const labelSpan = container.querySelector('span.font-bold'); expect(labelSpan).toHaveTextContent(''); }); });