All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 13m30s
111 lines
4.4 KiB
TypeScript
111 lines
4.4 KiB
TypeScript
// src/pages/admin/components/PasswordStrengthIndicator.test.tsx
|
|
import React from 'react';
|
|
import { screen } from '@testing-library/react';
|
|
import { describe, it, expect, vi, type Mock } from 'vitest';
|
|
import { PasswordStrengthIndicator } from './PasswordStrengthIndicator';
|
|
import { renderWithProviders } from '../tests/utils/renderWithProviders';
|
|
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 } = renderWithProviders(<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 } = renderWithProviders(<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: [],
|
|
},
|
|
});
|
|
renderWithProviders(<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'],
|
|
},
|
|
});
|
|
renderWithProviders(<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'] },
|
|
});
|
|
renderWithProviders(<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 } = renderWithProviders(<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 } = renderWithProviders(<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('');
|
|
});
|
|
});
|