eionet2-dashboard 3.2.1 → 3.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/Jenkinsfile +2 -5
  3. package/api/getGraphData/index.js +3 -4
  4. package/package.json +2 -2
  5. package/tabs/package-lock.json +2927 -1866
  6. package/tabs/package.json +3 -3
  7. package/tabs/public/auth-start.html +2 -2
  8. package/tabs/src/components/App.test.jsx +67 -0
  9. package/tabs/src/components/BottomMenu.test.jsx +94 -0
  10. package/tabs/src/components/CustomColumnResizeIcon.jsx +1 -1
  11. package/tabs/src/components/CustomColumnResizeIcon.test.jsx +17 -0
  12. package/tabs/src/components/CustomDrawer.test.jsx +14 -0
  13. package/tabs/src/components/EventDialogTitle.test.jsx +25 -0
  14. package/tabs/src/components/HtmlBox.test.jsx +41 -0
  15. package/tabs/src/components/Privacy.test.jsx +11 -0
  16. package/tabs/src/components/ResizableGrid.test.jsx +64 -0
  17. package/tabs/src/components/Tab.test.jsx +463 -0
  18. package/tabs/src/components/TabConfig.test.jsx +27 -0
  19. package/tabs/src/components/TabPanel.test.jsx +31 -0
  20. package/tabs/src/components/TermsOfUse.test.jsx +11 -0
  21. package/tabs/src/components/UnderConstruction.test.jsx +13 -0
  22. package/tabs/src/components/UserMenu.test.jsx +53 -0
  23. package/tabs/src/components/activity/Activity.test.jsx +218 -0
  24. package/tabs/src/components/activity/ConsultationList.test.jsx +114 -0
  25. package/tabs/src/components/activity/EventList.test.jsx +164 -0
  26. package/tabs/src/components/activity/GroupsTags.test.jsx +23 -0
  27. package/tabs/src/components/activity/ObligationList.test.jsx +46 -0
  28. package/tabs/src/components/activity/PublicationList.test.jsx +46 -0
  29. package/tabs/src/components/activity/Reporting.test.jsx +11 -0
  30. package/tabs/src/components/event_rating/EventRating.test.jsx +63 -0
  31. package/tabs/src/components/event_rating/EventRatingDialog.test.jsx +28 -0
  32. package/tabs/src/components/event_registration/Approval.test.jsx +25 -0
  33. package/tabs/src/components/event_registration/ApprovalDialog.test.jsx +25 -0
  34. package/tabs/src/components/event_registration/ApprovalList.test.jsx +36 -0
  35. package/tabs/src/components/event_registration/EventExternalRegistration.test.jsx +219 -0
  36. package/tabs/src/components/event_registration/EventRegistration.test.jsx +208 -0
  37. package/tabs/src/components/my_country/AtAGlance.test.jsx +157 -0
  38. package/tabs/src/components/my_country/CountryMembers.test.jsx +117 -0
  39. package/tabs/src/components/my_country/CountryProgress.test.jsx +21 -0
  40. package/tabs/src/components/my_country/DataReporters.jsx +2 -3
  41. package/tabs/src/components/my_country/DataReporters.test.jsx +155 -0
  42. package/tabs/src/components/my_country/FullCircularProgress.test.jsx +19 -0
  43. package/tabs/src/components/my_country/GroupView.test.jsx +165 -0
  44. package/tabs/src/components/my_country/GroupsBoard.test.jsx +30 -0
  45. package/tabs/src/components/my_country/IndicatorCard.test.jsx +27 -0
  46. package/tabs/src/components/my_country/ManagementBoard.test.jsx +119 -0
  47. package/tabs/src/components/my_country/MyCountry.test.jsx +220 -0
  48. package/tabs/src/components/my_country/ProgressGauge.test.jsx +34 -0
  49. package/tabs/src/components/my_country/ScientificCommittee.test.jsx +11 -0
  50. package/tabs/src/components/my_country/UserCard.test.jsx +24 -0
  51. package/tabs/src/components/my_country/YearlyProgress.test.jsx +33 -0
  52. package/tabs/src/components/self_service/UserEdit.test.jsx +213 -0
  53. package/tabs/src/data/apiProvider.test.js +228 -0
  54. package/tabs/src/data/icsHelper.test.js +76 -0
  55. package/tabs/src/data/provider.test.js +351 -0
  56. package/tabs/src/data/reportingProvider.test.js +103 -0
  57. package/tabs/src/data/selfServiceProvider.test.js +108 -0
  58. package/tabs/src/data/selfServiceSharepointProvider.test.js +100 -0
  59. package/tabs/src/data/sharepointProvider.test.js +669 -0
  60. package/tabs/src/data/validator.test.js +34 -2
  61. package/tabs/yarn.lock +415 -414
  62. package/tabs/src/components/CustomGridToolbar.jsx +0 -202
