@eeacms/volto-bise-policy 1.3.7 → 1.3.9
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 +28 -0
- package/package.json +1 -1
- package/src/components/Widgets/EUNISObjectListWidget.test.jsx +391 -0
- package/src/components/Widgets/NRRWidgets.jsx +136 -112
- package/src/components/Widgets/NRRWidgets.test.jsx +255 -0
- package/src/components/Widgets/styles.less +4 -0
- package/src/index.js +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,34 @@ 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
|
+
### [1.3.9](https://github.com/eea/volto-bise-policy/compare/1.3.8...1.3.9) - 27 August 2026
|
|
8
|
+
|
|
9
|
+
#### :bug: Bug Fixes
|
|
10
|
+
|
|
11
|
+
- fix(NRRWidgets): small fix for Measures widgets [laszlocseh - [`8989933`](https://github.com/eea/volto-bise-policy/commit/89899332011e4882b3c77209103b2460b3dfce3c)]
|
|
12
|
+
|
|
13
|
+
#### :hammer_and_wrench: Others
|
|
14
|
+
|
|
15
|
+
- test(NRRWidgets): update test to expect leading label for measures [laszlocseh - [`913e8ad`](https://github.com/eea/volto-bise-policy/commit/913e8adb1d5a575978f96c2bf8667d924322e26d)]
|
|
16
|
+
### [1.3.8](https://github.com/eea/volto-bise-policy/compare/1.3.7...1.3.8) - 26 August 2026
|
|
17
|
+
|
|
18
|
+
#### :rocket: New Features
|
|
19
|
+
|
|
20
|
+
- feat: add NRR widget views and update HTML structure [laszlocseh - [`3ba2075`](https://github.com/eea/volto-bise-policy/commit/3ba207547d4f56790b5a10a23b6aefabb9bb3b70)]
|
|
21
|
+
|
|
22
|
+
#### :nail_care: Enhancements
|
|
23
|
+
|
|
24
|
+
- refactor(NRRWidgets): refactor shared components and parse helper [laszlocseh - [`8a0bbfa`](https://github.com/eea/volto-bise-policy/commit/8a0bbfaac42647a244b180b393ff3dea61a8a5c5)]
|
|
25
|
+
|
|
26
|
+
#### :house: Internal changes
|
|
27
|
+
|
|
28
|
+
- style: Automated code fix [eea-jenkins - [`0d9e222`](https://github.com/eea/volto-bise-policy/commit/0d9e222a45bad204e1d22ca7f1939ab6c1166549)]
|
|
29
|
+
|
|
30
|
+
#### :hammer_and_wrench: Others
|
|
31
|
+
|
|
32
|
+
- test(EUNISObjectListWidget): mock uuid module [laszlocseh - [`4053526`](https://github.com/eea/volto-bise-policy/commit/405352685a516f18e1c4f04d36f069dfcc584f36)]
|
|
33
|
+
- test(EUNISObjectListWidget): mock volto helpers to fix jest resolution [laszlocseh - [`92910f0`](https://github.com/eea/volto-bise-policy/commit/92910f09ebb1fa7ebed4caf1442db7935cb58684)]
|
|
34
|
+
- test: add unit tests for EUNIS and NRR widgets [laszlocseh - [`862db64`](https://github.com/eea/volto-bise-policy/commit/862db648d7a3546e3f7c7c5d812d35497116adb4)]
|
|
7
35
|
### [1.3.7](https://github.com/eea/volto-bise-policy/compare/1.3.6...1.3.7) - 20 August 2026
|
|
8
36
|
|
|
9
37
|
#### :rocket: New Features
|
package/package.json
CHANGED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { IntlProvider } from 'react-intl';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
EUNISMSFDView,
|
|
8
|
+
EUNISCodeView,
|
|
9
|
+
EUNISHDView,
|
|
10
|
+
EUNISEuropeanRedListView,
|
|
11
|
+
EUNISLinksToFinerEUNISHabitatsView,
|
|
12
|
+
EUNISCountryCodeView,
|
|
13
|
+
EUNISRegionalSeaConventionValueView,
|
|
14
|
+
EUNISEuropeanRedListWidget,
|
|
15
|
+
EUNISMSFDWidget,
|
|
16
|
+
EUNISHDWidget,
|
|
17
|
+
EUNISLinksToFinerEUNISHabitatsWidget,
|
|
18
|
+
EUNISRegionalSeaConventionValueWidget,
|
|
19
|
+
EUNISCountryCodeWidget,
|
|
20
|
+
} from './EUNISObjectListWidget';
|
|
21
|
+
|
|
22
|
+
// Mock the heavy @plone/volto building blocks used by BaseObjectListWidget so
|
|
23
|
+
// that we can render the widget without mounting react-beautiful-dnd, the
|
|
24
|
+
// ObjectWidget editor or the full FormFieldWrapper machinery.
|
|
25
|
+
jest.mock('@plone/volto/components/manage/DragDropList/DragDropList', () => ({
|
|
26
|
+
__esModule: true,
|
|
27
|
+
default: ({ childList, onMoveItem, children }) => (
|
|
28
|
+
<div data-testid="dragdrop-list">
|
|
29
|
+
{childList.map((childEntry, index) =>
|
|
30
|
+
children({
|
|
31
|
+
child: childEntry[1],
|
|
32
|
+
childId: childEntry[0],
|
|
33
|
+
index,
|
|
34
|
+
draginfo: {
|
|
35
|
+
innerRef: jest.fn(),
|
|
36
|
+
draggableProps: {},
|
|
37
|
+
dragHandleProps: {},
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
jest.mock('@plone/volto/components/manage/Widgets/FormFieldWrapper', () => ({
|
|
46
|
+
__esModule: true,
|
|
47
|
+
default: ({ children }) => <div data-testid="form-wrapper">{children}</div>,
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
jest.mock('@plone/volto/components/manage/Widgets/ObjectWidget', () => ({
|
|
51
|
+
__esModule: true,
|
|
52
|
+
default: () => <div data-testid="object-widget" />,
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
jest.mock('@plone/volto/components/theme/Icon/Icon', () => ({
|
|
56
|
+
__esModule: true,
|
|
57
|
+
default: () => <span data-testid="icon" />,
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// Mock the volto helpers too: the real `Blocks.js` requires `uuid`, which
|
|
61
|
+
// cannot always be resolved from within node_modules in the jest environment
|
|
62
|
+
// (and the repository already mocks @plone/volto/helpers in other suites).
|
|
63
|
+
// The schemas used here have no `default`s, so `applySchemaDefaults` is
|
|
64
|
+
// faithfully reproduced by returning the input data.
|
|
65
|
+
jest.mock('@plone/volto/helpers/Blocks/Blocks', () => ({
|
|
66
|
+
__esModule: true,
|
|
67
|
+
applySchemaDefaults: jest.fn(({ data }) => data),
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
jest.mock('@plone/volto/helpers/Utils/Utils', () => ({
|
|
71
|
+
__esModule: true,
|
|
72
|
+
reorderArray: jest.fn((array) => array),
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
// `uuid` is imported (and used) by the widget module directly. In some jest
|
|
76
|
+
// environments it cannot be resolved from the add-on source, so use a virtual
|
|
77
|
+
// mock to bypass module resolution entirely and return unique, deterministic ids
|
|
78
|
+
// (same pattern as volto-freshwater-policy's NWRMObjectListWidget test).
|
|
79
|
+
jest.mock(
|
|
80
|
+
'uuid',
|
|
81
|
+
() => {
|
|
82
|
+
let value = 0;
|
|
83
|
+
return {
|
|
84
|
+
v4: jest.fn(() => `uuid-${value++}`),
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
{ virtual: true },
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// EUNISCodeView reads the current content data from the Redux store. Expose a
|
|
91
|
+
// mutable slice so each test can control what useSelector returns.
|
|
92
|
+
let mockContentData = {};
|
|
93
|
+
jest.mock('react-redux', () => ({
|
|
94
|
+
...jest.requireActual('react-redux'),
|
|
95
|
+
useSelector: (selector) => selector({ content: { data: mockContentData } }),
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
const renderView = (Component, props) => render(<Component {...props} />);
|
|
99
|
+
|
|
100
|
+
describe('EUNISMSFDView', () => {
|
|
101
|
+
it('renders nothing when value is empty', () => {
|
|
102
|
+
const { container } = renderView(EUNISMSFDView, { value: '' });
|
|
103
|
+
expect(container.firstChild).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('renders nothing on invalid JSON', () => {
|
|
107
|
+
const { container } = renderView(EUNISMSFDView, { value: 'not json' });
|
|
108
|
+
expect(container.firstChild).toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('renders relation and value for each item', () => {
|
|
112
|
+
const value = JSON.stringify([
|
|
113
|
+
{ '@id': '/1', relation: '=', value: 'V1' },
|
|
114
|
+
{ '@id': '/2', relation: '#', value: 'V2' },
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
const { container } = renderView(EUNISMSFDView, { value });
|
|
118
|
+
|
|
119
|
+
const view = container.querySelector('.eunis-widget-view');
|
|
120
|
+
expect(view).toBeInTheDocument();
|
|
121
|
+
|
|
122
|
+
const items = view.querySelectorAll('.eunis-widget-item');
|
|
123
|
+
expect(items).toHaveLength(2);
|
|
124
|
+
expect(items[0].textContent).toContain('=');
|
|
125
|
+
expect(items[0].textContent).toContain('V1');
|
|
126
|
+
expect(items[1].textContent).toContain('#');
|
|
127
|
+
expect(items[1].textContent).toContain('V2');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('supports an object value with a value array', () => {
|
|
131
|
+
const value = { value: [{ '@id': '/1', relation: '=', value: 'V' }] };
|
|
132
|
+
const { container } = renderView(EUNISMSFDView, { value });
|
|
133
|
+
expect(container.querySelector('.eunis-widget-view')).toBeInTheDocument();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('EUNISHDView', () => {
|
|
138
|
+
it('renders nothing when value is empty', () => {
|
|
139
|
+
const { container } = renderView(EUNISHDView, { value: [] });
|
|
140
|
+
expect(container.firstChild).toBeNull();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('renders the item.link href when present', () => {
|
|
144
|
+
const value = JSON.stringify([
|
|
145
|
+
{ '@id': '/1', relation: '=', value: 'V', link: 'http://x/1' },
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
const { container } = renderView(EUNISHDView, { value });
|
|
149
|
+
|
|
150
|
+
const a = container.querySelector('.eunis-widget-item a');
|
|
151
|
+
expect(a).toHaveAttribute('href', 'http://x/1');
|
|
152
|
+
expect(a).toHaveTextContent('V');
|
|
153
|
+
expect(
|
|
154
|
+
container.querySelector('.eunis-widget-item span'),
|
|
155
|
+
).toHaveTextContent('=');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('renders the Annex I URL when no link is present', () => {
|
|
159
|
+
const value = JSON.stringify([{ '@id': '/1', relation: '>', value: 'A1' }]);
|
|
160
|
+
|
|
161
|
+
const { container } = renderView(EUNISHDView, { value });
|
|
162
|
+
|
|
163
|
+
const a = container.querySelector('.eunis-widget-item a');
|
|
164
|
+
expect(a).toHaveAttribute('href', '/habitats/ANNEX1_A1');
|
|
165
|
+
expect(a).toHaveAttribute('target', '_blank');
|
|
166
|
+
expect(a).toHaveAttribute('rel', 'noopener');
|
|
167
|
+
expect(a).toHaveTextContent('A1');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('EUNISEuropeanRedListView', () => {
|
|
172
|
+
it('renders the item.link href when present', () => {
|
|
173
|
+
const value = JSON.stringify([
|
|
174
|
+
{ '@id': '/1', relation: '=', value: 'V', link: 'http://x/2' },
|
|
175
|
+
]);
|
|
176
|
+
|
|
177
|
+
const { container } = renderView(EUNISEuropeanRedListView, { value });
|
|
178
|
+
|
|
179
|
+
expect(container.querySelector('.eunis-widget-item a')).toHaveAttribute(
|
|
180
|
+
'href',
|
|
181
|
+
'http://x/2',
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('builds the Red List URL from the code part of the value', () => {
|
|
186
|
+
const value = JSON.stringify([
|
|
187
|
+
{ '@id': '/1', relation: '=', value: 'Kelp communities - BAL1' },
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
const { container } = renderView(EUNISEuropeanRedListView, { value });
|
|
191
|
+
|
|
192
|
+
const a = container.querySelector('.eunis-widget-item a');
|
|
193
|
+
expect(a).toHaveAttribute('href', '/habitats_rl/REDLIST_BAL1');
|
|
194
|
+
expect(a).toHaveAttribute('target', '_blank');
|
|
195
|
+
expect(a).toHaveTextContent('Kelp communities - BAL1');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe('EUNISLinksToFinerEUNISHabitatsView', () => {
|
|
200
|
+
it('renders the item.link href when present', () => {
|
|
201
|
+
const value = JSON.stringify([
|
|
202
|
+
{ '@id': '/1', value: 'E1', link: 'http://x/3' },
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
const { container } = renderView(EUNISLinksToFinerEUNISHabitatsView, {
|
|
206
|
+
value,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
expect(container.querySelector('.eunis-widget-item a')).toHaveAttribute(
|
|
210
|
+
'href',
|
|
211
|
+
'http://x/3',
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('builds the EUNIS revised URL when no link is present', () => {
|
|
216
|
+
const value = JSON.stringify([{ '@id': '/1', value: 'E1' }]);
|
|
217
|
+
|
|
218
|
+
const { container } = renderView(EUNISLinksToFinerEUNISHabitatsView, {
|
|
219
|
+
value,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const a = container.querySelector('.eunis-widget-item a');
|
|
223
|
+
expect(a).toHaveAttribute('href', '/habitats_eunis_revised/EUNISrev_E1');
|
|
224
|
+
expect(a).toHaveAttribute('target', '_blank');
|
|
225
|
+
expect(a).toHaveTextContent('E1');
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('EUNISCountryCodeView', () => {
|
|
230
|
+
it('renders nothing when value is empty', () => {
|
|
231
|
+
const { container } = renderView(EUNISCountryCodeView, { value: '' });
|
|
232
|
+
expect(container.firstChild).toBeNull();
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('renders countries and their national items', () => {
|
|
236
|
+
const value = JSON.stringify([
|
|
237
|
+
{
|
|
238
|
+
'@id': '/cc1',
|
|
239
|
+
countryCode: ['BE', 'NL'],
|
|
240
|
+
national: [{ '@id': '/n1', relation: '=', value: 'V1' }],
|
|
241
|
+
},
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
const { container } = renderView(EUNISCountryCodeView, { value });
|
|
245
|
+
|
|
246
|
+
expect(container.querySelector('.country-item')).toHaveTextContent(
|
|
247
|
+
'BE, NL',
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const items = container.querySelectorAll('.eunis-widget-item');
|
|
251
|
+
expect(items).toHaveLength(1);
|
|
252
|
+
expect(items[0].textContent).toContain('=');
|
|
253
|
+
expect(items[0].textContent).toContain('V1');
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe('EUNISRegionalSeaConventionValueView', () => {
|
|
258
|
+
it('behaves like the MSFD view', () => {
|
|
259
|
+
const value = JSON.stringify([{ '@id': '/1', relation: '=', value: 'V' }]);
|
|
260
|
+
|
|
261
|
+
const { container } = renderView(EUNISRegionalSeaConventionValueView, {
|
|
262
|
+
value,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
expect(container.querySelector('.eunis-widget-view')).toBeInTheDocument();
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe('EUNISCodeView', () => {
|
|
270
|
+
beforeEach(() => {
|
|
271
|
+
mockContentData = {};
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('builds the default EUNIS revised href', () => {
|
|
275
|
+
const { container } = renderView(EUNISCodeView, { value: 'E123' });
|
|
276
|
+
|
|
277
|
+
const a = container.querySelector('a');
|
|
278
|
+
expect(a).toHaveAttribute('href', '/habitats_eunis_revised/EUNISrev_E123');
|
|
279
|
+
expect(a).toHaveAttribute('target', '_blank');
|
|
280
|
+
expect(a).toHaveAttribute('rel', 'noopener');
|
|
281
|
+
expect(a).toHaveTextContent('E123');
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('uses the trimmed eunis_code_alt_url from the store when present', () => {
|
|
285
|
+
mockContentData = { eunis_code_alt_url: ' http://alt/1 ' };
|
|
286
|
+
const { container } = renderView(EUNISCodeView, { value: 'E123' });
|
|
287
|
+
|
|
288
|
+
expect(container.querySelector('a')).toHaveAttribute(
|
|
289
|
+
'href',
|
|
290
|
+
'http://alt/1',
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('prefers the content prop over the store', () => {
|
|
295
|
+
mockContentData = { eunis_code_alt_url: 'http://store/1' };
|
|
296
|
+
const { container } = renderView(EUNISCodeView, {
|
|
297
|
+
value: 'E123',
|
|
298
|
+
content: { eunis_code_alt_url: 'http://content/2' },
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
expect(container.querySelector('a')).toHaveAttribute(
|
|
302
|
+
'href',
|
|
303
|
+
'http://content/2',
|
|
304
|
+
);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('returns the value unchanged when no value is provided', () => {
|
|
308
|
+
const { container } = renderView(EUNISCodeView, { value: null });
|
|
309
|
+
expect(container.firstChild).toBeNull();
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const renderWidget = (Widget, props) =>
|
|
314
|
+
render(
|
|
315
|
+
<IntlProvider locale="en" messages={{}}>
|
|
316
|
+
<Widget
|
|
317
|
+
id="w"
|
|
318
|
+
value={{ value: [] }}
|
|
319
|
+
fieldSet="default"
|
|
320
|
+
block={{}}
|
|
321
|
+
{...props}
|
|
322
|
+
/>
|
|
323
|
+
</IntlProvider>,
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
describe('BaseObjectListWidget (via the named widget wrappers)', () => {
|
|
327
|
+
it.each([
|
|
328
|
+
['MSFD', EUNISMSFDWidget, 'Add MSFD'],
|
|
329
|
+
['HD (Annex I)', EUNISHDWidget, 'Add HD (Annex I)'],
|
|
330
|
+
['European Red List', EUNISEuropeanRedListWidget, 'Add European Red List'],
|
|
331
|
+
[
|
|
332
|
+
'Links to finer EUNIS habitats',
|
|
333
|
+
EUNISLinksToFinerEUNISHabitatsWidget,
|
|
334
|
+
'Add Links to finer EUNIS habitats',
|
|
335
|
+
],
|
|
336
|
+
[
|
|
337
|
+
'Regional Sea Convention Value',
|
|
338
|
+
EUNISRegionalSeaConventionValueWidget,
|
|
339
|
+
'Add Regional Sea Convention Value',
|
|
340
|
+
],
|
|
341
|
+
['Country Code', EUNISCountryCodeWidget, 'Add Country Code'],
|
|
342
|
+
])('renders the add button for %s', (title, Widget, label) => {
|
|
343
|
+
const { getByRole } = renderWidget(Widget, { onChange: jest.fn() });
|
|
344
|
+
expect(getByRole('button', { name: label })).toBeInTheDocument();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('renders the empty-state input when there are no items', () => {
|
|
348
|
+
const { container } = renderWidget(EUNISMSFDWidget, {
|
|
349
|
+
onChange: jest.fn(),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const hidden = container.querySelector('input[type="hidden"]');
|
|
353
|
+
expect(hidden).toHaveValue('Empty object list');
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('adds a new item when the add button is clicked', () => {
|
|
357
|
+
const onChange = jest.fn();
|
|
358
|
+
const { getByRole } = renderWidget(EUNISMSFDWidget, { onChange });
|
|
359
|
+
|
|
360
|
+
fireEvent.click(getByRole('button', { name: 'Add MSFD' }));
|
|
361
|
+
|
|
362
|
+
expect(onChange).toHaveBeenCalledWith('w', {
|
|
363
|
+
value: expect.arrayContaining([
|
|
364
|
+
expect.objectContaining({ '@id': expect.any(String) }),
|
|
365
|
+
]),
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('removes an item when the remove button is clicked', () => {
|
|
370
|
+
const onChange = jest.fn();
|
|
371
|
+
const value = {
|
|
372
|
+
value: [{ '@id': '/1', relation: '=', value: 'V' }],
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const { getByRole } = render(
|
|
376
|
+
<IntlProvider locale="en" messages={{}}>
|
|
377
|
+
<EUNISMSFDWidget
|
|
378
|
+
id="w"
|
|
379
|
+
value={value}
|
|
380
|
+
fieldSet="default"
|
|
381
|
+
block={{}}
|
|
382
|
+
onChange={onChange}
|
|
383
|
+
/>
|
|
384
|
+
</IntlProvider>,
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
fireEvent.click(getByRole('button', { name: 'Remove item #1' }));
|
|
388
|
+
|
|
389
|
+
expect(onChange).toHaveBeenCalledWith('w', { value: [] });
|
|
390
|
+
});
|
|
391
|
+
});
|
|
@@ -1,165 +1,189 @@
|
|
|
1
1
|
import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
|
|
2
2
|
import { Popup } from 'semantic-ui-react';
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a relation/array field value into a plain array.
|
|
6
|
+
* Handles a JSON-encoded string, a raw array, or an object with a `value` array.
|
|
7
|
+
* Returns [] when the value can't be parsed or is empty.
|
|
8
|
+
*/
|
|
9
|
+
const parseItems = (value) => {
|
|
4
10
|
let parsedValue = value;
|
|
5
11
|
|
|
6
12
|
if (typeof value === 'string') {
|
|
7
13
|
try {
|
|
8
14
|
parsedValue = JSON.parse(value);
|
|
9
15
|
} catch (e) {
|
|
10
|
-
return
|
|
16
|
+
return [];
|
|
11
17
|
}
|
|
12
18
|
}
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
: parsedValue?.value || [];
|
|
20
|
+
return Array.isArray(parsedValue) ? parsedValue : parsedValue?.value || [];
|
|
21
|
+
};
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
const Label = ({ label, secondary = false }) =>
|
|
24
|
+
label ? (
|
|
25
|
+
<span className={secondary ? 'secondary' : undefined}>
|
|
26
|
+
<b>{label}: </b>
|
|
27
|
+
</span>
|
|
28
|
+
) : null;
|
|
29
|
+
|
|
30
|
+
/** Renders a labeled list of items separated by commas. */
|
|
31
|
+
const InlineList = ({ label, secondary, items, render }) => (
|
|
32
|
+
<p>
|
|
33
|
+
<Label label={label} secondary={secondary} />
|
|
34
|
+
{items.map((item, index) => (
|
|
35
|
+
<span key={item['@id'] ?? index}>
|
|
36
|
+
{render(item)}
|
|
37
|
+
{index < items.length - 1 ? ', ' : ''}
|
|
38
|
+
</span>
|
|
39
|
+
))}
|
|
40
|
+
</p>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
/** Renders a labeled <ul> of items. */
|
|
44
|
+
const LinkedList = ({ label, secondary, items, render }) => (
|
|
45
|
+
<div className="nrr-linked-list">
|
|
46
|
+
<Label label={label} secondary={secondary} />
|
|
47
|
+
<ul>
|
|
48
|
+
{items.map((item, index) => (
|
|
49
|
+
<li key={item['@id'] ?? index}>{render(item)}</li>
|
|
50
|
+
))}
|
|
51
|
+
</ul>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
export const NRRTypologyOfMeasuresView = ({ value }) => {
|
|
56
|
+
const items = parseItems(value);
|
|
57
|
+
if (items.length === 0) return null;
|
|
19
58
|
|
|
20
59
|
return (
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
</li>
|
|
33
|
-
);
|
|
34
|
-
})}
|
|
35
|
-
</ul>
|
|
36
|
-
</div>
|
|
60
|
+
<LinkedList
|
|
61
|
+
label="Related typology of measures"
|
|
62
|
+
secondary
|
|
63
|
+
items={items}
|
|
64
|
+
render={(item) => {
|
|
65
|
+
const parts = item.title.split(' - ');
|
|
66
|
+
const code = parts.slice(0, 2).join(' - ');
|
|
67
|
+
const description = parts[parts.length - 1];
|
|
68
|
+
return <Popup content={description} trigger={<b>{code}</b>} />;
|
|
69
|
+
}}
|
|
70
|
+
/>
|
|
37
71
|
);
|
|
38
72
|
};
|
|
39
73
|
|
|
40
74
|
export const NRRArticleView = ({ value }) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (typeof value === 'string') {
|
|
44
|
-
try {
|
|
45
|
-
parsedValue = JSON.parse(value);
|
|
46
|
-
} catch (e) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const items = Array.isArray(parsedValue)
|
|
52
|
-
? parsedValue
|
|
53
|
-
: parsedValue?.value || [];
|
|
54
|
-
|
|
55
|
-
if (!items || items.length === 0) return null;
|
|
75
|
+
const items = parseItems(value);
|
|
76
|
+
if (items.length === 0) return null;
|
|
56
77
|
|
|
57
78
|
return (
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
{item.title.split(' - ')[0].split(' ')[1]}
|
|
65
|
-
{items.indexOf(item) < items.length - 1 ? ', ' : ''}
|
|
66
|
-
</span>
|
|
67
|
-
))}
|
|
68
|
-
</div>
|
|
79
|
+
<InlineList
|
|
80
|
+
label="NRR Article"
|
|
81
|
+
secondary
|
|
82
|
+
items={items}
|
|
83
|
+
render={(item) => item.title.split(' - ')[0].split(' ')[1]}
|
|
84
|
+
/>
|
|
69
85
|
);
|
|
70
86
|
};
|
|
71
87
|
|
|
72
88
|
export const NRRrelatedCaseStudiesView = ({ value }) => {
|
|
73
|
-
|
|
89
|
+
const items = parseItems(value);
|
|
90
|
+
if (items.length === 0) return null;
|
|
74
91
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
return (
|
|
93
|
+
<LinkedList
|
|
94
|
+
label="Related Case Studies"
|
|
95
|
+
secondary
|
|
96
|
+
items={items}
|
|
97
|
+
render={(item) => (
|
|
98
|
+
<UniversalLink href={item['@id']}>{item.title}</UniversalLink>
|
|
99
|
+
)}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
86
103
|
|
|
87
|
-
|
|
104
|
+
export const NRRMeasuresImplementedView = ({ value }) => {
|
|
105
|
+
const items = parseItems(value);
|
|
106
|
+
if (items.length === 0) return null;
|
|
88
107
|
|
|
89
108
|
return (
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
</li>
|
|
99
|
-
))}
|
|
100
|
-
</ul>
|
|
101
|
-
</div>
|
|
109
|
+
<LinkedList
|
|
110
|
+
label="Measures Implemented"
|
|
111
|
+
items={items}
|
|
112
|
+
secondary
|
|
113
|
+
render={(item) => (
|
|
114
|
+
<UniversalLink href={item['@id']}>{item.title}</UniversalLink>
|
|
115
|
+
)}
|
|
116
|
+
/>
|
|
102
117
|
);
|
|
103
118
|
};
|
|
104
119
|
|
|
105
|
-
export const
|
|
106
|
-
|
|
120
|
+
export const NRREcosystemTypologyView = ({ value }) => {
|
|
121
|
+
const items = parseItems(value);
|
|
122
|
+
if (items.length === 0) return null;
|
|
107
123
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
return (
|
|
125
|
+
<InlineList
|
|
126
|
+
label="Ecosystem type"
|
|
127
|
+
secondary
|
|
128
|
+
items={items}
|
|
129
|
+
render={(item) => item.title}
|
|
130
|
+
/>
|
|
131
|
+
);
|
|
132
|
+
};
|
|
115
133
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
export const NRRHabitatEcosystemTypeView = ({ value }) => {
|
|
135
|
+
const items = parseItems(value);
|
|
136
|
+
if (items.length === 0) return null;
|
|
119
137
|
|
|
120
|
-
|
|
138
|
+
return (
|
|
139
|
+
<InlineList
|
|
140
|
+
label="Habitat/Ecosystem type"
|
|
141
|
+
secondary
|
|
142
|
+
items={items}
|
|
143
|
+
render={(item) => item.title}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const NRRScaleOfPlanningView = ({ value }) => {
|
|
149
|
+
const items = parseItems(value);
|
|
150
|
+
if (items.length === 0) return null;
|
|
121
151
|
|
|
122
152
|
return (
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
))}
|
|
130
|
-
</ul>
|
|
131
|
-
</div>
|
|
153
|
+
<InlineList
|
|
154
|
+
label="Scale of planning"
|
|
155
|
+
secondary
|
|
156
|
+
items={items}
|
|
157
|
+
render={(item) => item.title}
|
|
158
|
+
/>
|
|
132
159
|
);
|
|
133
160
|
};
|
|
134
161
|
|
|
135
|
-
export const
|
|
162
|
+
export const NRRCurrentStatusView = ({ value }) => {
|
|
136
163
|
let parsedValue = value;
|
|
137
164
|
|
|
138
165
|
if (typeof value === 'string') {
|
|
139
166
|
try {
|
|
140
167
|
parsedValue = JSON.parse(value);
|
|
141
168
|
} catch (e) {
|
|
142
|
-
|
|
169
|
+
// Not JSON — keep the raw string value.
|
|
143
170
|
}
|
|
144
171
|
}
|
|
145
172
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
: parsedValue?.value || [];
|
|
173
|
+
// Choice field: value is a single token, not an array.
|
|
174
|
+
if (!parsedValue) return null;
|
|
149
175
|
|
|
150
|
-
|
|
176
|
+
const text =
|
|
177
|
+
typeof parsedValue === 'string'
|
|
178
|
+
? parsedValue
|
|
179
|
+
: parsedValue?.title || parsedValue?.token || parsedValue?.value;
|
|
180
|
+
|
|
181
|
+
if (!text) return null;
|
|
151
182
|
|
|
152
183
|
return (
|
|
153
|
-
<
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{items.map((item) => (
|
|
158
|
-
<span key={item['@id']}>
|
|
159
|
-
{item.title}
|
|
160
|
-
{items.indexOf(item) < items.length - 1 ? ', ' : ''}
|
|
161
|
-
</span>
|
|
162
|
-
))}
|
|
163
|
-
</div>
|
|
184
|
+
<p>
|
|
185
|
+
<Label label="Current status" secondary />
|
|
186
|
+
{text}
|
|
187
|
+
</p>
|
|
164
188
|
);
|
|
165
189
|
};
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
NRRTypologyOfMeasuresView,
|
|
7
|
+
NRRArticleView,
|
|
8
|
+
NRRrelatedCaseStudiesView,
|
|
9
|
+
NRRMeasuresImplementedView,
|
|
10
|
+
NRREcosystemTypologyView,
|
|
11
|
+
NRRHabitatEcosystemTypeView,
|
|
12
|
+
NRRScaleOfPlanningView,
|
|
13
|
+
NRRCurrentStatusView,
|
|
14
|
+
} from './NRRWidgets';
|
|
15
|
+
|
|
16
|
+
// Mock the heavy @plone/volto UniversalLink for the linked-list views so that
|
|
17
|
+
// we only assert on the rendered markup without having to mount a Router.
|
|
18
|
+
jest.mock('@plone/volto/components/manage/UniversalLink/UniversalLink', () => ({
|
|
19
|
+
__esModule: true,
|
|
20
|
+
default: ({ href, children }) => <a href={href}>{children}</a>,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// Render the semantic-ui-react Popup inline (instead of via a portal) so the
|
|
24
|
+
// trigger and content are directly assertable. The mock factory uses
|
|
25
|
+
// jest.requireActual to avoid the TDZ hoisting issue of referencing a const.
|
|
26
|
+
jest.mock('semantic-ui-react', () => {
|
|
27
|
+
const actual = jest.requireActual('semantic-ui-react');
|
|
28
|
+
return {
|
|
29
|
+
...actual,
|
|
30
|
+
Popup: ({ content, trigger }) => (
|
|
31
|
+
<div className="popup">
|
|
32
|
+
<div className="trigger">{trigger}</div>
|
|
33
|
+
<div className="content">{content}</div>
|
|
34
|
+
</div>
|
|
35
|
+
),
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('NRRTypologyOfMeasuresView', () => {
|
|
40
|
+
it('renders nothing when value is empty', () => {
|
|
41
|
+
const { container } = render(<NRRTypologyOfMeasuresView value="" />);
|
|
42
|
+
expect(container.firstChild).toBeNull();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('renders nothing when value is invalid JSON', () => {
|
|
46
|
+
const { container } = render(
|
|
47
|
+
<NRRTypologyOfMeasuresView value="not json" />,
|
|
48
|
+
);
|
|
49
|
+
expect(container.firstChild).toBeNull();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('renders a labeled linked list and splits title into code/description', () => {
|
|
53
|
+
const value = JSON.stringify([
|
|
54
|
+
{ '@id': '/1', title: '001 - Sector A - Description one' },
|
|
55
|
+
{ '@id': '/2', title: '002 - Sector B - Description two' },
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
const { container } = render(<NRRTypologyOfMeasuresView value={value} />);
|
|
59
|
+
|
|
60
|
+
const list = container.querySelector('.nrr-linked-list');
|
|
61
|
+
expect(list).toBeInTheDocument();
|
|
62
|
+
expect(list.textContent).toContain('Related typology of measures: ');
|
|
63
|
+
|
|
64
|
+
const popups = list.querySelectorAll('.popup');
|
|
65
|
+
expect(popups).toHaveLength(2);
|
|
66
|
+
|
|
67
|
+
// code = first two parts joined, description = last part
|
|
68
|
+
expect(popups[0].querySelector('.trigger')).toHaveTextContent(
|
|
69
|
+
'001 - Sector A',
|
|
70
|
+
);
|
|
71
|
+
expect(popups[0].querySelector('.content')).toHaveTextContent(
|
|
72
|
+
'Description one',
|
|
73
|
+
);
|
|
74
|
+
expect(popups[1].querySelector('.trigger')).toHaveTextContent(
|
|
75
|
+
'002 - Sector B',
|
|
76
|
+
);
|
|
77
|
+
expect(popups[1].querySelector('.content')).toHaveTextContent(
|
|
78
|
+
'Description two',
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('NRRArticleView', () => {
|
|
84
|
+
it('renders nothing when value is empty', () => {
|
|
85
|
+
const { container } = render(<NRRArticleView value={[]} />);
|
|
86
|
+
expect(container.firstChild).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('extracts the article number from the title and renders an inline list', () => {
|
|
90
|
+
const value = JSON.stringify([
|
|
91
|
+
{ '@id': '/1', title: 'Article 5 - xxx' },
|
|
92
|
+
{ '@id': '/2', title: 'Article 7 - yyy' },
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
const { container } = render(<NRRArticleView value={value} />);
|
|
96
|
+
|
|
97
|
+
const p = container.querySelector('p');
|
|
98
|
+
expect(p).toBeInTheDocument();
|
|
99
|
+
expect(p.textContent).toContain('NRR Article: ');
|
|
100
|
+
// render = title.split(' - ')[0].split(' ')[1]
|
|
101
|
+
expect(p.textContent).toContain('5');
|
|
102
|
+
expect(p.textContent).toContain('7');
|
|
103
|
+
// one label span plus one span per item
|
|
104
|
+
expect(p.querySelectorAll('span').length).toBe(3);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('NRRrelatedCaseStudiesView', () => {
|
|
109
|
+
it('renders nothing when value is empty', () => {
|
|
110
|
+
const { container } = render(<NRRrelatedCaseStudiesView value={null} />);
|
|
111
|
+
expect(container.firstChild).toBeNull();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('renders links to each related case study', () => {
|
|
115
|
+
const value = JSON.stringify([
|
|
116
|
+
{ '@id': '/c1', title: 'Case One' },
|
|
117
|
+
{ '@id': '/c2', title: 'Case Two' },
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
const { container } = render(<NRRrelatedCaseStudiesView value={value} />);
|
|
121
|
+
|
|
122
|
+
const list = container.querySelector('.nrr-linked-list');
|
|
123
|
+
expect(list.textContent).toContain('Related Case Studies: ');
|
|
124
|
+
|
|
125
|
+
const links = list.querySelectorAll('a');
|
|
126
|
+
expect(links).toHaveLength(2);
|
|
127
|
+
expect(links[0]).toHaveAttribute('href', '/c1');
|
|
128
|
+
expect(links[0]).toHaveTextContent('Case One');
|
|
129
|
+
expect(links[1]).toHaveAttribute('href', '/c2');
|
|
130
|
+
expect(links[1]).toHaveTextContent('Case Two');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('NRRMeasuresImplementedView', () => {
|
|
135
|
+
it('renders nothing when value is empty', () => {
|
|
136
|
+
const { container } = render(<NRRMeasuresImplementedView value="" />);
|
|
137
|
+
expect(container.firstChild).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('renders measures as links with a leading label', () => {
|
|
141
|
+
const value = [
|
|
142
|
+
{ '@id': '/m1', title: 'Measure One' },
|
|
143
|
+
{ '@id': '/m2', title: 'Measure Two' },
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
const { container } = render(<NRRMeasuresImplementedView value={value} />);
|
|
147
|
+
|
|
148
|
+
const list = container.querySelector('.nrr-linked-list');
|
|
149
|
+
expect(list).toBeInTheDocument();
|
|
150
|
+
|
|
151
|
+
// labeled like the other linked-list views
|
|
152
|
+
expect(list.querySelector('b')).toHaveTextContent('Measures Implemented:');
|
|
153
|
+
|
|
154
|
+
const links = list.querySelectorAll('a');
|
|
155
|
+
expect(links).toHaveLength(2);
|
|
156
|
+
expect(links[0]).toHaveAttribute('href', '/m1');
|
|
157
|
+
expect(links[0]).toHaveTextContent('Measure One');
|
|
158
|
+
expect(links[1]).toHaveAttribute('href', '/m2');
|
|
159
|
+
expect(links[1]).toHaveTextContent('Measure Two');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('NRREcosystemTypologyView', () => {
|
|
164
|
+
it('renders nothing when value is empty', () => {
|
|
165
|
+
const { container } = render(<NRREcosystemTypologyView value={[]} />);
|
|
166
|
+
expect(container.firstChild).toBeNull();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('renders the ecosystem types as an inline list of titles', () => {
|
|
170
|
+
const value = JSON.stringify([
|
|
171
|
+
{ '@id': '/1', title: 'Grassland' },
|
|
172
|
+
{ '@id': '/2', title: 'Wetland' },
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
const { container } = render(<NRREcosystemTypologyView value={value} />);
|
|
176
|
+
|
|
177
|
+
const p = container.querySelector('p');
|
|
178
|
+
expect(p.textContent).toContain('Ecosystem type: ');
|
|
179
|
+
expect(p.textContent).toContain('Grassland');
|
|
180
|
+
expect(p.textContent).toContain('Wetland');
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('NRRHabitatEcosystemTypeView', () => {
|
|
185
|
+
it('renders a labeled inline list of habitat/ecosystem types', () => {
|
|
186
|
+
const value = [
|
|
187
|
+
{ '@id': '/1', title: 'Forest' },
|
|
188
|
+
{ '@id': '/2', title: 'Heathland' },
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
const { container } = render(<NRRHabitatEcosystemTypeView value={value} />);
|
|
192
|
+
|
|
193
|
+
const p = container.querySelector('p');
|
|
194
|
+
expect(p.textContent).toContain('Habitat/Ecosystem type: ');
|
|
195
|
+
expect(p.textContent).toContain('Forest');
|
|
196
|
+
expect(p.textContent).toContain('Heathland');
|
|
197
|
+
// the label span of a "secondary" view carries the secondary class
|
|
198
|
+
expect(container.querySelector('p span.secondary')).toBeInTheDocument();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('NRRScaleOfPlanningView', () => {
|
|
203
|
+
it('renders a labeled inline list of planning scales', () => {
|
|
204
|
+
const value = JSON.stringify([
|
|
205
|
+
{ '@id': '/1', title: 'National' },
|
|
206
|
+
{ '@id': '/2', title: 'Regional' },
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
const { container } = render(<NRRScaleOfPlanningView value={value} />);
|
|
210
|
+
|
|
211
|
+
const p = container.querySelector('p');
|
|
212
|
+
expect(p.textContent).toContain('Scale of planning: ');
|
|
213
|
+
expect(p.textContent).toContain('National');
|
|
214
|
+
expect(p.textContent).toContain('Regional');
|
|
215
|
+
expect(container.querySelector('p span.secondary')).toBeInTheDocument();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe('NRRCurrentStatusView', () => {
|
|
220
|
+
it('renders nothing when value is empty', () => {
|
|
221
|
+
const { container } = render(<NRRCurrentStatusView value="" />);
|
|
222
|
+
expect(container.firstChild).toBeNull();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('renders nothing for undefined or null', () => {
|
|
226
|
+
const { container } = render(<NRRCurrentStatusView value={undefined} />);
|
|
227
|
+
expect(container.firstChild).toBeNull();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('renders the raw string value when it is not JSON', () => {
|
|
231
|
+
const { container } = render(<NRRCurrentStatusView value="Ongoing" />);
|
|
232
|
+
expect(container.textContent).toContain('Current status: ');
|
|
233
|
+
expect(container.textContent).toContain('Ongoing');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('renders the title of an object value', () => {
|
|
237
|
+
const value = JSON.stringify({ title: 'Active' });
|
|
238
|
+
const { container } = render(<NRRCurrentStatusView value={value} />);
|
|
239
|
+
expect(container.textContent).toContain('Active');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('falls back to token then value for object values', () => {
|
|
243
|
+
const { container } = render(
|
|
244
|
+
<NRRCurrentStatusView value={{ token: 't1', value: 'raw' }} />,
|
|
245
|
+
);
|
|
246
|
+
expect(container.textContent).toContain('t1');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('renders nothing for an array value', () => {
|
|
250
|
+
const { container } = render(
|
|
251
|
+
<NRRCurrentStatusView value={JSON.stringify(['a', 'b'])} />,
|
|
252
|
+
);
|
|
253
|
+
expect(container.firstChild).toBeNull();
|
|
254
|
+
});
|
|
255
|
+
});
|
package/src/index.js
CHANGED
|
@@ -32,6 +32,9 @@ import {
|
|
|
32
32
|
NRRArticleView,
|
|
33
33
|
NRRrelatedCaseStudiesView,
|
|
34
34
|
NRRMeasuresImplementedView,
|
|
35
|
+
NRRHabitatEcosystemTypeView,
|
|
36
|
+
NRRScaleOfPlanningView,
|
|
37
|
+
NRRCurrentStatusView,
|
|
35
38
|
} from './components/Widgets/NRRWidgets';
|
|
36
39
|
import RichTextWidget from './components/Widgets/RichTextWidget';
|
|
37
40
|
|
|
@@ -283,7 +286,11 @@ const applyConfig = (config) => {
|
|
|
283
286
|
config.widgets.id.geolocation = GeolocationWidget;
|
|
284
287
|
config.widgets.views.id.nrr_typology_of_measures = NRRTypologyOfMeasuresView;
|
|
285
288
|
config.widgets.views.id.nrr_ecosystem_typology = NRREcosystemTypologyView;
|
|
289
|
+
config.widgets.views.id.nrr_habitat_ecosystem_type =
|
|
290
|
+
NRRHabitatEcosystemTypeView;
|
|
291
|
+
config.widgets.views.id.nrr_scale_of_planning = NRRScaleOfPlanningView;
|
|
286
292
|
config.widgets.views.id.nrr_article = NRRArticleView;
|
|
293
|
+
config.widgets.views.id.nrr_current_status = NRRCurrentStatusView;
|
|
287
294
|
// EUNIS Widgets
|
|
288
295
|
config.widgets.id.eunis_national_json = EUNISCountryCodeWidget;
|
|
289
296
|
config.widgets.views.id.eunis_national_json = EUNISCountryCodeView;
|