@cdc/map 4.26.2 → 4.26.4

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 (118) hide show
  1. package/CONFIG.md +235 -0
  2. package/README.md +70 -24
  3. package/dist/cdcmap-CY9IcPSi.es.js +6 -0
  4. package/dist/cdcmap-DlpiY3fQ.es.js +4 -0
  5. package/dist/cdcmap.js +31260 -27946
  6. package/examples/{testing-layer-2.json → __data__/testing-layer-2.json} +1 -1
  7. package/examples/{testing-layer.json → __data__/testing-layer.json} +1 -1
  8. package/examples/county-hsa-toggle.json +51993 -0
  9. package/examples/custom-map-layers.json +2 -2
  10. package/examples/default-county.json +3 -3
  11. package/examples/minimal-example.json +69 -0
  12. package/examples/private/annotation-bug.json +642 -0
  13. package/examples/private/css-issue.json +314 -0
  14. package/examples/private/region-breaking.json +1639 -0
  15. package/examples/private/test1.json +27247 -0
  16. package/package.json +4 -4
  17. package/src/CdcMap.tsx +3 -14
  18. package/src/CdcMapComponent.tsx +302 -164
  19. package/src/_stories/CdcMap.Defaults.smoke.stories.tsx +76 -0
  20. package/src/_stories/CdcMap.Editor.ColumnsSectionTests.stories.tsx +601 -0
  21. package/src/_stories/CdcMap.Editor.DataTableSectionTests.stories.tsx +404 -0
  22. package/src/_stories/CdcMap.Editor.FiltersSectionTests.stories.tsx +229 -0
  23. package/src/_stories/CdcMap.Editor.GeneralSectionTests.stories.tsx +262 -0
  24. package/src/_stories/CdcMap.Editor.LegendSectionTests.stories.tsx +541 -0
  25. package/src/_stories/CdcMap.Editor.MultiCountryWorldMapTests.stories.tsx +359 -0
  26. package/src/_stories/CdcMap.Editor.PatternSettingsSectionTests.stories.tsx +516 -0
  27. package/src/_stories/CdcMap.Editor.SmallMultiplesSectionTests.stories.tsx +165 -0
  28. package/src/_stories/CdcMap.Editor.TextAnnotationsSectionTests.stories.tsx +145 -0
  29. package/src/_stories/CdcMap.Editor.TypeSectionTests.stories.tsx +312 -0
  30. package/src/_stories/CdcMap.Editor.VisualSectionTests.stories.tsx +359 -0
  31. package/src/_stories/CdcMap.Editor.ZoomControlsTests.stories.tsx +88 -0
  32. package/src/_stories/{CdcMap.stories.tsx → CdcMap.smoke.stories.tsx} +23 -1
  33. package/src/_stories/Map.HTMLInDataTable.stories.tsx +385 -0
  34. package/src/_stories/_mock/legends/legend-tests.json +3 -3
  35. package/src/_stories/_mock/multi-state-show-unselected.json +82 -0
  36. package/src/cdcMapComponent.styles.css +2 -2
  37. package/src/components/Annotation/Annotation.Draggable.styles.css +4 -4
  38. package/src/components/Annotation/AnnotationDropdown.styles.css +1 -1
  39. package/src/components/Annotation/AnnotationList.styles.css +13 -13
  40. package/src/components/Annotation/AnnotationList.tsx +1 -1
  41. package/src/components/EditorPanel/components/EditorPanel.tsx +905 -416
  42. package/src/components/EditorPanel/components/HexShapeSettings.tsx +1 -1
  43. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +112 -117
  44. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings-style.css +1 -1
  45. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +31 -15
  46. package/src/components/EditorPanel/components/editorPanel.styles.css +55 -25
  47. package/src/components/Legend/components/Legend.tsx +12 -7
  48. package/src/components/Legend/components/LegendGroup/legend.group.css +5 -5
  49. package/src/components/Legend/components/LegendItem.Hex.tsx +4 -2
  50. package/src/components/Legend/components/index.scss +2 -3
  51. package/src/components/NavigationMenu.tsx +2 -1
  52. package/src/components/SmallMultiples/SmallMultiples.css +5 -5
  53. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +1 -1
  54. package/src/components/UsaMap/components/SingleState/SingleState.StateOutput.tsx +32 -17
  55. package/src/components/UsaMap/components/TerritoriesSection.tsx +3 -2
  56. package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +13 -8
  57. package/src/components/UsaMap/components/UsaMap.County.tsx +629 -231
  58. package/src/components/UsaMap/components/UsaMap.Region.styles.css +1 -1
  59. package/src/components/UsaMap/components/UsaMap.SingleState.styles.css +2 -2
  60. package/src/components/UsaMap/components/UsaMap.State.tsx +14 -9
  61. package/src/components/UsaMap/data/cb_2019_us_county_20m.json +75817 -1
  62. package/src/components/UsaMap/data/hsa_fips_mapping.json +3144 -0
  63. package/src/components/WorldMap/WorldMap.tsx +10 -13
  64. package/src/components/WorldMap/data/world-topo-updated.json +1 -0
  65. package/src/components/WorldMap/data/world-topo.json +1 -1
  66. package/src/components/WorldMap/worldMap.styles.css +1 -1
  67. package/src/components/ZoomControls.tsx +49 -18
  68. package/src/components/zoomControls.styles.css +27 -11
  69. package/src/data/initial-state.js +15 -5
  70. package/src/data/legacy-defaults.ts +8 -0
  71. package/src/data/supported-counties.json +1 -1
  72. package/src/data/supported-geos.js +19 -0
  73. package/src/helpers/colors.ts +2 -1
  74. package/src/helpers/countyTerritories.ts +38 -0
  75. package/src/helpers/dataTableHelpers.ts +85 -0
  76. package/src/helpers/displayGeoName.ts +19 -11
  77. package/src/helpers/getMapContainerClasses.ts +8 -2
  78. package/src/helpers/getMatchingPatternForRow.ts +67 -0
  79. package/src/helpers/getPatternForRow.ts +11 -18
  80. package/src/helpers/tests/countyTerritories.test.ts +87 -0
  81. package/src/helpers/tests/dataTableHelpers.test.ts +78 -0
  82. package/src/helpers/tests/displayGeoName.test.ts +17 -0
  83. package/src/helpers/tests/getMatchingPatternForRow.test.ts +150 -0
  84. package/src/helpers/tests/getPatternForRow.test.ts +140 -2
  85. package/src/helpers/urlDataHelpers.ts +7 -1
  86. package/src/hooks/useApplyTooltipsToGeo.tsx +7 -4
  87. package/src/hooks/useMapLayers.tsx +1 -1
  88. package/src/hooks/useResizeObserver.ts +36 -22
  89. package/src/hooks/useTooltip.test.tsx +64 -0
  90. package/src/hooks/useTooltip.ts +46 -15
  91. package/src/scss/editor-panel.scss +1 -1
  92. package/src/scss/main.scss +140 -6
  93. package/src/scss/map.scss +9 -4
  94. package/src/store/map.actions.ts +5 -0
  95. package/src/store/map.reducer.ts +13 -0
  96. package/src/test/CdcMap.test.jsx +26 -2
  97. package/src/types/MapConfig.ts +28 -4
  98. package/src/types/MapContext.ts +5 -1
  99. package/topojson-updater/README.txt +1 -1
  100. package/dist/cdcmap-Cf9_fbQf.es.js +0 -6
  101. package/examples/__data__/city-state-data.json +0 -668
  102. package/examples/city-state.json +0 -434
  103. package/examples/default-world-data.json +0 -1450
  104. package/examples/new-cities.json +0 -656
  105. package/src/_stories/CdcMap.Editor.stories.tsx +0 -3475
  106. package/src/helpers/componentHelpers.ts +0 -8
  107. package/topojson-updater/package-lock.json +0 -223
  108. /package/src/_stories/{CdcMap.ColumnWrap.stories.tsx → CdcMap.ColumnWrap.smoke.stories.tsx} +0 -0
  109. /package/src/_stories/{CdcMap.DistrictOfColumbia.stories.tsx → CdcMap.DistrictOfColumbia.smoke.stories.tsx} +0 -0
  110. /package/src/_stories/{CdcMap.Filters.stories.tsx → CdcMap.Filters.smoke.stories.tsx} +0 -0
  111. /package/src/_stories/{CdcMap.Legend.Gradient.stories.tsx → CdcMap.Legend.Gradient.smoke.stories.tsx} +0 -0
  112. /package/src/_stories/{CdcMap.Legend.stories.tsx → CdcMap.Legend.smoke.stories.tsx} +0 -0
  113. /package/src/_stories/{CdcMap.Patterns.stories.tsx → CdcMap.Patterns.smoke.stories.tsx} +0 -0
  114. /package/src/_stories/{CdcMap.SmallMultiples.stories.tsx → CdcMap.SmallMultiples.smoke.stories.tsx} +0 -0
  115. /package/src/_stories/{CdcMap.Table.stories.tsx → CdcMap.Table.smoke.stories.tsx} +0 -0
  116. /package/src/_stories/{CdcMap.ZeroColor.stories.tsx → CdcMap.ZeroColor.smoke.stories.tsx} +0 -0
  117. /package/src/_stories/{GoogleMap.stories.tsx → GoogleMap.smoke.stories.tsx} +0 -0
  118. /package/src/_stories/{UsaMap.NoData.stories.tsx → UsaMap.NoData.smoke.stories.tsx} +0 -0
