Files
fast-next-template/frontend/tests/components/auth/OAuthButtons.test.tsx
Felipe Cardoso aeed9dfdbc Add unit tests for OAuthButtons and LinkedAccountsSettings components
- Introduced comprehensive test coverage for `OAuthButtons` and `LinkedAccountsSettings`, including loading states, button behaviors, error handling, and custom class support.
- Implemented `LinkedAccountsPage` tests for rendering and component integration.
- Adjusted E2E coverage exclusions in various components, focusing on UI-heavy and animation-based flows best suited for E2E tests.
- Refined Jest coverage thresholds to align with improved unit test additions.
2025-11-25 08:52:11 +01:00

217 lines
5.9 KiB
TypeScript

/**
* Tests for OAuthButtons Component
*/
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { OAuthButtons } from '@/components/auth/OAuthButtons';
import { useOAuthProviders, useOAuthStart } from '@/lib/api/hooks/useOAuth';
// Mock the OAuth hooks
jest.mock('@/lib/api/hooks/useOAuth', () => ({
useOAuthProviders: jest.fn(),
useOAuthStart: jest.fn(),
}));
// Mock next-intl
jest.mock('next-intl', () => ({
useTranslations: () => (key: string, params?: { provider?: string }) => {
const translations: Record<string, string> = {
loading: 'Loading...',
divider: 'or continue with',
continueWith: `Continue with ${params?.provider || ''}`,
signUpWith: `Sign up with ${params?.provider || ''}`,
};
return translations[key] || key;
},
}));
// Mock config - must be complete to avoid undefined access
jest.mock('@/config/app.config', () => ({
__esModule: true,
default: {
oauth: {
enabled: true,
providers: {
google: { name: 'Google', enabled: true },
github: { name: 'GitHub', enabled: true },
},
callbackPath: '/auth/callback',
},
routes: {
dashboard: '/dashboard',
login: '/login',
profile: '/settings/profile',
},
app: {
url: 'http://localhost:3000',
},
},
}));
describe('OAuthButtons', () => {
const mockProviders = {
enabled: true,
providers: [
{ provider: 'google', name: 'Google' },
{ provider: 'github', name: 'GitHub' },
],
};
const mockOAuthStart = {
mutateAsync: jest.fn(),
isPending: false,
};
beforeEach(() => {
jest.clearAllMocks();
(useOAuthProviders as jest.Mock).mockReturnValue({
data: mockProviders,
isLoading: false,
error: null,
});
(useOAuthStart as jest.Mock).mockReturnValue(mockOAuthStart);
});
it('renders nothing when OAuth is disabled', () => {
(useOAuthProviders as jest.Mock).mockReturnValue({
data: { enabled: false, providers: [] },
isLoading: false,
error: null,
});
const { container } = render(<OAuthButtons mode="login" />);
expect(container).toBeEmptyDOMElement();
});
it('renders nothing when no providers available', () => {
(useOAuthProviders as jest.Mock).mockReturnValue({
data: { enabled: true, providers: [] },
isLoading: false,
error: null,
});
const { container } = render(<OAuthButtons mode="login" />);
expect(container).toBeEmptyDOMElement();
});
it('renders nothing when there is an error', () => {
(useOAuthProviders as jest.Mock).mockReturnValue({
data: mockProviders,
isLoading: false,
error: new Error('Failed to fetch'),
});
const { container } = render(<OAuthButtons mode="login" />);
expect(container).toBeEmptyDOMElement();
});
it('shows loading state', () => {
(useOAuthProviders as jest.Mock).mockReturnValue({
data: null,
isLoading: true,
error: null,
});
render(<OAuthButtons mode="login" />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
expect(screen.getByRole('button')).toBeDisabled();
});
it('renders provider buttons in login mode', () => {
render(<OAuthButtons mode="login" />);
expect(screen.getByText('Continue with Google')).toBeInTheDocument();
expect(screen.getByText('Continue with GitHub')).toBeInTheDocument();
});
it('renders provider buttons in register mode', () => {
render(<OAuthButtons mode="register" />);
expect(screen.getByText('Sign up with Google')).toBeInTheDocument();
expect(screen.getByText('Sign up with GitHub')).toBeInTheDocument();
});
it('renders divider when showDivider is true (default)', () => {
render(<OAuthButtons mode="login" />);
expect(screen.getByText('or continue with')).toBeInTheDocument();
});
it('does not render divider when showDivider is false', () => {
render(<OAuthButtons mode="login" showDivider={false} />);
expect(screen.queryByText('or continue with')).not.toBeInTheDocument();
});
it('calls OAuth start when clicking provider button', async () => {
render(<OAuthButtons mode="login" />);
const googleButton = screen.getByText('Continue with Google');
fireEvent.click(googleButton);
await waitFor(() => {
expect(mockOAuthStart.mutateAsync).toHaveBeenCalledWith({
provider: 'google',
mode: 'login',
});
});
});
it('calls onStart callback when OAuth flow starts', async () => {
const onStart = jest.fn();
render(<OAuthButtons mode="login" onStart={onStart} />);
const googleButton = screen.getByText('Continue with Google');
fireEvent.click(googleButton);
await waitFor(() => {
expect(onStart).toHaveBeenCalledWith('google');
});
});
it('calls onError callback when OAuth start fails', async () => {
const error = new Error('OAuth failed');
mockOAuthStart.mutateAsync.mockRejectedValue(error);
const onError = jest.fn();
render(<OAuthButtons mode="login" onError={onError} />);
const googleButton = screen.getByText('Continue with Google');
fireEvent.click(googleButton);
await waitFor(() => {
expect(onError).toHaveBeenCalledWith(error);
});
});
it('disables buttons while OAuth is pending', () => {
(useOAuthStart as jest.Mock).mockReturnValue({
...mockOAuthStart,
isPending: true,
});
render(<OAuthButtons mode="login" />);
const buttons = screen.getAllByRole('button');
buttons.forEach((button) => {
expect(button).toBeDisabled();
});
});
it('applies custom className', () => {
render(<OAuthButtons mode="login" className="custom-class" />);
const container = document.querySelector('.custom-class');
expect(container).toBeInTheDocument();
});
it('renders provider icons', () => {
render(<OAuthButtons mode="login" />);
// Icons are SVG elements
const svgs = document.querySelectorAll('svg');
expect(svgs.length).toBeGreaterThan(0);
});
});