372 lines
11 KiB
TypeScript
372 lines
11 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { render, screen, waitFor, act } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { MemoryRouter } from 'react-router-dom';
|
|
import { VerifyEmailPage } from './VerifyEmailPage';
|
|
import { verifyEmail, resendVerificationEmail } from '../services/authService';
|
|
|
|
// Mock dependencies
|
|
vi.mock('../services/authService', () => ({
|
|
verifyEmail: vi.fn(),
|
|
resendVerificationEmail: vi.fn(),
|
|
}));
|
|
|
|
const mockNavigate = vi.fn();
|
|
vi.mock('react-router-dom', async () => {
|
|
const actual = await vi.importActual('react-router-dom');
|
|
return {
|
|
...actual,
|
|
useNavigate: () => mockNavigate,
|
|
};
|
|
});
|
|
|
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter>{children}</MemoryRouter>
|
|
);
|
|
|
|
describe('VerifyEmailPage', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockNavigate.mockClear();
|
|
localStorage.clear();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllTimers();
|
|
});
|
|
|
|
it('should render verifying state when token is present', async () => {
|
|
vi.mocked(verifyEmail).mockResolvedValue(undefined);
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=test-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Should show verifying state initially
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Vérification de l'email")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('should display error message when token is missing', () => {
|
|
render(<VerifyEmailPage />, { wrapper });
|
|
|
|
expect(screen.getByText("Vérification de l'email")).toBeInTheDocument();
|
|
expect(
|
|
screen.getByText('Lien de vérification invalide ou manquant'),
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it('should extract token from URL and verify email', async () => {
|
|
vi.mocked(verifyEmail).mockResolvedValue(undefined);
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=abc123']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for verification to complete
|
|
await waitFor(
|
|
() => {
|
|
expect(verifyEmail).toHaveBeenCalledWith('abc123');
|
|
},
|
|
{ timeout: 3000 },
|
|
);
|
|
});
|
|
|
|
it('should display success message after successful verification', async () => {
|
|
vi.mocked(verifyEmail).mockResolvedValue(undefined);
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=test-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for verifyEmail to be called
|
|
await waitFor(
|
|
() => {
|
|
expect(verifyEmail).toHaveBeenCalled();
|
|
},
|
|
{ timeout: 2000 },
|
|
);
|
|
|
|
// Wait for success message
|
|
await waitFor(
|
|
() => {
|
|
expect(screen.getByText('Email vérifié')).toBeInTheDocument();
|
|
},
|
|
{ timeout: 2000 },
|
|
);
|
|
});
|
|
|
|
it('should display error message when verification fails', async () => {
|
|
const error = { message: 'Token invalide ou expiré' };
|
|
vi.mocked(verifyEmail).mockRejectedValue(error);
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=invalid-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for error message
|
|
await waitFor(
|
|
() => {
|
|
expect(
|
|
screen.getByText('Token invalide ou expiré'),
|
|
).toBeInTheDocument();
|
|
},
|
|
{ timeout: 3000 },
|
|
);
|
|
});
|
|
|
|
it('should redirect to login after successful verification', async () => {
|
|
vi.useFakeTimers();
|
|
vi.mocked(verifyEmail).mockResolvedValue(undefined);
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=test-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for success
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Email vérifié')).toBeInTheDocument();
|
|
});
|
|
|
|
// Fast-forward the timer (3 seconds)
|
|
await act(async () => {
|
|
vi.advanceTimersByTime(3000);
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(mockNavigate).toHaveBeenCalledWith('/login', { replace: true });
|
|
});
|
|
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
it('should display retry button when verification fails', async () => {
|
|
const error = { message: 'Verification failed' };
|
|
vi.mocked(verifyEmail).mockRejectedValue(error);
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=test-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for error state
|
|
await waitFor(
|
|
() => {
|
|
expect(screen.getByText('Réessayer')).toBeInTheDocument();
|
|
},
|
|
{ timeout: 3000 },
|
|
);
|
|
});
|
|
|
|
it('should call verifyEmail when retry button is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
const error = { message: 'Verification failed' };
|
|
vi.mocked(verifyEmail)
|
|
.mockRejectedValueOnce(error)
|
|
.mockResolvedValueOnce(undefined);
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=test-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for error state
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Réessayer')).toBeInTheDocument();
|
|
});
|
|
|
|
// Click retry button
|
|
const retryButton = screen.getByText('Réessayer');
|
|
await act(async () => {
|
|
await user.click(retryButton);
|
|
});
|
|
|
|
// Should call verifyEmail again
|
|
await waitFor(() => {
|
|
expect(verifyEmail).toHaveBeenCalledTimes(2);
|
|
});
|
|
});
|
|
|
|
it('should display resend email button', async () => {
|
|
const error = { message: 'Verification failed' };
|
|
vi.mocked(verifyEmail).mockRejectedValue(error);
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=test-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for error state
|
|
await waitFor(
|
|
() => {
|
|
expect(
|
|
screen.getByText(/Renvoyer l'email de vérification/),
|
|
).toBeInTheDocument();
|
|
},
|
|
{ timeout: 3000 },
|
|
);
|
|
});
|
|
|
|
it('should call resendVerificationEmail when resend button is clicked', async () => {
|
|
const user = userEvent.setup();
|
|
const error = { message: 'Verification failed' };
|
|
vi.mocked(verifyEmail).mockRejectedValue(error);
|
|
vi.mocked(resendVerificationEmail).mockResolvedValue(undefined);
|
|
|
|
localStorage.setItem('pendingVerificationEmail', 'test@example.com');
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=test-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for error state
|
|
await waitFor(
|
|
() => {
|
|
expect(
|
|
screen.getByText(/Renvoyer l'email de vérification/),
|
|
).toBeInTheDocument();
|
|
},
|
|
{ timeout: 3000 },
|
|
);
|
|
|
|
// Click resend button
|
|
const resendButton = screen.getByText(/Renvoyer l'email de vérification/);
|
|
await act(async () => {
|
|
await user.click(resendButton);
|
|
});
|
|
|
|
// Should call resendVerificationEmail
|
|
await waitFor(
|
|
() => {
|
|
expect(resendVerificationEmail).toHaveBeenCalledWith(
|
|
'test@example.com',
|
|
);
|
|
},
|
|
{ timeout: 3000 },
|
|
);
|
|
});
|
|
|
|
it('should set cooldown after resending email', async () => {
|
|
const user = userEvent.setup();
|
|
const error = { message: 'Verification failed' };
|
|
vi.mocked(verifyEmail).mockRejectedValue(error);
|
|
vi.mocked(resendVerificationEmail).mockResolvedValue(undefined);
|
|
|
|
localStorage.setItem('pendingVerificationEmail', 'test@example.com');
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=test-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for error state
|
|
await waitFor(
|
|
() => {
|
|
expect(
|
|
screen.getByText(/Renvoyer l'email de vérification/),
|
|
).toBeInTheDocument();
|
|
},
|
|
{ timeout: 2000 },
|
|
);
|
|
|
|
// Click resend button
|
|
const resendButton = screen.getByText(/Renvoyer l'email de vérification/);
|
|
await act(async () => {
|
|
await user.click(resendButton);
|
|
});
|
|
|
|
// Verify that resendVerificationEmail was called
|
|
await waitFor(
|
|
() => {
|
|
expect(resendVerificationEmail).toHaveBeenCalledWith(
|
|
'test@example.com',
|
|
);
|
|
},
|
|
{ timeout: 2000 },
|
|
);
|
|
});
|
|
|
|
it('should handle resend when email is not found', async () => {
|
|
const user = userEvent.setup();
|
|
const error = { message: 'Verification failed' };
|
|
vi.mocked(verifyEmail).mockRejectedValue(error);
|
|
|
|
// No email in localStorage
|
|
localStorage.clear();
|
|
|
|
const wrapperWithToken = ({ children }: { children: React.ReactNode }) => (
|
|
<MemoryRouter initialEntries={['/verify-email?token=test-token']}>
|
|
{children}
|
|
</MemoryRouter>
|
|
);
|
|
|
|
render(<VerifyEmailPage />, { wrapper: wrapperWithToken });
|
|
|
|
// Wait for error state
|
|
await waitFor(
|
|
() => {
|
|
expect(
|
|
screen.getByText(/Renvoyer l'email de vérification/),
|
|
).toBeInTheDocument();
|
|
},
|
|
{ timeout: 2000 },
|
|
);
|
|
|
|
// Click resend button
|
|
const resendButton = screen.getByText(/Renvoyer l'email de vérification/);
|
|
await act(async () => {
|
|
await user.click(resendButton);
|
|
});
|
|
|
|
// The component should handle the missing email gracefully
|
|
// resendVerificationEmail should NOT be called since email is missing
|
|
// Wait a bit for async operations
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
expect(resendVerificationEmail).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should display footer links', () => {
|
|
render(<VerifyEmailPage />, { wrapper });
|
|
|
|
const loginLink = screen.getByText('Retour à la connexion');
|
|
expect(loginLink).toBeInTheDocument();
|
|
expect(loginLink.closest('a')).toHaveAttribute('href', '/login');
|
|
});
|
|
});
|