@@ -0,0 +1,359 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { within, userEvent, expect } from 'storybook/test'
3
+ import CdcMap from '../CdcMap'
4
+ import usaStateGradientConfig from './_mock/usa-state-gradient.json'
5
+ import multiCountryConfig from './_mock/multi-country.json'
6
+ import wastewaterMapSmallMultiplesConfig from './_mock/small_multiples/wastewater-map-small-multiples.json'
7
+ import { performAndAssert, waitForEditor, waitForPresence, openAccordion } from '@cdc/core/helpers/testing'
8
+
9
+ type Story = StoryObj<typeof CdcMap>
10
+
11
+ const mapMeta: Meta<typeof CdcMap> = {
12
+ title: 'Components/Templates/Map/Editor Tests',
13
+ component: CdcMap,
14
+ parameters: {
15
+ layout: 'fullscreen'
16
+ }
17
+ }
18
+
19
+ export default mapMeta
20
+
21
+ const DEFAULT_ARGS = {
22
+ isEditor: true,
23
+ config: usaStateGradientConfig
24
+ }
25
+
26
+ export const MultiCountryWorldMapTests: Story = {
27
+ args: {
28
+ ...DEFAULT_ARGS,
29
+ config: {
30
+ ...multiCountryConfig,
31
+ general: {
32
+ ...multiCountryConfig.general,
33
+ countriesPicked: [] // Start with no countries selected for testing
34
+ }
35
+ }
36
+ },
37
+ play: async ({ canvasElement }) => {
38
+ const canvas = within(canvasElement)
39
+
40
+ await waitForEditor(canvas)
41
+ await waitForPresence('.map-container', canvasElement)
42
+
43
+ // Ensure we're testing a world map - Type accordion should be open by default
44
+ await openAccordion(canvas, 'Type')
45
+
46
+ // Wait for world map to load (topology loads asynchronously)
47
+ // The config already starts with geoType: 'world', so we just need to wait for it to render
48
+ await waitForPresence('path[data-country-code]', canvasElement)
49
+
50
+ // Wait for the country selector to appear (only shows for world maps)
51
+ await waitForPresence('.cove-multiselect', canvasElement)
52
+
53
+ // Stay in Type accordion section - this is where multi-country controls are located
54
+
55
+ // ==========================================================================
56
+ // Helper functions to capture visual state
57
+ // ==========================================================================
58
+ const getCountryVisualState = () => {
59
+ const mapContainer = canvasElement.querySelector('.map-container')
60
+ const allCountryPaths = canvasElement.querySelectorAll('path[data-country-code]')
61
+ const visibleCountries = Array.from(allCountryPaths).filter(
62
+ path => !path.classList.contains('hidden') && !path.classList.contains('grayed-out')
63
+ )
64
+ const grayedCountries = Array.from(allCountryPaths).filter(path => path.classList.contains('grayed-out'))
65
+ const hiddenCountries = Array.from(allCountryPaths).filter(path => {
66
+ const hasHiddenClass = path.classList.contains('hidden')
67
+ const computedStyle = window.getComputedStyle(path as Element)
68
+ const isDisplayNone = computedStyle.display === 'none'
69
+ return hasHiddenClass || isDisplayNone
70
+ })
71
+
72
+ return {
73
+ totalCountries: allCountryPaths.length,
74
+ visibleCountries: visibleCountries.length,
75
+ grayedCountries: grayedCountries.length,
76
+ hiddenCountries: hiddenCountries.length,
77
+ mapClasses: mapContainer ? Array.from(mapContainer.classList) : [],
78
+ hasMultiCountryClass: mapContainer?.classList.contains('multi-country-selected') || false,
79
+ selectedCountryCodes: Array.from(
80
+ new Set(
81
+ Array.from(visibleCountries)
82
+ .map(path => path.getAttribute('data-country-code'))
83
+ .filter(Boolean)
84
+ )
85
+ )
86
+ }
87
+ }
88
+
89
+ // ==========================================================================
90
+ // TEST: Country Multi-Select Dropdown Interaction
91
+ // ==========================================================================
92
+ const countryMultiSelect = Array.from(canvasElement.querySelectorAll('.cove-multiselect')).find(ms => {
93
+ const parentLabel = ms.closest('label')
94
+ const labelSpan = parentLabel?.querySelector('span')
95
+ return labelSpan?.textContent?.includes('Countries Selector')
96
+ }) as HTMLElement
97
+ expect(countryMultiSelect).toBeTruthy()
98
+
99
+ // Get the expand button to open the dropdown
100
+ const expandButton = countryMultiSelect.querySelector('button[aria-label="Expand"]') as HTMLButtonElement
101
+ expect(expandButton).toBeTruthy()
102
+
103
+ // Test selecting first country (France)
104
+ await performAndAssert(
105
+ 'Multi-Country → Select France',
106
+ getCountryVisualState,
107
+ async () => {
108
+ // Open the dropdown
109
+ await userEvent.click(expandButton)
110
+ // Find and click France option
111
+ const franceOption = Array.from(countryMultiSelect.querySelectorAll('li[role="option"]')).find(li =>
112
+ li.textContent?.includes('France')
113
+ ) as HTMLElement
114
+ expect(franceOption).toBeTruthy()
115
+ await userEvent.click(franceOption)
116
+ },
117
+ (before, after) => {
118
+ // When countries are selected, map should show multi-country state
119
+ return (
120
+ after.hasMultiCountryClass &&
121
+ after.selectedCountryCodes.includes('FRA') &&
122
+ after.visibleCountries < before.totalCountries // Some countries should be filtered
123
+ )
124
+ }
125
+ )
126
+
127
+ // Test selecting second country (Germany)
128
+ await performAndAssert(
129
+ 'Multi-Country → Add Germany to selection',
130
+ getCountryVisualState,
131
+ async () => {
132
+ // Expand dropdown again
133
+ const expandBtn = countryMultiSelect.querySelector('button[aria-label="Expand"]') as HTMLButtonElement
134
+ await userEvent.click(expandBtn)
135
+ // Find and click Germany option
136
+ const germanyOption = Array.from(countryMultiSelect.querySelectorAll('li[role="option"]')).find(li =>
137
+ li.textContent?.includes('Germany')
138
+ ) as HTMLElement
139
+ expect(germanyOption).toBeTruthy()
140
+ await userEvent.click(germanyOption)
141
+ },
142
+ (before, after) => {
143
+ // Both France and Germany should be selected
144
+ return (
145
+ after.selectedCountryCodes.includes('FRA') &&
146
+ after.selectedCountryCodes.includes('DEU') &&
147
+ after.selectedCountryCodes.length === 2
148
+ )
149
+ }
150
+ )
151
+
152
+ // Test adding a third country (Italy)
153
+ await performAndAssert(
154
+ 'Multi-Country → Add Italy to selection',
155
+ getCountryVisualState,
156
+ async () => {
157
+ // Expand dropdown again
158
+ const expandBtn = countryMultiSelect.querySelector('button[aria-label="Expand"]') as HTMLButtonElement
159
+ await userEvent.click(expandBtn)
160
+ // Find and click Italy option
161
+ const italyOption = Array.from(countryMultiSelect.querySelectorAll('li[role="option"]')).find(li =>
162
+ li.textContent?.includes('Italy')
163
+ ) as HTMLElement
164
+ expect(italyOption).toBeTruthy()
165
+ await userEvent.click(italyOption)
166
+ },
167
+ (before, after) => {
168
+ // All three countries should be selected
169
+ return (
170
+ after.selectedCountryCodes.includes('FRA') &&
171
+ after.selectedCountryCodes.includes('DEU') &&
172
+ after.selectedCountryCodes.includes('ITA') &&
173
+ after.selectedCountryCodes.length === 3
174
+ )
175
+ }
176
+ )
177
+
178
+ // ==========================================================================
179
+ // TEST: Hide Unselected Countries Toggle (Grey-out vs Hide behavior)
180
+ // ==========================================================================
181
+ // Ensure map has fully rendered with country paths before testing toggle
182
+ await waitForPresence('path[data-country-code]', canvasElement)
183
+
184
+ // InputToggle renders as a div with a hidden checkbox
185
+ const hideUnselectedToggle = Array.from(
186
+ canvasElement.querySelectorAll('input[name*="hideUnselectedCountries"]')
187
+ ).find(input => {
188
+ const label = input.closest('label') || input.parentElement?.querySelector('label')
189
+ return label?.textContent?.includes('Hide Unselected Countries')
190
+ }) as HTMLInputElement
191
+ expect(hideUnselectedToggle).toBeTruthy()
192
+
193
+ // By default (unchecked), unselected countries should be grayed out
194
+ // First, ensure the toggle is unchecked (hideUnselectedCountries = false, so countries are grayed)
195
+ if (hideUnselectedToggle.checked) {
196
+ await userEvent.click(hideUnselectedToggle)
197
+ await new Promise(resolve => setTimeout(resolve, 100))
198
+ }
199
+
200
+ const beforeState = getCountryVisualState()
201
+
202
+ // Now check it (should hide unselected countries)
203
+ await userEvent.click(hideUnselectedToggle)
204
+ await new Promise(resolve => setTimeout(resolve, 100)) // Give time for re-render
205
+
206
+ const afterState = getCountryVisualState()
207
+
208
+ const testResult =
209
+ afterState.hiddenCountries > beforeState.hiddenCountries &&
210
+ afterState.grayedCountries === 0 && // No grayed countries when hiding
211
+ afterState.selectedCountryCodes.length === 3 // Only our 3 selected countries (by unique ISO codes)
212
+
213
+ expect(testResult).toBe(true)
214
+
215
+ // Test unchecking "Hide Unselected Countries" again (should grey-out unselected countries)
216
+ await performAndAssert(
217
+ 'Hide Unselected Countries → Grey-out unselected countries',
218
+ getCountryVisualState,
219
+ async () => {
220
+ await userEvent.click(hideUnselectedToggle)
221
+ },
222
+ (before, after) => {
223
+ // When not hiding (default), unselected countries should be grayed out
224
+ return (
225
+ after.grayedCountries > before.grayedCountries &&
226
+ after.hiddenCountries === 0 && // No hidden countries when showing grayed
227
+ after.selectedCountryCodes.length === 3 // Our 3 selected countries remain visible (by unique ISO codes)
228
+ )
229
+ }
230
+ )
231
+
232
+ // ==========================================================================
233
+ // TEST: Map Centering and Bounds Changes
234
+ // ==========================================================================
235
+ await performAndAssert(
236
+ 'Multi-Country Selection → Map centers on selected countries',
237
+ getCountryVisualState,
238
+ async () => {
239
+ // Clear selection and select different countries (Japan, Australia) to test centering
240
+ // Click expand button
241
+ const expandBtn = countryMultiSelect.querySelector('button[aria-label="Expand"]') as HTMLButtonElement
242
+ await userEvent.click(expandBtn)
243
+
244
+ // Clear existing selections first by clicking remove buttons
245
+ const removeButtons = countryMultiSelect.querySelectorAll('button[aria-label="Remove"]')
246
+ for (const button of Array.from(removeButtons)) {
247
+ await userEvent.click(button as HTMLButtonElement)
248
+ }
249
+
250
+ // Select Japan
251
+ await userEvent.click(expandBtn) // Reopen dropdown
252
+ const japanOption = Array.from(countryMultiSelect.querySelectorAll('li[role="option"]')).find(li =>
253
+ li.textContent?.includes('Japan')
254
+ ) as HTMLElement
255
+ expect(japanOption).toBeTruthy()
256
+ await userEvent.click(japanOption)
257
+
258
+ // Select Australia
259
+ await userEvent.click(expandBtn) // Reopen dropdown
260
+ const australiaOption = Array.from(countryMultiSelect.querySelectorAll('li[role="option"]')).find(li =>
261
+ li.textContent?.includes('Australia')
262
+ ) as HTMLElement
263
+ expect(australiaOption).toBeTruthy()
264
+ await userEvent.click(australiaOption)
265
+
266
+ // Wait for map to re-render with new selection
267
+ await new Promise(resolve => setTimeout(resolve, 300))
268
+ },
269
+ (before, after) => {
270
+ // Verify that the selected countries changed (Japan and Australia instead of France, Germany, Italy)
271
+ // The centering happens automatically via useCountryZoom hook
272
+ return (
273
+ after.selectedCountryCodes.includes('JPN') &&
274
+ after.selectedCountryCodes.includes('AUS') &&
275
+ !after.selectedCountryCodes.includes('FRA') &&
276
+ after.selectedCountryCodes.length === 2
277
+ )
278
+ }
279
+ )
280
+
281
+ // ==========================================================================
282
+ // TEST: Clear Country Selection (Reset to full world map)
283
+ // ==========================================================================
284
+ await performAndAssert(
285
+ 'Multi-Country → Clear all selections',
286
+ getCountryVisualState,
287
+ async () => {
288
+ // Remove all selected countries using the remove buttons
289
+ const removeButtons = countryMultiSelect.querySelectorAll('button[aria-label="Remove"]')
290
+ for (const button of Array.from(removeButtons)) {
291
+ await userEvent.click(button as HTMLButtonElement)
292
+ }
293
+ },
294
+ (before, after) => {
295
+ // When all countries are cleared, should return to normal world map state
296
+ // Note: selectedCountryCodes will contain ALL countries since all are visible (not grayed/hidden)
297
+ return (
298
+ !after.hasMultiCountryClass &&
299
+ after.grayedCountries === 0 &&
300
+ after.hiddenCountries === 0 &&
301
+ after.visibleCountries === after.totalCountries // All countries visible
302
+ )
303
+ }
304
+ )
305
+
306
+ // ==========================================================================
307
+ // TEST: Country Selection with Data Integration
308
+ // ==========================================================================
309
+ await performAndAssert(
310
+ 'Multi-Country Data → Selected countries show data values',
311
+ getCountryVisualState,
312
+ async () => {
313
+ // Select countries that should have data (France, Germany, Italy - these have data in the config)
314
+ const expandBtn = countryMultiSelect.querySelector('button[aria-label="Expand"]') as HTMLButtonElement
315
+
316
+ // Select France
317
+ await userEvent.click(expandBtn)
318
+ const franceOption = Array.from(countryMultiSelect.querySelectorAll('li[role="option"]')).find(li =>
319
+ li.textContent?.includes('France')
320
+ ) as HTMLElement
321
+
322
+ expect(franceOption).toBeTruthy()
323
+ await userEvent.click(franceOption)
324
+
325
+ // Select Germany
326
+ await userEvent.click(expandBtn)
327
+ const germanyOption = Array.from(countryMultiSelect.querySelectorAll('li[role="option"]')).find(li =>
328
+ li.textContent?.includes('Germany')
329
+ ) as HTMLElement
330
+
331
+ expect(germanyOption).toBeTruthy()
332
+ await userEvent.click(germanyOption)
333
+
334
+ // Select Italy
335
+ await userEvent.click(expandBtn)
336
+ const italyOption = Array.from(countryMultiSelect.querySelectorAll('li[role="option"]')).find(li =>
337
+ li.textContent?.includes('Italy')
338
+ ) as HTMLElement
339
+
340
+ expect(italyOption).toBeTruthy()
341
+ await userEvent.click(italyOption)
342
+
343
+ // Wait for data to be applied
344
+ await new Promise(resolve => setTimeout(resolve, 300))
345
+ },
346
+ (before, after) => {
347
+ // Verify that the countries with data are correctly selected
348
+ // (Data rendering and legend display are separate from multi-country selection functionality)
349
+ return (
350
+ after.selectedCountryCodes.includes('FRA') &&
351
+ after.selectedCountryCodes.includes('DEU') &&
352
+ after.selectedCountryCodes.includes('ITA') &&
353
+ after.hasMultiCountryClass &&
354
+ after.selectedCountryCodes.length === 3
355
+ )
356
+ }
357
+ )
358
+ }
359
+ }