@eeacms/volto-cca-policy 1.0.34 → 1.0.36

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 CHANGED
@@ -4,7 +4,31 @@ 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.34](https://github.com/eea/volto-cca-policy/compare/1.0.33...1.0.34) - 5 October 2026
7
+ ### [1.0.36](https://github.com/eea/volto-cca-policy/compare/1.0.35...1.0.36) - 8 October 2026
8
+
9
+ #### :rocket: New Features
10
+
11
+ - feat(navigator): update geographic coverage functionality, add runtime mappings and facet value retrieval [kreafox - [`4787c3b`](https://github.com/eea/volto-cca-policy/commit/4787c3b890b69d46ee0806084983ac3c0f36fe43)]
12
+ - feat: add tooltips for usability and functionality criteria in CompareToolsView - refs #304612 [kreafox - [`d23175c`](https://github.com/eea/volto-cca-policy/commit/d23175c7e3e1df85e2c75deba6612be3dd94b88d)]
13
+
14
+ #### :nail_care: Enhancements
15
+
16
+ - refactor(navigator): clean up imports and test files [kreafox - [`4a79b41`](https://github.com/eea/volto-cca-policy/commit/4a79b414b713d387af0fcf7baefd699da747cdac)]
17
+
18
+ ### [1.0.35](https://github.com/eea/volto-cca-policy/compare/1.0.34...1.0.35) - 6 October 2026
19
+
20
+ #### :bug: Bug Fixes
21
+
22
+ - fix(test): add waitFor to image load assertions [kreafox - [`84eed2e`](https://github.com/eea/volto-cca-policy/commit/84eed2e15a911d9a8a4a1d20e7d29b3de261bb7d)]
23
+
24
+ #### :nail_care: Enhancements
25
+
26
+ - change(navigator): conditionally render result count based on selections [kreafox - [`0ed5449`](https://github.com/eea/volto-cca-policy/commit/0ed54490c16c9a5b4652fd1de47adbad8d2c8256)]
27
+ - change(NavigatorGuide): implement sorting for guide options - refs #304612 [kreafox - [`4ffcbff`](https://github.com/eea/volto-cca-policy/commit/4ffcbff4f98a2c95b1c88dfc0f662c2906678c13)]
28
+ - change(navigator): implement default sorting in guide me tools listing - refs #304612 [kreafox - [`a9f7fb3`](https://github.com/eea/volto-cca-policy/commit/a9f7fb31b276ed2c28d0fa24b800bebfd0ff5425)]
29
+ - change(CatalogueChatView): remove CompareToolsPanel and update tests - refs #309337 [kreafox - [`a5bb420`](https://github.com/eea/volto-cca-policy/commit/a5bb420e8a3229b1a04fd38f36ee87266bb0e59d)]
30
+
31
+ ### [1.0.34](https://github.com/eea/volto-cca-policy/compare/1.0.33...1.0.34) - 6 October 2026
8
32
 
9
33
  #### :nail_care: Enhancements
10
34
 
@@ -602,8 +626,42 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
602
626
  #### :hammer_and_wrench: Others
603
627
 
604
628
  - test: pin Chromium version 149 to work with Cypress [valentinab25 - [`659ec82`](https://github.com/eea/volto-cca-policy/commit/659ec823dd71fe592a3a7accda22effbe1fb07bc)]
605
- ## [1.0.0](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.31...1.0.0) - 12 June 2026
629
+ ## [1.0.0](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.33...1.0.0) - 12 June 2026
630
+
631
+ ### [1.0.0-alpha.33](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.32...1.0.0-alpha.33) - 7 October 2026
632
+
633
+ ### [1.0.0-alpha.32](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.31...1.0.0-alpha.32) - 7 October 2026
634
+
635
+ #### :bug: Bug Fixes
636
+
637
+ - fix(test): add waitFor to image load assertions [kreafox - [`84eed2e`](https://github.com/eea/volto-cca-policy/commit/84eed2e15a911d9a8a4a1d20e7d29b3de261bb7d)]
638
+
639
+ #### :nail_care: Enhancements
606
640
 
641
+ - change(navigator): conditionally render result count based on selections [kreafox - [`0ed5449`](https://github.com/eea/volto-cca-policy/commit/0ed54490c16c9a5b4652fd1de47adbad8d2c8256)]
642
+ - change(NavigatorGuide): implement sorting for guide options - refs #304612 [kreafox - [`4ffcbff`](https://github.com/eea/volto-cca-policy/commit/4ffcbff4f98a2c95b1c88dfc0f662c2906678c13)]
643
+ - change(navigator): implement default sorting in guide me tools listing - refs #304612 [kreafox - [`a9f7fb3`](https://github.com/eea/volto-cca-policy/commit/a9f7fb31b276ed2c28d0fa24b800bebfd0ff5425)]
644
+ - change(CatalogueChatView): remove CompareToolsPanel and update tests - refs #309337 [kreafox - [`a5bb420`](https://github.com/eea/volto-cca-policy/commit/a5bb420e8a3229b1a04fd38f36ee87266bb0e59d)]
645
+ - change(locales): update translations [kreafox - [`4541062`](https://github.com/eea/volto-cca-policy/commit/45410626484ff0cc536447ff75895b662e3e9554)]
646
+ - change(navigator): update adaptation stage questions [kreafox - [`a1e3705`](https://github.com/eea/volto-cca-policy/commit/a1e3705f1004777ce72a1395f89149c9d8f8e539)]
647
+ - change(locales): add "Governance level" translations [kreafox - [`23ae204`](https://github.com/eea/volto-cca-policy/commit/23ae2046557203a55b2dd40b761ed99e84b78d80)]
648
+ - change(navigator): update implementation level messages to governance level [kreafox - [`f28a93e`](https://github.com/eea/volto-cca-policy/commit/f28a93e408ba24a81ac27229947de1b959a5c42b)]
649
+
650
+ #### :house: Internal changes
651
+
652
+ - style: Automated code fix [eea-jenkins - [`3a0597d`](https://github.com/eea/volto-cca-policy/commit/3a0597dd9358ba2c7f365bef9d3e4ffae36ee68b)]
653
+ - style: Automated code fix [eea-jenkins - [`d91a798`](https://github.com/eea/volto-cca-policy/commit/d91a798f30b29e4bc058cc44ebcc02f07ac216ab)]
654
+
655
+ #### :hammer_and_wrench: Others
656
+
657
+ - update SonarQube messages [iugin - [`cf21375`](https://github.com/eea/volto-cca-policy/commit/cf21375202f946f7adb2959db45cb0e991bfae68)]
658
+ - add mapstyle test [iugin - [`e80b843`](https://github.com/eea/volto-cca-policy/commit/e80b8438e6ff47cb73e43cc8927511e7b8979a8f)]
659
+ - remove test [iugin - [`dcd7fb2`](https://github.com/eea/volto-cca-policy/commit/dcd7fb2873101fd7c0e6612e0eb11012b18412a3)]
660
+ - Refs #296263 - add more tests [iugin - [`898d8e7`](https://github.com/eea/volto-cca-policy/commit/898d8e7878e49a7e777c749e1f88dc6c94ce416b)]
661
+ - Refs #296263 - update tests img alt [iugin - [`1f720a8`](https://github.com/eea/volto-cca-policy/commit/1f720a83b389cb1cb5dbed33c96418fc8def73d9)]
662
+ - Refs #296263 - add tests [iugin - [`c9012e0`](https://github.com/eea/volto-cca-policy/commit/c9012e0e7ca850acda608459e9b78c70f37e7ad4)]
663
+ - Refs #296263 - duplication country profile mapstyle [iugin - [`a6771e9`](https://github.com/eea/volto-cca-policy/commit/a6771e921bcdc6b9ce2f0f9f40413a2fefe78e84)]
664
+ - Refs #296263 - duplicate content [iugin - [`400b661`](https://github.com/eea/volto-cca-policy/commit/400b6618cc3fb50df44f9007253aeaba5e5d36b0)]
607
665
  ### [1.0.0-alpha.31](https://github.com/eea/volto-cca-policy/compare/1.0.0-alpha.30...1.0.0-alpha.31) - 2 October 2026
608
666
 
609
667
  #### :rocket: New Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "@eeacms/volto-cca-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -4,11 +4,11 @@ import { defineMessages, useIntl } from 'react-intl';
4
4
  import { Term } from '@eeacms/search/components';
5
5
  import { useSearchContext } from '@eeacms/search/lib/hocs';
6
6
  import { markSelectedFacetValuesFromFilters } from '@eeacms/search/lib/search/helpers';
7
-
8
- export const CHARACTERISATION_FIELD = 'cca_geographic_characterisation.keyword';
9
- export const TRANSNATIONAL_REGION_FIELD =
10
- 'cca_geographic_transnational_region.keyword';
11
- export const COUNTRIES_FIELD = 'cca_geographic_countries.keyword';
7
+ import {
8
+ CHARACTERISATION_FIELD,
9
+ COUNTRIES_FIELD,
10
+ TRANSNATIONAL_REGION_FIELD,
11
+ } from '../../../search/navigator_catalogue/geographicCoverage';
12
12
 
13
13
  const messages = defineMessages({
14
14
  noOptionsAvailable: {
@@ -196,29 +196,53 @@ const NavigatorGeographicCoverageFacet = ({
196
196
  TRANSNATIONAL_REGION_FIELD,
197
197
  );
198
198
  const countryOptions = getFacetOptions(facets, filters, COUNTRIES_FIELD);
199
+ const selectedDirectValues =
200
+ filters.find(({ field }) => field === CHARACTERISATION_FIELD)?.values || [];
201
+
202
+ const clearFilter = (field) => removeFilter(field, null, 'any');
203
+
204
+ const updateDirectFilter = (option) => {
205
+ if (option.selected) {
206
+ onRemove(option.value);
207
+ return;
208
+ }
209
+
210
+ clearFilter(COUNTRIES_FIELD);
211
+ clearFilter(TRANSNATIONAL_REGION_FIELD);
212
+ onSelect(option.value);
213
+ };
199
214
 
200
- const updateChildFilter = (field, option) =>
201
- option.selected
202
- ? removeFilter(field, option.value, 'any')
203
- : addFilter(field, option.value, 'any');
215
+ const updateChildFilter = (field, option) => {
216
+ if (option.selected) {
217
+ removeFilter(field, option.value, 'any');
218
+ return;
219
+ }
220
+
221
+ clearFilter(CHARACTERISATION_FIELD);
222
+ addFilter(field, option.value, 'any');
223
+ };
204
224
 
205
225
  return (
206
226
  <fieldset
207
227
  className={`sui-facet searchlib-multiterm-facet navigator-geographic-facet ${className}`}
208
228
  >
209
229
  <div className="sui-multi-checkbox-facet navigator-geographic-facet-direct-options">
210
- {options.map((option) => (
211
- <FacetOption
212
- key={valueAsText(option.value)}
213
- field={CHARACTERISATION_FIELD}
214
- option={option}
215
- onChange={(selectedOption) =>
216
- selectedOption.selected
217
- ? onRemove(selectedOption.value)
218
- : onSelect(selectedOption.value)
219
- }
220
- />
221
- ))}
230
+ {options.map((option) => {
231
+ const directOption = {
232
+ ...option,
233
+ selected:
234
+ option.selected || selectedDirectValues.includes(option.value),
235
+ };
236
+
237
+ return (
238
+ <FacetOption
239
+ key={valueAsText(option.value)}
240
+ field={CHARACTERISATION_FIELD}
241
+ option={directOption}
242
+ onChange={updateDirectFilter}
243
+ />
244
+ );
245
+ })}
222
246
  </div>
223
247
  <ExpandableFacetGroup
224
248
  field={COUNTRIES_FIELD}
@@ -3,10 +3,12 @@ import '@testing-library/jest-dom';
3
3
  import { fireEvent, render, screen } from '@testing-library/react';
4
4
  import { IntlProvider } from 'react-intl';
5
5
  import { useSearchContext } from '@eeacms/search/lib/hocs';
6
- import NavigatorGeographicCoverageFacet, {
6
+ import NavigatorGeographicCoverageFacet from './NavigatorGeographicCoverageFacet';
7
+ import {
8
+ CHARACTERISATION_FIELD,
7
9
  COUNTRIES_FIELD,
8
10
  TRANSNATIONAL_REGION_FIELD,
9
- } from './NavigatorGeographicCoverageFacet';
11
+ } from '../../../search/navigator_catalogue/geographicCoverage';
10
12
 
11
13
  jest.mock('@eeacms/search/components', () => ({
12
14
  Term: ({ term }) => <>{term}</>,
@@ -16,6 +18,7 @@ jest.mock('@eeacms/search/lib/hocs', () => ({
16
18
  }));
17
19
 
18
20
  const renderFacet = ({
21
+ detailedOptions = true,
19
22
  filters = [],
20
23
  onRemove = jest.fn(),
21
24
  onSelect = jest.fn(),
@@ -30,18 +33,22 @@ const renderFacet = ({
30
33
  facets: {
31
34
  [TRANSNATIONAL_REGION_FIELD]: [
32
35
  {
33
- data: [
34
- { value: 'Alpine Space', count: 3 },
35
- { value: 'Outermost regions', count: 2 },
36
- ],
36
+ data: detailedOptions
37
+ ? [
38
+ { value: 'Alpine Space', count: 3 },
39
+ { value: 'Outermost regions', count: 2 },
40
+ ]
41
+ : [],
37
42
  },
38
43
  ],
39
44
  [COUNTRIES_FIELD]: [
40
45
  {
41
- data: [
42
- { value: 'Germany', count: 4 },
43
- { value: 'Spain', count: 2 },
44
- ],
46
+ data: detailedOptions
47
+ ? [
48
+ { value: 'Germany', count: 4 },
49
+ { value: 'Spain', count: 2 },
50
+ ]
51
+ : [],
45
52
  },
46
53
  ],
47
54
  },
@@ -58,14 +65,8 @@ const renderFacet = ({
58
65
  defaultMessage: 'Transnational regions',
59
66
  }}
60
67
  options={[
61
- { value: 'Global', count: 5, selected: false },
62
68
  { value: 'Europe', count: 8, selected: false },
63
- {
64
- value: 'Macro-Transnational region',
65
- count: 4,
66
- selected: false,
67
- },
68
- { value: 'Countries', count: 6, selected: false },
69
+ { value: 'Global', count: 5, selected: false },
69
70
  ]}
70
71
  />
71
72
  </IntlProvider>,
@@ -79,15 +80,37 @@ describe('NavigatorGeographicCoverageFacet', () => {
79
80
  jest.clearAllMocks();
80
81
  });
81
82
 
82
- it('shows Globe and Europe as direct options and filters by the raw value', () => {
83
- const { onSelect } = renderFacet();
83
+ it('shows Global and Europe as direct options', () => {
84
+ renderFacet();
85
+
86
+ expect(
87
+ screen
88
+ .getAllByRole('checkbox')
89
+ .slice(0, 2)
90
+ .map((option) => option.getAttribute('aria-label')),
91
+ ).toEqual(['Europe', 'Global']);
92
+ });
84
93
 
85
- expect(screen.getByText('Global')).toBeInTheDocument();
86
- expect(screen.getByText('Europe')).toBeInTheDocument();
94
+ it('clears detailed coverage before selecting a direct scope', () => {
95
+ const { onSelect, removeFilter } = renderFacet({
96
+ filters: [
97
+ {
98
+ field: COUNTRIES_FIELD,
99
+ values: ['Germany'],
100
+ type: 'any',
101
+ },
102
+ ],
103
+ });
87
104
 
88
- fireEvent.click(screen.getByLabelText('Global'));
105
+ fireEvent.click(screen.getByLabelText('Europe'));
89
106
 
90
- expect(onSelect).toHaveBeenCalledWith('Global');
107
+ expect(removeFilter).toHaveBeenCalledWith(COUNTRIES_FIELD, null, 'any');
108
+ expect(removeFilter).toHaveBeenCalledWith(
109
+ TRANSNATIONAL_REGION_FIELD,
110
+ null,
111
+ 'any',
112
+ );
113
+ expect(onSelect).toHaveBeenCalledWith('Europe');
91
114
  });
92
115
 
93
116
  it('expands and filters Macro-Transnational region values', () => {
@@ -124,4 +147,47 @@ describe('NavigatorGeographicCoverageFacet', () => {
124
147
  'any',
125
148
  );
126
149
  });
150
+
151
+ it('shows empty detailed coverage groups while Europe is selected', () => {
152
+ const { onRemove } = renderFacet({
153
+ detailedOptions: false,
154
+ filters: [
155
+ {
156
+ field: CHARACTERISATION_FIELD,
157
+ values: ['Europe'],
158
+ type: 'any',
159
+ },
160
+ ],
161
+ });
162
+
163
+ expect(screen.getByLabelText('Europe')).toBeChecked();
164
+ fireEvent.click(screen.getByRole('button', { name: 'Countries' }));
165
+ fireEvent.click(
166
+ screen.getByRole('button', { name: 'Transnational regions' }),
167
+ );
168
+
169
+ expect(screen.getAllByText('No options available.')).toHaveLength(2);
170
+
171
+ fireEvent.click(screen.getByLabelText('Europe'));
172
+
173
+ expect(onRemove).toHaveBeenCalledWith('Europe');
174
+ });
175
+
176
+ it('keeps detailed coverage available when a country is selected', () => {
177
+ renderFacet({
178
+ filters: [
179
+ {
180
+ field: COUNTRIES_FIELD,
181
+ values: ['Germany'],
182
+ type: 'any',
183
+ },
184
+ ],
185
+ });
186
+
187
+ expect(screen.getByRole('button', { name: 'Countries' })).toBeEnabled();
188
+ expect(screen.getByLabelText('Germany')).toBeEnabled();
189
+ expect(
190
+ screen.getByRole('button', { name: 'Transnational regions' }),
191
+ ).toBeEnabled();
192
+ });
127
193
  });
@@ -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, sortAdaptationSteps } from './utils';
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
- <div className="navigator-guide-result-count">
343
- <span className="navigator-guide-result-count-value">
344
- {isLoading ? '…' : totalResults || 0}
345
- </span>
346
- <p>
347
- {intl.formatMessage(messages.toolsMatch, {
348
- count: totalResults || 0,
349
- })}
350
- </p>
351
- </div>
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', () => ({ useSearchContext: jest.fn() }));
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 appConfig={{ previewResultsLimit: 3 }} />
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 { mergeGuideOptions, sortAdaptationSteps } from './utils';
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
- <ChatWindow
47
- {...props}
48
- hideSourcesTab
49
- extraRemarkPlugins={extraRemarkPlugins}
50
- extraMarkdownComponents={extraMarkdownComponents}
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
  });
@@ -7,6 +7,7 @@ import {
7
7
  Icon,
8
8
  Loader,
9
9
  Message,
10
+ Popup,
10
11
  Table,
11
12
  Button,
12
13
  } from 'semantic-ui-react';
@@ -84,10 +85,20 @@ const messages = defineMessages({
84
85
  id: 'Usability',
85
86
  defaultMessage: 'Usability',
86
87
  },
88
+ usabilityTooltip: {
89
+ id: 'This indicates how easy the tool and its outputs are to understand and interpret, with low = high-level expertise needed, moderate = some prior technical/scientific knowledge needed, high = general user-friendly, minimal technical knowledge needed.',
90
+ defaultMessage:
91
+ 'This indicates how easy the tool and its outputs are to understand and interpret, with low = high-level expertise needed, moderate = some prior technical/scientific knowledge needed, high = general user-friendly, minimal technical knowledge needed.',
92
+ },
87
93
  functionality: {
88
94
  id: 'Functionality',
89
95
  defaultMessage: 'Functionality',
90
96
  },
97
+ functionalityTooltip: {
98
+ id: 'Number of adaptation support cycle steps supported, from 1 to 6 steps.',
99
+ defaultMessage:
100
+ 'Number of adaptation support cycle steps supported, from 1 to 6 steps.',
101
+ },
91
102
  spatialScale: {
92
103
  id: 'Spatial scale',
93
104
  defaultMessage: 'Spatial scale',
@@ -231,6 +242,53 @@ const FunctionalityScore = ({ value }) => {
231
242
  );
232
243
  };
233
244
 
245
+ const UsabilityTooltipContent = ({ text }) => {
246
+ const parts = text.match(
247
+ /^(.* with) low = (.*), moderate = (.*), high = (.*)$/,
248
+ );
249
+
250
+ if (!parts) return text;
251
+
252
+ const [, introduction, low, moderate, high] = parts;
253
+ const levels = [
254
+ ['low', low],
255
+ ['moderate', moderate],
256
+ ['high', high],
257
+ ];
258
+
259
+ return (
260
+ <div className="usability-tooltip-content">
261
+ <div className="usability-tooltip-introduction">{introduction}:</div>
262
+ {levels.map(([level, description]) => (
263
+ <div className="usability-tooltip-level" key={level}>
264
+ <strong>{level}</strong>
265
+ <span>{` = ${description}`}</span>
266
+ </div>
267
+ ))}
268
+ </div>
269
+ );
270
+ };
271
+
272
+ const CriterionLabel = ({ label, tooltip, tooltipContent }) => (
273
+ <div className="compare-criteria-title">
274
+ <span>{label}</span>
275
+ <Popup
276
+ className="compare-criteria-popup"
277
+ content={tooltipContent || tooltip}
278
+ position="top left"
279
+ trigger={
280
+ <button
281
+ type="button"
282
+ className="compare-criteria-tooltip"
283
+ aria-label={tooltip}
284
+ >
285
+ <Icon className="ri-information-line" aria-hidden="true" />
286
+ </button>
287
+ }
288
+ />
289
+ </div>
290
+ );
291
+
234
292
  const getCompareUids = (search) => {
235
293
  const params = new URLSearchParams(search);
236
294
 
@@ -297,6 +355,7 @@ const CompareToolsView = () => {
297
355
  const registry = config.settings.searchlib;
298
356
  const landingPageURL = getNavigatorCataloguePageURL(currentLang);
299
357
  const compareToolsTitle = intl.formatMessage(messages.compareTools);
358
+ const usabilityTooltip = intl.formatMessage(messages.usabilityTooltip);
300
359
  const returnURL =
301
360
  location.state?.returnURL ||
302
361
  getReturnURL(location.search) ||
@@ -519,11 +578,19 @@ const CompareToolsView = () => {
519
578
 
520
579
  <Table.Body>
521
580
  <Table.Row>
522
- <Table.Cell as="th" scope="row">
581
+ <Table.Cell
582
+ as="th"
583
+ scope="row"
584
+ aria-label={intl.formatMessage(messages.usability)}
585
+ >
523
586
  <div className="compare-criteria">
524
- <div className="compare-criteria-title">
525
- {intl.formatMessage(messages.usability)}
526
- </div>
587
+ <CriterionLabel
588
+ label={intl.formatMessage(messages.usability)}
589
+ tooltip={usabilityTooltip}
590
+ tooltipContent={
591
+ <UsabilityTooltipContent text={usabilityTooltip} />
592
+ }
593
+ />
527
594
  </div>
528
595
  </Table.Cell>
529
596
 
@@ -537,11 +604,18 @@ const CompareToolsView = () => {
537
604
  </Table.Row>
538
605
 
539
606
  <Table.Row>
540
- <Table.Cell as="th" scope="row">
607
+ <Table.Cell
608
+ as="th"
609
+ scope="row"
610
+ aria-label={intl.formatMessage(messages.functionality)}
611
+ >
541
612
  <div className="compare-criteria">
542
- <div className="compare-criteria-title">
543
- {intl.formatMessage(messages.functionality)}
544
- </div>
613
+ <CriterionLabel
614
+ label={intl.formatMessage(messages.functionality)}
615
+ tooltip={intl.formatMessage(
616
+ messages.functionalityTooltip,
617
+ )}
618
+ />
545
619
  </div>
546
620
  </Table.Cell>
547
621
 
@@ -1,6 +1,12 @@
1
1
  import React from 'react';
2
2
  import '@testing-library/jest-dom';
3
- import { fireEvent, render, screen, within } from '@testing-library/react';
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';
@@ -122,6 +128,16 @@ describe('CompareToolsView', () => {
122
128
  expect(
123
129
  screen.getByRole('rowheader', { name: 'Functionality' }),
124
130
  ).toHaveAttribute('scope', 'row');
131
+ expect(
132
+ screen.getByRole('button', {
133
+ name: 'This indicates how easy the tool and its outputs are to understand and interpret, with low = high-level expertise needed, moderate = some prior technical/scientific knowledge needed, high = general user-friendly, minimal technical knowledge needed.',
134
+ }),
135
+ ).toBeInTheDocument();
136
+ expect(
137
+ screen.getByRole('button', {
138
+ name: 'Number of adaptation support cycle steps supported, from 1 to 6 steps.',
139
+ }),
140
+ ).toBeInTheDocument();
125
141
  expect(
126
142
  screen.getByRole('button', {
127
143
  name: 'Remove Tool one from comparison',
@@ -210,8 +226,10 @@ describe('CompareToolsView', () => {
210
226
 
211
227
  fireEvent.load(img);
212
228
 
213
- expect(img.style.display).toBe('');
214
- expect(thumbnail.querySelector('.ri-file-line')).not.toBeInTheDocument();
229
+ await waitFor(() => {
230
+ expect(img.style.display).toBe('');
231
+ expect(thumbnail.querySelector('.ri-file-line')).not.toBeInTheDocument();
232
+ });
215
233
  });
216
234
 
217
235
  it('falls back to the file icon when the image fails', async () => {
package/src/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { defineMessages, FormattedMessage } from 'react-intl';
4
4
  import loadable from '@loadable/component';
5
5
  import { compose } from 'redux';
6
+ import { buildTermFacetAggregationRequest } from '@eeacms/search/lib/search';
6
7
  import Sitemap from '@plone/volto/components/theme/Sitemap/Sitemap';
7
8
  import DefaultView from '@plone/volto/components/theme/View/DefaultView';
8
9
  import SelectAutoCompleteWidget from '@plone/volto/components/manage/Widgets/SelectAutoComplete';
@@ -57,6 +58,7 @@ import NavigatorCatalogueContentView from './components/Search/NavigatorCatalogu
57
58
  import NavigatorGeographicCoverageFacet from './components/Search/NavigatorCatalogue/NavigatorGeographicCoverageFacet';
58
59
  import NavigatorGuideLayout from './components/Search/NavigatorGuide/NavigatorGuideLayout';
59
60
  import NavigatorGuideContentView from './components/Search/NavigatorGuide/NavigatorGuideContentView';
61
+ import { getGeographicCoverageFacetValue } from './search/navigator_catalogue/geographicCoverage';
60
62
 
61
63
  import { langRedirection } from './store/middleware';
62
64
 
@@ -437,7 +439,9 @@ const applyConfig = (config) => {
437
439
  };
438
440
  config.settings.searchlib.resolve.NavigatorGeographicCoverageFacet = {
439
441
  ...config.settings.searchlib.resolve.MultiTermFacet,
442
+ buildRequest: buildTermFacetAggregationRequest,
440
443
  component: NavigatorGeographicCoverageFacet,
444
+ getValue: getGeographicCoverageFacetValue,
441
445
  };
442
446
  config.settings.searchlib.resolve.NavigatorGuideLayout = {
443
447
  component: NavigatorGuideLayout,
@@ -4,6 +4,7 @@ import { getClientProxyAddress } from './../utils';
4
4
  import { vocab } from './../vocabulary';
5
5
 
6
6
  import facets from './facets';
7
+ import { geographicCoverageRuntimeMappings } from './geographicCoverage';
7
8
  import views from './views';
8
9
 
9
10
  const navigatorCatalogueConfig = {
@@ -46,7 +47,10 @@ export default function installNavigatorCatalogueSearch(config) {
46
47
  index_name: 'data_searchui',
47
48
  host: process.env.RAZZLE_ES_PROXY_ADDR || 'http://localhost:3000',
48
49
  vocab,
49
- runtime_mappings: build_runtime_mappings(clusters),
50
+ runtime_mappings: {
51
+ ...build_runtime_mappings(clusters),
52
+ ...geographicCoverageRuntimeMappings,
53
+ },
50
54
  };
51
55
 
52
56
  const { navigatorCatalogueSearch } = config.searchui;
@@ -5,6 +5,7 @@ import {
5
5
  geographic_countries,
6
6
  language,
7
7
  } from './../common';
8
+ import { CHARACTERISATION_FIELD } from './geographicCoverage';
8
9
 
9
10
  const messages = defineMessages({
10
11
  geographicCoverage: {
@@ -68,7 +69,7 @@ const facets = [
68
69
  alwaysVisible: false,
69
70
  }),
70
71
  multiTermFacet({
71
- field: 'cca_geographic_characterisation.keyword',
72
+ field: CHARACTERISATION_FIELD,
72
73
  factory: 'NavigatorGeographicCoverageFacet',
73
74
  isFilterable: false,
74
75
  isMulti: true,
@@ -0,0 +1,47 @@
1
+ export const CHARACTERISATION_FIELD = 'cca_navigator_geographic_scope';
2
+ export const TRANSNATIONAL_REGION_FIELD =
3
+ 'cca_geographic_transnational_region.keyword';
4
+ export const COUNTRIES_FIELD = 'cca_geographic_countries.keyword';
5
+
6
+ export const geographicCoverageRuntimeMappings = {
7
+ [CHARACTERISATION_FIELD]: {
8
+ type: 'keyword',
9
+ script: {
10
+ source: `
11
+ boolean hasCountries = doc.containsKey('cca_geographic_countries.keyword') && doc['cca_geographic_countries.keyword'].size() != 0;
12
+ boolean hasTransnational = doc.containsKey('cca_geographic_transnational_region.keyword') && doc['cca_geographic_transnational_region.keyword'].size() != 0;
13
+ boolean hasSubnational = doc.containsKey('cca_sub_nationals.keyword') && doc['cca_sub_nationals.keyword'].size() != 0;
14
+ boolean hasCity = doc.containsKey('cca_city.keyword') && doc['cca_city.keyword'].size() != 0;
15
+
16
+ if (!hasCountries && !hasTransnational && !hasSubnational && !hasCity) {
17
+ for (def value : doc['cca_geographic_characterisation.keyword']) {
18
+ emit(value);
19
+ }
20
+ }
21
+ `,
22
+ },
23
+ },
24
+ };
25
+
26
+ export const getGeographicCoverageFacetValue = ({
27
+ aggregations,
28
+ fieldName,
29
+ }) => {
30
+ const aggregation = aggregations?.[fieldName];
31
+ if (!aggregation) return undefined;
32
+
33
+ const counts = Object.fromEntries(
34
+ (aggregation.buckets || []).map(({ key, doc_count }) => [key, doc_count]),
35
+ );
36
+
37
+ return [
38
+ {
39
+ field: fieldName,
40
+ type: 'value',
41
+ data: ['Europe', 'Global'].map((value) => ({
42
+ value,
43
+ count: counts[value] || 0,
44
+ })),
45
+ },
46
+ ];
47
+ };
@@ -0,0 +1,65 @@
1
+ import {
2
+ CHARACTERISATION_FIELD,
3
+ geographicCoverageRuntimeMappings,
4
+ getGeographicCoverageFacetValue,
5
+ } from './geographicCoverage';
6
+
7
+ describe('geographic coverage runtime mapping', () => {
8
+ it('defines an exclusive keyword scope from all specific location fields', () => {
9
+ const mapping = geographicCoverageRuntimeMappings[CHARACTERISATION_FIELD];
10
+
11
+ expect(mapping.type).toBe('keyword');
12
+ expect(mapping.script.source).toContain(
13
+ "doc['cca_geographic_countries.keyword']",
14
+ );
15
+ expect(mapping.script.source).toContain(
16
+ "doc['cca_geographic_transnational_region.keyword']",
17
+ );
18
+ expect(mapping.script.source).toContain("doc['cca_sub_nationals.keyword']");
19
+ expect(mapping.script.source).toContain("doc['cca_city.keyword']");
20
+ expect(mapping.script.source).toContain(
21
+ "doc['cca_geographic_characterisation.keyword']",
22
+ );
23
+ });
24
+ });
25
+
26
+ describe('geographic coverage facet value', () => {
27
+ it('keeps the facet available with zero counts when its buckets are empty', () => {
28
+ expect(
29
+ getGeographicCoverageFacetValue({
30
+ aggregations: {
31
+ [CHARACTERISATION_FIELD]: { buckets: [] },
32
+ },
33
+ fieldName: CHARACTERISATION_FIELD,
34
+ }),
35
+ ).toEqual([
36
+ {
37
+ field: CHARACTERISATION_FIELD,
38
+ type: 'value',
39
+ data: [
40
+ { value: 'Europe', count: 0 },
41
+ { value: 'Global', count: 0 },
42
+ ],
43
+ },
44
+ ]);
45
+ });
46
+
47
+ it('returns Europe and Global alphabetically with their counts', () => {
48
+ expect(
49
+ getGeographicCoverageFacetValue({
50
+ aggregations: {
51
+ [CHARACTERISATION_FIELD]: {
52
+ buckets: [
53
+ { key: 'Global', doc_count: 25 },
54
+ { key: 'Europe', doc_count: 20 },
55
+ ],
56
+ },
57
+ },
58
+ fieldName: CHARACTERISATION_FIELD,
59
+ })[0].data,
60
+ ).toEqual([
61
+ { value: 'Europe', count: 20 },
62
+ { value: 'Global', count: 25 },
63
+ ]);
64
+ });
65
+ });
@@ -8,6 +8,7 @@ const previewResultsLimit = 3;
8
8
 
9
9
  const navigatorGuideConfig = {
10
10
  title: 'Navigator Guide',
11
+ defaultSort: 'title.index|asc',
11
12
  alwaysSearchOnInitialLoad: true,
12
13
  resultsPerPage: previewResultsLimit,
13
14
  previewResultsLimit,
@@ -354,6 +354,22 @@
354
354
  margin-left: 0.5rem !important;
355
355
  }
356
356
 
357
+ .ui.popup.compare-criteria-popup {
358
+ max-width: min(450px, calc(100vw - 2rem));
359
+
360
+ .usability-tooltip-introduction {
361
+ margin-bottom: 0.65rem;
362
+ }
363
+
364
+ .usability-tooltip-level {
365
+ strong {
366
+ font-weight: 700;
367
+ text-transform: capitalize;
368
+ color: @secondaryColor;
369
+ }
370
+ }
371
+ }
372
+
357
373
  .navigator-catalogue-compare-view {
358
374
  overflow-x: auto;
359
375
 
@@ -431,6 +447,20 @@
431
447
  font-weight: 500;
432
448
  }
433
449
 
450
+ .compare-criteria-tooltip {
451
+ padding: 0;
452
+ border: 0;
453
+ margin-left: 0.4rem;
454
+ background: transparent;
455
+ color: @primaryColor;
456
+ cursor: pointer;
457
+ line-height: 1;
458
+
459
+ i.icon {
460
+ margin: 0;
461
+ }
462
+ }
463
+
434
464
  .usability-value,
435
465
  .functionality-value {
436
466
  color: @secondaryColor;