Refactor database services and add component tests

- Remove deprecated CouchDB service files
- Update database test configurations
- Add test files for components and auth modules
- Update user context and admin interface
- Remove migration script for unified config
- Fix User interface properties in tests (use status instead of isActive)
This commit is contained in:
William Valentin
2025-09-08 18:30:43 -07:00
parent 31e08d730d
commit ac3643f76d
10 changed files with 934 additions and 1614 deletions

View File

@@ -0,0 +1,334 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
// Mock component for demonstration purposes
// This would normally import an actual component
const MockButton: React.FC<{
onClick: () => void;
children: React.ReactNode;
disabled?: boolean;
}> = ({ onClick, children, disabled = false }) => (
<button
onClick={onClick}
disabled={disabled}
data-testid='mock-button'
className={`btn ${disabled ? 'btn-disabled' : 'btn-primary'}`}
>
{children}
</button>
);
const MockMedicationCard: React.FC<{
medication: {
id: string;
name: string;
dosage: string;
frequency: string;
};
onEdit: (id: string) => void;
onDelete: (id: string) => void;
}> = ({ medication, onEdit, onDelete }) => (
<div data-testid='medication-card' className='medication-card'>
<h3 data-testid='medication-name'>{medication.name}</h3>
<p data-testid='medication-dosage'>Dosage: {medication.dosage}</p>
<p data-testid='medication-frequency'>Frequency: {medication.frequency}</p>
<div className='actions'>
<MockButton onClick={() => onEdit(medication.id)}>Edit</MockButton>
<MockButton onClick={() => onDelete(medication.id)}>Delete</MockButton>
</div>
</div>
);
describe('Component Testing Examples', () => {
describe('MockButton Component', () => {
test('renders button with correct text', () => {
const mockClick = jest.fn();
render(<MockButton onClick={mockClick}>Click Me</MockButton>);
const button = screen.getByTestId('mock-button');
expect(button).toBeInTheDocument();
expect(button).toHaveTextContent('Click Me');
});
test('calls onClick handler when clicked', () => {
const mockClick = jest.fn();
render(<MockButton onClick={mockClick}>Click Me</MockButton>);
const button = screen.getByTestId('mock-button');
fireEvent.click(button);
expect(mockClick).toHaveBeenCalledTimes(1);
});
test('applies disabled state correctly', () => {
const mockClick = jest.fn();
render(
<MockButton onClick={mockClick} disabled>
Disabled Button
</MockButton>
);
const button = screen.getByTestId('mock-button');
expect(button).toBeDisabled();
expect(button).toHaveClass('btn-disabled');
fireEvent.click(button);
expect(mockClick).not.toHaveBeenCalled();
});
test('applies correct CSS classes', () => {
const mockClick = jest.fn();
const { rerender } = render(
<MockButton onClick={mockClick}>Normal Button</MockButton>
);
let button = screen.getByTestId('mock-button');
expect(button).toHaveClass('btn', 'btn-primary');
expect(button).not.toHaveClass('btn-disabled');
rerender(
<MockButton onClick={mockClick} disabled>
Disabled Button
</MockButton>
);
button = screen.getByTestId('mock-button');
expect(button).toHaveClass('btn', 'btn-disabled');
expect(button).not.toHaveClass('btn-primary');
});
});
describe('MockMedicationCard Component', () => {
const mockMedication = {
id: 'med-123',
name: 'Aspirin',
dosage: '100mg',
frequency: 'Daily',
};
const defaultProps = {
medication: mockMedication,
onEdit: jest.fn(),
onDelete: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
});
test('renders medication information correctly', () => {
render(<MockMedicationCard {...defaultProps} />);
expect(screen.getByTestId('medication-card')).toBeInTheDocument();
expect(screen.getByTestId('medication-name')).toHaveTextContent(
'Aspirin'
);
expect(screen.getByTestId('medication-dosage')).toHaveTextContent(
'Dosage: 100mg'
);
expect(screen.getByTestId('medication-frequency')).toHaveTextContent(
'Frequency: Daily'
);
});
test('renders action buttons', () => {
render(<MockMedicationCard {...defaultProps} />);
const editButton = screen.getByText('Edit');
const deleteButton = screen.getByText('Delete');
expect(editButton).toBeInTheDocument();
expect(deleteButton).toBeInTheDocument();
});
test('calls onEdit with correct medication ID when edit button is clicked', () => {
const mockOnEdit = jest.fn();
render(<MockMedicationCard {...defaultProps} onEdit={mockOnEdit} />);
const editButton = screen.getByText('Edit');
fireEvent.click(editButton);
expect(mockOnEdit).toHaveBeenCalledTimes(1);
expect(mockOnEdit).toHaveBeenCalledWith('med-123');
});
test('calls onDelete with correct medication ID when delete button is clicked', () => {
const mockOnDelete = jest.fn();
render(<MockMedicationCard {...defaultProps} onDelete={mockOnDelete} />);
const deleteButton = screen.getByText('Delete');
fireEvent.click(deleteButton);
expect(mockOnDelete).toHaveBeenCalledTimes(1);
expect(mockOnDelete).toHaveBeenCalledWith('med-123');
});
test('handles different medication data', () => {
const differentMedication = {
id: 'med-456',
name: 'Vitamin D',
dosage: '1000 IU',
frequency: 'Weekly',
};
render(
<MockMedicationCard
{...defaultProps}
medication={differentMedication}
/>
);
expect(screen.getByTestId('medication-name')).toHaveTextContent(
'Vitamin D'
);
expect(screen.getByTestId('medication-dosage')).toHaveTextContent(
'Dosage: 1000 IU'
);
expect(screen.getByTestId('medication-frequency')).toHaveTextContent(
'Frequency: Weekly'
);
});
test('maintains proper component structure', () => {
render(<MockMedicationCard {...defaultProps} />);
const card = screen.getByTestId('medication-card');
expect(card).toHaveClass('medication-card');
const actionsContainer = card.querySelector('.actions');
expect(actionsContainer).toBeInTheDocument();
// Verify buttons are within the actions container
const buttons = actionsContainer?.querySelectorAll('button');
expect(buttons).toHaveLength(2);
});
});
describe('Component Integration Tests', () => {
test('multiple components work together', () => {
const medications = [
{
id: 'med-1',
name: 'Aspirin',
dosage: '100mg',
frequency: 'Daily',
},
{
id: 'med-2',
name: 'Vitamin C',
dosage: '500mg',
frequency: 'Twice daily',
},
];
const MockMedicationList: React.FC<{
medications: typeof medications;
onEdit: (id: string) => void;
onDelete: (id: string) => void;
}> = ({ medications, onEdit, onDelete }) => (
<div data-testid='medication-list'>
{medications.map(med => (
<MockMedicationCard
key={med.id}
medication={med}
onEdit={onEdit}
onDelete={onDelete}
/>
))}
</div>
);
const mockOnEdit = jest.fn();
const mockOnDelete = jest.fn();
render(
<MockMedicationList
medications={medications}
onEdit={mockOnEdit}
onDelete={mockOnDelete}
/>
);
// Verify all medications are rendered
expect(screen.getAllByTestId('medication-card')).toHaveLength(2);
expect(screen.getByText('Aspirin')).toBeInTheDocument();
expect(screen.getByText('Vitamin C')).toBeInTheDocument();
// Test interaction with first medication
const editButtons = screen.getAllByText('Edit');
fireEvent.click(editButtons[0]);
expect(mockOnEdit).toHaveBeenCalledWith('med-1');
// Test interaction with second medication
const deleteButtons = screen.getAllByText('Delete');
fireEvent.click(deleteButtons[1]);
expect(mockOnDelete).toHaveBeenCalledWith('med-2');
});
});
describe('Accessibility Tests', () => {
test('buttons have proper accessibility attributes', () => {
const mockClick = jest.fn();
render(<MockButton onClick={mockClick}>Accessible Button</MockButton>);
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
expect(button).toHaveAccessibleName('Accessible Button');
});
test('medication card structure supports screen readers', () => {
render(
<MockMedicationCard
{...{
medication: {
id: 'med-123',
name: 'Test Medicine',
dosage: '50mg',
frequency: 'As needed',
},
onEdit: jest.fn(),
onDelete: jest.fn(),
}}
/>
);
// Check that important information is properly structured
const heading = screen.getByRole('heading', { level: 3 });
expect(heading).toHaveTextContent('Test Medicine');
const editButton = screen.getByRole('button', { name: 'Edit' });
const deleteButton = screen.getByRole('button', { name: 'Delete' });
expect(editButton).toBeInTheDocument();
expect(deleteButton).toBeInTheDocument();
});
});
describe('Error Handling', () => {
test('components handle missing props gracefully', () => {
// Test with minimal props
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {
// Mock implementation for testing
});
render(
<MockMedicationCard
medication={{
id: '',
name: '',
dosage: '',
frequency: '',
}}
onEdit={jest.fn()}
onDelete={jest.fn()}
/>
);
// Component should still render even with empty data
expect(screen.getByTestId('medication-card')).toBeInTheDocument();
consoleSpy.mockRestore();
});
});
});

