@eeacms/volto-cca-policy 1.0.34 → 1.0.35
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 +14 -1
- package/package.json +1 -1
- package/src/components/Search/NavigatorGuide/NavigatorGuideContentView.jsx +37 -16
- package/src/components/Search/NavigatorGuide/NavigatorGuideContentView.test.jsx +73 -3
- package/src/components/Search/NavigatorGuide/utils.js +7 -0
- package/src/components/Search/NavigatorGuide/utils.test.js +30 -1
- package/src/components/manage/Blocks/ChatbotCatalogue/CatalogueChatView.jsx +6 -14
- package/src/components/manage/Blocks/ChatbotCatalogue/CatalogueChatView.test.jsx +0 -9
- package/src/components/theme/CompareTools/CompareToolsView.test.jsx +11 -3
- package/src/search/navigator_guide/config.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,20 @@ 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.0.
|
|
7
|
+
### [1.0.35](https://github.com/eea/volto-cca-policy/compare/1.0.34...1.0.35) - 6 October 2026
|
|
8
|
+
|
|
9
|
+
#### :bug: Bug Fixes
|
|
10
|
+
|
|
11
|
+
- fix(test): add waitFor to image load assertions [kreafox - [`84eed2e`](https://github.com/eea/volto-cca-policy/commit/84eed2e15a911d9a8a4a1d20e7d29b3de261bb7d)]
|
|
12
|
+
|
|
13
|
+
#### :nail_care: Enhancements
|
|
14
|
+
|
|
15
|
+
- change(navigator): conditionally render result count based on selections [kreafox - [`0ed5449`](https://github.com/eea/volto-cca-policy/commit/0ed54490c16c9a5b4652fd1de47adbad8d2c8256)]
|
|
16
|
+
- change(NavigatorGuide): implement sorting for guide options - refs #304612 [kreafox - [`4ffcbff`](https://github.com/eea/volto-cca-policy/commit/4ffcbff4f98a2c95b1c88dfc0f662c2906678c13)]
|
|
17
|
+
- change(navigator): implement default sorting in guide me tools listing - refs #304612 [kreafox - [`a9f7fb3`](https://github.com/eea/volto-cca-policy/commit/a9f7fb31b276ed2c28d0fa24b800bebfd0ff5425)]
|
|
18
|
+
- change(CatalogueChatView): remove CompareToolsPanel and update tests - refs #309337 [kreafox - [`a5bb420`](https://github.com/eea/volto-cca-policy/commit/a5bb420e8a3229b1a04fd38f36ee87266bb0e59d)]
|
|
19
|
+
|
|
20
|
+
### [1.0.34](https://github.com/eea/volto-cca-policy/compare/1.0.33...1.0.34) - 6 October 2026
|
|
8
21
|
|
|
9
22
|
#### :nail_care: Enhancements
|
|
10
23
|
|
package/package.json
CHANGED
|
@@ -5,11 +5,11 @@ import { useHistory } from 'react-router-dom';
|
|
|
5
5
|
import { defineMessages, useIntl } from 'react-intl';
|
|
6
6
|
import { Button, Checkbox, Icon, Loader, Message } from 'semantic-ui-react';
|
|
7
7
|
import URLManager from '@elastic/search-ui/lib/cjs/URLManager';
|
|
8
|
-
import { useSearchContext } from '@eeacms/search/lib/hocs';
|
|
8
|
+
import { useSearchContext, useSearchDriver } from '@eeacms/search/lib/hocs';
|
|
9
9
|
import ToolThumbnail from '@eeacms/volto-cca-policy/components/theme/ToolThumbnail/ToolThumbnail';
|
|
10
10
|
import guideSteps from '../../../search/navigator_guide/guideSteps';
|
|
11
11
|
import { navigatorGuideStepAtom } from '../../../state';
|
|
12
|
-
import { mergeGuideOptions,
|
|
12
|
+
import { mergeGuideOptions, sortGuideOptions } from './utils';
|
|
13
13
|
import { rawValueAsArray } from '../NavigatorCatalogue/utils';
|
|
14
14
|
import useGuideFacetOptions from './useGuideFacetOptions';
|
|
15
15
|
import { getNavigatorCataloguePageURL } from '../../Search/NavigatorCatalogue/utils';
|
|
@@ -106,6 +106,7 @@ const NavigatorGuideContentView = ({ appConfig }) => {
|
|
|
106
106
|
const history = useHistory();
|
|
107
107
|
const currentLang = useSelector((state) => state.intl.locale || 'en');
|
|
108
108
|
const searchContext = useSearchContext();
|
|
109
|
+
const driver = useSearchDriver();
|
|
109
110
|
const {
|
|
110
111
|
addFilter,
|
|
111
112
|
facets,
|
|
@@ -113,8 +114,12 @@ const NavigatorGuideContentView = ({ appConfig }) => {
|
|
|
113
114
|
isLoading,
|
|
114
115
|
removeFilter,
|
|
115
116
|
results,
|
|
117
|
+
searchTerm,
|
|
118
|
+
sortDirection,
|
|
119
|
+
sortField,
|
|
116
120
|
totalResults,
|
|
117
121
|
} = searchContext;
|
|
122
|
+
const defaultSortApplied = React.useRef(false);
|
|
118
123
|
const steps = guideSteps;
|
|
119
124
|
const allFacetOptions = useGuideFacetOptions(appConfig, steps);
|
|
120
125
|
const [storedActiveStep, setActiveStep] = useAtom(navigatorGuideStepAtom);
|
|
@@ -137,10 +142,7 @@ const NavigatorGuideContentView = ({ appConfig }) => {
|
|
|
137
142
|
selectedValues,
|
|
138
143
|
hasSelections,
|
|
139
144
|
);
|
|
140
|
-
const options =
|
|
141
|
-
step?.id === 'adaptationStage'
|
|
142
|
-
? sortAdaptationSteps(mergedOptions)
|
|
143
|
-
: mergedOptions;
|
|
145
|
+
const options = sortGuideOptions(mergedOptions, step?.id, currentLang);
|
|
144
146
|
const isLastStep = activeStep === steps.length - 1;
|
|
145
147
|
const selectedStepLabels = steps
|
|
146
148
|
.filter(({ field }) => isStepSelected(filters, field))
|
|
@@ -171,6 +173,23 @@ const NavigatorGuideContentView = ({ appConfig }) => {
|
|
|
171
173
|
}
|
|
172
174
|
}, [activeStep, setActiveStep, storedActiveStep]);
|
|
173
175
|
|
|
176
|
+
React.useEffect(() => {
|
|
177
|
+
if (!driver || defaultSortApplied.current) return;
|
|
178
|
+
defaultSortApplied.current = true;
|
|
179
|
+
|
|
180
|
+
if (
|
|
181
|
+
searchTerm?.trim() ||
|
|
182
|
+
sortField ||
|
|
183
|
+
sortDirection ||
|
|
184
|
+
!appConfig.defaultSort
|
|
185
|
+
) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const [field, direction] = appConfig.defaultSort.split('|');
|
|
190
|
+
if (field && direction) driver.setSort(field, direction);
|
|
191
|
+
}, [appConfig.defaultSort, driver, searchTerm, sortDirection, sortField]);
|
|
192
|
+
|
|
174
193
|
const toggleValue = (value) => {
|
|
175
194
|
if (selectedValues.includes(value)) {
|
|
176
195
|
removeFilter(step.field, value, step.filterType);
|
|
@@ -339,16 +358,18 @@ const NavigatorGuideContentView = ({ appConfig }) => {
|
|
|
339
358
|
{intl.formatMessage(messages.livePreview)}
|
|
340
359
|
</div>
|
|
341
360
|
</div>
|
|
342
|
-
|
|
343
|
-
<
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
361
|
+
{hasSelections && (
|
|
362
|
+
<div className="navigator-guide-result-count">
|
|
363
|
+
<span className="navigator-guide-result-count-value">
|
|
364
|
+
{isLoading ? '…' : totalResults || 0}
|
|
365
|
+
</span>
|
|
366
|
+
<p>
|
|
367
|
+
{intl.formatMessage(messages.toolsMatch, {
|
|
368
|
+
count: totalResults || 0,
|
|
369
|
+
})}
|
|
370
|
+
</p>
|
|
371
|
+
</div>
|
|
372
|
+
)}
|
|
352
373
|
{selectedStepLabels.length > 0 && (
|
|
353
374
|
<p className="navigator-guide-preview-refinements">
|
|
354
375
|
{intl.formatMessage(messages.refinedBy, {
|
|
@@ -5,7 +5,7 @@ import { IntlProvider } from 'react-intl';
|
|
|
5
5
|
import { useAtom } from 'jotai';
|
|
6
6
|
import { useSelector } from 'react-redux';
|
|
7
7
|
import { useHistory } from 'react-router-dom';
|
|
8
|
-
import { useSearchContext } from '@eeacms/search/lib/hocs';
|
|
8
|
+
import { useSearchContext, useSearchDriver } from '@eeacms/search/lib/hocs';
|
|
9
9
|
import NavigatorGuideContentView from './NavigatorGuideContentView';
|
|
10
10
|
import useGuideFacetOptions from './useGuideFacetOptions';
|
|
11
11
|
|
|
@@ -15,7 +15,10 @@ jest.mock('jotai', () => ({
|
|
|
15
15
|
}));
|
|
16
16
|
jest.mock('react-redux', () => ({ useSelector: jest.fn() }));
|
|
17
17
|
jest.mock('react-router-dom', () => ({ useHistory: jest.fn() }));
|
|
18
|
-
jest.mock('@eeacms/search/lib/hocs', () => ({
|
|
18
|
+
jest.mock('@eeacms/search/lib/hocs', () => ({
|
|
19
|
+
useSearchContext: jest.fn(),
|
|
20
|
+
useSearchDriver: jest.fn(),
|
|
21
|
+
}));
|
|
19
22
|
jest.mock('./useGuideFacetOptions', () => ({
|
|
20
23
|
__esModule: true,
|
|
21
24
|
default: jest.fn(),
|
|
@@ -32,11 +35,20 @@ const renderGuide = (result, searchContext = {}) => {
|
|
|
32
35
|
});
|
|
33
36
|
return render(
|
|
34
37
|
<IntlProvider locale="en">
|
|
35
|
-
<NavigatorGuideContentView
|
|
38
|
+
<NavigatorGuideContentView
|
|
39
|
+
appConfig={{
|
|
40
|
+
previewResultsLimit: 3,
|
|
41
|
+
defaultSort: 'title.index|asc',
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
36
44
|
</IntlProvider>,
|
|
37
45
|
);
|
|
38
46
|
};
|
|
39
47
|
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
useSearchDriver.mockReturnValue(undefined);
|
|
50
|
+
});
|
|
51
|
+
|
|
40
52
|
describe('NavigatorGuideContentView thumbnails', () => {
|
|
41
53
|
beforeEach(() => {
|
|
42
54
|
jest.clearAllMocks();
|
|
@@ -94,6 +106,64 @@ describe('NavigatorGuideContentView thumbnails', () => {
|
|
|
94
106
|
});
|
|
95
107
|
});
|
|
96
108
|
|
|
109
|
+
describe('NavigatorGuideContentView empty preview', () => {
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
jest.clearAllMocks();
|
|
112
|
+
useAtom.mockReturnValue([0, jest.fn()]);
|
|
113
|
+
useSelector.mockReturnValue('en');
|
|
114
|
+
useHistory.mockReturnValue({ push: jest.fn() });
|
|
115
|
+
useGuideFacetOptions.mockReturnValue({});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('hides the result count while the preview empty state is visible', () => {
|
|
119
|
+
const { container } = renderGuide(
|
|
120
|
+
{ title: 'Guide tool', href: '/tools/guide-tool' },
|
|
121
|
+
{ filters: [] },
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
expect(
|
|
125
|
+
container.querySelector('.navigator-guide-preview-empty'),
|
|
126
|
+
).toBeInTheDocument();
|
|
127
|
+
expect(
|
|
128
|
+
container.querySelector('.navigator-guide-result-count'),
|
|
129
|
+
).not.toBeInTheDocument();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('NavigatorGuideContentView sorting', () => {
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
jest.clearAllMocks();
|
|
136
|
+
useAtom.mockReturnValue([0, jest.fn()]);
|
|
137
|
+
useSelector.mockReturnValue('en');
|
|
138
|
+
useHistory.mockReturnValue({ push: jest.fn() });
|
|
139
|
+
useGuideFacetOptions.mockReturnValue({});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('starts with title A-Z when URL filters leave the sort unset', () => {
|
|
143
|
+
const driver = { setSort: jest.fn() };
|
|
144
|
+
useSearchDriver.mockReturnValue(driver);
|
|
145
|
+
|
|
146
|
+
renderGuide(
|
|
147
|
+
{ title: 'Guide tool', href: '/tools/guide-tool' },
|
|
148
|
+
{ searchTerm: '', sortField: '', sortDirection: '' },
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(driver.setSort).toHaveBeenCalledWith('title.index', 'asc');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('preserves an active sort', () => {
|
|
155
|
+
const driver = { setSort: jest.fn() };
|
|
156
|
+
useSearchDriver.mockReturnValue(driver);
|
|
157
|
+
|
|
158
|
+
renderGuide(
|
|
159
|
+
{ title: 'Guide tool', href: '/tools/guide-tool' },
|
|
160
|
+
{ sortField: 'title.index', sortDirection: 'desc' },
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(driver.setSort).not.toHaveBeenCalled();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
97
167
|
describe('NavigatorGuideContentView adaptation steps', () => {
|
|
98
168
|
beforeEach(() => {
|
|
99
169
|
jest.clearAllMocks();
|
|
@@ -43,3 +43,10 @@ export const sortAdaptationSteps = (options) =>
|
|
|
43
43
|
if (bStep) return 1;
|
|
44
44
|
return a.value.localeCompare(b.value);
|
|
45
45
|
});
|
|
46
|
+
|
|
47
|
+
export const sortGuideOptions = (options, stepId, locale = 'en') =>
|
|
48
|
+
stepId === 'adaptationStage'
|
|
49
|
+
? sortAdaptationSteps(options)
|
|
50
|
+
: [...options].sort((a, b) =>
|
|
51
|
+
a.value.localeCompare(b.value, locale, { sensitivity: 'base' }),
|
|
52
|
+
);
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
mergeGuideOptions,
|
|
3
|
+
sortAdaptationSteps,
|
|
4
|
+
sortGuideOptions,
|
|
5
|
+
} from './utils';
|
|
2
6
|
|
|
3
7
|
describe('Navigator Guide utilities', () => {
|
|
4
8
|
it('keeps unavailable values and marks them as disabled', () => {
|
|
@@ -54,4 +58,29 @@ describe('Navigator Guide utilities', () => {
|
|
|
54
58
|
]);
|
|
55
59
|
expect(options[0].value).toBe('Step 10: Review');
|
|
56
60
|
});
|
|
61
|
+
|
|
62
|
+
it('sorts regular guide options alphabetically without mutating them', () => {
|
|
63
|
+
const options = [
|
|
64
|
+
{ value: 'Water', count: 3 },
|
|
65
|
+
{ value: 'Agriculture', count: 8 },
|
|
66
|
+
{ value: 'Biodiversity', count: 5 },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
expect(
|
|
70
|
+
sortGuideOptions(options, 'adaptationSectors').map(({ value }) => value),
|
|
71
|
+
).toEqual(['Agriculture', 'Biodiversity', 'Water']);
|
|
72
|
+
expect(options[0].value).toBe('Water');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('uses numeric cycle-step order for the adaptation stage', () => {
|
|
76
|
+
const options = [
|
|
77
|
+
{ value: 'Step 10: Review', count: 3 },
|
|
78
|
+
{ value: 'Step 2: Assess', count: 8 },
|
|
79
|
+
{ value: 'Step 1: Prepare', count: 5 },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
expect(
|
|
83
|
+
sortGuideOptions(options, 'adaptationStage').map(({ value }) => value),
|
|
84
|
+
).toEqual(['Step 1: Prepare', 'Step 2: Assess', 'Step 10: Review']);
|
|
85
|
+
});
|
|
57
86
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { ChatWindow } from '@eeacms/volto-eea-chatbot/ChatBlock/chat';
|
|
3
|
-
import { CompareToolsPanel } from '../../../theme/CompareTools/CompareToolsPanel';
|
|
4
3
|
|
|
5
4
|
import { CcaDocCard } from './DocumentCard';
|
|
6
5
|
import { remarkCcaDocCards } from './docCards';
|
|
@@ -33,24 +32,17 @@ const extraRemarkPlugins = [remarkCcaDocCards, remarkCcaNextSteps];
|
|
|
33
32
|
* turned into inline Navigator-style catalogue cards (`CcaDocCard`),
|
|
34
33
|
* and "Suggested next steps" sections are turned into styled callout boxes.
|
|
35
34
|
*
|
|
36
|
-
* It also renders `<CompareToolsPanel />` so that when users activate the
|
|
37
|
-
* "Compare" checkbox on any document card, the floating comparison panel
|
|
38
|
-
* appears and allows navigating to the side-by-side comparison view.
|
|
39
|
-
*
|
|
40
35
|
* `ChatBlockView` renders this as `<Presentation persona {...blockData} />`,
|
|
41
36
|
* so all block fields arrive as top-level props and are forwarded unchanged.
|
|
42
37
|
*/
|
|
43
38
|
function CatalogueChatView(props) {
|
|
44
39
|
return (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/>
|
|
52
|
-
<CompareToolsPanel />
|
|
53
|
-
</>
|
|
40
|
+
<ChatWindow
|
|
41
|
+
{...props}
|
|
42
|
+
hideSourcesTab
|
|
43
|
+
extraRemarkPlugins={extraRemarkPlugins}
|
|
44
|
+
extraMarkdownComponents={extraMarkdownComponents}
|
|
45
|
+
/>
|
|
54
46
|
);
|
|
55
47
|
}
|
|
56
48
|
|
|
@@ -17,10 +17,6 @@ jest.mock('@eeacms/volto-eea-chatbot/ChatBlock/chat', () => ({
|
|
|
17
17
|
)),
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
|
-
jest.mock('../../../theme/CompareTools/CompareToolsPanel', () => ({
|
|
21
|
-
CompareToolsPanel: jest.fn(() => <div data-testid="compare-tools-panel" />),
|
|
22
|
-
}));
|
|
23
|
-
|
|
24
20
|
describe('CatalogueChatView', () => {
|
|
25
21
|
beforeEach(() => {
|
|
26
22
|
ChatWindow.mockClear();
|
|
@@ -74,9 +70,4 @@ describe('CatalogueChatView', () => {
|
|
|
74
70
|
expect(secondMd).toBe(firstMd);
|
|
75
71
|
expect(secondPlugins).toBe(firstPlugins);
|
|
76
72
|
});
|
|
77
|
-
|
|
78
|
-
it('renders the CompareToolsPanel alongside the chat window', () => {
|
|
79
|
-
const { getByTestId } = render(<CatalogueChatView persona={{}} />);
|
|
80
|
-
expect(getByTestId('compare-tools-panel')).toBeInTheDocument();
|
|
81
|
-
});
|
|
82
73
|
});
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import '@testing-library/jest-dom';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
fireEvent,
|
|
5
|
+
render,
|
|
6
|
+
screen,
|
|
7
|
+
waitFor,
|
|
8
|
+
within,
|
|
9
|
+
} from '@testing-library/react';
|
|
4
10
|
import { IntlProvider } from 'react-intl';
|
|
5
11
|
import { useAtom } from 'jotai';
|
|
6
12
|
import { useDispatch, useSelector } from 'react-redux';
|
|
@@ -210,8 +216,10 @@ describe('CompareToolsView', () => {
|
|
|
210
216
|
|
|
211
217
|
fireEvent.load(img);
|
|
212
218
|
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
await waitFor(() => {
|
|
220
|
+
expect(img.style.display).toBe('');
|
|
221
|
+
expect(thumbnail.querySelector('.ri-file-line')).not.toBeInTheDocument();
|
|
222
|
+
});
|
|
215
223
|
});
|
|
216
224
|
|
|
217
225
|
it('falls back to the file icon when the image fails', async () => {
|