eionet2-dashboard 3.2.4 → 3.2.6
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.
- package/CHANGELOG.md +8 -1
- package/Jenkinsfile +12 -4
- package/package.json +1 -1
- package/tabs/src/components/BottomMenu.jsx +4 -2
- package/tabs/src/components/ResizableGrid.jsx +3 -3
- package/tabs/src/components/ResizableGrid.test.jsx +3 -3
- package/tabs/src/components/Tab.jsx +5 -3
- package/tabs/src/components/TabConfig.jsx +23 -23
- package/tabs/src/components/TabConfig.test.jsx +15 -9
- package/tabs/src/components/UserMenu.jsx +8 -6
- package/tabs/src/components/event_registration/EventExternalRegistration.jsx +1 -1
- package/tabs/src/components/event_registration/EventExternalRegistration.test.jsx +233 -9
- package/tabs/src/components/my_country/CountryProgress.jsx +8 -6
- package/tabs/src/components/self_service/UserEdit.jsx +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,12 +4,19 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
### [3.2.6](https://github.com/eea/eionet2-dashboard/compare/3.2.5...3.2.6) - 14 May 2026
|
|
8
|
+
|
|
9
|
+
#### :house: Internal changes
|
|
10
|
+
|
|
11
|
+
- chore: sonar [Mihai Nicolae - [`b60d90b`](https://github.com/eea/eionet2-dashboard/commit/b60d90b945ce2d76b273edccda547042b1327e16)]
|
|
12
|
+
|
|
13
|
+
### [3.2.5](https://github.com/eea/eionet2-dashboard/compare/3.2.4...3.2.5) - 14 May 2026
|
|
14
|
+
|
|
7
15
|
### [3.2.4](https://github.com/eea/eionet2-dashboard/compare/3.2.3...3.2.4) - 14 May 2026
|
|
8
16
|
|
|
9
17
|
#### :house: Internal changes
|
|
10
18
|
|
|
11
19
|
- chore: sonar fix [Mihai Nicolae - [`8f86721`](https://github.com/eea/eionet2-dashboard/commit/8f8672179694b26bd95ca2fc8c5092e2eca0bc7a)]
|
|
12
|
-
- chore: sonar [Mihai Nicolae - [`12a2a41`](https://github.com/eea/eionet2-dashboard/commit/12a2a41084b8dcc4c11ae5e3149a8a359bf5369c)]
|
|
13
20
|
|
|
14
21
|
### [3.2.3](https://github.com/eea/eionet2-dashboard/compare/3.2.2...3.2.3) - 24 April 2026
|
|
15
22
|
|
package/Jenkinsfile
CHANGED
|
@@ -30,7 +30,9 @@ pipeline {
|
|
|
30
30
|
when {
|
|
31
31
|
allOf {
|
|
32
32
|
environment name: 'CHANGE_ID', value: ''
|
|
33
|
-
|
|
33
|
+
expression {
|
|
34
|
+
return !(sh(returnStdout: true, script: 'git log -1 --pretty=%s').trim() ==~ /Automated release [0-9.]+/)
|
|
35
|
+
}
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
steps {
|
|
@@ -47,7 +49,9 @@ pipeline {
|
|
|
47
49
|
when {
|
|
48
50
|
allOf {
|
|
49
51
|
environment name: 'CHANGE_ID', value: ''
|
|
50
|
-
|
|
52
|
+
expression {
|
|
53
|
+
return !(sh(returnStdout: true, script: 'git log -1 --pretty=%s').trim() ==~ /Automated release [0-9.]+/)
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
steps {
|
|
@@ -61,7 +65,9 @@ pipeline {
|
|
|
61
65
|
when {
|
|
62
66
|
allOf {
|
|
63
67
|
environment name: 'CHANGE_ID', value: ''
|
|
64
|
-
|
|
68
|
+
expression {
|
|
69
|
+
return !(sh(returnStdout: true, script: 'git log -1 --pretty=%s').trim() ==~ /Automated release [0-9.]+/)
|
|
70
|
+
}
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
steps {
|
|
@@ -98,7 +104,9 @@ pipeline {
|
|
|
98
104
|
when {
|
|
99
105
|
allOf {
|
|
100
106
|
environment name: 'CHANGE_ID', value: ''
|
|
101
|
-
|
|
107
|
+
expression {
|
|
108
|
+
return !(sh(returnStdout: true, script: 'git log -1 --pretty=%s').trim() ==~ /Automated release [0-9.]+/)
|
|
109
|
+
}
|
|
102
110
|
anyOf {
|
|
103
111
|
branch 'master'
|
|
104
112
|
branch 'develop'
|
package/package.json
CHANGED
|
@@ -44,8 +44,10 @@ export function BottomMenu({ configuration }) {
|
|
|
44
44
|
anchorEl={anchorEl}
|
|
45
45
|
open={open}
|
|
46
46
|
onClose={handleClose}
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
slotProps={{
|
|
48
|
+
list: {
|
|
49
|
+
'aria-labelledby': 'basic-button',
|
|
50
|
+
},
|
|
49
51
|
}}
|
|
50
52
|
>
|
|
51
53
|
<MenuItem
|
|
@@ -31,10 +31,10 @@ export default function ResizableGrid(props) {
|
|
|
31
31
|
return (
|
|
32
32
|
<DataGrid
|
|
33
33
|
sx={{ height: '98%' }}
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
slots={{
|
|
35
|
+
columnResizeIcon: ColumnResizeIcon,
|
|
36
36
|
}}
|
|
37
|
-
|
|
37
|
+
slotProps={{
|
|
38
38
|
panel: {
|
|
39
39
|
placement: 'auto',
|
|
40
40
|
},
|
|
@@ -36,11 +36,11 @@ describe('ResizableGrid', () => {
|
|
|
36
36
|
const props = DataGrid.mock.calls[0][0];
|
|
37
37
|
expect(props.columns).toEqual(columns);
|
|
38
38
|
expect(props.getRowHeight()).toBe(Constants.GridRowHeight);
|
|
39
|
-
expect(props.
|
|
40
|
-
|
|
39
|
+
expect(props.slots).toMatchObject({
|
|
40
|
+
columnResizeIcon: expect.any(Function),
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
renderToStaticMarkup(<props.
|
|
43
|
+
renderToStaticMarkup(<props.slots.columnResizeIcon />);
|
|
44
44
|
expect(mockResizeIcon).toHaveBeenCalledWith(
|
|
45
45
|
expect.objectContaining({ id: 'grid1', onWidthChanged: expect.any(Function) }),
|
|
46
46
|
);
|
|
@@ -350,8 +350,10 @@ export default function Tab() {
|
|
|
350
350
|
anchorEl={anchorEl}
|
|
351
351
|
open={openMobileMenu}
|
|
352
352
|
onClose={handleMenuClose}
|
|
353
|
-
|
|
354
|
-
|
|
353
|
+
slotProps={{
|
|
354
|
+
list: {
|
|
355
|
+
'aria-labelledby': 'basic-button',
|
|
356
|
+
},
|
|
355
357
|
}}
|
|
356
358
|
>
|
|
357
359
|
<MenuItem
|
|
@@ -391,7 +393,7 @@ export default function Tab() {
|
|
|
391
393
|
width: 80,
|
|
392
394
|
},
|
|
393
395
|
}}
|
|
394
|
-
|
|
396
|
+
slotProps={{
|
|
395
397
|
paper: {
|
|
396
398
|
sx: {
|
|
397
399
|
width: 100,
|
|
@@ -10,31 +10,31 @@ import * as microsoftTeams from '@microsoft/teams-js';
|
|
|
10
10
|
class TabConfig extends React.Component {
|
|
11
11
|
render() {
|
|
12
12
|
// Initialize the Microsoft Teams SDK
|
|
13
|
-
microsoftTeams.initialize()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
microsoftTeams.app.initialize().then(() => {
|
|
14
|
+
/**
|
|
15
|
+
* When the user clicks "Save", save the url for your configured tab.
|
|
16
|
+
* This allows for the addition of query string parameters based on
|
|
17
|
+
* the settings selected by the user.
|
|
18
|
+
*/
|
|
19
|
+
microsoftTeams.pages.config.registerOnSaveHandler((saveEvent) => {
|
|
20
|
+
const baseUrl = `https://${window.location.hostname}:${window.location.port}`;
|
|
21
|
+
microsoftTeams.pages.config.setConfig({
|
|
22
|
+
suggestedDisplayName: 'Eionet2 Dashboard',
|
|
23
|
+
entityId: 'dashboard',
|
|
24
|
+
contentUrl: baseUrl + '/index.html#/tab',
|
|
25
|
+
websiteUrl: baseUrl + '/index.html#/tab',
|
|
26
|
+
});
|
|
27
|
+
saveEvent.notifySuccess();
|
|
27
28
|
});
|
|
28
|
-
saveEvent.notifySuccess();
|
|
29
|
-
});
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
/**
|
|
31
|
+
* After verifying that the settings for your tab are correctly
|
|
32
|
+
* filled in by the user you need to set the state of the dialog
|
|
33
|
+
* to be valid. This will enable the save button in the configuration
|
|
34
|
+
* dialog.
|
|
35
|
+
*/
|
|
36
|
+
microsoftTeams.pages.config.setValidityState(true);
|
|
37
|
+
});
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<div>
|
|
@@ -4,24 +4,30 @@ import TabConfig from './TabConfig';
|
|
|
4
4
|
import * as microsoftTeams from '@microsoft/teams-js';
|
|
5
5
|
|
|
6
6
|
jest.mock('@microsoft/teams-js', () => ({
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
app: {
|
|
8
|
+
initialize: jest.fn(() => Promise.resolve()),
|
|
9
|
+
},
|
|
10
|
+
pages: {
|
|
11
|
+
config: {
|
|
12
|
+
registerOnSaveHandler: jest.fn(),
|
|
13
|
+
setConfig: jest.fn(),
|
|
14
|
+
setValidityState: jest.fn(),
|
|
15
|
+
},
|
|
12
16
|
},
|
|
13
17
|
}));
|
|
14
18
|
|
|
15
19
|
describe('TabConfig', () => {
|
|
16
|
-
test('renders configuration content and initializes teams settings', () => {
|
|
20
|
+
test('renders configuration content and initializes teams settings', async () => {
|
|
17
21
|
const originalWindow = global.window;
|
|
18
22
|
global.window = { location: { hostname: 'localhost', port: '3000' } };
|
|
19
23
|
const html = renderToStaticMarkup(<TabConfig />);
|
|
20
24
|
|
|
25
|
+
await Promise.resolve();
|
|
26
|
+
|
|
21
27
|
expect(html).toContain('Tab Configuration');
|
|
22
|
-
expect(microsoftTeams.initialize).toHaveBeenCalled();
|
|
23
|
-
expect(microsoftTeams.
|
|
24
|
-
expect(microsoftTeams.
|
|
28
|
+
expect(microsoftTeams.app.initialize).toHaveBeenCalled();
|
|
29
|
+
expect(microsoftTeams.pages.config.registerOnSaveHandler).toHaveBeenCalled();
|
|
30
|
+
expect(microsoftTeams.pages.config.setValidityState).toHaveBeenCalledWith(true);
|
|
25
31
|
global.window = originalWindow;
|
|
26
32
|
});
|
|
27
33
|
});
|
|
@@ -62,12 +62,14 @@ export function UserMenu({
|
|
|
62
62
|
</Button>
|
|
63
63
|
<Menu
|
|
64
64
|
id="demo-customized-menu"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
slotProps={{
|
|
66
|
+
list: {
|
|
67
|
+
'aria-labelledby': 'demo-customized-button',
|
|
68
|
+
},
|
|
69
|
+
paper: {
|
|
70
|
+
style: {
|
|
71
|
+
width: 300,
|
|
72
|
+
},
|
|
71
73
|
},
|
|
72
74
|
}}
|
|
73
75
|
anchorEl={anchorEl}
|
|
@@ -137,7 +137,7 @@ export function EventExternalRegistration({ event, userInfo }) {
|
|
|
137
137
|
onChange={(e) => {
|
|
138
138
|
participant.ParticipantName = e.target.value;
|
|
139
139
|
}}
|
|
140
|
-
|
|
140
|
+
slotProps={{ htmlInput: { style: { textTransform: 'capitalize' } } }}
|
|
141
141
|
error={Boolean(errors?.name)}
|
|
142
142
|
helperText={errors?.name}
|
|
143
143
|
onBlur={validateField}
|
|
@@ -14,6 +14,9 @@ import { postParticipant } from '../../data/sharepointProvider';
|
|
|
14
14
|
import { getUserByMail } from '../../data/provider';
|
|
15
15
|
|
|
16
16
|
const buttonHandlers = [];
|
|
17
|
+
const fieldHandlers = {};
|
|
18
|
+
const checkboxHandlers = [];
|
|
19
|
+
const stateSetters = [];
|
|
17
20
|
|
|
18
21
|
jest.mock('@mui/material', () => {
|
|
19
22
|
const ReactLocal = require('react');
|
|
@@ -38,14 +41,27 @@ jest.mock('@mui/material', () => {
|
|
|
38
41
|
return {
|
|
39
42
|
Alert: passthrough(),
|
|
40
43
|
Box: passthrough(),
|
|
41
|
-
Checkbox:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
Checkbox: ({ onChange, checked, disabled }) => {
|
|
45
|
+
checkboxHandlers.push({ onChange, checked, disabled });
|
|
46
|
+
return ReactLocal.createElement('input', { type: 'checkbox' });
|
|
47
|
+
},
|
|
48
|
+
TextField: ({ label, id, onChange, onBlur }) => {
|
|
49
|
+
if (id) {
|
|
50
|
+
fieldHandlers[id] = { onChange, onBlur };
|
|
51
|
+
}
|
|
52
|
+
return <div>{label || ''}</div>;
|
|
53
|
+
},
|
|
54
|
+
Button: ({ onClick, children, endIcon }) => {
|
|
44
55
|
const label = labelToText(children);
|
|
45
56
|
if (onClick) {
|
|
46
57
|
buttonHandlers.push({ label, onClick });
|
|
47
58
|
}
|
|
48
|
-
return
|
|
59
|
+
return (
|
|
60
|
+
<button>
|
|
61
|
+
{children}
|
|
62
|
+
{endIcon}
|
|
63
|
+
</button>
|
|
64
|
+
);
|
|
49
65
|
},
|
|
50
66
|
FormControlLabel: ({ label, control }) => (
|
|
51
67
|
<div>
|
|
@@ -83,17 +99,20 @@ jest.mock('../HtmlBox', () => ({
|
|
|
83
99
|
|
|
84
100
|
function mockStateSequence(values) {
|
|
85
101
|
let index = 0;
|
|
102
|
+
stateSetters.length = 0;
|
|
86
103
|
React.useState.mockImplementation((initialValue) => {
|
|
104
|
+
const setter = jest.fn();
|
|
105
|
+
stateSetters.push(setter);
|
|
87
106
|
if (index < values.length) {
|
|
88
107
|
const current = values[index];
|
|
89
108
|
index += 1;
|
|
90
|
-
return [current,
|
|
109
|
+
return [current, setter];
|
|
91
110
|
}
|
|
92
|
-
return [initialValue,
|
|
111
|
+
return [initialValue, setter];
|
|
93
112
|
});
|
|
94
113
|
}
|
|
95
114
|
|
|
96
|
-
function buildState(participantOverride = {}) {
|
|
115
|
+
function buildState(participantOverride = {}, overrides = {}) {
|
|
97
116
|
const participant = {
|
|
98
117
|
MeetingId: 1,
|
|
99
118
|
ParticipantName: 'John Doe',
|
|
@@ -109,7 +128,15 @@ function buildState(participantOverride = {}) {
|
|
|
109
128
|
...participantOverride,
|
|
110
129
|
};
|
|
111
130
|
|
|
112
|
-
return [
|
|
131
|
+
return [
|
|
132
|
+
participant,
|
|
133
|
+
overrides.loading ?? false,
|
|
134
|
+
overrides.errorText ?? '',
|
|
135
|
+
overrides.errors ?? {},
|
|
136
|
+
overrides.successRegister ?? false,
|
|
137
|
+
overrides.physical ?? false,
|
|
138
|
+
overrides.reimbursement ?? false,
|
|
139
|
+
];
|
|
113
140
|
}
|
|
114
141
|
|
|
115
142
|
describe('EventExternalRegistration', () => {
|
|
@@ -123,7 +150,14 @@ describe('EventExternalRegistration', () => {
|
|
|
123
150
|
beforeEach(() => {
|
|
124
151
|
jest.clearAllMocks();
|
|
125
152
|
buttonHandlers.length = 0;
|
|
126
|
-
|
|
153
|
+
checkboxHandlers.length = 0;
|
|
154
|
+
stateSetters.length = 0;
|
|
155
|
+
Object.keys(fieldHandlers).forEach((k) => delete fieldHandlers[k]);
|
|
156
|
+
React.useState.mockImplementation((initialValue) => {
|
|
157
|
+
const setter = jest.fn();
|
|
158
|
+
stateSetters.push(setter);
|
|
159
|
+
return [initialValue, setter];
|
|
160
|
+
});
|
|
127
161
|
getUserByMail.mockResolvedValue(null);
|
|
128
162
|
postParticipant.mockResolvedValue({ id: 77 });
|
|
129
163
|
});
|
|
@@ -216,4 +250,194 @@ describe('EventExternalRegistration', () => {
|
|
|
216
250
|
expect(getUserByMail).toHaveBeenCalledWith('existing@domain.org');
|
|
217
251
|
expect(postParticipant).not.toHaveBeenCalled();
|
|
218
252
|
});
|
|
253
|
+
|
|
254
|
+
test('rejects emails containing a plus sign', async () => {
|
|
255
|
+
const state = buildState({ Email: 'user+alias@example.org', ParticipantName: 'Valid Name' });
|
|
256
|
+
mockStateSequence(state);
|
|
257
|
+
|
|
258
|
+
renderToStaticMarkup(
|
|
259
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const register = buttonHandlers.find((b) => b.label === 'Register');
|
|
263
|
+
await register.onClick();
|
|
264
|
+
|
|
265
|
+
expect(getUserByMail).not.toHaveBeenCalled();
|
|
266
|
+
expect(postParticipant).not.toHaveBeenCalled();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('skips registration when required fields are empty', async () => {
|
|
270
|
+
const event = { ...baseEvent, Participants: [] };
|
|
271
|
+
const state = buildState({ ParticipantName: '', Email: '' });
|
|
272
|
+
mockStateSequence(state);
|
|
273
|
+
|
|
274
|
+
renderToStaticMarkup(<EventExternalRegistration event={event} userInfo={{ country: 'RO' }} />);
|
|
275
|
+
|
|
276
|
+
const register = buttonHandlers.find((b) => b.label === 'Register');
|
|
277
|
+
await register.onClick();
|
|
278
|
+
|
|
279
|
+
expect(getUserByMail).not.toHaveBeenCalled();
|
|
280
|
+
expect(postParticipant).not.toHaveBeenCalled();
|
|
281
|
+
expect(event.Participants.length).toBe(0);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
test('does not mutate event when postParticipant returns no response', async () => {
|
|
285
|
+
postParticipant.mockResolvedValueOnce(undefined);
|
|
286
|
+
const event = { ...baseEvent, Participants: [] };
|
|
287
|
+
const state = buildState({ Email: 'new@example.org', ParticipantName: 'New One' });
|
|
288
|
+
const participant = state[0];
|
|
289
|
+
mockStateSequence(state);
|
|
290
|
+
|
|
291
|
+
renderToStaticMarkup(<EventExternalRegistration event={event} userInfo={{ country: 'RO' }} />);
|
|
292
|
+
|
|
293
|
+
const register = buttonHandlers.find((b) => b.label === 'Register');
|
|
294
|
+
await register.onClick();
|
|
295
|
+
|
|
296
|
+
expect(postParticipant).toHaveBeenCalled();
|
|
297
|
+
expect(participant.id).toBeUndefined();
|
|
298
|
+
expect(event.Participants.length).toBe(0);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test('field onChange handlers mutate the participant', () => {
|
|
302
|
+
const state = buildState();
|
|
303
|
+
const participant = state[0];
|
|
304
|
+
mockStateSequence(state);
|
|
305
|
+
|
|
306
|
+
renderToStaticMarkup(
|
|
307
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
fieldHandlers.name.onChange({ target: { value: 'Jane Roe' } });
|
|
311
|
+
fieldHandlers.email.onChange({ target: { value: 'jane.roe@example.org' } });
|
|
312
|
+
|
|
313
|
+
expect(participant.ParticipantName).toBe('Jane Roe');
|
|
314
|
+
expect(participant.Email).toBe('jane.roe@example.org');
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test('validateField records a name error on blur of an empty name field', () => {
|
|
318
|
+
const state = buildState({ ParticipantName: '' });
|
|
319
|
+
mockStateSequence(state);
|
|
320
|
+
|
|
321
|
+
renderToStaticMarkup(
|
|
322
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const setErrors = stateSetters[3];
|
|
326
|
+
fieldHandlers.name.onBlur({ target: { id: 'name' } });
|
|
327
|
+
|
|
328
|
+
expect(setErrors).toHaveBeenCalled();
|
|
329
|
+
expect(setErrors.mock.calls[0][0].name).toBeTruthy();
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
test('validateField records an email error on blur of an empty email field', () => {
|
|
333
|
+
const state = buildState({ Email: '' });
|
|
334
|
+
mockStateSequence(state);
|
|
335
|
+
|
|
336
|
+
renderToStaticMarkup(
|
|
337
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const setErrors = stateSetters[3];
|
|
341
|
+
fieldHandlers.email.onBlur({ target: { id: 'email' } });
|
|
342
|
+
|
|
343
|
+
expect(setErrors).toHaveBeenCalled();
|
|
344
|
+
expect(setErrors.mock.calls[0][0].email).toBeTruthy();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test('validateField logs a warning for an unknown field id', () => {
|
|
348
|
+
mockStateSequence(buildState());
|
|
349
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
350
|
+
|
|
351
|
+
renderToStaticMarkup(
|
|
352
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
fieldHandlers.name.onBlur({ target: { id: 'unknown' } });
|
|
356
|
+
|
|
357
|
+
expect(logSpy).toHaveBeenCalledWith('Undefined field for validation');
|
|
358
|
+
logSpy.mockRestore();
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test('checking physical participation updates the participant', () => {
|
|
362
|
+
const state = buildState();
|
|
363
|
+
const participant = state[0];
|
|
364
|
+
mockStateSequence(state);
|
|
365
|
+
|
|
366
|
+
renderToStaticMarkup(
|
|
367
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
checkboxHandlers[0].onChange({}, true);
|
|
371
|
+
|
|
372
|
+
expect(participant.PhysicalParticipation).toBe(true);
|
|
373
|
+
expect(stateSetters[5]).toHaveBeenCalledWith(true);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test('unchecking physical participation also resets reimbursement', () => {
|
|
377
|
+
const state = buildState(
|
|
378
|
+
{ PhysicalParticipation: true, EEAReimbursementRequested: true },
|
|
379
|
+
{ physical: true, reimbursement: true },
|
|
380
|
+
);
|
|
381
|
+
const participant = state[0];
|
|
382
|
+
mockStateSequence(state);
|
|
383
|
+
|
|
384
|
+
renderToStaticMarkup(
|
|
385
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
checkboxHandlers[0].onChange({}, false);
|
|
389
|
+
|
|
390
|
+
expect(participant.PhysicalParticipation).toBe(false);
|
|
391
|
+
expect(participant.EEAReimbursementRequested).toBe(false);
|
|
392
|
+
expect(stateSetters[6]).toHaveBeenCalledWith(false);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test('checking reimbursement updates the participant', () => {
|
|
396
|
+
const state = buildState({ PhysicalParticipation: true }, { physical: true });
|
|
397
|
+
const participant = state[0];
|
|
398
|
+
mockStateSequence(state);
|
|
399
|
+
|
|
400
|
+
renderToStaticMarkup(
|
|
401
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
checkboxHandlers[1].onChange({}, true);
|
|
405
|
+
|
|
406
|
+
expect(participant.EEAReimbursementRequested).toBe(true);
|
|
407
|
+
expect(stateSetters[6]).toHaveBeenCalledWith(true);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test('does not render offline checkboxes when the event is online', () => {
|
|
411
|
+
mockStateSequence(buildState());
|
|
412
|
+
|
|
413
|
+
renderToStaticMarkup(
|
|
414
|
+
<EventExternalRegistration
|
|
415
|
+
event={{ ...baseEvent, IsOffline: false }}
|
|
416
|
+
userInfo={{ country: 'RO' }}
|
|
417
|
+
/>,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
expect(checkboxHandlers).toHaveLength(0);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test('renders the error alert when errorText is set', () => {
|
|
424
|
+
mockStateSequence(buildState({}, { errorText: 'Something went wrong' }));
|
|
425
|
+
|
|
426
|
+
const html = renderToStaticMarkup(
|
|
427
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
expect(html).toContain('Something went wrong');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test('renders the success alert with a check icon when registration succeeded', () => {
|
|
434
|
+
mockStateSequence(buildState({}, { successRegister: true }));
|
|
435
|
+
|
|
436
|
+
const html = renderToStaticMarkup(
|
|
437
|
+
<EventExternalRegistration event={{ ...baseEvent }} userInfo={{ country: 'RO' }} />,
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
expect(html).toContain('Invitation success');
|
|
441
|
+
expect(html).toContain('check-icon');
|
|
442
|
+
});
|
|
219
443
|
});
|
|
@@ -49,12 +49,14 @@ export function CountryProgress({ lastYears, configuration }) {
|
|
|
49
49
|
Yearly overview:
|
|
50
50
|
</Typography>
|
|
51
51
|
<Tabs
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
slotProps={{
|
|
53
|
+
indicator: {
|
|
54
|
+
sx: {
|
|
55
|
+
bottom: 0,
|
|
56
|
+
height: 10,
|
|
57
|
+
backgroundColor: 'secondary.main',
|
|
58
|
+
clipPath: 'polygon(50% 0, 0 100%, 100% 100%)',
|
|
59
|
+
},
|
|
58
60
|
},
|
|
59
61
|
}}
|
|
60
62
|
value={tabsValue}
|
|
@@ -164,7 +164,7 @@ export function UserEdit({ user, configuration }) {
|
|
|
164
164
|
user.FirstName = e.target.value;
|
|
165
165
|
validateField(e);
|
|
166
166
|
}}
|
|
167
|
-
|
|
167
|
+
slotProps={{ htmlInput: { style: { textTransform: 'capitalize' } } }}
|
|
168
168
|
error={Boolean(errors?.firstName)}
|
|
169
169
|
helperText={errors?.firstName}
|
|
170
170
|
onBlur={validateField}
|
|
@@ -181,7 +181,7 @@ export function UserEdit({ user, configuration }) {
|
|
|
181
181
|
user.LastName = e.target.value;
|
|
182
182
|
validateField(e);
|
|
183
183
|
}}
|
|
184
|
-
|
|
184
|
+
slotProps={{ htmlInput: { style: { textTransform: 'capitalize' } } }}
|
|
185
185
|
error={Boolean(errors?.lastName)}
|
|
186
186
|
helperText={errors?.lastName}
|
|
187
187
|
onBlur={validateField}
|
|
@@ -197,7 +197,7 @@ export function UserEdit({ user, configuration }) {
|
|
|
197
197
|
user.JobTitle = e.target.value;
|
|
198
198
|
validateField(e);
|
|
199
199
|
}}
|
|
200
|
-
|
|
200
|
+
slotProps={{ htmlInput: { style: { textTransform: 'capitalize' } } }}
|
|
201
201
|
/>
|
|
202
202
|
<TextField
|
|
203
203
|
autoComplete="off"
|
|
@@ -210,7 +210,7 @@ export function UserEdit({ user, configuration }) {
|
|
|
210
210
|
user.Phone = e.target.value;
|
|
211
211
|
validateField(e);
|
|
212
212
|
}}
|
|
213
|
-
|
|
213
|
+
slotProps={{ htmlInput: { maxLength: 15 } }}
|
|
214
214
|
error={Boolean(errors?.phone)}
|
|
215
215
|
helperText={errors?.phone}
|
|
216
216
|
onBlur={validateField}
|