@eeacms/volto-bise-policy 1.3.11 → 1.3.13

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 (39) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/package.json +2 -1
  3. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyCard.jsx +404 -0
  4. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyCard.test.jsx +366 -0
  5. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.jsx +192 -91
  6. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyExplorerView.test.jsx +205 -58
  7. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.jsx +329 -178
  8. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMap.test.jsx +243 -123
  9. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMapLazy.jsx +61 -0
  10. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyMapLazy.test.jsx +89 -0
  11. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyResults.jsx +128 -0
  12. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyResults.test.jsx +160 -0
  13. package/src/components/manage/Blocks/CaseStudyExplorer/FacetPopover.jsx +180 -0
  14. package/src/components/manage/Blocks/CaseStudyExplorer/FacetPopover.test.jsx +125 -0
  15. package/src/components/manage/Blocks/CaseStudyExplorer/FilterPanel.jsx +171 -0
  16. package/src/components/manage/Blocks/CaseStudyExplorer/FilterPanel.test.jsx +181 -0
  17. package/src/components/manage/Blocks/CaseStudyExplorer/Pagination.jsx +81 -0
  18. package/src/components/manage/Blocks/CaseStudyExplorer/filters.js +350 -0
  19. package/src/components/manage/Blocks/CaseStudyExplorer/filters.test.js +399 -0
  20. package/src/components/manage/Blocks/CaseStudyExplorer/hooks.js +28 -4
  21. package/src/components/manage/Blocks/CaseStudyExplorer/hooks.test.js +73 -0
  22. package/src/components/manage/Blocks/CaseStudyExplorer/messages.js +138 -0
  23. package/src/components/manage/Blocks/CaseStudyExplorer/styles.less +1157 -317
  24. package/src/components/manage/Blocks/CaseStudyExplorer/testUtils.js +44 -46
  25. package/src/components/manage/Blocks/CaseStudyExplorer/utils.js +68 -184
  26. package/src/components/manage/Blocks/CaseStudyExplorer/utils.test.js +91 -158
  27. package/src/index.js +39 -0
  28. package/src/openlayers.js +51 -0
  29. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyFilters.jsx +0 -383
  30. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyFilters.test.jsx +0 -90
  31. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.jsx +0 -342
  32. package/src/components/manage/Blocks/CaseStudyExplorer/CaseStudyListing.test.jsx +0 -196
  33. package/src/components/manage/Blocks/CaseStudyExplorer/FeatureDisplay.jsx +0 -24
  34. package/src/components/manage/Blocks/CaseStudyExplorer/FeatureDisplay.test.jsx +0 -49
  35. package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.jsx +0 -100
  36. package/src/components/manage/Blocks/CaseStudyExplorer/FeatureInteraction.test.jsx +0 -189
  37. package/src/components/manage/Blocks/CaseStudyExplorer/InfoOverlay.jsx +0 -111
  38. package/src/components/manage/Blocks/CaseStudyExplorer/InfoOverlay.test.jsx +0 -159
  39. package/src/components/manage/Blocks/CaseStudyExplorer/styles.less_old +0 -201
@@ -1,90 +1,237 @@
1
+ import './mockJsdom';
1
2
  import React from 'react';
2
- import { render } from '@testing-library/react';
3
3
  import '@testing-library/jest-dom';
4
- import { useCases } from './hooks';
5
- import CaseStudyExplorerView from './CaseStudyExplorerView';
6
-
7
- jest.mock('@plone/volto/helpers', () => ({
8
- addAppURL: jest.fn((url) => url),
9
- }));
4
+ import { render, screen, fireEvent, within } from '@testing-library/react';
5
+ import { IntlProvider } from 'react-intl';
10
6
 
