Files
flyer-crawler.projectium.com/src/components/PasswordStrengthIndicator.test.tsx

110 lines
4.3 KiB
TypeScript

// 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(<PasswordStrengthIndicator password="" />);
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(<PasswordStrengthIndicator password="some-password" />);
// 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(<PasswordStrengthIndicator password="password" />);
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(<PasswordStrengthIndicator password="pass" />);
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(<PasswordStrengthIndicator password="password" />);
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(<PasswordStrengthIndicator />);
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(<PasswordStrengthIndicator password="test" />);
// 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('');
});
});