import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { renderHook, act } from '@testing-library/react'; import { AuthProvider, useAuth, LoginForm, RegisterForm, UserProfile, AuthPage } from '../../components/Auth'; import React from 'react'; // Wrapper component for testing hooks that require AuthProvider const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); describe('AuthProvider', () => { beforeEach(() => { vi.clearAllMocks(); (global.fetch as ReturnType).mockResolvedValue({ ok: true, json: () => Promise.resolve({ authEnabled: true, localAuthEnabled: true }) }); }); afterEach(() => { vi.clearAllMocks(); }); it('provides auth context to children', () => { render(
Child content
); expect(screen.getByTestId('child')).toBeInTheDocument(); }); it('initializes with loading state', () => { const { result } = renderHook(() => useAuth(), { wrapper }); // Initially loading expect(result.current.loading).toBe(true); }); it('fetches auth state on mount', async () => { renderHook(() => useAuth(), { wrapper }); await waitFor(() => { expect(fetch).toHaveBeenCalledWith('/api/auth/state'); }); }); it('throws error when useAuth is used outside AuthProvider', () => { expect(() => { renderHook(() => useAuth()); }).toThrow('useAuth must be used within AuthProvider'); }); }); describe('LoginForm', () => { const mockAuthState = { authEnabled: true, localAuthEnabled: true, oidcEnabled: false, registrationEnabled: true, hasUsers: true, needsSetup: false, oidcProviderName: '' }; beforeEach(() => { vi.clearAllMocks(); (global.fetch as ReturnType) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockAuthState) }) .mockResolvedValueOnce({ status: 401, ok: false }); }); it('renders login form', async () => { render( ); await waitFor(() => { expect(screen.getByText(/MedAssist/i)).toBeInTheDocument(); }); }); it('renders username and password fields', async () => { render( ); await waitFor(() => { expect(screen.getByLabelText(/auth\.username/i)).toBeInTheDocument(); expect(screen.getByLabelText(/auth\.password/i)).toBeInTheDocument(); }); }); it('renders remember me checkbox', async () => { render( ); await waitFor(() => { expect(screen.getByText(/auth\.rememberMe/i)).toBeInTheDocument(); }); }); it('renders create account link when registration enabled', async () => { const onSwitchToRegister = vi.fn(); render( ); await waitFor(() => { const createAccountBtn = screen.getByText(/auth\.createAccount/i); expect(createAccountBtn).toBeInTheDocument(); }); }); it('handles form input changes', async () => { render( ); await waitFor(() => { expect(screen.getByLabelText(/auth\.username/i)).toBeInTheDocument(); }); fireEvent.change(screen.getByLabelText(/auth\.username/i), { target: { value: 'testuser' } }); fireEvent.change(screen.getByLabelText(/auth\.password/i), { target: { value: 'password123' } }); expect(screen.getByLabelText(/auth\.username/i)).toHaveValue('testuser'); expect(screen.getByLabelText(/auth\.password/i)).toHaveValue('password123'); }); it('renders submit button', async () => { render( ); await waitFor(() => { const buttons = screen.getAllByRole('button'); const submitBtn = buttons.find(btn => btn.getAttribute('type') === 'submit'); expect(submitBtn).toBeInTheDocument(); }); }); }); describe('RegisterForm', () => { const mockAuthState = { authEnabled: true, localAuthEnabled: true, oidcEnabled: false, registrationEnabled: true, hasUsers: false, needsSetup: true, oidcProviderName: '' }; beforeEach(() => { vi.clearAllMocks(); (global.fetch as ReturnType) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockAuthState) }) .mockResolvedValueOnce({ status: 401, ok: false }); }); it('renders registration form', async () => { render( ); await waitFor(() => { expect(screen.getByText(/MedAssist/i)).toBeInTheDocument(); }); }); it('renders all required fields', async () => { render( ); await waitFor(() => { // Check for username field expect(screen.getByLabelText(/auth\.username/i)).toBeInTheDocument(); // Check for password field expect(screen.getByLabelText(/auth\.password/i)).toBeInTheDocument(); }); }); it('renders switch to login link', async () => { const onSwitchToLogin = vi.fn(); render( ); await waitFor(() => { const loginLink = screen.getByText(/auth\.alreadyHaveAccount/i); expect(loginLink).toBeInTheDocument(); }); }); it('calls onSwitchToLogin when clicked', async () => { const onSwitchToLogin = vi.fn(); render( ); await waitFor(() => { const loginLink = screen.getByText(/auth\.alreadyHaveAccount/i); fireEvent.click(loginLink); }); expect(onSwitchToLogin).toHaveBeenCalled(); }); }); describe('AuthPage', () => { const mockAuthState = { authEnabled: true, localAuthEnabled: true, oidcEnabled: false, registrationEnabled: true, hasUsers: true, needsSetup: false, oidcProviderName: '' }; beforeEach(() => { vi.clearAllMocks(); (global.fetch as ReturnType) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockAuthState) }) .mockResolvedValueOnce({ status: 401, ok: false }); }); it('renders login form by default', async () => { render( ); await waitFor(() => { // Should show login form with username field expect(screen.getByLabelText(/auth\.username/i)).toBeInTheDocument(); }); }); }); describe('UserProfile', () => { const mockUser = { id: 1, username: 'testuser', avatarUrl: null }; beforeEach(() => { vi.clearAllMocks(); (global.fetch as ReturnType) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ authEnabled: true, localAuthEnabled: true }) }) .mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockUser) }); }); it('renders user profile when user is logged in', async () => { render( ); await waitFor(() => { expect(screen.getByText('testuser')).toBeInTheDocument(); }); }); it('displays user avatar initial when no avatar', async () => { render( ); await waitFor(() => { // The avatar shows first letter of username expect(screen.getByText('T')).toBeInTheDocument(); }); }); it('renders change password section', async () => { render( ); await waitFor(() => { expect(screen.getByText(/auth\.changePassword/i)).toBeInTheDocument(); }); }); it('renders cancel button that calls onClose', async () => { const onClose = vi.fn(); render( ); await waitFor(() => { const cancelBtn = screen.getByText(/common\.cancel/i); fireEvent.click(cancelBtn); }); expect(onClose).toHaveBeenCalled(); }); });