eionet2-dashboard 3.2.1 → 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 (60) 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 +2894 -1833
  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.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.test.jsx +156 -0
  41. package/tabs/src/components/my_country/FullCircularProgress.test.jsx +19 -0
  42. package/tabs/src/components/my_country/GroupView.test.jsx +165 -0
  43. package/tabs/src/components/my_country/GroupsBoard.test.jsx +30 -0
  44. package/tabs/src/components/my_country/IndicatorCard.test.jsx +27 -0
  45. package/tabs/src/components/my_country/ManagementBoard.test.jsx +119 -0
  46. package/tabs/src/components/my_country/MyCountry.test.jsx +220 -0
  47. package/tabs/src/components/my_country/ProgressGauge.test.jsx +34 -0
  48. package/tabs/src/components/my_country/ScientificCommittee.test.jsx +11 -0
  49. package/tabs/src/components/my_country/UserCard.test.jsx +24 -0
  50. package/tabs/src/components/my_country/YearlyProgress.test.jsx +33 -0
  51. package/tabs/src/components/self_service/UserEdit.test.jsx +213 -0
  52. package/tabs/src/data/apiProvider.test.js +228 -0
  53. package/tabs/src/data/icsHelper.test.js +76 -0
  54. package/tabs/src/data/provider.test.js +351 -0
  55. package/tabs/src/data/reportingProvider.test.js +103 -0
  56. package/tabs/src/data/selfServiceProvider.test.js +108 -0
  57. package/tabs/src/data/selfServiceSharepointProvider.test.js +100 -0
  58. package/tabs/src/data/sharepointProvider.test.js +669 -0
  59. package/tabs/src/data/validator.test.js +34 -2
  60. package/tabs/yarn.lock +415 -414
