mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 07:54:22 +00:00
Add tests for AccountSettings, DangerZoneSettings, ProfileSettings, and SecuritySettings components
This commit is contained in:
246
app/src/components/settings/account/AccountSettings.test.tsx
Normal file
246
app/src/components/settings/account/AccountSettings.test.tsx
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import {
|
||||||
|
render as rtlRender,
|
||||||
|
screen,
|
||||||
|
fireEvent,
|
||||||
|
waitFor,
|
||||||
|
} from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { MantineProvider } from '@mantine/core';
|
||||||
|
import AccountSettings from './AccountSettings';
|
||||||
|
|
||||||
|
// Mock the auth context
|
||||||
|
const mockUser = {
|
||||||
|
id: 1,
|
||||||
|
email: 'test@example.com',
|
||||||
|
displayName: 'Test User',
|
||||||
|
role: 'editor' as const,
|
||||||
|
};
|
||||||
|
const mockRefreshUser = vi.fn();
|
||||||
|
vi.mock('../../../contexts/AuthContext', () => ({
|
||||||
|
useAuth: () => ({
|
||||||
|
user: mockUser,
|
||||||
|
refreshUser: mockRefreshUser,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the profile settings hook
|
||||||
|
const mockUpdateProfile = vi.fn();
|
||||||
|
vi.mock('../../../hooks/useProfileSettings', () => ({
|
||||||
|
useProfileSettings: () => ({
|
||||||
|
loading: false,
|
||||||
|
updateProfile: mockUpdateProfile,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock notifications
|
||||||
|
vi.mock('@mantine/notifications', () => ({
|
||||||
|
notifications: {
|
||||||
|
show: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the sub-components
|
||||||
|
vi.mock('./ProfileSettings', () => ({
|
||||||
|
default: ({
|
||||||
|
settings,
|
||||||
|
onInputChange,
|
||||||
|
}: {
|
||||||
|
settings: {
|
||||||
|
displayName?: string;
|
||||||
|
email?: string;
|
||||||
|
};
|
||||||
|
onInputChange: (field: string, value: string) => void;
|
||||||
|
}) => (
|
||||||
|
<div data-testid="profile-settings">
|
||||||
|
<input
|
||||||
|
data-testid="display-name-input"
|
||||||
|
value={settings.displayName || ''}
|
||||||
|
onChange={(e) => onInputChange('displayName', e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
data-testid="email-input"
|
||||||
|
value={settings.email || ''}
|
||||||
|
onChange={(e) => onInputChange('email', e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('./SecuritySettings', () => ({
|
||||||
|
default: ({
|
||||||
|
settings,
|
||||||
|
onInputChange,
|
||||||
|
}: {
|
||||||
|
settings: {
|
||||||
|
currentPassword?: string;
|
||||||
|
newPassword?: string;
|
||||||
|
};
|
||||||
|
onInputChange: (field: string, value: string) => void;
|
||||||
|
}) => (
|
||||||
|
<div data-testid="security-settings">
|
||||||
|
<input
|
||||||
|
data-testid="current-password-input"
|
||||||
|
value={settings.currentPassword || ''}
|
||||||
|
onChange={(e) => onInputChange('currentPassword', e.target.value)}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
data-testid="new-password-input"
|
||||||
|
value={settings.newPassword || ''}
|
||||||
|
onChange={(e) => onInputChange('newPassword', e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('./DangerZoneSettings', () => ({
|
||||||
|
default: () => <div data-testid="danger-zone-settings">Danger Zone</div>,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../modals/account/EmailPasswordModal', () => ({
|
||||||
|
default: ({
|
||||||
|
opened,
|
||||||
|
onConfirm,
|
||||||
|
}: {
|
||||||
|
opened: boolean;
|
||||||
|
onConfirm: (password: string) => void;
|
||||||
|
}) =>
|
||||||
|
opened ? (
|
||||||
|
<div data-testid="email-password-modal">
|
||||||
|
<button
|
||||||
|
onClick={() => void onConfirm('test-password')}
|
||||||
|
data-testid="confirm-email"
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Helper wrapper component for testing
|
||||||
|
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Custom render function
|
||||||
|
const render = (ui: React.ReactElement) => {
|
||||||
|
return rtlRender(ui, { wrapper: TestWrapper });
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('AccountSettings', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockUpdateProfile.mockResolvedValue(mockUser);
|
||||||
|
mockRefreshUser.mockResolvedValue(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders modal with all sections', () => {
|
||||||
|
render(<AccountSettings opened={true} onClose={vi.fn()} />);
|
||||||
|
|
||||||
|
expect(screen.getByText('Account Settings')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('profile-settings')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('security-settings')).toBeInTheDocument();
|
||||||
|
expect(screen.getByTestId('danger-zone-settings')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows unsaved changes badge when settings are modified', () => {
|
||||||
|
render(<AccountSettings opened={true} onClose={vi.fn()} />);
|
||||||
|
|
||||||
|
const displayNameInput = screen.getByTestId('display-name-input');
|
||||||
|
fireEvent.change(displayNameInput, { target: { value: 'Updated Name' } });
|
||||||
|
|
||||||
|
expect(screen.getByText('Unsaved Changes')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('enables save button when there are changes', () => {
|
||||||
|
render(<AccountSettings opened={true} onClose={vi.fn()} />);
|
||||||
|
|
||||||
|
const saveButton = screen.getByRole('button', { name: 'Save Changes' });
|
||||||
|
expect(saveButton).toBeDisabled();
|
||||||
|
|
||||||
|
const displayNameInput = screen.getByTestId('display-name-input');
|
||||||
|
fireEvent.change(displayNameInput, { target: { value: 'Updated Name' } });
|
||||||
|
|
||||||
|
expect(saveButton).not.toBeDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('saves profile changes successfully', async () => {
|
||||||
|
const mockOnClose = vi.fn();
|
||||||
|
render(<AccountSettings opened={true} onClose={mockOnClose} />);
|
||||||
|
|
||||||
|
const displayNameInput = screen.getByTestId('display-name-input');
|
||||||
|
fireEvent.change(displayNameInput, { target: { value: 'Updated Name' } });
|
||||||
|
|
||||||
|
const saveButton = screen.getByRole('button', { name: 'Save Changes' });
|
||||||
|
fireEvent.click(saveButton);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockUpdateProfile).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ displayName: 'Updated Name' })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockRefreshUser).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockOnClose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens email confirmation modal for email changes', () => {
|
||||||
|
render(<AccountSettings opened={true} onClose={vi.fn()} />);
|
||||||
|
|
||||||
|
const emailInput = screen.getByTestId('email-input');
|
||||||
|
fireEvent.change(emailInput, { target: { value: 'new@example.com' } });
|
||||||
|
|
||||||
|
const saveButton = screen.getByRole('button', { name: 'Save Changes' });
|
||||||
|
fireEvent.click(saveButton);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('email-password-modal')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('completes email change with password confirmation', async () => {
|
||||||
|
const mockOnClose = vi.fn();
|
||||||
|
render(<AccountSettings opened={true} onClose={mockOnClose} />);
|
||||||
|
|
||||||
|
const emailInput = screen.getByTestId('email-input');
|
||||||
|
fireEvent.change(emailInput, { target: { value: 'new@example.com' } });
|
||||||
|
|
||||||
|
const saveButton = screen.getByRole('button', { name: 'Save Changes' });
|
||||||
|
fireEvent.click(saveButton);
|
||||||
|
|
||||||
|
const confirmButton = screen.getByTestId('confirm-email');
|
||||||
|
fireEvent.click(confirmButton);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockUpdateProfile).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
email: 'new@example.com',
|
||||||
|
currentPassword: 'test-password',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockOnClose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('closes modal when cancel is clicked', () => {
|
||||||
|
const mockOnClose = vi.fn();
|
||||||
|
render(<AccountSettings opened={true} onClose={mockOnClose} />);
|
||||||
|
|
||||||
|
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
||||||
|
fireEvent.click(cancelButton);
|
||||||
|
|
||||||
|
expect(mockOnClose).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render when closed', () => {
|
||||||
|
render(<AccountSettings opened={false} onClose={vi.fn()} />);
|
||||||
|
|
||||||
|
expect(screen.queryByText('Account Settings')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
140
app/src/components/settings/account/DangerZoneSettings.test.tsx
Normal file
140
app/src/components/settings/account/DangerZoneSettings.test.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import {
|
||||||
|
render as rtlRender,
|
||||||
|
screen,
|
||||||
|
fireEvent,
|
||||||
|
waitFor,
|
||||||
|
} from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { MantineProvider } from '@mantine/core';
|
||||||
|
import DangerZoneSettings from './DangerZoneSettings';
|
||||||
|
|
||||||
|
// Mock the auth context
|
||||||
|
const mockLogout = vi.fn();
|
||||||
|
vi.mock('../../../contexts/AuthContext', () => ({
|
||||||
|
useAuth: () => ({ logout: mockLogout }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the profile settings hook
|
||||||
|
const mockDeleteAccount = vi.fn();
|
||||||
|
vi.mock('../../../hooks/useProfileSettings', () => ({
|
||||||
|
useProfileSettings: () => ({ deleteAccount: mockDeleteAccount }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock the DeleteAccountModal
|
||||||
|
vi.mock('../../modals/account/DeleteAccountModal', () => ({
|
||||||
|
default: ({
|
||||||
|
opened,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
}: {
|
||||||
|
opened: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirm: (password: string) => void;
|
||||||
|
}) =>
|
||||||
|
opened ? (
|
||||||
|
<div data-testid="delete-account-modal">
|
||||||
|
<button onClick={onClose} data-testid="modal-close">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => onConfirm('test-password')}
|
||||||
|
data-testid="modal-confirm"
|
||||||
|
>
|
||||||
|
Confirm Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Helper wrapper component for testing
|
||||||
|
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Custom render function
|
||||||
|
const render = (ui: React.ReactElement) => {
|
||||||
|
return rtlRender(ui, { wrapper: TestWrapper });
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('DangerZoneSettings', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockDeleteAccount.mockResolvedValue(true);
|
||||||
|
mockLogout.mockResolvedValue(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders delete button with warning text', () => {
|
||||||
|
render(<DangerZoneSettings />);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByRole('button', { name: 'Delete Account' })
|
||||||
|
).toBeInTheDocument();
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
'Once you delete your account, there is no going back. Please be certain.'
|
||||||
|
)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('opens and closes delete modal', () => {
|
||||||
|
render(<DangerZoneSettings />);
|
||||||
|
|
||||||
|
const deleteButton = screen.getByRole('button', { name: 'Delete Account' });
|
||||||
|
fireEvent.click(deleteButton);
|
||||||
|
|
||||||
|
expect(screen.getByTestId('delete-account-modal')).toBeInTheDocument();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId('modal-close'));
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('delete-account-modal')
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('completes account deletion and logout flow', async () => {
|
||||||
|
render(<DangerZoneSettings />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Delete Account' }));
|
||||||
|
fireEvent.click(screen.getByTestId('modal-confirm'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockDeleteAccount).toHaveBeenCalledWith('test-password');
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockLogout).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('delete-account-modal')
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps modal open when deletion fails', async () => {
|
||||||
|
mockDeleteAccount.mockResolvedValue(false);
|
||||||
|
|
||||||
|
render(<DangerZoneSettings />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Delete Account' }));
|
||||||
|
fireEvent.click(screen.getByTestId('modal-confirm'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockDeleteAccount).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByTestId('delete-account-modal')).toBeInTheDocument();
|
||||||
|
expect(mockLogout).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows cancellation of deletion process', () => {
|
||||||
|
render(<DangerZoneSettings />);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Delete Account' }));
|
||||||
|
fireEvent.click(screen.getByTestId('modal-close'));
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId('delete-account-modal')
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
expect(mockDeleteAccount).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
113
app/src/components/settings/account/ProfileSettings.test.tsx
Normal file
113
app/src/components/settings/account/ProfileSettings.test.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { MantineProvider } from '@mantine/core';
|
||||||
|
import ProfileSettings from './ProfileSettings';
|
||||||
|
import type { UserProfileSettings } from '@/types/models';
|
||||||
|
|
||||||
|
// Helper wrapper component for testing
|
||||||
|
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Custom render function
|
||||||
|
const render = (ui: React.ReactElement) => {
|
||||||
|
return rtlRender(ui, { wrapper: TestWrapper });
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('ProfileSettings', () => {
|
||||||
|
const mockOnInputChange = vi.fn();
|
||||||
|
|
||||||
|
const defaultSettings: UserProfileSettings = {
|
||||||
|
displayName: 'John Doe',
|
||||||
|
email: 'john.doe@example.com',
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const emptySettings: UserProfileSettings = {
|
||||||
|
displayName: '',
|
||||||
|
email: '',
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders form fields with current values', () => {
|
||||||
|
render(
|
||||||
|
<ProfileSettings
|
||||||
|
settings={defaultSettings}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const displayNameInput = screen.getByTestId('display-name-input');
|
||||||
|
const emailInput = screen.getByTestId('email-input');
|
||||||
|
|
||||||
|
expect(displayNameInput).toHaveValue('John Doe');
|
||||||
|
expect(emailInput).toHaveValue('john.doe@example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders with empty settings', () => {
|
||||||
|
render(
|
||||||
|
<ProfileSettings
|
||||||
|
settings={emptySettings}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const displayNameInput = screen.getByTestId('display-name-input');
|
||||||
|
const emailInput = screen.getByTestId('email-input');
|
||||||
|
|
||||||
|
expect(displayNameInput).toHaveValue('');
|
||||||
|
expect(emailInput).toHaveValue('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onInputChange when display name is modified', () => {
|
||||||
|
render(
|
||||||
|
<ProfileSettings
|
||||||
|
settings={defaultSettings}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const displayNameInput = screen.getByTestId('display-name-input');
|
||||||
|
fireEvent.change(displayNameInput, { target: { value: 'Jane Smith' } });
|
||||||
|
|
||||||
|
expect(mockOnInputChange).toHaveBeenCalledWith('displayName', 'Jane Smith');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onInputChange when email is modified', () => {
|
||||||
|
render(
|
||||||
|
<ProfileSettings
|
||||||
|
settings={defaultSettings}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const emailInput = screen.getByTestId('email-input');
|
||||||
|
fireEvent.change(emailInput, { target: { value: 'jane@example.com' } });
|
||||||
|
|
||||||
|
expect(mockOnInputChange).toHaveBeenCalledWith('email', 'jane@example.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has correct input types and accessibility', () => {
|
||||||
|
render(
|
||||||
|
<ProfileSettings
|
||||||
|
settings={defaultSettings}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const displayNameInput = screen.getByTestId('display-name-input');
|
||||||
|
const emailInput = screen.getByTestId('email-input');
|
||||||
|
|
||||||
|
expect(displayNameInput).toHaveAttribute('type', 'text');
|
||||||
|
expect(emailInput).toHaveAttribute('type', 'email');
|
||||||
|
expect(displayNameInput).toHaveAccessibleName();
|
||||||
|
expect(emailInput).toHaveAccessibleName();
|
||||||
|
});
|
||||||
|
});
|
||||||
137
app/src/components/settings/account/SecuritySettings.test.tsx
Normal file
137
app/src/components/settings/account/SecuritySettings.test.tsx
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { MantineProvider } from '@mantine/core';
|
||||||
|
import SecuritySettings from './SecuritySettings';
|
||||||
|
import type { UserProfileSettings } from '@/types/models';
|
||||||
|
|
||||||
|
// Helper wrapper component for testing
|
||||||
|
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
|
||||||
|
<MantineProvider defaultColorScheme="light">{children}</MantineProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Custom render function
|
||||||
|
const render = (ui: React.ReactElement) => {
|
||||||
|
return rtlRender(ui, { wrapper: TestWrapper });
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('SecuritySettings', () => {
|
||||||
|
const mockOnInputChange = vi.fn();
|
||||||
|
|
||||||
|
const defaultSettings: UserProfileSettings = {
|
||||||
|
displayName: 'John Doe',
|
||||||
|
email: 'john@example.com',
|
||||||
|
currentPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders all password fields', () => {
|
||||||
|
render(
|
||||||
|
<SecuritySettings
|
||||||
|
settings={defaultSettings}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('Current Password')).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText('New Password')).toBeInTheDocument();
|
||||||
|
expect(screen.getByLabelText('Confirm New Password')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onInputChange for current password', () => {
|
||||||
|
render(
|
||||||
|
<SecuritySettings
|
||||||
|
settings={defaultSettings}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentPasswordInput = screen.getByLabelText('Current Password');
|
||||||
|
fireEvent.change(currentPasswordInput, { target: { value: 'oldpass123' } });
|
||||||
|
|
||||||
|
expect(mockOnInputChange).toHaveBeenCalledWith(
|
||||||
|
'currentPassword',
|
||||||
|
'oldpass123'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls onInputChange for new password', () => {
|
||||||
|
render(
|
||||||
|
<SecuritySettings
|
||||||
|
settings={defaultSettings}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const newPasswordInput = screen.getByLabelText('New Password');
|
||||||
|
fireEvent.change(newPasswordInput, { target: { value: 'newpass123' } });
|
||||||
|
|
||||||
|
expect(mockOnInputChange).toHaveBeenCalledWith('newPassword', 'newpass123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows error when passwords do not match', () => {
|
||||||
|
render(
|
||||||
|
<SecuritySettings
|
||||||
|
settings={{ ...defaultSettings, newPassword: 'password123' }}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const confirmPasswordInput = screen.getByLabelText('Confirm New Password');
|
||||||
|
fireEvent.change(confirmPasswordInput, {
|
||||||
|
target: { value: 'different123' },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('Passwords do not match')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clears error when passwords match', () => {
|
||||||
|
render(
|
||||||
|
<SecuritySettings
|
||||||
|
settings={{ ...defaultSettings, newPassword: 'password123' }}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const confirmPasswordInput = screen.getByLabelText('Confirm New Password');
|
||||||
|
|
||||||
|
// First make them not match
|
||||||
|
fireEvent.change(confirmPasswordInput, {
|
||||||
|
target: { value: 'different123' },
|
||||||
|
});
|
||||||
|
expect(screen.getByText('Passwords do not match')).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Then make them match
|
||||||
|
fireEvent.change(confirmPasswordInput, {
|
||||||
|
target: { value: 'password123' },
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
screen.queryByText('Passwords do not match')
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has correct input types and help text', () => {
|
||||||
|
render(
|
||||||
|
<SecuritySettings
|
||||||
|
settings={defaultSettings}
|
||||||
|
onInputChange={mockOnInputChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentPasswordInput = screen.getByLabelText('Current Password');
|
||||||
|
const newPasswordInput = screen.getByLabelText('New Password');
|
||||||
|
const confirmPasswordInput = screen.getByLabelText('Confirm New Password');
|
||||||
|
|
||||||
|
expect(currentPasswordInput).toHaveAttribute('type', 'password');
|
||||||
|
expect(newPasswordInput).toHaveAttribute('type', 'password');
|
||||||
|
expect(confirmPasswordInput).toHaveAttribute('type', 'password');
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.getByText(/Password must be at least 8 characters long/)
|
||||||
|
).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user