View File

@@ -1,7 +1,7 @@
import React, { useState, useEffect } from 'react';
import { User, UserRole } from '../../types';
import { AccountStatus } from '../../services/auth/auth.constants';
import { dbService } from '../../services/couchdb.factory';
import { databaseService } from '../../services/database';
import { useUser } from '../../contexts/UserContext';
interface AdminInterfaceProps {
@@ -22,8 +22,8 @@ const AdminInterface: React.FC<AdminInterfaceProps> = ({ onClose }) => {
const loadUsers = async () => {
try {
const allUsers = await dbService.getAllUsers();
setUsers(allUsers);
const users = await databaseService.getAllUsers();
setUsers(users);
} catch (error) {
setError('Failed to load users');
console.error('Error loading users:', error);
@@ -34,7 +34,7 @@ const AdminInterface: React.FC<AdminInterfaceProps> = ({ onClose }) => {
const handleSuspendUser = async (userId: string) => {
try {
await dbService.suspendUser(userId);
await databaseService.suspendUser(userId);
await loadUsers();
} catch (error) {
setError('Failed to suspend user');
@@ -44,7 +44,7 @@ const AdminInterface: React.FC<AdminInterfaceProps> = ({ onClose }) => {
const handleActivateUser = async (userId: string) => {
try {
await dbService.activateUser(userId);
await databaseService.activateUser(userId);
await loadUsers();
} catch (error) {
setError('Failed to activate user');
@@ -62,7 +62,7 @@ const AdminInterface: React.FC<AdminInterfaceProps> = ({ onClose }) => {
}
try {
await dbService.deleteUser(userId);
await databaseService.deleteUser(userId);
await loadUsers();
} catch (error) {
setError('Failed to delete user');
@@ -77,7 +77,7 @@ const AdminInterface: React.FC<AdminInterfaceProps> = ({ onClose }) => {
}
try {
await dbService.changeUserPassword(userId, newPassword);
await databaseService.changeUserPassword(userId, newPassword);
setNewPassword('');
setSelectedUser(null);
setError('');

View File

@@ -0,0 +1,475 @@
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import AvatarDropdown from '../AvatarDropdown';
import { User, UserRole } from '../../../types';
import { AccountStatus } from '../../../services/auth/auth.constants';
// Mock user data
const mockRegularUser: User = {
_id: '1',
_rev: '1-abc123',
username: 'John Doe',
email: 'john@example.com',
role: UserRole.USER,
status: AccountStatus.ACTIVE,
createdAt: new Date(),
};
const mockAdminUser: User = {
_id: '2',
_rev: '1-def456',
username: 'Admin User',
email: 'admin@example.com',
role: UserRole.ADMIN,
status: AccountStatus.ACTIVE,
createdAt: new Date(),
};
const mockUserWithAvatar: User = {
...mockRegularUser,
avatar: 'https://example.com/avatar.jpg',
};
const mockUserWithPassword: User = {
...mockRegularUser,
password: 'hashed-password',
};
describe('AvatarDropdown', () => {
const mockOnLogout = jest.fn();
const mockOnAdmin = jest.fn();
const mockOnChangePassword = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
});
describe('rendering', () => {
test('should render avatar button with user initials', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
const button = screen.getByRole('button', { name: /user menu/i });
expect(button).toBeInTheDocument();
expect(button).toHaveTextContent('J');
});
test('should render avatar image when user has avatar', () => {
render(
<AvatarDropdown user={mockUserWithAvatar} onLogout={mockOnLogout} />
);
const avatar = screen.getByAltText('User avatar');
expect(avatar).toBeInTheDocument();
expect(avatar).toHaveAttribute('src', 'https://example.com/avatar.jpg');
});
test('should render fallback character for empty username', () => {
const userWithEmptyName = { ...mockRegularUser, username: '' };
render(
<AvatarDropdown user={userWithEmptyName} onLogout={mockOnLogout} />
);
const button = screen.getByRole('button', { name: /user menu/i });
expect(button).toHaveTextContent('?');
});
test('should not render dropdown menu initially', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
expect(screen.queryByText('Signed in as')).not.toBeInTheDocument();
});
});
describe('dropdown functionality', () => {
test('should open dropdown when avatar button is clicked', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
const button = screen.getByRole('button', { name: /user menu/i });
fireEvent.click(button);
expect(screen.getByText('Signed in as')).toBeInTheDocument();
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
test('should close dropdown when avatar button is clicked again', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
const button = screen.getByRole('button', { name: /user menu/i });
// Open dropdown
fireEvent.click(button);
expect(screen.getByText('Signed in as')).toBeInTheDocument();
// Close dropdown
fireEvent.click(button);
expect(screen.queryByText('Signed in as')).not.toBeInTheDocument();
});
test('should close dropdown when clicking outside', async () => {
render(
<div>
<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />
<div data-testid='outside'>Outside element</div>
</div>
);
const button = screen.getByRole('button', { name: /user menu/i });
const outside = screen.getByTestId('outside');
// Open dropdown
fireEvent.click(button);
expect(screen.getByText('Signed in as')).toBeInTheDocument();
// Click outside
fireEvent.mouseDown(outside);
await waitFor(() => {
expect(screen.queryByText('Signed in as')).not.toBeInTheDocument();
});
});
});
describe('user information display', () => {
test('should display username in dropdown', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
test('should display administrator badge for admin users', () => {
render(<AvatarDropdown user={mockAdminUser} onLogout={mockOnLogout} />);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.getByText('Administrator')).toBeInTheDocument();
});
test('should not display administrator badge for regular users', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.queryByText('Administrator')).not.toBeInTheDocument();
});
test('should truncate long usernames', () => {
const userWithLongName = {
...mockRegularUser,
username: 'Very Long Username That Should Be Truncated',
};
render(
<AvatarDropdown user={userWithLongName} onLogout={mockOnLogout} />
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
const usernameElement = screen.getByText(
'Very Long Username That Should Be Truncated'
);
expect(usernameElement).toHaveClass('truncate');
});
});
describe('logout functionality', () => {
test('should call onLogout when logout button is clicked', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
fireEvent.click(screen.getByText('Logout'));
expect(mockOnLogout).toHaveBeenCalledTimes(1);
});
test('should close dropdown after logout is clicked', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
fireEvent.click(screen.getByText('Logout'));
expect(screen.queryByText('Signed in as')).not.toBeInTheDocument();
});
test('should always display logout button', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.getByText('Logout')).toBeInTheDocument();
});
});
describe('admin functionality', () => {
test('should display admin interface button for admin users when onAdmin provided', () => {
render(
<AvatarDropdown
user={mockAdminUser}
onLogout={mockOnLogout}
onAdmin={mockOnAdmin}
/>
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.getByText('Admin Interface')).toBeInTheDocument();
});
test('should not display admin interface button for regular users', () => {
render(
<AvatarDropdown
user={mockRegularUser}
onLogout={mockOnLogout}
onAdmin={mockOnAdmin}
/>
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.queryByText('Admin Interface')).not.toBeInTheDocument();
});
test('should not display admin interface button when onAdmin not provided', () => {
render(<AvatarDropdown user={mockAdminUser} onLogout={mockOnLogout} />);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.queryByText('Admin Interface')).not.toBeInTheDocument();
});
test('should call onAdmin when admin interface button is clicked', () => {
render(
<AvatarDropdown
user={mockAdminUser}
onLogout={mockOnLogout}
onAdmin={mockOnAdmin}
/>
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
fireEvent.click(screen.getByText('Admin Interface'));
expect(mockOnAdmin).toHaveBeenCalledTimes(1);
});
test('should close dropdown after admin interface is clicked', () => {
render(
<AvatarDropdown
user={mockAdminUser}
onLogout={mockOnLogout}
onAdmin={mockOnAdmin}
/>
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
fireEvent.click(screen.getByText('Admin Interface'));
expect(screen.queryByText('Signed in as')).not.toBeInTheDocument();
});
});
describe('change password functionality', () => {
test('should display change password button for users with password when onChangePassword provided', () => {
render(
<AvatarDropdown
user={mockUserWithPassword}
onLogout={mockOnLogout}
onChangePassword={mockOnChangePassword}
/>
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.getByText('Change Password')).toBeInTheDocument();
});
test('should not display change password button for users without password', () => {
render(
<AvatarDropdown
user={mockRegularUser}
onLogout={mockOnLogout}
onChangePassword={mockOnChangePassword}
/>
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.queryByText('Change Password')).not.toBeInTheDocument();
});
test('should not display change password button when onChangePassword not provided', () => {
render(
<AvatarDropdown user={mockUserWithPassword} onLogout={mockOnLogout} />
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
expect(screen.queryByText('Change Password')).not.toBeInTheDocument();
});
test('should call onChangePassword when change password button is clicked', () => {
render(
<AvatarDropdown
user={mockUserWithPassword}
onLogout={mockOnLogout}
onChangePassword={mockOnChangePassword}
/>
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
fireEvent.click(screen.getByText('Change Password'));
expect(mockOnChangePassword).toHaveBeenCalledTimes(1);
});
test('should close dropdown after change password is clicked', () => {
render(
<AvatarDropdown
user={mockUserWithPassword}
onLogout={mockOnLogout}
onChangePassword={mockOnChangePassword}
/>
);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
fireEvent.click(screen.getByText('Change Password'));
expect(screen.queryByText('Signed in as')).not.toBeInTheDocument();
});
});
describe('getInitials function', () => {
test('should return first character uppercase for regular names', () => {
const userWithLowercase = { ...mockRegularUser, username: 'john doe' };
render(
<AvatarDropdown user={userWithLowercase} onLogout={mockOnLogout} />
);
const button = screen.getByRole('button', { name: /user menu/i });
expect(button).toHaveTextContent('J');
});
test('should return question mark for empty string', () => {
const userWithEmptyName = { ...mockRegularUser, username: '' };
render(
<AvatarDropdown user={userWithEmptyName} onLogout={mockOnLogout} />
);
const button = screen.getByRole('button', { name: /user menu/i });
expect(button).toHaveTextContent('?');
});
test('should handle single character names', () => {
const userWithSingleChar = { ...mockRegularUser, username: 'x' };
render(
<AvatarDropdown user={userWithSingleChar} onLogout={mockOnLogout} />
);
const button = screen.getByRole('button', { name: /user menu/i });
expect(button).toHaveTextContent('X');
});
test('should handle special characters', () => {
const userWithSpecialChar = { ...mockRegularUser, username: '@john' };
render(
<AvatarDropdown user={userWithSpecialChar} onLogout={mockOnLogout} />
);
const button = screen.getByRole('button', { name: /user menu/i });
expect(button).toHaveTextContent('@');
});
});
describe('accessibility', () => {
test('should have proper aria-label for avatar button', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
const button = screen.getByRole('button', { name: /user menu/i });
expect(button).toHaveAttribute('aria-label', 'User menu');
});
test('should be keyboard accessible', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
const button = screen.getByRole('button', { name: /user menu/i });
button.focus();
expect(button).toHaveFocus();
});
test('should have proper focus styles', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
const button = screen.getByRole('button', { name: /user menu/i });
expect(button).toHaveClass('focus:outline-none', 'focus:ring-2');
});
});
describe('styling and theming', () => {
test('should apply dark mode classes', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
const button = screen.getByRole('button', { name: /user menu/i });
expect(button).toHaveClass('dark:bg-slate-700', 'dark:text-slate-300');
fireEvent.click(button);
const dropdown = screen.getByText('Signed in as').closest('div');
expect(dropdown).toHaveClass(
'dark:bg-slate-800',
'dark:border-slate-700'
);
});
test('should apply hover styles to menu items', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
fireEvent.click(screen.getByRole('button', { name: /user menu/i }));
const logoutButton = screen.getByText('Logout');
expect(logoutButton).toHaveClass(
'hover:bg-slate-100',
'dark:hover:bg-slate-700'
);
});
});
describe('edge cases', () => {
test('should handle clicking outside when dropdown is closed', async () => {
render(
<div>
<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />
<div data-testid='outside'>Outside element</div>
</div>
);
const outside = screen.getByTestId('outside');
fireEvent.mouseDown(outside);
// Should not throw any errors
expect(screen.queryByText('Signed in as')).not.toBeInTheDocument();
});
test('should handle rapid clicking', () => {
render(<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />);
const button = screen.getByRole('button', { name: /user menu/i });
// Rapid clicks
fireEvent.click(button);
fireEvent.click(button);
fireEvent.click(button);
// Should end up closed
expect(screen.queryByText('Signed in as')).not.toBeInTheDocument();
});
test('should cleanup event listeners on unmount', () => {
const removeEventListenerSpy = jest.spyOn(
document,
'removeEventListener'
);
const { unmount } = render(
<AvatarDropdown user={mockRegularUser} onLogout={mockOnLogout} />
);
unmount();
expect(removeEventListenerSpy).toHaveBeenCalledWith(
'mousedown',
expect.any(Function)
);
removeEventListenerSpy.mockRestore();
});
});
});