@@ -0,0 +1,463 @@
1
+ jest.mock('react', () => {
2
+ const actual = jest.requireActual('react');
3
+ return {
4
+ ...actual,
5
+ React: actual,
6
+ useState: jest.fn(actual.useState),
7
+ useEffect: jest.fn(),
8
+ useCallback: jest.fn((fn) => fn),
9
+ };
10
+ });
11
+
12
+ import React from 'react';
13
+ import { renderToStaticMarkup } from 'react-dom/server';
14
+ import Tab from './Tab';
15
+ import { useMediaQuery } from 'react-responsive';
16
+ import { useConfiguration } from '../data/hooks/useConfiguration';
17
+ import { getMe } from '../data/provider';
18
+ import { getCountries, getCurrentParticipant, getParticipants } from '../data/sharepointProvider';
19
+
20
+ jest.mock('./Tab.scss', () => ({}));
21
+
22
+ jest.mock('@mui/material/styles', () => ({
23
+ ThemeProvider: ({ children }) => <>{children}</>,
24
+ createTheme: () => ({
25
+ breakpoints: {
26
+ up: () => '@media (min-width: 600px)',
27
+ },
28
+ }),
29
+ }));
30
+
31
+ const mockMenuItem = jest.fn(({ children }) => <button>{children}</button>);
32
+ const mockIconButton = jest.fn(({ children }) => <button>{children}</button>);
33
+ const mockAutocomplete = jest.fn(({ renderInput, renderOption, options, onChange }) => {
34
+ if (onChange && options?.[0]) {
35
+ onChange(null, options[0]);
36
+ }
37
+ return (
38
+ <div>
39
+ {renderInput ? renderInput({}) : null}
40
+ {options && options[0] && renderOption ? renderOption({}, options[0]) : null}
41
+ </div>
42
+ );
43
+ });
44
+
45
+ jest.mock('@mui/material', () => {
46
+ const ReactLocal = require('react');
47
+ const passthrough =
48
+ (Tag = 'div') =>
49
+ ({ children }) =>
50
+ ReactLocal.createElement(Tag, {}, children);
51
+
52
+ const Menu = ({ open, children }) => (open ? <div>{children}</div> : null);
53
+ const Dialog = ({ open, children }) => (open ? <div>{children}</div> : null);
54
+
55
+ return {
56
+ Backdrop: passthrough(),
57
+ CircularProgress: passthrough('span'),
58
+ AppBar: passthrough('header'),
59
+ Toolbar: passthrough(),
60
+ Menu,
61
+ MenuItem: (props) => mockMenuItem(props),
62
+ Typography: passthrough('span'),
63
+ Autocomplete: (props) => mockAutocomplete(props),
64
+ Box: passthrough(),
65
+ TextField: passthrough('input'),
66
+ Paper: passthrough(),
67
+ Dialog,
68
+ DialogTitle: passthrough(),
69
+ IconButton: (props) => mockIconButton(props),
70
+ };
71
+ });
72
+
73
+ jest.mock('@mui/icons-material/Close', () => () => <span>close-icon</span>);
74
+ jest.mock('@mui/icons-material/Menu', () => () => <span>menu-icon</span>);
75
+ jest.mock('@mui/icons-material/ChevronLeft', () => () => <span>chevron-left-icon</span>);
76
+ jest.mock('@mui/icons-material/Summarize', () => () => <span>summarize-icon</span>);
77
+
78
+ jest.mock('react-responsive', () => ({
79
+ useMediaQuery: jest.fn(),
80
+ }));
81
+
82
+ jest.mock('../data/provider', () => ({
83
+ getMe: jest.fn(),
84
+ }));
85
+
86
+ jest.mock('../data/hooks/useConfiguration', () => ({
87
+ useConfiguration: jest.fn(),
88
+ }));
89
+
90
+ jest.mock('../data/sharepointProvider', () => ({
91
+ getCountries: jest.fn(),
92
+ getCurrentParticipant: jest.fn(),
93
+ getParticipants: jest.fn(),
94
+ }));
95
+
96
+ jest.mock('@microsoft/applicationinsights-react-js', () => ({
97
+ AppInsightsContext: {
98
+ Provider: ({ children }) => children,
99
+ },
100
+ }));
101
+
102
+ jest.mock('../data/appInsights', () => ({
103
+ reactPlugin: {},
104
+ }));
105
+
106
+ jest.mock('./BottomMenu', () => ({
107
+ BottomMenu: () => <div>bottom-menu</div>,
108
+ }));
109
+
110
+ const mockActivity = jest.fn(() => <div>activity-component</div>);
111
+ jest.mock('./activity/Activity', () => ({
112
+ Activity: (props) => mockActivity(props),
113
+ }));
114
+
115
+ jest.mock('./my_country/MyCountry', () => ({
116
+ MyCountry: () => <div>my-country-component</div>,
117
+ }));
118
+
119
+ jest.mock('./self_service/UserEdit', () => ({
120
+ UserEdit: () => <div>user-edit-component</div>,
121
+ }));
122
+
123
+ const mockApprovalDialog = jest.fn(({ open }) => (
124
+ <div>{open ? 'approval-open' : 'approval-closed'}</div>
125
+ ));
126
+ jest.mock('./event_registration/ApprovalDialog', () => ({
127
+ ApprovalDialog: (props) => mockApprovalDialog(props),
128
+ }));
129
+
130
+ const mockEventRatingDialog = jest.fn(({ open }) => (
131
+ <div>{open ? 'rating-open' : 'rating-closed'}</div>
132
+ ));
133
+ jest.mock('./event_rating/EventRatingDialog', () => ({
134
+ EventRatingDialog: (props) => mockEventRatingDialog(props),
135
+ }));
136
+
137
+ jest.mock('./HtmlBox', () => ({
138
+ HtmlBox: ({ html }) => <div>{html || ''}</div>,
139
+ }));
140
+
141
+ const mockUserMenu = jest.fn(() => <div>user-menu-component</div>);
142
+ jest.mock('./UserMenu', () => ({
143
+ UserMenu: (props) => mockUserMenu(props),
144
+ }));
145
+
146
+ function mockStateSequence(values) {
147
+ let index = 0;
148
+ React.useState.mockImplementation((initialValue) => {
149
+ if (index < values.length) {
150
+ const next = values[index];
151
+ index += 1;
152
+ return [next, jest.fn()];
153
+ }
154
+ return [initialValue, jest.fn()];
155
+ });
156
+ }
157
+
158
+ describe('Tab', () => {
159
+ const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
160
+ const waitForMockCall = async (mockFn, retries = 25) => {
161
+ for (let i = 0; i < retries; i += 1) {
162
+ if (mockFn.mock.calls.length > 0) {
163
+ return;
164
+ }
165
+ await flush();
166
+ }
167
+ };
168
+
169
+ beforeEach(() => {
170
+ jest.clearAllMocks();
171
+ process.env.REACT_APP_VERSION = '1.0.0';
172
+ React.useCallback.mockImplementation((fn) => fn);
173
+ React.useEffect.mockImplementation(() => {});
174
+ React.useState.mockImplementation((initialValue) => [initialValue, jest.fn()]);
175
+ });
176
+
177
+ test('renders activity mode with open dialogs and version message', () => {
178
+ useMediaQuery.mockReturnValue(false);
179
+ useConfiguration.mockReturnValue({ AppVersionMessage: 'Please reload' });
180
+
181
+ mockStateSequence([
182
+ {
183
+ isAdmin: false,
184
+ isNFP: true,
185
+ isGuest: false,
186
+ country: 'RO',
187
+ isLoaded: true,
188
+ isEionetUser: true,
189
+ },
190
+ { isLoaded: true },
191
+ { event2Approve: [{ id: 1 }], events2Rate: [{ id: 2 }], allEvents: [] },
192
+ true,
193
+ 'RO',
194
+ ['RO', 'DE'],
195
+ true,
196
+ false,
197
+ { id: 1 },
198
+ { Title: 'Event', HasVoted: false, AllowVote: true },
199
+ true,
200
+ true,
201
+ true,
202
+ false,
203
+ 1,
204
+ null,
205
+ ]);
206
+
207
+ const html = renderToStaticMarkup(<Tab />);
208
+
209
+ expect(html).toContain('activity-component');
210
+ expect(html).toContain('approval-open');
211
+ expect(html).toContain('rating-open');
212
+ expect(html).toContain('Please reload');
213
+ expect(html).toContain('user-menu-component');
214
+ expect(html).toContain('bottom-menu');
215
+ });
216
+
217
+ test('renders my-country view when menu is set to 2', () => {
218
+ useMediaQuery.mockReturnValue(false);
219
+ useConfiguration.mockReturnValue({});
220
+
221
+ mockStateSequence([
222
+ {
223
+ isAdmin: false,
224
+ isNFP: false,
225
+ isGuest: false,
226
+ country: 'RO',
227
+ isLoaded: true,
228
+ isEionetUser: true,
229
+ },
230
+ { isLoaded: true },
231
+ { event2Approve: [], events2Rate: [], allEvents: [] },
232
+ true,
233
+ 'RO',
234
+ ['RO'],
235
+ false,
236
+ false,
237
+ {},
238
+ {},
239
+ false,
240
+ false,
241
+ false,
242
+ true,
243
+ 2,
244
+ null,
245
+ ]);
246
+
247
+ const html = renderToStaticMarkup(<Tab />);
248
+ expect(html).toContain('my-country-component');
249
+ });
250
+
251
+ test('renders self service view when menu is set to 4', () => {
252
+ useMediaQuery.mockReturnValue(false);
253
+ useConfiguration.mockReturnValue({});
254
+
255
+ mockStateSequence([
256
+ {
257
+ isAdmin: false,
258
+ isNFP: false,
259
+ isGuest: false,
260
+ country: 'RO',
261
+ isLoaded: true,
262
+ isEionetUser: true,
263
+ },
264
+ { isLoaded: true, FirstName: 'User' },
265
+ { event2Approve: [], events2Rate: [], allEvents: [] },
266
+ true,
267
+ 'RO',
268
+ ['RO'],
269
+ false,
270
+ false,
271
+ {},
272
+ {},
273
+ false,
274
+ false,
275
+ false,
276
+ true,
277
+ 4,
278
+ null,
279
+ ]);
280
+
281
+ const html = renderToStaticMarkup(<Tab />);
282
+ expect(html).toContain('user-edit-component');
283
+ });
284
+
285
+ test('renders mobile header controls', () => {
286
+ useMediaQuery.mockReturnValue(true);
287
+ useConfiguration.mockReturnValue({});
288
+
289
+ mockStateSequence([
290
+ {
291
+ isAdmin: false,
292
+ isNFP: false,
293
+ isGuest: false,
294
+ country: 'UK',
295
+ isLoaded: true,
296
+ isEionetUser: true,
297
+ },
298
+ { isLoaded: true },
299
+ { event2Approve: [], events2Rate: [], allEvents: [] },
300
+ true,
301
+ 'UK',
302
+ ['UK'],
303
+ false,
304
+ false,
305
+ {},
306
+ {},
307
+ false,
308
+ false,
309
+ false,
310
+ false,
311
+ 1,
312
+ null,
313
+ ]);
314
+
315
+ const html = renderToStaticMarkup(<Tab />);
316
+ expect(html).toContain('menu-icon');
317
+ expect(html).toContain('summarize-icon');
318
+ expect(html).toContain('flagcdn.com/h20/gb.png');
319
+ });
320
+
321
+ test('runs initialization effect and can change country for admin users', async () => {
322
+ useMediaQuery.mockReturnValue(false);
323
+ useConfiguration.mockReturnValue({ DashboardVersion: '2.0.0' });
324
+ getMe.mockResolvedValue({
325
+ isAdmin: true,
326
+ isNFP: false,
327
+ isGuest: false,
328
+ country: 'EL',
329
+ isEionetUser: true,
330
+ mail: 'user@example.org',
331
+ displayName: 'User',
332
+ givenName: 'Given',
333
+ surname: 'Name',
334
+ });
335
+ getCountries.mockResolvedValue(['EL', 'RO']);
336
+
337
+ React.useEffect.mockImplementation((fn) => {
338
+ fn();
339
+ });
340
+
341
+ mockStateSequence([
342
+ { isLoaded: false },
343
+ {},
344
+ { event2Approve: [], events2Rate: [], allEvents: [] },
345
+ true,
346
+ 'EL',
347
+ ['EL', 'RO'],
348
+ true,
349
+ false,
350
+ {},
351
+ {},
352
+ false,
353
+ false,
354
+ false,
355
+ true,
356
+ 1,
357
+ null,
358
+ ]);
359
+
360
+ const html = renderToStaticMarkup(<Tab />);
361
+ await waitForMockCall(getMe);
362
+ await waitForMockCall(getCountries);
363
+
364
+ expect(getMe).toHaveBeenCalled();
365
+ expect(getCountries).toHaveBeenCalled();
366
+ expect(html).toContain('flagcdn.com/h20/gr.png');
367
+ expect(html).toContain('flagcdn.com/w20/gr.png');
368
+ });
369
+
370
+ test('wires menu and dialog handlers through child props', async () => {
371
+ useMediaQuery.mockReturnValue(false);
372
+ useConfiguration.mockReturnValue({});
373
+ getCurrentParticipant.mockResolvedValue({ id: 'participant' });
374
+ getParticipants.mockResolvedValue([{ id: 'p1' }]);
375
+
376
+ const selectedEvent = { id: 10, Title: 'Event X', HasVoted: false, AllowVote: true };
377
+ mockStateSequence([
378
+ {
379
+ isAdmin: false,
380
+ isNFP: true,
381
+ isGuest: false,
382
+ country: 'RO',
383
+ isLoaded: true,
384
+ isEionetUser: true,
385
+ mail: 'user@example.org',
386
+ },
387
+ { isLoaded: true },
388
+ {
389
+ event2Approve: [],
390
+ events2Rate: [],
391
+ allEvents: [{ id: 1, IsUpcoming: false, AllowVote: true }],
392
+ },
393
+ true,
394
+ 'RO',
395
+ ['RO'],
396
+ false,
397
+ false,
398
+ {},
399
+ selectedEvent,
400
+ true,
401
+ true,
402
+ true,
403
+ false,
404
+ 1,
405
+ null,
406
+ ]);
407
+
408
+ renderToStaticMarkup(<Tab />);
409
+
410
+ await mockUserMenu.mock.calls[0][0].openRating({ id: 10, Title: 'Event X' });
411
+ await mockUserMenu.mock.calls[0][0].openApproval({ id: 10, Title: 'Event X' });
412
+ mockApprovalDialog.mock.calls[0][0].handleClose();
413
+ mockEventRatingDialog.mock.calls[0][0].handleClose(true);
414
+ mockUserMenu.mock.calls[0][0].openSelfService();
415
+ mockMenuItem.mock.calls[0][0].onClick();
416
+ mockMenuItem.mock.calls[1][0].onClick();
417
+
418
+ expect(getCurrentParticipant).toHaveBeenCalledTimes(2);
419
+ expect(getParticipants).toHaveBeenCalledWith(10, 'RO');
420
+ expect(selectedEvent.HasVoted).toBe(true);
421
+ expect(selectedEvent.AllowVote).toBe(false);
422
+ });
423
+
424
+ test('handles mobile menu and drawer callbacks', () => {
425
+ useMediaQuery.mockReturnValue(true);
426
+ useConfiguration.mockReturnValue({});
427
+
428
+ mockStateSequence([
429
+ {
430
+ isAdmin: false,
431
+ isNFP: false,
432
+ isGuest: false,
433
+ country: 'IO',
434
+ isLoaded: true,
435
+ isEionetUser: true,
436
+ },
437
+ { isLoaded: true },
438
+ { event2Approve: [], events2Rate: [], allEvents: [] },
439
+ true,
440
+ 'IO',
441
+ ['IO'],
442
+ false,
443
+ false,
444
+ {},
445
+ {},
446
+ false,
447
+ false,
448
+ false,
449
+ true,
450
+ 1,
451
+ { id: 'anchor' },
452
+ ]);
453
+
454
+ const html = renderToStaticMarkup(<Tab />);
455
+
456
+ mockIconButton.mock.calls[0][0].onClick();
457
+ mockIconButton.mock.calls[1][0].onClick({ currentTarget: { id: 'new-anchor' } });
458
+ mockMenuItem.mock.calls[0][0].onClick();
459
+ mockMenuItem.mock.calls[1][0].onClick();
460
+
461
+ expect(html).not.toContain('flagcdn.com/h20/.png');
462
+ });
463
+ });
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import TabConfig from './TabConfig';
4
+ import * as microsoftTeams from '@microsoft/teams-js';
5
+
6
+ jest.mock('@microsoft/teams-js', () => ({
7
+ initialize: jest.fn(),
8
+ settings: {
9
+ registerOnSaveHandler: jest.fn(),
10
+ setSettings: jest.fn(),
11
+ setValidityState: jest.fn(),
12
+ },
13
+ }));
14
+
15
+ describe('TabConfig', () => {
16
+ test('renders configuration content and initializes teams settings', () => {
17
+ const originalWindow = global.window;
18
+ global.window = { location: { hostname: 'localhost', port: '3000' } };
19
+ const html = renderToStaticMarkup(<TabConfig />);
20
+
21
+ expect(html).toContain('Tab Configuration');
22
+ expect(microsoftTeams.initialize).toHaveBeenCalled();
23
+ expect(microsoftTeams.settings.registerOnSaveHandler).toHaveBeenCalled();
24
+ expect(microsoftTeams.settings.setValidityState).toHaveBeenCalledWith(true);
25
+ global.window = originalWindow;
26
+ });
27
+ });
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import TabPanel from './TabPanel';
4
+
5
+ describe('TabPanel', () => {
6
+ test('renders content when selected tab index matches value', () => {
7
+ const html = renderToStaticMarkup(
8
+ <TabPanel value={1} index={1}>
9
+ Visible content
10
+ </TabPanel>,
11
+ );
12
+
13
+ expect(html).toContain('role="tabpanel"');
14
+ expect(html).toContain('id="simple-tabpanel-1"');
15
+ expect(html).toContain('aria-labelledby="simple-tab-1"');
16
+ expect(html).not.toContain('hidden=""');
17
+ expect(html).toContain('Visible content');
18
+ });
19
+
20
+ test('hides and does not render content when index does not match value', () => {
21
+ const html = renderToStaticMarkup(
22
+ <TabPanel value={0} index={1}>
23
+ Hidden content
24
+ </TabPanel>,
25
+ );
26
+
27
+ expect(html).toContain('role="tabpanel"');
28
+ expect(html).toContain('hidden=""');
29
+ expect(html).not.toContain('Hidden content');
30
+ });
31
+ });
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import TermsOfUse from './TermsOfUse';
4
+
5
+ describe('TermsOfUse', () => {
6
+ test('renders terms of use title', () => {
7
+ const html = renderToStaticMarkup(<TermsOfUse />);
8
+
9
+ expect(html).toContain('<h1>Terms of Use</h1>');
10
+ });
11
+ });
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { UnderConstruction } from './UnderConstruction';
4
+
5
+ describe('UnderConstruction', () => {
6
+ test('renders the under construction message and icon container', () => {
7
+ const html = renderToStaticMarkup(<UnderConstruction />);
8
+
9
+ expect(html).toContain('Page under construction');
10
+ expect(html).toContain('svg');
11
+ expect(html).toContain('data-testid="ConstructionIcon"');
12
+ });
13
+ });
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { useMediaQuery } from 'react-responsive';
4
+
5
+ jest.mock('react', () => {
6
+ const actual = jest.requireActual('react');
7
+ return {
8
+ ...actual,
9
+ React: actual,
10
+ };
11
+ });
12
+
13
+ jest.mock('react-responsive', () => ({
14
+ useMediaQuery: jest.fn(),
15
+ }));
16
+
17
+ import { UserMenu } from './UserMenu';
18
+
19
+ describe('UserMenu', () => {
20
+ const baseProps = {
21
+ userInfo: {
22
+ displayName: 'John Doe',
23
+ isNFP: true,
24
+ },
25
+ openSelfService: jest.fn(),
26
+ events2Rate: [{ id: 1, Title: 'Event A' }],
27
+ events2Approve: [{ id: 2, Title: 'Event B' }],
28
+ openRating: jest.fn(),
29
+ openApproval: jest.fn(),
30
+ };
31
+
32
+ beforeEach(() => {
33
+ jest.clearAllMocks();
34
+ });
35
+
36
+ test('renders display name on desktop', () => {
37
+ useMediaQuery.mockReturnValue(false);
38
+
39
+ const html = renderToStaticMarkup(<UserMenu {...baseProps} />);
40
+
41
+ expect(html).toContain('John Doe');
42
+ expect(html).toContain('user-menu-button');
43
+ });
44
+
45
+ test('hides display name on mobile', () => {
46
+ useMediaQuery.mockReturnValue(true);
47
+
48
+ const html = renderToStaticMarkup(<UserMenu {...baseProps} />);
49
+
50
+ expect(html).not.toContain('John Doe');
51
+ expect(html).toContain('user-menu-button');
52
+ });
53
+ });