11
- jest.mock('@plone/volto/registry', () => ({
12
- __esModule: true,
13
- default: {
14
- settings: { prefixPath: '' },
15
- },
16
- }));
17
-
18
- jest.mock('./CaseStudyMap', () =>
19
- jest.fn(() => <div id="case-study-map-mock">CaseStudyMapMock</div>),
20
- );
21
-
22
- jest.mock('./CaseStudyFilters', () => ({
23
- ActiveFilters: jest.fn(() => (
24
- <div id="active-filters-mock">ActiveFiltersMock</div>
25
- )),
26
- CaseStudyFilters: jest.fn(() => (
27
- <div id="case-study-fiters-mock">CaseStudyFiltersMock</div>
28
- )),
29
- SearchBox: jest.fn(() => <div id="search-box-mock">SearchBoxMock</div>),
30
- }));
7
+ import CaseStudyExplorerView from './CaseStudyExplorerView';
8
+ import { useCases } from './hooks';
31
9
 
32
- jest.mock('./utils', () => ({
33
- filterCases: jest.fn(() => [{ id: 1, title: 'Filtered Case' }]),
34
- filterCasesByPath: jest.fn((cases) => cases),
35
- getFilters: jest.fn(() => ['filter1', 'filter2']),
36
- }));
10
+ global.__SERVER__ = false;
37
11
 
38
12
  jest.mock('./hooks', () => ({
39
13
  useCases: jest.fn(),
40
14
  }));
41
15
 
42
- beforeAll(() => {
43
- global.__SERVER__ = false;
44
- });
16
+ // The real map needs OpenLayers: stand in for it with one button per item so
17
+ // tests can select a case study the way a map click would.
18
+ jest.mock('./CaseStudyMapLazy', () => ({ items, onSelect }) => (
19
+ <div data-testid="case-study-map">
20
+ {items.map((item) => (
21
+ <button
22
+ type="button"
23
+ key={item.properties.path}
24
+ onClick={() => onSelect(item.properties.path)}
25
+ >
26
+ {`Map pin: ${item.properties.title}`}
27
+ </button>
28
+ ))}
29
+ </div>
30
+ ));
31
+
32
+ const cases = [
33
+ {
34
+ geometry: { type: 'Point', coordinates: [14.4, 50.1] },
35
+ properties: {
36
+ title: 'Peatland rewetting',
37
+ path: '/case-peatland',
38
+ current_status: 'ongoing',
39
+ nrr_legislation: ['Article 4 - Terrestrial'],
40
+ typology: [{ code: 'NRPM_001', family: 'MA', title: 'Rewetting' }],
41
+ habitat_ecosystem_type: ['Inland wetlands'],
42
+ measures: [{ id: 'm-a', title: 'Restore peatlands', path: '/m/a' }],
43
+ effective: '2026-02-11T09:36:06+00:00',
44
+ },
45
+ },
46
+ {
47
+ geometry: { type: 'Point', coordinates: [10.4, 51.1] },
48
+ properties: {
49
+ title: 'River connectivity',
50
+ path: '/case-rivers',
51
+ current_status: 'completed',
52
+ nrr_legislation: ['Article 9 - Rivers'],
53
+ habitat_ecosystem_type: ['Rivers and canals'],
54
+ measures: [{ id: 'm-b', title: 'Restore rivers', path: '/m/b' }],
55
+ effective: '2026-01-02T00:00:00+00:00',
56
+ },
57
+ },
58
+ ];
59
+
60
+ const renderView = (props = {}) =>
61
+ render(
62
+ <IntlProvider locale="en">
63
+ <CaseStudyExplorerView {...props} />
64
+ </IntlProvider>,
65
+ );
45
66
 