@@ -0,0 +1,117 @@
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((fn) => fn()),
8
+ };
9
+ });
10
+
11
+ import React from 'react';
12
+ jest.mock('./my_country.scss', () => ({}));
13
+ import { renderToStaticMarkup } from 'react-dom/server';
14
+ import { CountryMembers } from './CountryMembers';
15
+ import { getADUserInfos } from '../../data/sharepointProvider';
16
+
17
+ jest.mock('../../data/sharepointProvider', () => ({
18
+ getADUserInfos: jest.fn(),
19
+ }));
20
+
21
+ jest.mock('./UserCard', () => ({
22
+ UserCard: ({ userInfo }) => <div>{userInfo.UserName}</div>,
23
+ }));
24
+
25
+ function mockStateSequence(values) {
26
+ let index = 0;
27
+ React.useState.mockImplementation((initialValue) => {
28
+ if (index < values.length) {
29
+ const value = values[index];
30
+ index += 1;
31
+ return [value, jest.fn()];
32
+ }
33
+ return [initialValue, jest.fn()];
34
+ });
35
+ }
36
+
37
+ describe('CountryMembers', () => {
38
+ beforeEach(() => {
39
+ jest.clearAllMocks();
40
+ React.useState.mockImplementation((initialValue) => [initialValue, jest.fn()]);
41
+ React.useEffect.mockImplementation((fn) => fn());
42
+ getADUserInfos.mockResolvedValue([]);
43
+ });
44
+
45
+ test('renders wrapper and no sections when countryInfo is missing', () => {
46
+ const html = renderToStaticMarkup(<CountryMembers countryInfo={null} />);
47
+
48
+ expect(html).toContain('grid-container');
49
+ expect(html).not.toContain('Country desk officer for');
50
+ expect(html).not.toContain('Country desk officer team members');
51
+ });
52
+
53
+ test('renders leads and members sections when state contains users', () => {
54
+ mockStateSequence([
55
+ false,
56
+ [{ id: 'l1', UserName: 'Lead User' }],
57
+ [{ id: 'm1', UserName: 'Member User' }],
58
+ ]);
59
+
60
+ const html = renderToStaticMarkup(
61
+ <CountryMembers countryInfo={{ CountryName: 'Romania', CDO: [], TeamMember: [] }} />,
62
+ );
63
+
64
+ expect(html).toContain('Country desk officer for Romania');
65
+ expect(html).toContain('Country desk officer team members');
66
+ expect(html).toContain('Lead User');
67
+ expect(html).toContain('Member User');
68
+ });
69
+
70
+ test('loads AD users for CDO and team members', async () => {
71
+ const countryInfo = {
72
+ CountryName: 'Romania',
73
+ CDO: [{ LookupId: 101 }],
74
+ TeamMember: [{ LookupId: 202 }],
75
+ };
76
+
77
+ getADUserInfos.mockResolvedValue([
78
+ {
79
+ id: 'a',
80
+ lookupId: 101,
81
+ displayName: 'CDO 1',
82
+ mail: 'cdo@example.org',
83
+ companyName: 'EEA',
84
+ businessPhones: ['+40'],
85
+ jobTitle: 'Officer',
86
+ department: 'Dept',
87
+ },
88
+ {
89
+ id: 'b',
90
+ lookupId: 202,
91
+ displayName: 'Team 1',
92
+ mail: 'team@example.org',
93
+ companyName: 'EEA',
94
+ businessPhones: [],
95
+ jobTitle: 'Member',
96
+ department: 'Dept',
97
+ base64Photo: 'abc123',
98
+ },
99
+ null,
100
+ ]);
101
+
102
+ renderToStaticMarkup(<CountryMembers countryInfo={countryInfo} />);
103
+ await Promise.resolve();
104
+ await Promise.resolve();
105
+
106
+ expect(getADUserInfos).toHaveBeenCalledWith([101, 202]);
107
+ });
108
+
109
+ test('does not load users when there are no member ids', async () => {
110
+ renderToStaticMarkup(
111
+ <CountryMembers countryInfo={{ CountryName: 'Romania', CDO: [], TeamMember: [] }} />,
112
+ );
113
+ await Promise.resolve();
114
+
115
+ expect(getADUserInfos).not.toHaveBeenCalled();
116
+ });
117
+ });
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ jest.mock('./my_country.scss', () => ({}));
3
+ import { renderToStaticMarkup } from 'react-dom/server';
4
+ import { CountryProgress } from './CountryProgress';
5
+
6
+ jest.mock('./YearlyProgress', () => ({
7
+ YearlyProgress: ({ yearData }) => <div>yearly-{yearData.year}</div>,
8
+ }));
9
+
10
+ describe('CountryProgress', () => {
11
+ test('renders yearly overview tabs', () => {
12
+ const html = renderToStaticMarkup(
13
+ <CountryProgress configuration={{}} lastYears={[{ year: 2024 }, { year: 2023 }]} />,
14
+ );
15
+
16
+ expect(html).toContain('Yearly overview:');
17
+ expect(html).toContain('2024');
18
+ expect(html).toContain('2023');
19
+ expect(html).toContain('yearly-2024');
20
+ });
21
+ });
@@ -0,0 +1,156 @@
1
+ import React from 'react';
2
+ jest.mock('./my_country.scss', () => ({}));
3
+ import { renderToStaticMarkup } from 'react-dom/server';
4
+ import { DataReporters } from './DataReporters';
5
+ import { getFlows } from '../../data/reportingProvider';
6
+
7
+ jest.mock('../../data/reportingProvider', () => ({
8
+ getFlows: jest.fn(),
9
+ }));
10
+
11
+ const mockResizableGrid = jest.fn(({ rows }) => <div>grid-rows-{rows?.length || 0}</div>);
12
+ jest.mock('../ResizableGrid', () => ({
13
+ __esModule: true,
14
+ default: (props) => mockResizableGrid(props),
15
+ }));
16
+ jest.mock('../HtmlBox', () => ({
17
+ HtmlBox: ({ html }) => <div>{html}</div>,
18
+ }));
19
+ jest.mock('@mui/x-data-grid', () => ({
20
+ GridToolbarContainer: ({ children }) => <div>{children}</div>,
21
+ GridToolbarFilterButton: () => <button>filter</button>,
22
+ GridToolbarExport: () => <button>export</button>,
23
+ }));
24
+
25
+ describe('DataReporters', () => {
26
+ beforeEach(() => {
27
+ jest.clearAllMocks();
28
+ global.window = { open: jest.fn() };
29
+ });
30
+
31
+ test('renders coordinator text and grid', () => {
32
+ const html = renderToStaticMarkup(
33
+ <DataReporters
34
+ configuration={{
35
+ DataflowCoordinatorsTag: 'Coordinator',
36
+ ReportingInfoText: 'Reporting info',
37
+ DateFormatDashboard: 'dd-MMM-yyyy',
38
+ }}
39
+ country="RO"
40
+ users={[{ Title: 'John Doe', AllMemberships: ['Coordinator'] }]}
41
+ />,
42
+ );
43
+
44
+ expect(html).toContain('National Dataflow Coordinator: John Doe');
45
+ expect(html).toContain('Reporting info');
46
+ expect(html).toContain('grid-rows-0');
47
+ });
48
+
49
+ test('returns empty output when country is missing', () => {
50
+ const html = renderToStaticMarkup(
51
+ <DataReporters
52
+ configuration={{
53
+ DataflowCoordinatorsTag: 'Coordinator',
54
+ ReportingInfoText: 'Reporting info',
55
+ }}
56
+ country=""
57
+ users={[]}
58
+ />,
59
+ );
60
+
61
+ expect(html).toBe('');
62
+ expect(mockResizableGrid).not.toHaveBeenCalled();
63
+ });
64
+
65
+ test('configures toolbar and column renderers', () => {
66
+ renderToStaticMarkup(
67
+ <DataReporters
68
+ configuration={{
69
+ DataflowCoordinatorsTag: 'Coordinator',
70
+ ReportingInfoText: 'Reporting info',
71
+ DateFormatDashboard: 'dd-MMM-yyyy',
72
+ }}
73
+ country="RO"
74
+ users={[]}
75
+ />,
76
+ );
77
+
78
+ const gridProps = mockResizableGrid.mock.calls[0][0];
79
+ expect(gridProps.showToolbar).toBe(true);
80
+ expect(gridProps.pageSizeOptions).toEqual([25, 50, 100]);
81
+ expect(gridProps.hideFooterSelectedRowCount).toBe(true);
82
+ expect(gridProps.initialState.pagination.paginationModel.pageSize).toBe(25);
83
+ expect(gridProps.initialState.sorting.sortModel[0]).toEqual({
84
+ field: 'firstReleaseDate',
85
+ sort: 'desc',
86
+ });
87
+ expect(gridProps.getRowHeight()).toBe('auto');
88
+
89
+ const toolbarHtml = renderToStaticMarkup(<gridProps.components.Toolbar />);
90
+ expect(toolbarHtml).toContain('filter');
91
+ expect(toolbarHtml).toContain('export');
92
+
93
+ const titleColumn = gridProps.columns.find((c) => c.field === 'dataflowName');
94
+ const reportersColumn = gridProps.columns.find((c) => c.field === 'reporterEmailsString');
95
+ const deadlineColumn = gridProps.columns.find((c) => c.field === 'deadlineDate');
96
+ const releaseColumn = gridProps.columns.find((c) => c.field === 'firstReleaseDate');
97
+ const statusColumn = gridProps.columns.find((c) => c.field === 'status');
98
+ const deliveryColumn = gridProps.columns.find((c) => c.field === 'deliveryStatus');
99
+ const coreColumn = gridProps.columns.find((c) => c.field === 'isEEACore');
100
+
101
+ const sampleRow = {
102
+ dataflowName: 'Dataflow A',
103
+ dataflowURL: 'https://example.org/df',
104
+ obligationName: 'Obligation B',
105
+ obligationURL: '',
106
+ legalInstrumentName: 'Instrument C',
107
+ legalInstrumentURL: 'https://example.org/li',
108
+ reporterEmails: ['a@example.org', 'b@example.org'],
109
+ deadlineDate: new Date('2024-01-10'),
110
+ firstReleaseDate: new Date('2024-01-01'),
111
+ lastReleaseDate: new Date('2024-02-01'),
112
+ status: 'Open',
113
+ deliveryStatus: 'Final feedback',
114
+ isEEACore: true,
115
+ };
116
+
117
+ const titleHeaderHtml = renderToStaticMarkup(<>{titleColumn.renderHeader()}</>);
118
+ expect(titleHeaderHtml).toContain('Dataflow');
119
+ expect(titleHeaderHtml).toContain('Obligation');
120
+ expect(titleHeaderHtml).toContain('Legal instrument');
121
+
122
+ const titleCellHtml = renderToStaticMarkup(<>{titleColumn.renderCell({ row: sampleRow })}</>);
123
+ expect(titleCellHtml).toContain('Dataflow A');
124
+ expect(titleCellHtml).toContain('Obligation B');
125
+ expect(titleCellHtml).toContain('Instrument C');
126
+
127
+ const reportersHtml = renderToStaticMarkup(
128
+ <>{reportersColumn.renderCell({ row: sampleRow })}</>,
129
+ );
130
+ expect(reportersHtml).toContain('mailto:a@example.org');
131
+ expect(reportersHtml).toContain('mailto:b@example.org');
132
+
133
+ const deadlineHtml = renderToStaticMarkup(<>{deadlineColumn.renderCell({ row: sampleRow })}</>);
134
+ expect(deadlineHtml).toContain('10-Jan-2024');
135
+
136
+ const releaseHtml = renderToStaticMarkup(<>{releaseColumn.renderCell({ row: sampleRow })}</>);
137
+ expect(releaseHtml).toContain('(first)');
138
+ expect(releaseHtml).toContain('(latest)');
139
+
140
+ const statusHtml = renderToStaticMarkup(<>{statusColumn.renderCell({ row: sampleRow })}</>);
141
+ expect(statusHtml).toContain('status-open');
142
+ expect(statusHtml).toContain('Open');
143
+
144
+ const closedStatusHtml = renderToStaticMarkup(
145
+ <>{statusColumn.renderCell({ row: { ...sampleRow, status: 'Closed' } })}</>,
146
+ );
147
+ expect(closedStatusHtml).toContain('status-closed');
148
+ expect(closedStatusHtml).toContain('Closed');
149
+
150
+ const deliveryHtml = renderToStaticMarkup(<>{deliveryColumn.renderCell({ row: sampleRow })}</>);
151
+ expect(deliveryHtml).toContain('delivery-status-finalfeedback');
152
+
153
+ const coreHtml = renderToStaticMarkup(<>{coreColumn.renderCell({ row: sampleRow })}</>);
154
+ expect(coreHtml).toContain('svg');
155
+ });
156
+ });
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { FullCircularProgress } from './FullCircularProgress';
4
+
5
+ describe('FullCircularProgress', () => {
6
+ test('renders determinate progress circles with computed value', () => {
7
+ const html = renderToStaticMarkup(<FullCircularProgress totalCount={10} responseCount={5} />);
8
+
9
+ expect(html).toContain('MuiCircularProgress-root');
10
+ expect(html).toContain('50');
11
+ });
12
+
13
+ test('falls back to 0 value when total count is zero', () => {
14
+ const html = renderToStaticMarkup(<FullCircularProgress totalCount={0} responseCount={5} />);
15
+
16
+ expect(html).toContain('MuiCircularProgress-root');
17
+ expect(html).toContain('0');
18
+ });
19
+ });
@@ -0,0 +1,165 @@
1
+ let mockRunEffect = true;
2
+
3
+ jest.mock('react', () => {
4
+ const actual = jest.requireActual('react');
5
+ return {
6
+ ...actual,
7
+ React: actual,
8
+ useState: jest.fn(actual.useState),
9
+ useEffect: jest.fn((fn) => {
10
+ if (mockRunEffect) {
11
+ mockRunEffect = false;
12
+ fn();
13
+ }
14
+ }),
15
+ };
16
+ });
17
+
18
+ import React from 'react';
19
+ jest.mock('./my_country.scss', () => ({}));
20
+ import { renderToStaticMarkup } from 'react-dom/server';
21
+ import { GroupView } from './GroupView';
22
+ import { getADUserInfos } from '../../data/sharepointProvider';
23
+
24
+ const gridProps = [];
25
+
26
+ jest.mock('../../data/sharepointProvider', () => ({
27
+ getADUserInfos: jest.fn(),
28
+ }));
29
+
30
+ jest.mock('./UserCard', () => ({
31
+ UserCard: ({ userInfo }) => <div>{userInfo.UserName}</div>,
32
+ }));
33
+
34
+ jest.mock('../ResizableGrid', () => ({
35
+ __esModule: true,
36
+ default: (props) => {
37
+ gridProps.push(props);
38
+ return <div>grid-rows-{props.rows?.length || 0}</div>;
39
+ },
40
+ }));
41
+
42
+ describe('GroupView', () => {
43
+ const baseGroup = {
44
+ GroupName: 'Air',
45
+ OfficialGroupName: 'Air Group',
46
+ ETCManagerIds: [202],
47
+ EEAGroupLeadsIds: [101],
48
+ OtherMembership: true,
49
+ Users: [
50
+ { id: 1, Title: 'Member 1', Organisation: 'Org', Email: 'm1@example.org', PCP: ['Air'] },
51
+ ],
52
+ };
53
+
54
+ beforeEach(() => {
55
+ jest.clearAllMocks();
56
+ mockRunEffect = true;
57
+ gridProps.length = 0;
58
+ React.useState.mockImplementation((initialValue) => [initialValue, jest.fn()]);
59
+
60
+ getADUserInfos.mockResolvedValue([
61
+ {
62
+ id: 'a',
63
+ lookupId: 101,
64
+ displayName: 'Lead User',
65
+ mail: 'lead@example.org',
66
+ companyName: 'EEA',
67
+ businessPhones: ['+1'],
68
+ jobTitle: 'Lead',
69
+ department: 'Dept',
70
+ },
71
+ {
72
+ id: 'b',
73
+ lookupId: 202,
74
+ displayName: 'ETC User',
75
+ mail: 'etc@example.org',
76
+ companyName: 'EEA',
77
+ businessPhones: [],
78
+ jobTitle: 'Manager',
79
+ department: 'Dept',
80
+ base64Photo: 'abc',
81
+ },
82
+ ]);
83
+ });
84
+
85
+ test('renders group title and users grid', () => {
86
+ const html = renderToStaticMarkup(
87
+ <GroupView configuration={{ DashboardLeadIconTooltip: 'Lead' }} group={{ ...baseGroup }} />,
88
+ );
89
+
90
+ expect(html).toContain('Air Group');
91
+ expect(html).toContain('grid-rows-1');
92
+ expect(gridProps[0].id).toBe('Air');
93
+ });
94
+
95
+ test('renders no content for missing group', () => {
96
+ const html = renderToStaticMarkup(
97
+ <GroupView configuration={{ DashboardLeadIconTooltip: 'Lead' }} group={null} />,
98
+ );
99
+
100
+ expect(html).toContain('grid-container');
101
+ expect(html).not.toContain('grid-rows-');
102
+ });
103
+
104
+ test('renders lead and etc manager sections from state', () => {
105
+ let call = 0;
106
+ React.useState.mockImplementation((initialValue) => {
107
+ call += 1;
108
+ if (call === 1) return [false, jest.fn()];
109
+ if (call === 2) return [[{ id: 'l1', UserName: 'Lead User' }], jest.fn()];
110
+ if (call === 3) return [[{ id: 'e1', UserName: 'ETC User' }], jest.fn()];
111
+ if (call === 4) return [true, jest.fn()];
112
+ return [initialValue, jest.fn()];
113
+ });
114
+
115
+ const html = renderToStaticMarkup(
116
+ <GroupView configuration={{ DashboardLeadIconTooltip: 'Lead' }} group={{ ...baseGroup }} />,
117
+ );
118
+
119
+ expect(html).toContain('EEA ETC Lead:');
120
+ expect(html).toContain('ETC Manager:');
121
+ expect(html).toContain('Lead User');
122
+ expect(html).toContain('ETC User');
123
+ });
124
+
125
+ test('loads AD user infos for leads and etc managers', async () => {
126
+ renderToStaticMarkup(
127
+ <GroupView configuration={{ DashboardLeadIconTooltip: 'Lead' }} group={{ ...baseGroup }} />,
128
+ );
129
+
130
+ await Promise.resolve();
131
+ await Promise.resolve();
132
+
133
+ expect(getADUserInfos).toHaveBeenCalledWith([101]);
134
+ });
135
+
136
+ test('skips AD call when no lead ids exist', async () => {
137
+ renderToStaticMarkup(
138
+ <GroupView
139
+ configuration={{ DashboardLeadIconTooltip: 'Lead' }}
140
+ group={{ ...baseGroup, EEAGroupLeadsIds: [], ETCManagerIds: [], OtherMembership: false }}
141
+ />,
142
+ );
143
+
144
+ await Promise.resolve();
145
+
146
+ expect(getADUserInfos).not.toHaveBeenCalled();
147
+ });
148
+
149
+ test('renders title column with PCP icon from grid renderCell', () => {
150
+ renderToStaticMarkup(
151
+ <GroupView
152
+ configuration={{ DashboardLeadIconTooltip: 'Lead tooltip' }}
153
+ group={{ ...baseGroup }}
154
+ />,
155
+ );
156
+
157
+ const titleColumn = gridProps[0].columns.find((c) => c.field === 'Title');
158
+ const cellHtml = renderToStaticMarkup(
159
+ titleColumn.renderCell({ row: { Title: 'Member 1', PCP: ['Air'] } }),
160
+ );
161
+
162
+ expect(cellHtml).toContain('Member 1');
163
+ expect(cellHtml).toContain('svg');
164
+ });
165
+ });
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ jest.mock('./my_country.scss', () => ({}));
3
+ import { renderToStaticMarkup } from 'react-dom/server';
4
+ import { GroupsBoard } from './GroupsBoard';
5
+
6
+ jest.mock('react-responsive', () => ({
7
+ useMediaQuery: () => false,
8
+ }));
9
+ jest.mock('./GroupView', () => ({
10
+ GroupView: ({ group }) => <div>group-view-{group?.GroupName}</div>,
11
+ }));
12
+ jest.mock('../CustomDrawer', () => ({
13
+ __esModule: true,
14
+ default: ({ drawerOptions }) => <div>{drawerOptions}</div>,
15
+ }));
16
+
17
+ describe('GroupsBoard', () => {
18
+ test('renders group list and selected group view', () => {
19
+ const html = renderToStaticMarkup(
20
+ <GroupsBoard
21
+ configuration={{}}
22
+ users={[{ AllMemberships: ['Air'] }]}
23
+ mappings={[{ Membership: 'Air', OfficialGroupName: 'Air Group' }]}
24
+ />,
25
+ );
26
+
27
+ expect(html).toContain('Air');
28
+ expect(html).toContain('group-view-Air');
29
+ });
30
+ });
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { IndicatorCard } from './IndicatorCard';
4
+
5
+ describe('IndicatorCard', () => {
6
+ test('renders label and value', () => {
7
+ const html = renderToStaticMarkup(
8
+ <IndicatorCard labelText="Users" valueText="42" infoText="Details" />,
9
+ );
10
+
11
+ expect(html).toContain('Users');
12
+ expect(html).toContain('42');
13
+ });
14
+
15
+ test('renders details link when url exists', () => {
16
+ const html = renderToStaticMarkup(
17
+ <IndicatorCard
18
+ labelText="Users"
19
+ valueText="42"
20
+ infoText="Details"
21
+ url="https://example.org"
22
+ />,
23
+ );
24
+
25
+ expect(html).toContain('Details');
26
+ });
27
+ });
@@ -0,0 +1,119 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { ManagementBoard } from './ManagementBoard';
4
+
5
+ const gridProps = [];
6
+
7
+ jest.mock('@mui/material', () => ({
8
+ Box: ({ children, className }) => <div className={className}>{children}</div>,
9
+ Chip: ({ label }) => <span>{label}</span>,
10
+ Tooltip: ({ title, children }) => (
11
+ <div>
12
+ <span>{title}</span>
13
+ {children}
14
+ </div>
15
+ ),
16
+ }));
17
+
18
+ jest.mock('../ResizableGrid', () => ({
19
+ __esModule: true,
20
+ default: (props) => {
21
+ gridProps.push(props);
22
+ return <div>grid-rows-{props.rows?.length || 0}</div>;
23
+ },
24
+ }));
25
+
26
+ describe('ManagementBoard', () => {
27
+ beforeEach(() => {
28
+ jest.clearAllMocks();
29
+ gridProps.length = 0;
30
+ });
31
+
32
+ test('renders filtered board users in grid', () => {
33
+ const html = renderToStaticMarkup(
34
+ <ManagementBoard
35
+ mappings={[{ ManagementBoard: true, Membership: 'Board' }]}
36
+ users={[
37
+ {
38
+ id: 1,
39
+ Organisation: 'Org',
40
+ Title: 'Member',
41
+ Email: 'm@example.org',
42
+ Membership: ['Board'],
43
+ OtherMemberships: ['Other'],
44
+ },
45
+ {
46
+ id: 2,
47
+ Organisation: 'Org2',
48
+ Title: 'Ignored',
49
+ Email: 'i@example.org',
50
+ Membership: ['NonBoard'],
51
+ OtherMemberships: [],
52
+ },
53
+ ]}
54
+ />,
55
+ );
56
+
57
+ expect(html).toContain('grid-rows-1');
58
+ expect(gridProps[0].rows[0].BoardMembership).toBe('Board');
59
+ expect(gridProps[0].rows[0].Name).toBe('Member');
60
+ });
61
+
62
+ test('includes NFP users even without board mapping membership', () => {
63
+ renderToStaticMarkup(
64
+ <ManagementBoard
65
+ mappings={[{ ManagementBoard: true, Membership: 'Board' }]}
66
+ users={[
67
+ {
68
+ id: 3,
69
+ Organisation: 'Org3',
70
+ Title: 'NFP User',
71
+ Email: 'nfp@example.org',
72
+ NFP: 'NFP role',
73
+ Membership: ['NonBoard'],
74
+ OtherMemberships: [],
75
+ },
76
+ ]}
77
+ />,
78
+ );
79
+
80
+ expect(gridProps[0].rows).toHaveLength(1);
81
+ expect(gridProps[0].rows[0].BoardMembership).toBe('NFP role');
82
+ });
83
+
84
+ test('renders other memberships cell chips excluding board membership', () => {
85
+ renderToStaticMarkup(
86
+ <ManagementBoard
87
+ mappings={[{ ManagementBoard: true, Membership: 'Board' }]}
88
+ users={[
89
+ {
90
+ id: 4,
91
+ Organisation: 'Org4',
92
+ Title: 'Board Member',
93
+ Email: 'board@example.org',
94
+ NFP: 'NFP role',
95
+ Membership: ['Board', 'Group A'],
96
+ OtherMemberships: ['Group B'],
97
+ },
98
+ ]}
99
+ />,
100
+ );
101
+
102
+ const otherMembershipsColumn = gridProps[0].columns.find((c) => c.field === 'OtherMemberships');
103
+ const cellHtml = renderToStaticMarkup(
104
+ otherMembershipsColumn.renderCell({
105
+ row: {
106
+ BoardMembership: 'Board',
107
+ Membership: ['Board', 'Group A'],
108
+ OtherMemberships: ['Group B'],
109
+ NFP: 'NFP role',
110
+ },
111
+ }),
112
+ );
113
+
114
+ expect(cellHtml).toContain('Group A');
115
+ expect(cellHtml).toContain('Group B');
116
+ expect(cellHtml).toContain('NFP role');
117
+ expect(cellHtml).not.toContain('>Board<');
118
+ });
119
+ });