Skip to content

Commit 1bf6659

Browse files
committed
frontend: add tests for login component
1 parent 130bfa1 commit 1bf6659

File tree

1 file changed

+66
-17
lines changed

1 file changed

+66
-17
lines changed

frontend/src/components/__tests__/login.test.tsx

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { render, fireEvent, waitFor, screen } from '@testing-library/preact';
22
import { describe, it, expect, vi, beforeEach } from 'vitest';
3+
import { h } from 'preact';
34

45
// Mock utils before importing component to avoid side effects
56
vi.mock('../../utils', () => ({
@@ -14,8 +15,8 @@ vi.mock('../../utils', () => ({
1415

1516
// Subpath mock for Form used as default import replicating global test-setup structure
1617
vi.mock('react-bootstrap/Form', () => {
17-
const { h } = require('preact');
18-
const Form = ({ children, onSubmit }: { children?: unknown; onSubmit?: (e: Event) => void }) => h('form', { onSubmit }, children);
18+
const Form = ({ children, onSubmit }: { children?: any; onSubmit?: (e: Event) => void }) =>
19+
h('form', { onSubmit }, children as any);
1920
Form.Group = ({ children, controlId }: { children?: any; controlId?: string }) => {
2021
if (children && Array.isArray(children)) {
2122
children = children.map((child) => {
@@ -24,11 +25,31 @@ vi.mock('react-bootstrap/Form', () => {
2425
return child;
2526
});
2627
}
27-
return h('div', {}, children);
28+
return h('div', {}, children as any);
2829
};
29-
Form.Label = ({ children, controlId }: { children?: unknown; controlId?: string }) => h('label', { htmlFor: controlId }, children);
30-
Form.Control = ({ type, value, onChange, isInvalid, controlId }: { type: string; value?: string; onChange?: (e: Event) => void; isInvalid?: boolean; controlId?: string }) =>
31-
h('input', { id: controlId, type, value, onChange, 'data-testid': `${type}-input`, className: isInvalid ? 'invalid' : '' });
30+
Form.Label = ({ children, controlId }: { children?: any; controlId?: string }) =>
31+
h('label', { htmlFor: controlId }, children as any);
32+
Form.Control = ({
33+
type,
34+
value,
35+
onChange,
36+
isInvalid,
37+
controlId,
38+
}: {
39+
type: string;
40+
value?: string;
41+
onChange?: (e: Event) => void;
42+
isInvalid?: boolean;
43+
controlId?: string;
44+
}) =>
45+
h('input', {
46+
id: controlId,
47+
type,
48+
value,
49+
onChange,
50+
'data-testid': `${type}-input`,
51+
className: isInvalid ? 'invalid' : '',
52+
} as any);
3253
return { default: Form };
3354
});
3455

@@ -52,20 +73,22 @@ describe('Login Component', () => {
5273
let mockUtils: any;
5374
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5475
let mockAlert: any;
55-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
56-
let mockBase64: any;
5776

5877
beforeEach(async () => {
5978
vi.clearAllMocks();
6079
mockUtils = await import('../../utils');
6180
mockAlert = await import('../Alert');
62-
mockBase64 = await import('js-base64');
81+
await import('js-base64');
6382

6483
// default happy path mocks
6584
mockUtils.get_salt_for_user.mockResolvedValue(new Uint8Array([1, 2, 3]));
6685
mockUtils.generate_hash.mockResolvedValue(new Uint8Array([9, 10, 11]));
6786
mockUtils.fetchClient.POST.mockResolvedValue({ response: { status: 200 }, error: null });
68-
mockUtils.fetchClient.GET.mockResolvedValue({ data: { secret_salt: [5, 6, 7] }, response: { status: 200 }, error: null });
87+
mockUtils.fetchClient.GET.mockResolvedValue({
88+
data: { secret_salt: [5, 6, 7] },
89+
response: { status: 200 },
90+
error: null,
91+
});
6992
});
7093

7194
function fillAndSubmit(email = '[email protected]', password = 'ValidPass123!') {
@@ -99,7 +122,12 @@ describe('Login Component', () => {
99122
fillAndSubmit();
100123

101124
await waitFor(() => {
102-
expect(mockAlert.showAlert).toHaveBeenCalledWith('login.verify_before_login', 'danger', 'login', 'login.verify_before_login_heading');
125+
expect(mockAlert.showAlert).toHaveBeenCalledWith(
126+
'login.verify_before_login',
127+
'danger',
128+
'login',
129+
'login.verify_before_login_heading'
130+
);
103131
expect(mockUtils.fetchClient.GET).not.toHaveBeenCalled();
104132
});
105133
});
@@ -114,11 +142,18 @@ describe('Login Component', () => {
114142
});
115143

116144
it('alerts when secret retrieval fails (non-200)', async () => {
117-
mockUtils.fetchClient.GET.mockResolvedValue({ data: null, response: { status: 500 }, error: 'boom' });
145+
mockUtils.fetchClient.GET.mockResolvedValue({
146+
data: null,
147+
response: { status: 500 },
148+
error: 'boom',
149+
});
118150
fillAndSubmit();
119151

120152
await waitFor(() => {
121-
expect(mockAlert.showAlert).toHaveBeenCalledWith('Failed with status 500: boom', 'danger');
153+
expect(mockAlert.showAlert).toHaveBeenCalledWith(
154+
'Failed with status 500: boom',
155+
'danger'
156+
);
122157
});
123158
});
124159

@@ -140,14 +175,22 @@ describe('Login Component', () => {
140175
expect(screen.getByTestId('modal')).toBeTruthy();
141176

142177
mockUtils.fetchClient.GET.mockResolvedValueOnce({ response: { status: 200 } });
143-
const emailInput = screen.getAllByRole('textbox', { name: 'email' }).find(i => (i as HTMLInputElement).id === 'startRecoveryEmail')!;
178+
const emailInput = screen
179+
.getAllByRole('textbox', { name: 'email' })
180+
.find((i) => (i as HTMLInputElement).id === 'startRecoveryEmail');
181+
if (!emailInput) throw new Error('Recovery email input not found');
144182
fireEvent.change(emailInput, { target: { value: '[email protected]' } });
145183

146184
const sendBtn = screen.getByRole('button', { name: 'send' });
147185
fireEvent.click(sendBtn);
148186

149187
await waitFor(() => {
150-
expect(mockAlert.showAlert).toHaveBeenCalledWith('success_alert_text', 'success', 'login', 'success_alert_heading');
188+
expect(mockAlert.showAlert).toHaveBeenCalledWith(
189+
'success_alert_text',
190+
'success',
191+
'login',
192+
'success_alert_heading'
193+
);
151194
});
152195
});
153196

@@ -156,8 +199,14 @@ describe('Login Component', () => {
156199
fireEvent.click(screen.getByText('password_recovery'));
157200
expect(screen.getByTestId('modal')).toBeTruthy();
158201

159-
mockUtils.fetchClient.GET.mockResolvedValueOnce({ response: { status: 500 }, error: 'fail' });
160-
const emailInput = screen.getAllByRole('textbox', { name: 'email' }).find(i => (i as HTMLInputElement).id === 'startRecoveryEmail')!;
202+
mockUtils.fetchClient.GET.mockResolvedValueOnce({
203+
response: { status: 500 },
204+
error: 'fail',
205+
});
206+
const emailInput = screen
207+
.getAllByRole('textbox', { name: 'email' })
208+
.find((i) => (i as HTMLInputElement).id === 'startRecoveryEmail');
209+
if (!emailInput) throw new Error('Recovery email input not found');
161210
fireEvent.change(emailInput, { target: { value: '[email protected]' } });
162211

163212
const sendBtn = screen.getByRole('button', { name: 'send' });

0 commit comments

Comments
 (0)