46
67
  describe('CaseStudyExplorerView', () => {
47
68
  beforeEach(() => {
48
- jest.clearAllMocks();
69
+ useCases.mockReturnValue({ cases, loading: false, error: false });
49
70
  });
50
71
 
51
- it('renders filters and map when there are cases', () => {
52
- useCases.mockReturnValue([{ id: 1, title: 'Test Case' }]);
72
+ test('shows a loader while the case studies are being fetched', () => {
73
+ useCases.mockReturnValue({ cases: [], loading: true, error: false });
53
74
 
54
- const { container } = render(<CaseStudyExplorerView />);
75
+ renderView();
55
76
 
56
- expect(container.querySelector('#search-box-mock')).toBeInTheDocument();
57
- expect(container.querySelector('#active-filters-mock')).toBeInTheDocument();
77
+ expect(screen.getByRole('status')).toHaveTextContent(
78
+ 'Loading case studies',
79
+ );
80
+ expect(screen.queryByTestId('case-study-map')).not.toBeInTheDocument();
58
81
  expect(
59
- container.querySelector('#case-study-fiters-mock'),
82
+ screen.queryByText(/No case studies match your filters/),
83
+ ).not.toBeInTheDocument();
84
+ });
85
+
86
+ test('offers a retry when the case studies cannot be loaded', () => {
87
+ const reload = jest.fn();
88
+ useCases.mockReturnValue({
89
+ cases: [],
90
+ loading: false,
91
+ error: true,
92
+ reload,
93
+ });
94
+
95
+ renderView();
96
+
97
+ expect(screen.getByRole('alert')).toHaveTextContent(
98
+ 'The case studies could not be loaded.',
99
+ );
100
+
101
+ fireEvent.click(screen.getByRole('button', { name: /Try again/ }));
102
+ expect(reload).toHaveBeenCalled();
103
+ });
104
+
105
+ test('renders the filter panel, the map and both results', () => {
106
+ renderView();
107
+
108
+ expect(screen.getByText('Filter and explore')).toBeInTheDocument();
109
+ expect(screen.getByTestId('case-study-map')).toBeInTheDocument();
110
+ expect(screen.getByText('Found 2 case studies')).toBeInTheDocument();
111
+ expect(screen.getAllByRole('article')).toHaveLength(2);
112
+ });
113
+
114
+ test('applies a filter as soon as it is toggled', () => {
115
+ renderView();
116
+
117
+ expect(screen.getByText('Found 2 case studies')).toBeInTheDocument();
118
+
119
+ fireEvent.click(screen.getByRole('button', { name: /Ongoing/ }));
120
+
121
+ expect(screen.getByText('Found 1 case study')).toBeInTheDocument();
122
+ expect(screen.getByRole('button', { name: /Ongoing/ })).toHaveAttribute(
123
+ 'aria-pressed',
124
+ 'true',
125
+ );
126
+ });
127
+
128
+ test('searches the measures and the typology as free text', () => {
129
+ renderView();
130
+ const search = screen.getByRole('searchbox', {
131
+ name: /Search measures and typology/i,
132
+ });
133
+
134
+ fireEvent.change(search, { target: { value: 'restore rivers' } });
135
+ expect(screen.getByText('Found 1 case study')).toBeInTheDocument();
136
+ expect(screen.getByText('River connectivity')).toBeInTheDocument();
137
+
138
+ fireEvent.change(search, { target: { value: 'nrpm_001' } });
139
+ expect(screen.getByText('Found 1 case study')).toBeInTheDocument();
140
+ expect(screen.getByText('Peatland rewetting')).toBeInTheDocument();
141
+
142
+ fireEvent.change(search, { target: { value: 'nothing matches this' } });
143
+ expect(
144
+ screen.getByText('No case studies match your filters'),
60
145
  ).toBeInTheDocument();
61
- expect(container.querySelector('#case-study-map-mock')).toBeInTheDocument();
62
146
  });
63
147
 
64
- it('hides filters when caseStudiesIds is provided', () => {
65
- useCases.mockReturnValue([{ id: 1, title: 'Test Case' }]);
148
+ test('resets the filters', () => {
149
+ renderView();
66
150
 
67
- const { container } = render(
68
- <CaseStudyExplorerView caseStudiesIds={[1, 2]} />,
151
+ fireEvent.click(screen.getByRole('button', { name: /Ongoing/ }));
152
+ expect(screen.getByText('Found 1 case study')).toBeInTheDocument();
153
+
154
+ fireEvent.click(screen.getByRole('button', { name: /Reset filters/ }));
155
+
156
+ expect(screen.getByText('Found 2 case studies')).toBeInTheDocument();
157
+ expect(screen.getByRole('button', { name: /Ongoing/ })).toHaveAttribute(
158
+ 'aria-pressed',
159
+ 'false',
69
160
  );
161
+ });
162
+
163
+ test('turns "Show on map" into "Reset map" on the selected card', () => {
164
+ renderView();
70
165
 
71
- expect(container.querySelector('#search-box-mock')).not.toBeInTheDocument();
166
+ const [card] = screen.getAllByRole('article');
167
+ fireEvent.click(within(card).getByRole('button', { name: 'Show on map' }));
168
+
169
+ expect(card).toHaveAttribute('data-selected', 'true');
72
170
  expect(
73
- container.querySelector('#active-filters-mock'),
74
- ).not.toBeInTheDocument();
171
+ within(card).queryByRole('button', { name: 'Show on map' }),
172
+ ).toBeNull();
173
+
174
+ fireEvent.click(within(card).getByRole('button', { name: 'Reset map' }));
175
+
176
+ expect(card).not.toHaveAttribute('data-selected');
75
177
  expect(
76
- container.querySelector('#case-study-fiters-mock'),
77
- ).not.toBeInTheDocument();
78
- expect(container.querySelector('#case-study-map-mock')).toBeInTheDocument();
178
+ within(card).getByRole('button', { name: 'Show on map' }),
179
+ ).toBeInTheDocument();
180
+ });
181
+
182
+ test('jumps to the page that holds a case study selected on the map', () => {
183
+ const withTitle = (title, path) => ({
184
+ geometry: { type: 'Point', coordinates: [14.4, 50.1] },
185
+ properties: { title, path, current_status: 'ongoing' },
186
+ });
187
+ useCases.mockReturnValue({
188
+ cases: [
189
+ withTitle('Alpine meadows', '/case-alpine'),
190
+ withTitle('Boreal forest', '/case-boreal'),
191
+ withTitle('Coastal dunes', '/case-coastal'),
192
+ withTitle('Peatland rewetting', '/case-peatland'),
193
+ withTitle('River connectivity', '/case-rivers'),
194
+ ],
195
+ loading: false,
196
+ error: false,
197
+ });
198
+
199
+ renderView();
200
+
201
+ // Four cards per page: the last case study is on page 2.
202
+ expect(screen.queryByText('River connectivity')).not.toBeInTheDocument();
203
+
204
+ fireEvent.click(
205
+ screen.getByRole('button', { name: 'Map pin: River connectivity' }),
206
+ );
207
+
208
+ expect(screen.getByText('River connectivity')).toBeInTheDocument();
209
+ expect(screen.getByRole('article')).toHaveAttribute(
210
+ 'data-selected',
211
+ 'true',
212
+ );
213
+ expect(screen.getByRole('button', { name: 'Page 2' })).toHaveAttribute(
214
+ 'aria-current',
215
+ 'page',
216
+ );
79
217
  });
80
218
 
81
- it('does not render map when there are no cases', () => {
82
- useCases.mockReturnValue([]);
219
+ test('sorts by newest using the effective date', () => {
220
+ renderView();
83
221
 
84
- const { container } = render(<CaseStudyExplorerView />);
222
+ fireEvent.change(screen.getByLabelText('Sort by'), {
223
+ target: { value: 'newest' },
224
+ });
85
225
 
226
+ const cards = screen.getAllByRole('article');
86
227
  expect(
87
- container.querySelector('#case-study-map-mock'),
88
- ).not.toBeInTheDocument();
228
+ within(cards[0]).getByText('Peatland rewetting'),
229
+ ).toBeInTheDocument();
230
+ });
231
+
232
+ test('narrows the results to the case studies of one measure', () => {
233
+ renderView({ data: { filter_path: '/case-rivers' } });
234
+
235
+ expect(screen.getByText('Found 1 case study')).toBeInTheDocument();
89
236
  });
90
237
  });