Skip to content

Commit d7eb524

Browse files
Merge pull request #220 from vitruv-tools/test-for-components
Enhance OtpVerificationPage tests with additional scenarios
2 parents 5876286 + ebf0454 commit d7eb524

29 files changed

+1611
-0
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
5+
const mockAuthPage = jest.fn(() => <div>AuthPage mock</div>);
6+
7+
jest.mock('../../../components/auth/AuthPage', () => ({
8+
__esModule: true,
9+
AuthPage: (props: any) => mockAuthPage(props),
10+
}));
11+
12+
import { AuthPage } from '../../../components/auth/AuthPage';
13+
14+
describe('AuthPage', () => {
15+
it('renders AuthPage with default props', async () => {
16+
render(<AuthPage />);
17+
18+
expect(mockAuthPage).toHaveBeenCalled();
19+
});
20+
});
21+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { KeycloakRedirect } from '../../../components/auth/KeycloakRedirect';
4+
5+
describe('KeycloakRedirect', () => {
6+
it('renders redirect message', () => {
7+
render(<KeycloakRedirect />);
8+
9+
expect(
10+
screen.getByText(/Redirecting to Keycloak authentication/i),
11+
).toBeInTheDocument();
12+
expect(
13+
screen.getByText(/If you are not redirected automatically/i),
14+
).toBeInTheDocument();
15+
});
16+
});
17+
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import React from 'react';
2+
import { render, screen, waitFor } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import { SignIn } from '../../../components/auth/SignIn';
5+
6+
jest.mock('../../../contexts/AuthContext', () => ({
7+
useAuth: () => ({
8+
signIn: mockSignIn,
9+
}),
10+
}));
11+
12+
jest.mock('../../../services/api', () => ({
13+
apiService: {
14+
forgotPassword: jest.fn(),
15+
},
16+
}));
17+
18+
const mockSignIn = jest.fn();
19+
const mockOnSignInSuccess = jest.fn();
20+
const mockOnSwitchToSignUp = jest.fn();
21+
22+
describe('SignIn component', () => {
23+
beforeEach(() => {
24+
jest.clearAllMocks();
25+
});
26+
27+
it('renders username, password fields and sign in button', () => {
28+
render(
29+
<SignIn
30+
onSignInSuccess={mockOnSignInSuccess}
31+
onSwitchToSignUp={mockOnSwitchToSignUp}
32+
/>,
33+
);
34+
35+
expect(screen.getByLabelText(/Username or Email/i)).toBeInTheDocument();
36+
expect(screen.getByLabelText(/Password/i)).toBeInTheDocument();
37+
expect(screen.getByRole('button', { name: /Sign In/i })).toBeInTheDocument();
38+
});
39+
40+
it('shows validation error when submitting with empty fields', async () => {
41+
render(
42+
<SignIn
43+
onSignInSuccess={mockOnSignInSuccess}
44+
onSwitchToSignUp={mockOnSwitchToSignUp}
45+
/>,
46+
);
47+
48+
await userEvent.click(screen.getByRole('button', { name: /Sign In/i }));
49+
50+
expect(
51+
await screen.findByText(/Please fill in all fields/i),
52+
).toBeInTheDocument();
53+
expect(mockSignIn).not.toHaveBeenCalled();
54+
});
55+
56+
it('calls signIn and onSignInSuccess on successful submit', async () => {
57+
const fakeUser = { id: '1', emailVerified: true };
58+
mockSignIn.mockResolvedValueOnce(fakeUser);
59+
60+
render(
61+
<SignIn
62+
onSignInSuccess={mockOnSignInSuccess}
63+
onSwitchToSignUp={mockOnSwitchToSignUp}
64+
/>,
65+
);
66+
67+
await userEvent.type(
68+
screen.getByLabelText(/Username or Email/i),
69+
'john@example.com',
70+
);
71+
await userEvent.type(screen.getByLabelText(/Password/i), 'password123!');
72+
await userEvent.click(screen.getByRole('button', { name: /Sign In/i }));
73+
74+
await waitFor(() => {
75+
expect(mockSignIn).toHaveBeenCalledWith(
76+
'john@example.com',
77+
'password123!',
78+
);
79+
});
80+
expect(mockOnSignInSuccess).toHaveBeenCalledWith(fakeUser);
81+
});
82+
83+
it('shows error when signIn throws', async () => {
84+
mockSignIn.mockRejectedValueOnce(new Error('Invalid credentials'));
85+
86+
render(
87+
<SignIn
88+
onSignInSuccess={mockOnSignInSuccess}
89+
onSwitchToSignUp={mockOnSwitchToSignUp}
90+
/>,
91+
);
92+
93+
await userEvent.type(
94+
screen.getByLabelText(/Username or Email/i),
95+
'john@example.com',
96+
);
97+
await userEvent.type(screen.getByLabelText(/Password/i), 'wrong');
98+
await userEvent.click(screen.getByRole('button', { name: /Sign In/i }));
99+
100+
expect(
101+
await screen.findByText(/Invalid credentials/i),
102+
).toBeInTheDocument();
103+
});
104+
105+
it('opens forgot password modal and validates email', async () => {
106+
const { apiService } = jest.requireMock('../../../services/api') as {
107+
apiService: { forgotPassword: jest.Mock };
108+
};
109+
110+
render(
111+
<SignIn
112+
onSignInSuccess={mockOnSignInSuccess}
113+
onSwitchToSignUp={mockOnSwitchToSignUp}
114+
/>,
115+
);
116+
117+
await userEvent.click(
118+
screen.getByRole('button', { name: /Forgot your password\?/i }),
119+
);
120+
121+
const emailInput = await screen.findByLabelText(/Registered Email Address/i);
122+
const submitButton = screen.getByRole('button', {
123+
name: /Submit Request/i,
124+
});
125+
126+
// Invalid email
127+
await userEvent.type(emailInput, 'not-an-email');
128+
await userEvent.click(submitButton);
129+
expect(
130+
await screen.findByText(/Please enter a valid email address/i),
131+
).toBeInTheDocument();
132+
133+
// Valid email -> API is called
134+
apiService.forgotPassword.mockResolvedValueOnce({
135+
message: 'Reset email sent',
136+
data: {},
137+
});
138+
139+
await userEvent.clear(emailInput);
140+
await userEvent.type(emailInput, 'user@example.com');
141+
await userEvent.click(submitButton);
142+
143+
await waitFor(() => {
144+
expect(apiService.forgotPassword).toHaveBeenCalledWith('user@example.com');
145+
});
146+
});
147+
});
148+
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import React from 'react';
2+
import { render, screen, waitFor, act } from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import { SignUp } from '../../../components/auth/SignUp';
5+
6+
const mockSignUp = jest.fn();
7+
8+
jest.mock('../../../contexts/AuthContext', () => ({
9+
useAuth: () => ({
10+
signUp: mockSignUp,
11+
}),
12+
}));
13+
14+
describe('SignUp component', () => {
15+
beforeEach(() => {
16+
jest.clearAllMocks();
17+
jest.useRealTimers();
18+
});
19+
20+
it('renders all required fields and submit button', () => {
21+
render(
22+
<SignUp
23+
onSignUpSuccess={jest.fn()}
24+
onSwitchToSignIn={jest.fn()}
25+
/>,
26+
);
27+
28+
expect(screen.getByLabelText(/First Name \*/i)).toBeInTheDocument();
29+
expect(screen.getByLabelText(/Last Name \*/i)).toBeInTheDocument();
30+
expect(screen.getByLabelText(/Username \*/i)).toBeInTheDocument();
31+
expect(screen.getByLabelText(/Email \*/i)).toBeInTheDocument();
32+
expect(
33+
screen.getByPlaceholderText(/Create a strong password/i),
34+
).toBeInTheDocument();
35+
expect(
36+
screen.getByPlaceholderText(/Confirm your password/i),
37+
).toBeInTheDocument();
38+
expect(
39+
screen.getByRole('button', { name: /Create Account/i }),
40+
).toBeInTheDocument();
41+
});
42+
43+
it('shows validation error for too short first name', async () => {
44+
render(
45+
<SignUp
46+
onSignUpSuccess={jest.fn()}
47+
onSwitchToSignIn={jest.fn()}
48+
/>,
49+
);
50+
51+
await userEvent.type(screen.getByLabelText(/First Name \*/i), 'A');
52+
await userEvent.type(screen.getByLabelText(/Last Name \*/i), 'Doe');
53+
await userEvent.type(screen.getByLabelText(/Username \*/i), 'john');
54+
await userEvent.type(screen.getByLabelText(/Email \*/i), 'john@example.com');
55+
await userEvent.type(
56+
screen.getByPlaceholderText(/Create a strong password/i),
57+
'Password1!',
58+
);
59+
await userEvent.type(
60+
screen.getByPlaceholderText(/Confirm your password/i),
61+
'Password1!',
62+
);
63+
64+
await userEvent.click(
65+
screen.getByRole('button', { name: /Create Account/i }),
66+
);
67+
68+
expect(
69+
await screen.findByText(/First name is required \(at least 2 characters\)/i),
70+
).toBeInTheDocument();
71+
expect(mockSignUp).not.toHaveBeenCalled();
72+
});
73+
74+
it('calls signUp and onSignUpSuccess on valid submit', async () => {
75+
jest.useFakeTimers();
76+
const onSignUpSuccess = jest.fn();
77+
mockSignUp.mockResolvedValueOnce(undefined);
78+
79+
render(
80+
<SignUp
81+
onSignUpSuccess={onSignUpSuccess}
82+
onSwitchToSignIn={jest.fn()}
83+
/>,
84+
);
85+
86+
await userEvent.type(screen.getByLabelText(/First Name \*/i), 'John');
87+
await userEvent.type(screen.getByLabelText(/Last Name \*/i), 'Doe');
88+
await userEvent.type(screen.getByLabelText(/Username \*/i), 'johndoe');
89+
await userEvent.type(screen.getByLabelText(/Email \*/i), 'john@example.com');
90+
await userEvent.type(
91+
screen.getByPlaceholderText(/Create a strong password/i),
92+
'Password1!',
93+
);
94+
await userEvent.type(
95+
screen.getByPlaceholderText(/Confirm your password/i),
96+
'Password1!',
97+
);
98+
99+
await userEvent.click(
100+
screen.getByRole('button', { name: /Create Account/i }),
101+
);
102+
103+
await waitFor(() => {
104+
expect(mockSignUp).toHaveBeenCalled();
105+
});
106+
107+
expect(
108+
await screen.findByText(/Account Created Successfully!/i),
109+
).toBeInTheDocument();
110+
111+
await act(async () => {
112+
jest.advanceTimersByTime(2000);
113+
});
114+
115+
expect(onSignUpSuccess).toHaveBeenCalled();
116+
});
117+
});
118+

0 commit comments

Comments
 (0)