add tests
This commit is contained in:
260
apps/website/components/auth/AuthContext.test.tsx
Normal file
260
apps/website/components/auth/AuthContext.test.tsx
Normal file
@@ -0,0 +1,260 @@
|
||||
import React from 'react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { AuthProvider, useAuth } from './AuthContext';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useCurrentSession } from '@/hooks/auth/useCurrentSession';
|
||||
import { useLogout } from '@/hooks/auth/useLogout';
|
||||
|
||||
// Mock Next.js navigation
|
||||
vi.mock('next/navigation', () => ({
|
||||
useRouter: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock auth hooks
|
||||
vi.mock('@/hooks/auth/useCurrentSession', () => ({
|
||||
useCurrentSession: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/auth/useLogout', () => ({
|
||||
useLogout: vi.fn(),
|
||||
}));
|
||||
|
||||
// Test component that uses the auth context
|
||||
const TestConsumer = () => {
|
||||
const auth = useAuth();
|
||||
return (
|
||||
<div data-testid="auth-consumer">
|
||||
<div data-testid="session">{auth.session ? 'has-session' : 'no-session'}</div>
|
||||
<div data-testid="loading">{auth.loading ? 'loading' : 'not-loading'}</div>
|
||||
<button onClick={() => auth.login()}>Login</button>
|
||||
<button onClick={() => auth.logout()}>Logout</button>
|
||||
<button onClick={() => auth.refreshSession()}>Refresh</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
describe('AuthContext', () => {
|
||||
let mockRouter: any;
|
||||
let mockRefetch: any;
|
||||
let mockMutateAsync: any;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
mockRouter = {
|
||||
push: vi.fn(),
|
||||
refresh: vi.fn(),
|
||||
};
|
||||
|
||||
mockRefetch = vi.fn();
|
||||
mockMutateAsync = vi.fn().mockResolvedValue(undefined);
|
||||
|
||||
(useRouter as any).mockReturnValue(mockRouter);
|
||||
(useCurrentSession as any).mockReturnValue({
|
||||
data: null,
|
||||
isLoading: false,
|
||||
refetch: mockRefetch,
|
||||
});
|
||||
(useLogout as any).mockReturnValue({
|
||||
mutateAsync: mockMutateAsync,
|
||||
});
|
||||
});
|
||||
|
||||
describe('AuthProvider', () => {
|
||||
it('should provide default context values', () => {
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('session')).toHaveTextContent('no-session');
|
||||
expect(screen.getByTestId('loading')).toHaveTextContent('not-loading');
|
||||
});
|
||||
|
||||
it('should provide loading state', () => {
|
||||
(useCurrentSession as any).mockReturnValue({
|
||||
data: null,
|
||||
isLoading: true,
|
||||
refetch: mockRefetch,
|
||||
});
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('loading')).toHaveTextContent('loading');
|
||||
});
|
||||
|
||||
it('should provide session data', () => {
|
||||
const mockSession = { user: { id: '123', name: 'Test User' } };
|
||||
(useCurrentSession as any).mockReturnValue({
|
||||
data: mockSession,
|
||||
isLoading: false,
|
||||
refetch: mockRefetch,
|
||||
});
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('session')).toHaveTextContent('has-session');
|
||||
});
|
||||
|
||||
it('should provide initial session data', () => {
|
||||
const mockSession = { user: { id: '123', name: 'Test User' } };
|
||||
|
||||
render(
|
||||
<AuthProvider initialSession={mockSession}>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('session')).toHaveTextContent('has-session');
|
||||
});
|
||||
});
|
||||
|
||||
describe('useAuth hook', () => {
|
||||
it('should throw error when used outside AuthProvider', () => {
|
||||
// Suppress console.error for this test
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
expect(() => {
|
||||
render(<TestConsumer />);
|
||||
}).toThrow('useAuth must be used within an AuthProvider');
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should provide login function', async () => {
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
const loginButton = screen.getByText('Login');
|
||||
loginButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRouter.push).toHaveBeenCalledWith('/auth/login');
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide login function with returnTo parameter', async () => {
|
||||
const TestConsumerWithReturnTo = () => {
|
||||
const auth = useAuth();
|
||||
return (
|
||||
<button onClick={() => auth.login('/dashboard')}>
|
||||
Login with Return
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestConsumerWithReturnTo />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
const loginButton = screen.getByText('Login with Return');
|
||||
loginButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRouter.push).toHaveBeenCalledWith('/auth/login?returnTo=%2Fdashboard');
|
||||
});
|
||||
});
|
||||
|
||||
it('should provide logout function', async () => {
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
const logoutButton = screen.getByText('Logout');
|
||||
logoutButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockMutateAsync).toHaveBeenCalled();
|
||||
expect(mockRouter.push).toHaveBeenCalledWith('/');
|
||||
expect(mockRouter.refresh).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle logout failure gracefully', async () => {
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
mockMutateAsync.mockRejectedValue(new Error('Logout failed'));
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
const logoutButton = screen.getByText('Logout');
|
||||
logoutButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockMutateAsync).toHaveBeenCalled();
|
||||
expect(mockRouter.push).toHaveBeenCalledWith('/');
|
||||
});
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should provide refreshSession function', async () => {
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
const refreshButton = screen.getByText('Refresh');
|
||||
refreshButton.click();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRefetch).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle null initial session', () => {
|
||||
render(
|
||||
<AuthProvider initialSession={null}>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('session')).toHaveTextContent('no-session');
|
||||
});
|
||||
|
||||
it('should handle undefined initial session', () => {
|
||||
render(
|
||||
<AuthProvider initialSession={undefined}>
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('session')).toHaveTextContent('no-session');
|
||||
});
|
||||
|
||||
it('should handle multiple consumers', () => {
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestConsumer />
|
||||
<TestConsumer />
|
||||
</AuthProvider>
|
||||
);
|
||||
|
||||
const consumers = screen.getAllByTestId('auth-consumer');
|
||||
expect(consumers).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user