eionet2-dashboard 3.2.0 → 3.2.2

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 (61) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/Jenkinsfile +2 -2
  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.test.jsx +17 -0
  11. package/tabs/src/components/CustomDrawer.test.jsx +14 -0
  12. package/tabs/src/components/CustomGridToolbar.test.jsx +8 -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.scss +6 -0
  18. package/tabs/src/components/Tab.test.jsx +463 -0
  19. package/tabs/src/components/TabConfig.test.jsx +27 -0
  20. package/tabs/src/components/TabPanel.test.jsx +31 -0
  21. package/tabs/src/components/TermsOfUse.test.jsx +11 -0
  22. package/tabs/src/components/UnderConstruction.test.jsx +13 -0
  23. package/tabs/src/components/UserMenu.test.jsx +53 -0
  24. package/tabs/src/components/activity/Activity.test.jsx +218 -0
  25. package/tabs/src/components/activity/ConsultationList.test.jsx +114 -0
  26. package/tabs/src/components/activity/EventList.test.jsx +164 -0
  27. package/tabs/src/components/activity/GroupsTags.test.jsx +23 -0
  28. package/tabs/src/components/activity/ObligationList.test.jsx +46 -0
  29. package/tabs/src/components/activity/PublicationList.test.jsx +46 -0
  30. package/tabs/src/components/activity/Reporting.test.jsx +11 -0
  31. package/tabs/src/components/event_rating/EventRating.test.jsx +63 -0
  32. package/tabs/src/components/event_rating/EventRatingDialog.test.jsx +28 -0
  33. package/tabs/src/components/event_registration/Approval.test.jsx +25 -0
  34. package/tabs/src/components/event_registration/ApprovalDialog.test.jsx +25 -0
  35. package/tabs/src/components/event_registration/ApprovalList.test.jsx +36 -0
  36. package/tabs/src/components/event_registration/EventExternalRegistration.test.jsx +219 -0
  37. package/tabs/src/components/event_registration/EventRegistration.test.jsx +208 -0
  38. package/tabs/src/components/my_country/AtAGlance.test.jsx +157 -0
  39. package/tabs/src/components/my_country/CountryMembers.test.jsx +117 -0
  40. package/tabs/src/components/my_country/CountryProgress.test.jsx +21 -0
  41. package/tabs/src/components/my_country/DataReporters.test.jsx +156 -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
package/tabs/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/eionet2-dashboard",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "description": "MS Teams app for accessing Eionet activity and managing account information.",
6
6
  "dependencies": {
@@ -16,7 +16,7 @@
16
16
  "date-fns": "^2.30.0",
17
17
  "dompurify": "^3.2.4",
18
18
  "history": "^5.3.0",
19
- "ics": "^3.8.1",
19
+ "ics": "3.6.3",
20
20
  "postcss-scss": "^4.0.9",
21
21
  "react": "^18.3.1",
22
22
  "react-dom": "^18.3.1",
@@ -61,7 +61,7 @@
61
61
  },
