-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathLoginModal.test.tsx
More file actions
121 lines (96 loc) · 3.91 KB
/
LoginModal.test.tsx
File metadata and controls
121 lines (96 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import * as MUI from '@mui/material';
import { act, fireEvent, render, screen } from '@testing-library/react';
import type { Mock } from 'vitest';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import LoginModal from './LoginModal';
const { setPromptLoginMock } = vi.hoisted(() => ({
setPromptLoginMock: vi.fn(),
}));
const { handleLoginMock } = vi.hoisted(() => ({
handleLoginMock: vi.fn(),
}));
// i18n: return key as label
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, def?: string) => def ?? key,
}),
}));
// Global modal context
vi.mock('@/context/GlobalModalContext', () => ({
useGlobalModal: () => ({ setPromptLogin: setPromptLoginMock }),
}));
// Auth service
vi.mock('@/services/authService', () => ({
handleLogin: handleLoginMock,
}));
vi.mock('@mui/material', async (importOriginal) => {
const original = await importOriginal<typeof import('@mui/material')>();
return {
...original,
Modal: vi.fn(({ children, ...props }: any) => (
<div data-testid="login-modal-mock" {...props}>
{children}
</div>
)),
};
});
const getLastModalProps = () => {
const calls = (MUI.Modal as unknown as Mock).mock.calls;
return calls[calls.length - 1]?.[0] ?? {};
};
beforeEach(() => {
(MUI.Modal as unknown as Mock).mockClear();
setPromptLoginMock.mockClear();
handleLoginMock.mockClear();
});
describe('LoginModal', () => {
it('renders title, description and login button (i18n keys as labels)', () => {
render(<LoginModal />);
expect(screen.getByText('login_modal.continue')).toBeInTheDocument();
expect(screen.getByText('login_modal.description')).toBeInTheDocument();
const loginBtn = screen.getByRole('button', { name: 'login_modal.button' });
expect(loginBtn).toBeInTheDocument();
});
it('wires Modal props correctly and calls setPromptLogin(false) on Modal onClose', async () => {
render(<LoginModal />);
const props = getLastModalProps();
expect(props.open).toBe(true);
expect(props['aria-labelledby']).toBe('login-modal-title');
await act(async () => {
props.onClose?.({} as any);
});
expect(setPromptLoginMock).toHaveBeenCalledWith(false);
});
it('close IconButton triggers setPromptLogin(false)', () => {
render(<LoginModal />);
// Find the CloseIcon then climb to its button
const closeIcon = screen.getByTestId('CloseIcon');
const closeButton = closeIcon.closest('button') as HTMLButtonElement | null;
expect(closeButton).not.toBeNull();
closeButton && fireEvent.click(closeButton);
expect(setPromptLoginMock).toHaveBeenCalledWith(false);
});
it('GitHub login button calls handleLogin()', () => {
render(<LoginModal />);
const loginBtn = screen.getByRole('button', { name: 'login_modal.button' });
fireEvent.click(loginBtn);
expect(handleLoginMock).toHaveBeenCalledTimes(1);
});
it('renders ToS and Privacy links with correct attributes', () => {
render(<LoginModal />);
const termsLink = screen.getByRole('link', { name: 'login_modal.footer.terms_of_service' });
expect(termsLink).toHaveAttribute('href', expect.stringContaining('/terms-of-service'));
expect(termsLink).toHaveAttribute('target', '_blank');
expect(termsLink).toHaveAttribute('rel', 'noopener');
const privacyLink = screen.getByRole('link', { name: 'login_modal.footer.privacy_policy' });
expect(privacyLink).toHaveAttribute('href', expect.stringContaining('/privacy-policy'));
expect(privacyLink).toHaveAttribute('target', '_blank');
expect(privacyLink).toHaveAttribute('rel', 'noopener');
});
it('shows footnote i18n keys (placeholders)', () => {
render(<LoginModal />);
// Use flexible matcher to handle whitespace/line breaks
expect(screen.getByText((txt) => txt.includes('login_modal.footnote.1'))).toBeInTheDocument();
expect(screen.getByText((txt) => txt.includes('login_modal.footnote.2'))).toBeInTheDocument();
});
});