62
62
  "scripts": {
63
63
  "prepare": "npm run build",
64
- "test": "jest",
64
+ "test": "jest --coverage",
65
65
  "dev:teamsfx": "env-cmd --silent -f .env.teamsfx.local npm run start",
66
66
  "start": "cross-env REACT_APP_VERSION=$npm_package_version GENERATE_SOURCEMAP=false react-scripts start",
67
67
  "install:teamsfx": "npm install",
@@ -130,7 +130,7 @@
130
130
  for (var i = 0; i < 36; i++) {
131
131
  if (guidHolder[i] !== "-" && guidHolder[i] !== "4") {
132
132
  // each x and y needs to be random
133
- r = (Math.random() * 16) | 0;
133
+ r = Math.trunc(Math.random() * 16);
134
134
  }
135
135
  if (guidHolder[i] === "x") {
136
136
  guidResponse += hex[r];
@@ -175,4 +175,4 @@
175
175
  </script>
176
176
  </body>
177
177
 
178
- </html>
178
+ </html>
@@ -0,0 +1,67 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+
4
+ describe('App', () => {
5
+ beforeEach(() => {
6
+ jest.resetModules();
7
+ });
8
+
9
+ test('renders loading state', () => {
10
+ jest.doMock('./lib/useTeamsAuth', () => ({
11
+ useTeamsAuth: () => ({ theme: null, loading: true, error: null }),
12
+ }));
13
+ jest.doMock('./Privacy', () => () => null);
14
+ jest.doMock('./TermsOfUse', () => () => null);
15
+ jest.doMock('./Tab', () => () => null);
16
+ jest.doMock('./TabConfig', () => () => null);
17
+ jest.doMock('@fluentui/react-components', () => ({
18
+ FluentProvider: 'div',
19
+ teamsLightTheme: {},
20
+ Spinner: () => <div>loading-spinner</div>,
21
+ Text: 'span',
22
+ }));
23
+ jest.doMock('react-router-dom', () => ({
24
+ HashRouter: React.Fragment,
25
+ Navigate: () => null,
26
+ Route: () => null,
27
+ Routes: React.Fragment,
28
+ }));
29
+
30
+ const mod = require('./App');
31
+ const App = mod.default;
32
+ const html = renderToStaticMarkup(<App />);
33
+
34
+ expect(html).toContain('loading-spinner');
35
+ });
36
+
37
+ test('renders fatal error message when auth fails', () => {
38
+ jest.doMock('./lib/useTeamsAuth', () => ({
39
+ useTeamsAuth: () => ({ theme: null, loading: false, error: new Error('Auth failed') }),
40
+ }));
41
+ jest.doMock('./Privacy', () => () => null);
42
+ jest.doMock('./TermsOfUse', () => () => null);
43
+ jest.doMock('./Tab', () => () => null);
44
+ jest.doMock('./TabConfig', () => () => null);
45
+ jest.doMock('@fluentui/react-components', () => ({
46
+ FluentProvider: 'div',
47
+ teamsLightTheme: {},
48
+ Spinner: () => <div>loading-spinner</div>,
49
+ Text: 'span',
50
+ }));
51
+ jest.doMock('react-router-dom', () => ({
52
+ HashRouter: React.Fragment,
53
+ Navigate: () => null,
54
+ Route: () => null,
55
+ Routes: React.Fragment,
56
+ }));
57
+
58
+ const mod = require('./App');
59
+ const App = mod.default;
60
+ const html = renderToStaticMarkup(<App />);
61
+
62
+ expect(html).toContain(
63
+ 'Something went wrong while initializing Microsoft Teams authentication.',
64
+ );
65
+ expect(html).toContain('Auth failed');
66
+ });
67
+ });
@@ -0,0 +1,94 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { useMediaQuery } from 'react-responsive';
4
+
5
+ const mockButton = jest.fn(({ children }) => <div>{children}</div>);
6
+ const mockMenuItem = jest.fn(({ children }) => <div>{children}</div>);
7
+
8
+ jest.mock('react-responsive', () => ({
9
+ useMediaQuery: jest.fn(),
10
+ }));
11
+ jest.mock('./Tab.scss', () => ({}));
12
+ jest.mock('@mui/icons-material/OpenInNew', () => () => <span>open-icon</span>);
13
+ jest.mock('@mui/material', () => ({
14
+ Menu: ({ children }) => <div>{children}</div>,
15
+ MenuItem: (props) => mockMenuItem(props),
16
+ Typography: ({ children }) => <div>{children}</div>,
17
+ Box: ({ children }) => <div>{children}</div>,
18
+ BottomNavigation: ({ children }) => <div>{children}</div>,
19
+ Button: (props) => mockButton(props),
20
+ }));
21
+
22
+ import { BottomMenu } from './BottomMenu';
23
+
24
+ describe('BottomMenu', () => {
25
+ const configuration = {
26
+ ConsultationListUrl: 'https://example.org/consultations',
27
+ MeetingListUrl: 'https://example.org/events',
28
+ InquiryListUrl: 'https://example.org/enquiries',
29
+ OrganisationListUrl: 'https://example.org/organisations',
30
+ UserListUrl: 'https://example.org/users',
31
+ };
32
+
33
+ const getNodeText = (node) => {
34
+ if (node === null || node === undefined || typeof node === 'boolean') {
35
+ return '';
36
+ }
37
+ if (typeof node === 'string' || typeof node === 'number') {
38
+ return String(node);
39
+ }
40
+ if (Array.isArray(node)) {
41
+ return node.map(getNodeText).join('');
42
+ }
43
+ if (node.props && node.props.children !== undefined) {
44
+ return getNodeText(node.props.children);
45
+ }
46
+ return '';
47
+ };
48
+
49
+ const findByLabel = (mockFn, label) =>
50
+ mockFn.mock.calls
51
+ .map(([props]) => props)
52
+ .find((props) => getNodeText(props.children).includes(label));
53
+
54
+ beforeEach(() => {
55
+ jest.clearAllMocks();
56
+ process.env.REACT_APP_VERSION = '9.9.9';
57
+ global.window = { open: jest.fn() };
58
+ });
59
+
60
+ test('renders desktop details section and opens expected links', () => {
61
+ useMediaQuery.mockReturnValue(false);
62
+
63
+ const html = renderToStaticMarkup(<BottomMenu configuration={configuration} />);
64
+
65
+ findByLabel(mockButton, 'All consultations').onClick();
66
+ findByLabel(mockButton, 'All enquiries').onClick();
67
+ findByLabel(mockButton, 'All events').onClick();
68
+
69
+ expect(html).toContain('View details:');
70
+ expect(html).toContain('v9.9.9');
71
+ expect(window.open).toHaveBeenCalledWith(
72
+ 'https://example.org/consultations&useFiltersInViewXml=1&FilterFields2=IsECConsultation&FilterValues2=Eionet-and-EC%3B%23Eionet-only%3B%23N%2FA&FilterTypes2=Choice&FilterOp2=In',
73
+ '_blank',
74
+ );
75
+ expect(window.open).toHaveBeenCalledWith(
76
+ 'https://example.org/enquiries&useFiltersInViewXml=1&FilterFields2=IsECConsultation&FilterValues2=Eionet-and-EC%3B%23Eionet-only%3B%23N%2FA&FilterTypes2=Choice&FilterOp2=In',
77
+ '_blank',
78
+ );
79
+ expect(window.open).toHaveBeenCalledWith('https://example.org/events', '_blank');
80
+ });
81
+
82
+ test('renders mobile view and opens selected menu links', () => {
83
+ useMediaQuery.mockReturnValue(true);
84
+
85
+ const html = renderToStaticMarkup(<BottomMenu configuration={configuration} />);
86
+
87
+ findByLabel(mockButton, 'View details').onClick({ currentTarget: { id: 'anchor' } });
88
+ findByLabel(mockMenuItem, 'All users').onClick();
89
+
90
+ expect(html).toContain('View details');
91
+ expect(html).not.toContain('View details:');
92
+ expect(window.open).toHaveBeenCalledWith('https://example.org/users', '_blank');
93
+ });
94
+ });
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import CustomColumnResizeIcon from './CustomColumnResizeIcon';
4
+
5
+ jest.mock('@mui/x-data-grid', () => ({
6
+ GridSeparatorIcon: () => <span>grid-separator</span>,
7
+ }));
8
+
9
+ describe('CustomColumnResizeIcon', () => {
10
+ test('renders the draggable resize icon handle', () => {
11
+ const html = renderToStaticMarkup(<CustomColumnResizeIcon />);
12
+
13
+ expect(html).toContain('class="resizable"');
14
+ expect(html).toContain('draggable="true"');
15
+ expect(html).toContain('grid-separator');
16
+ });
17
+ });
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import CustomDrawer from './CustomDrawer';
4
+
5
+ describe('CustomDrawer', () => {
6
+ test('renders permanent drawer with provided content', () => {
7
+ const html = renderToStaticMarkup(
8
+ <CustomDrawer drawerOptions={<div>Drawer options content</div>} />,
9
+ );
10
+
11
+ expect(html).toContain(' drawer ');
12
+ expect(html).toContain('Drawer options content');
13
+ });
14
+ });
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+
3
+ describe('CustomGridToolbar', () => {
4
+ test('loads module even if exports are commented out', () => {
5
+ const mod = require('./CustomGridToolbar');
6
+ expect(mod).toBeDefined();
7
+ });
8
+ });
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { EventDialogTitle } from './EventDialogTitle';
4
+
5
+ jest.mock('../data/hooks/useConfiguration', () => ({
6
+ useConfiguration: jest.fn(() => ({})),
7
+ }));
8
+
9
+ describe('EventDialogTitle', () => {
10
+ test('renders dialog title and event title', () => {
11
+ const html = renderToStaticMarkup(
12
+ <EventDialogTitle
13
+ title="Registration"
14
+ event={{
15
+ Title: 'Climate meeting',
16
+ MeetingStart: new Date('2025-01-01T10:00:00Z'),
17
+ MeetingEnd: new Date('2025-01-01T11:00:00Z'),
18
+ }}
19
+ />,
20
+ );
21
+
22
+ expect(html).toContain('Registration');
23
+ expect(html).toContain('Event: Climate meeting');
24
+ });
25
+ });
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+
4
+ jest.mock('dompurify', () => ({
5
+ __esModule: true,
6
+ default: {
7
+ isSupported: true,
8
+ addHook: jest.fn(),
9
+ sanitize: jest.fn((html) => html),
10
+ },
11
+ }));
12
+
13
+ import DOMPurify from 'dompurify';
14
+ import { HtmlBox } from './HtmlBox';
15
+
16
+ describe('HtmlBox', () => {
17
+ beforeEach(() => {
18
+ DOMPurify.sanitize.mockClear();
19
+ DOMPurify.sanitize.mockImplementation((html) => html);
20
+ });
21
+
22
+ test('registers dompurify hook at module load', () => {
23
+ expect(DOMPurify.addHook).toHaveBeenCalledWith('afterSanitizeAttributes', expect.any(Function));
24
+ });
25
+
26
+ test('does not render content when html is missing', () => {
27
+ const html = renderToStaticMarkup(<HtmlBox />);
28
+
29
+ expect(html).toBe('<div></div>');
30
+ expect(DOMPurify.sanitize).not.toHaveBeenCalled();
31
+ });
32
+
33
+ test('sanitizes and renders html when provided', () => {
34
+ DOMPurify.sanitize.mockReturnValue('<p>Safe</p>');
35
+
36
+ const html = renderToStaticMarkup(<HtmlBox html={'<img src=x onerror=1 /><p>Safe</p>'} />);
37
+
38
+ expect(DOMPurify.sanitize).toHaveBeenCalledWith('<img src=x onerror=1 /><p>Safe</p>');
39
+ expect(html).toContain('<p>Safe</p>');
40
+ });
41
+ });
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import Privacy from './Privacy';
4
+
5
+ describe('Privacy', () => {
6
+ test('renders privacy statement title', () => {
7
+ const html = renderToStaticMarkup(<Privacy />);
8
+
9
+ expect(html).toContain('<h1>Privacy Statement</h1>');
10
+ });
11
+ });
@@ -0,0 +1,64 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { DataGrid } from '@mui/x-data-grid';
4
+ import ResizableGrid from './ResizableGrid';
5
+ import Constants from '../data/constants.json';
6
+
7
+ const mockResizeIcon = jest.fn(() => <div data-testid="resize-icon">resize-icon</div>);
8
+
9
+ jest.mock('@mui/x-data-grid', () => {
10
+ const React = require('react');
11
+ return {
12
+ DataGrid: jest.fn(() => <div>mock-data-grid</div>),
13
+ };
14
+ });
15
+
16
+ jest.mock('./CustomColumnResizeIcon', () => ({
17
+ __esModule: true,
18
+ default: (props) => mockResizeIcon(props),
19
+ }));
20
+
21
+ describe('ResizableGrid', () => {
22
+ beforeEach(() => {
23
+ DataGrid.mockClear();
24
+ mockResizeIcon.mockClear();
25
+ });
26
+
27
+ test('renders data grid with provided columns and rows and wires resize component', () => {
28
+ const columns = [{ field: 'title', headerName: 'Title' }];
29
+
30
+ const html = renderToStaticMarkup(
31
+ <ResizableGrid id="grid1" rows={[{ id: 1, title: 'A' }]} columns={columns} />,
32
+ );
33
+
34
+ expect(html).toContain('mock-data-grid');
35
+ expect(DataGrid).toHaveBeenCalled();
36
+ const props = DataGrid.mock.calls[0][0];
37
+ expect(props.columns).toEqual(columns);
38
+ expect(props.getRowHeight()).toBe(Constants.GridRowHeight);
39
+ expect(props.components).toMatchObject({
40
+ ColumnResizeIcon: expect.any(Function),
41
+ });
42
+
43
+ renderToStaticMarkup(<props.components.ColumnResizeIcon />);
44
+ expect(mockResizeIcon).toHaveBeenCalledWith(
45
+ expect.objectContaining({ id: 'grid1', onWidthChanged: expect.any(Function) }),
46
+ );
47
+ });
48
+
49
+ test('forwards extra props to DataGrid', () => {
50
+ renderToStaticMarkup(
51
+ <ResizableGrid
52
+ id="grid1"
53
+ autoHeight
54
+ rows={[{ id: 1, title: 'A' }]}
55
+ columns={[{ field: 'title', headerName: 'Title' }]}
56
+ />,
57
+ );
58
+
59
+ const props = DataGrid.mock.calls[0][0];
60
+ expect(props.id).toBe('grid1');
61
+ expect(props.autoHeight).toBe(true);
62
+ expect(props.rows).toEqual([{ id: 1, title: 'A' }]);
63
+ });
64
+ });
@@ -183,3 +183,9 @@ body {
183
183
  align-self: center !important;
184
184
  font-size: 14px !important;
185
185
  }
186
+
187
+ .MuiDataGrid-cell {
188
+ display: flex !important;
189
+ align-items: center !important;
190
+ /* vertical center */
191
+ }