@cdc/map 4.26.3 → 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 (79) 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 +27405 -26257
  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 +2 -2
  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/CdcMapComponent.tsx +96 -13
  18. package/src/_stories/CdcMap.Editor.ColumnsSectionTests.stories.tsx +601 -0
  19. package/src/_stories/CdcMap.Editor.DataTableSectionTests.stories.tsx +404 -0
  20. package/src/_stories/CdcMap.Editor.FiltersSectionTests.stories.tsx +229 -0
  21. package/src/_stories/CdcMap.Editor.GeneralSectionTests.stories.tsx +262 -0
  22. package/src/_stories/CdcMap.Editor.LegendSectionTests.stories.tsx +541 -0
  23. package/src/_stories/CdcMap.Editor.MultiCountryWorldMapTests.stories.tsx +359 -0
  24. package/src/_stories/CdcMap.Editor.PatternSettingsSectionTests.stories.tsx +516 -0
  25. package/src/_stories/CdcMap.Editor.SmallMultiplesSectionTests.stories.tsx +165 -0
  26. package/src/_stories/CdcMap.Editor.TextAnnotationsSectionTests.stories.tsx +145 -0
  27. package/src/_stories/CdcMap.Editor.TypeSectionTests.stories.tsx +312 -0
  28. package/src/_stories/CdcMap.Editor.VisualSectionTests.stories.tsx +359 -0
  29. package/src/_stories/CdcMap.Editor.ZoomControlsTests.stories.tsx +88 -0
  30. package/src/_stories/{CdcMap.stories.tsx → CdcMap.smoke.stories.tsx} +12 -0
  31. package/src/_stories/_mock/legends/legend-tests.json +3 -3
  32. package/src/components/Annotation/AnnotationList.tsx +1 -1
  33. package/src/components/EditorPanel/components/EditorPanel.tsx +504 -383
  34. package/src/components/EditorPanel/components/HexShapeSettings.tsx +1 -1
  35. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +112 -117
  36. package/src/components/EditorPanel/components/Panels/Panel.PatternSettings.tsx +26 -13
  37. package/src/components/EditorPanel/components/editorPanel.styles.css +22 -2
  38. package/src/components/Legend/components/Legend.tsx +3 -3
  39. package/src/components/Legend/components/LegendItem.Hex.tsx +4 -2
  40. package/src/components/SmallMultiples/SynchronizedTooltip.tsx +1 -1
  41. package/src/components/UsaMap/components/UsaMap.County.tsx +271 -100
  42. package/src/components/UsaMap/components/UsaMap.State.tsx +1 -1
  43. package/src/components/UsaMap/data/cb_2019_us_county_20m.json +75817 -1
  44. package/src/components/UsaMap/data/hsa_fips_mapping.json +3144 -0
  45. package/src/components/WorldMap/data/world-topo.json +1 -1
  46. package/src/data/initial-state.js +1 -0
  47. package/src/data/supported-counties.json +1 -1
  48. package/src/helpers/countyTerritories.ts +38 -0
  49. package/src/helpers/dataTableHelpers.ts +35 -6
  50. package/src/helpers/tests/countyTerritories.test.ts +87 -0
  51. package/src/hooks/useApplyTooltipsToGeo.tsx +7 -4
  52. package/src/hooks/useMapLayers.tsx +1 -1
  53. package/src/hooks/useTooltip.ts +18 -7
  54. package/src/store/map.actions.ts +5 -2
  55. package/src/store/map.reducer.ts +12 -3
  56. package/src/test/CdcMap.test.jsx +24 -0
  57. package/src/types/MapConfig.ts +6 -0
  58. package/src/types/MapContext.ts +3 -1
  59. package/topojson-updater/README.txt +1 -1
  60. package/LICENSE +0 -201
  61. package/dist/cdcmap-vr9HZwRt.es.js +0 -6
  62. package/examples/__data__/city-state-data.json +0 -668
  63. package/examples/city-state.json +0 -434
  64. package/examples/default-world-data.json +0 -1450
  65. package/examples/new-cities.json +0 -656
  66. package/src/_stories/CdcMap.Editor.stories.tsx +0 -3648
  67. package/topojson-updater/package-lock.json +0 -223
  68. /package/src/_stories/{CdcMap.ColumnWrap.stories.tsx → CdcMap.ColumnWrap.smoke.stories.tsx} +0 -0
  69. /package/src/_stories/{CdcMap.Defaults.stories.tsx → CdcMap.Defaults.smoke.stories.tsx} +0 -0
  70. /package/src/_stories/{CdcMap.DistrictOfColumbia.stories.tsx → CdcMap.DistrictOfColumbia.smoke.stories.tsx} +0 -0
  71. /package/src/_stories/{CdcMap.Filters.stories.tsx → CdcMap.Filters.smoke.stories.tsx} +0 -0
  72. /package/src/_stories/{CdcMap.Legend.Gradient.stories.tsx → CdcMap.Legend.Gradient.smoke.stories.tsx} +0 -0
  73. /package/src/_stories/{CdcMap.Legend.stories.tsx → CdcMap.Legend.smoke.stories.tsx} +0 -0
  74. /package/src/_stories/{CdcMap.Patterns.stories.tsx → CdcMap.Patterns.smoke.stories.tsx} +0 -0
  75. /package/src/_stories/{CdcMap.SmallMultiples.stories.tsx → CdcMap.SmallMultiples.smoke.stories.tsx} +0 -0
  76. /package/src/_stories/{CdcMap.Table.stories.tsx → CdcMap.Table.smoke.stories.tsx} +0 -0
  77. /package/src/_stories/{CdcMap.ZeroColor.stories.tsx → CdcMap.ZeroColor.smoke.stories.tsx} +0 -0
  78. /package/src/_stories/{GoogleMap.stories.tsx → GoogleMap.smoke.stories.tsx} +0 -0
  79. /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 VisualSectionTests: StoryObj<typeof CdcMap> = {
27
+ name: 'Visual Section Tests',
28
+ args: {
29
+ ...DEFAULT_ARGS
30
+ },
31
+ play: async ({ canvasElement }) => {
32
+ const canvas = within(canvasElement)
33
+
34
+ // Wait for the editor to load
35
+ await waitForEditor(canvas)
36
+
37
+ // Open the Visual accordion
38
+ await openAccordion(canvas, 'Visual')
39
+ await waitForPresence('path.single-geo', canvasElement)
40
+
41
+ // ==========================================================================
42
+ // TEST: Header Theme
43
+ // ==========================================================================
44
+ await performAndAssert(
45
+ 'Header Theme → Select purple theme',
46
+ () => {
47
+ // Check via the HeaderThemeSelector's selected button state
48
+ const colorPalettes = canvasElement.querySelectorAll('.color-palette')
49
+ const themeColorPalette = Array.from(colorPalettes).find(palette =>
50
+ Array.from(palette.querySelectorAll('button')).some(btn => btn.classList.contains('theme-purple'))
51
+ )
52
+ const selectedThemeBtn = themeColorPalette?.querySelector('button.selected') as HTMLElement | null
53
+ return {
54
+ currentTheme: selectedThemeBtn
55
+ ? Array.from(selectedThemeBtn.classList).find(cls => cls.startsWith('theme-')) || ''
56
+ : ''
57
+ }
58
+ },
59
+ async () => {
60
+ // Find the header theme selector and click purple
61
+ const colorPalettes = canvasElement.querySelectorAll('.color-palette')
62
+ const themeColorPalette = Array.from(colorPalettes).find(palette =>
63
+ Array.from(palette.querySelectorAll('button')).some(btn => btn.classList.contains('theme-purple'))
64
+ )
65
+ const purpleTheme = themeColorPalette?.querySelector('button.theme-purple') as HTMLElement
66
+ await userEvent.click(purpleTheme)
67
+ },
68
+ (before, after) => {
69
+ // After clicking, the purple theme button should be selected
70
+ return after.currentTheme === 'theme-purple'
71
+ }
72
+ )
73
+
74
+ // ==========================================================================
75
+ // TEST: Show Title
76
+ // ==========================================================================
77
+ const showTitleCheckbox = Array.from(canvasElement.querySelectorAll('input[type="checkbox"]')).find(checkbox => {
78
+ const label = checkbox.closest('label')
79
+ return label?.textContent?.includes('Show Title')
80
+ }) as HTMLInputElement
81
+
82
+ await performAndAssert(
83
+ 'Show Title → Toggle off',
84
+ () => {
85
+ const titleElement = canvasElement.querySelector('.cove-title, header.cove-visualization__header')
86
+ return {
87
+ isPresent: Boolean(titleElement)
88
+ }
89
+ },
90
+ async () => {
91
+ await userEvent.click(showTitleCheckbox)
92
+ },
93
+ (before, after) => {
94
+ // After toggling off, title should be removed from DOM
95
+ return before.isPresent && !after.isPresent
96
+ }
97
+ )
98
+
99
+ await performAndAssert(
100
+ 'Show Title → Toggle back on',
101
+ () => {
102
+ const titleElement = canvasElement.querySelector('.cove-title, header.cove-visualization__header')
103
+ return {
104
+ isPresent: Boolean(titleElement)
105
+ }
106
+ },
107
+ async () => {
108
+ await userEvent.click(showTitleCheckbox)
109
+ },
110
+ (before, after) => {
111
+ // After toggling back on, title should be present in DOM
112
+ return !before.isPresent && after.isPresent
113
+ }
114
+ )
115
+
116
+ // ==========================================================================
117
+ // TEST: Geo Border Color
118
+ // ==========================================================================
119
+ const geoBorderColorSelect = Array.from(canvasElement.querySelectorAll('select')).find(select => {
120
+ const label = select.closest('label')
121
+ return label?.textContent?.includes('Geo Border Color')
122
+ }) as HTMLSelectElement
123
+
124
+ await performAndAssert(
125
+ 'Geo Border Color → Change to White',
126
+ () => {
127
+ // Check the stroke attribute on SVG paths with class 'single-geo'
128
+ const geoPaths = canvasElement.querySelectorAll('path.single-geo')
129
+ const firstPath = geoPaths[0] as SVGPathElement
130
+ // Get the actual stroke value - could be in attribute, style, or computed
131
+ const strokeAttr = firstPath?.getAttribute('stroke') || ''
132
+ const strokeStyle = firstPath?.style?.stroke || ''
133
+ const computedStroke = firstPath ? window.getComputedStyle(firstPath).stroke : ''
134
+ return {
135
+ strokeAttr,
136
+ strokeStyle,
137
+ computedStroke,
138
+ pathCount: geoPaths.length
139
+ }
140
+ },
141
+ async () => {
142
+ await userEvent.selectOptions(geoBorderColorSelect, 'sameAsBackground')
143
+ },
144
+ (before, after) => {
145
+ // After changing to white, geo paths should still exist and their rendered stroke should become white.
146
+ const beforeStroke = before.strokeAttr || before.strokeStyle || before.computedStroke
147
+ const afterStroke = after.strokeAttr || after.strokeStyle || after.computedStroke
148
+ return (
149
+ before.pathCount > 0 &&
150
+ after.pathCount > 0 &&
151
+ beforeStroke !== '' &&
152
+ afterStroke !== '' &&
153
+ beforeStroke !== afterStroke &&
154
+ after.computedStroke === 'rgb(255, 255, 255)'
155
+ )
156
+ }
157
+ )
158
+
159
+ // ==========================================================================
160
+ // TEST: Map Color Palette - Sequential Palette Selection
161
+ // ==========================================================================
162
+ await performAndAssert(
163
+ 'Map Color Palette → Select different sequential palette',
164
+ () => {
165
+ // Check the fill colors of actual map path elements (same as we use for stroke)
166
+ const geoPaths = canvasElement.querySelectorAll('path.single-geo')
167
+ const firstPath = geoPaths[0] as SVGPathElement
168
+ // Get the fill value from any available source
169
+ const fillAttr = firstPath?.getAttribute('fill') || ''
170
+ const fillStyle = firstPath?.style?.fill || ''
171
+ const computedFill = firstPath ? window.getComputedStyle(firstPath).fill : ''
172
+ const firstColor = fillAttr || fillStyle || computedFill
173
+ return {
174
+ firstColor,
175
+ pathCount: geoPaths.length
176
+ }
177
+ },
178
+ async () => {
179
+ // Find the Sequential palette section
180
+ const spans = Array.from(canvasElement.querySelectorAll('span'))
181
+ const sequentialLabel = spans.find(span => span.textContent?.trim() === 'Sequential')
182
+ const paletteContainer = sequentialLabel?.nextElementSibling
183
+
184
+ // Try both li and button elements for palette selection
185
+ const palettes = Array.from(paletteContainer?.querySelectorAll('li, button') || []) as HTMLElement[]
186
+
187
+ // Select a different palette (skip the first one as it might be selected)
188
+ const alternativePalette = palettes.find((element, idx) => idx > 0 && !element.classList.contains('selected'))
189
+ if (alternativePalette) {
190
+ await userEvent.click(alternativePalette)
191
+
192
+ // Wait for changes to apply, then check for modal
193
+ await new Promise(resolve => setTimeout(resolve, 100))
194
+
195
+ // Check if modal appeared and handle it
196
+ const modal = canvasElement.querySelector('.modal-overlay')
197
+ if (modal) {
198
+ const confirmButton = Array.from(canvasElement.querySelectorAll('button')).find(btn =>
199
+ btn.textContent?.includes('Convert to New Palette')
200
+ )
201
+ if (confirmButton) {
202
+ await userEvent.click(confirmButton)
203
+ // Wait for modal to close and changes to apply
204
+ await new Promise(resolve => setTimeout(resolve, 500))
205
+ }
206
+ }
207
+ }
208
+ },
209
+ (before, after) => {
210
+ // After selecting a different palette, the map fill colors should change
211
+ return before.firstColor !== '' && after.firstColor !== '' && before.firstColor !== after.firstColor
212
+ }
213
+ )
214
+
215
+ // ==========================================================================
216
+ // TEST: Map Color Palette - Reverse Order
217
+ // ==========================================================================
218
+ // The reverse toggle is an InputToggle component which renders as a div slider
219
+ // Find it via the hidden checkbox with name containing "isReversed"
220
+ const reversePaletteCheckbox = canvasElement.querySelector('input[name*="isReversed"]') as HTMLInputElement
221
+ const reversePaletteToggle = reversePaletteCheckbox?.parentElement as HTMLElement
222
+
223
+ expect(reversePaletteToggle).toBeTruthy()
224
+
225
+ await performAndAssert(
226
+ 'Map Color Palette → Reverse palette order',
227
+ () => {
228
+ // Check the fill colors of map paths
229
+ const geoPaths = canvasElement.querySelectorAll('path.single-geo')
230
+ const firstPath = geoPaths[0] as SVGPathElement
231
+ const fillAttr = firstPath?.getAttribute('fill') || ''
232
+ const fillStyle = firstPath?.style?.fill || ''
233
+ const computedFill = firstPath ? window.getComputedStyle(firstPath).fill : ''
234
+ const firstColor = fillAttr || fillStyle || computedFill
235
+ return {
236
+ firstColor,
237
+ pathCount: geoPaths.length
238
+ }
239
+ },
240
+ async () => {
241
+ await userEvent.click(reversePaletteToggle)
242
+ // No modal should appear - reversing doesn't trigger conversion modal
243
+ },
244
+ (before, after) => {
245
+ // After reversing, the first color should be different
246
+ return before.firstColor !== '' && after.firstColor !== '' && before.firstColor !== after.firstColor
247
+ }
248
+ )
249
+
250
+ // ==========================================================================
251
+ // TEST: Geocode Circle Size
252
+ // ==========================================================================
253
+ const geocodeCircleSizeInput = Array.from(canvasElement.querySelectorAll('input[type="number"]')).find(input => {
254
+ const label = input.closest('label')
255
+ return label?.textContent?.includes('Geocode Circle Size')
256
+ }) as HTMLInputElement
257
+
258
+ expect(geocodeCircleSizeInput).toBeTruthy()
259
+
260
+ await performAndAssert(
261
+ 'Geocode Circle Size → Set to 15',
262
+ () => {
263
+ // Check the actual visualization - DC appears as a glyph on the map
264
+ // The glyph class element is a path or circle, look for actual circle element
265
+ const geoPoints = canvasElement.querySelectorAll('g.geo-point')
266
+ const firstGeoPoint = geoPoints[0]
267
+ const circle = firstGeoPoint?.querySelector('circle')
268
+ const r = circle?.getAttribute('r') || ''
269
+ // Also check path elements that might have size in their d attribute
270
+ const path = firstGeoPoint?.querySelector('path')
271
+ const d = path?.getAttribute('d') || ''
272
+ return {
273
+ r,
274
+ d,
275
+ inputValue: geocodeCircleSizeInput.value
276
+ }
277
+ },
278
+ async () => {
279
+ await userEvent.clear(geocodeCircleSizeInput)
280
+ await userEvent.type(geocodeCircleSizeInput, '15')
281
+ await userEvent.tab() // Trigger blur/change event
282
+ },
283
+ (before, after) => {
284
+ // Verify the input value changed to 15
285
+ expect(after.inputValue).toBe('15')
286
+ // After changing the size, either the radius or path data should change
287
+ return (
288
+ (before.r !== '' && after.r !== '' && before.r !== after.r) ||
289
+ (before.d !== '' && after.d !== '' && before.d !== after.d)
290
+ )
291
+ }
292
+ )
293
+
294
+ // ==========================================================================
295
+ // TEST: Default City Style
296
+ // ==========================================================================
297
+ const cityStyleSelect = Array.from(canvasElement.querySelectorAll('select')).find(select => {
298
+ const label = select.closest('label')
299
+ return label?.textContent?.includes('Default City Style')
300
+ }) as HTMLSelectElement
301
+
302
+ if (cityStyleSelect) {
303
+ await performAndAssert(
304
+ 'Default City Style → Change to Triangle',
305
+ () => {
306
+ // Check the actual visualization - look for glyph shapes
307
+ const circles = canvasElement.querySelectorAll('.visx-glyph-circle')
308
+ const triangles = canvasElement.querySelectorAll('.visx-glyph-triangle')
309
+ return {
310
+ circleCount: circles.length,
311
+ triangleCount: triangles.length
312
+ }
313
+ },
314
+ async () => {
315
+ await userEvent.selectOptions(cityStyleSelect, 'triangle')
316
+ },
317
+ (before, after) => {
318
+ // After changing to triangle, should have triangles instead of circles
319
+ return before.triangleCount !== after.triangleCount || before.circleCount !== after.circleCount
320
+ }
321
+ )
322
+ }
323
+
324
+ // ==========================================================================
325
+ // TEST: Label (Optional)
326
+ // ==========================================================================
327
+ const cityStyleLabelInput = Array.from(canvasElement.querySelectorAll('input[type="text"]')).find(input => {
328
+ const label = input.closest('label')
329
+ return label?.textContent?.includes('Label (Optional)')
330
+ }) as HTMLInputElement
331
+
332
+ if (cityStyleLabelInput) {
333
+ await performAndAssert(
334
+ 'Label (Optional) → Set custom label',
335
+ () => {
336
+ // Check if the label appears in the legend
337
+ const legend = canvasElement.querySelector('.legends')
338
+ const legendText = legend?.textContent || ''
339
+ return {
340
+ legendText
341
+ }
342
+ },
343
+ async () => {
344
+ await userEvent.clear(cityStyleLabelInput)
345
+ await userEvent.type(cityStyleLabelInput, 'City Locations')
346
+ },
347
+ (before, after) => {
348
+ // After setting the label, it should appear in the legend
349
+ return after.legendText.includes('City Locations')
350
+ }
351
+ )
352
+ }
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Pattern Settings Section Tests
358
+ * Tests controls in the Pattern Settings accordion (only visible for US maps)
359
+ */
@@ -0,0 +1,88 @@
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 { performAndAssert, waitForEditor, waitForPresence, openAccordion } from '@cdc/core/helpers/testing'
6
+
7
+ type Story = StoryObj<typeof CdcMap>
8
+
9
+ const mapMeta: Meta<typeof CdcMap> = {
10
+ title: 'Components/Templates/Map/Editor Tests',
11
+ component: CdcMap,
12
+ parameters: {
13
+ layout: 'fullscreen'
14
+ }
15
+ }
16
+
17
+ export default mapMeta
18
+
19
+ const DEFAULT_ARGS = {
20
+ isEditor: true,
21
+ config: usaStateGradientConfig
22
+ }
23
+
24
+ export const ZoomControlsTest: Story = {
25
+ args: {
26
+ ...DEFAULT_ARGS
27
+ },
28
+ play: async ({ canvasElement }) => {
29
+ const canvas = within(canvasElement)
30
+
31
+ await waitForEditor(canvas)
32
+ await waitForPresence('.map-container', canvasElement)
33
+ await openAccordion(canvas, 'Type')
34
+
35
+ const getZoomControlsState = () => {
36
+ const zoomControls = canvasElement.querySelector('.zoom-controls')
37
+ const zoomInButton = canvasElement.querySelector('button[aria-label="Zoom In"]')
38
+ const zoomOutButton = canvasElement.querySelector('button[aria-label="Zoom Out"]')
39
+ const mapContainer = canvasElement.querySelector('.map-container')
40
+
41
+ return {
42
+ hasZoomControls: Boolean(zoomControls),
43
+ hasZoomInButton: Boolean(zoomInButton),
44
+ hasZoomOutButton: Boolean(zoomOutButton),
45
+ mapClasses: mapContainer ? Array.from(mapContainer.classList) : []
46
+ }
47
+ }
48
+
49
+ const worldButton = Array.from(canvasElement.querySelectorAll('.geo-buttons button')).find(button =>
50
+ button.textContent?.trim().toLowerCase().includes('world')
51
+ ) as HTMLButtonElement
52
+ expect(worldButton).toBeTruthy()
53
+
54
+ await performAndAssert(
55
+ 'World Data Map → Switch geo type to world',
56
+ getZoomControlsState,
57
+ async () => {
58
+ await userEvent.click(worldButton)
59
+ },
60
+ (before, after) => !before.mapClasses.includes('world') && after.mapClasses.includes('world')
61
+ )
62
+
63
+ const mapTypeSelect = canvas.getByLabelText(/Map Type/i) as HTMLSelectElement
64
+ await performAndAssert(
65
+ 'World Data Map → Switch type to data',
66
+ getZoomControlsState,
67
+ async () => {
68
+ await userEvent.selectOptions(mapTypeSelect, 'data')
69
+ },
70
+ (_before, after) => after.mapClasses.includes('world')
71
+ )
72
+
73
+ const allowMapZoomingCheckbox = canvas.getByLabelText(/Allow Map Zooming/i) as HTMLInputElement
74
+ expect(allowMapZoomingCheckbox.checked)
75
+
76
+ await userEvent.click(allowMapZoomingCheckbox)
77
+ expect(!allowMapZoomingCheckbox.checked)
78
+
79
+ await performAndAssert(
80
+ 'World Data Map → Enable map zooming',
81
+ getZoomControlsState,
82
+ async () => {
83
+ await userEvent.click(allowMapZoomingCheckbox)
84
+ },
85
+ (before, after) => after.hasZoomControls && after.hasZoomInButton && after.hasZoomOutButton
86
+ )
87
+ }
88
+ }
@@ -1,6 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-vite'
2
2
  import { within, expect } from 'storybook/test'
3
3
  import CdcMap from '../CdcMap'
4
+ import MinimalExampleConfig from '../../examples/minimal-example.json'
4
5
  import { assertVisualizationRendered } from '@cdc/core/helpers/testing'
5
6
  import EqualNumberOptInExample from './_mock/DEV-7286.json'
6
7
  import EqualNumberMap from './_mock/equal-number.json'
@@ -67,6 +68,17 @@ export const Equal_Number_Opt_In: Story = {
67
68
  }
68
69
  }
69
70
 
71
+ export const Minimal_Config: Story = {
72
+ args: {
73
+ config: MinimalExampleConfig
74
+ },
75
+ play: async ({ canvasElement }) => {
76
+ await assertVisualizationRendered(canvasElement)
77
+ expect(canvasElement.querySelector('.map-container')).toBeInTheDocument()
78
+ expect(canvasElement.textContent).toContain('Minimal US Map')
79
+ }
80
+ }
81
+
70
82
  export const Equal_Number_Map: Story = {
71
83
  args: {
72
84
  isEditor: true,
@@ -100,7 +100,7 @@
100
100
  "layers": [
101
101
  {
102
102
  "name": "Layer One",
103
- "url": "./examples/testing-layer.json",
103
+ "url": "./examples/__data__/testing-layer.json",
104
104
  "namespace": "cove",
105
105
  "fill": "blue",
106
106
  "stroke": "orange",
@@ -108,7 +108,7 @@
108
108
  },
109
109
  {
110
110
  "name": "Layer Two",
111
- "url": "./examples/testing-layer-2.json",
111
+ "url": "./examples/__data__/testing-layer-2.json",
112
112
  "namespace": "cove",
113
113
  "fill": "blue",
114
114
  "stroke": "orange",
@@ -769,4 +769,4 @@
769
769
  "URL": "https://www.cdc.gov/"
770
770
  }
771
771
  ]
772
- }
772
+ }
@@ -8,7 +8,7 @@ type AnnotationListProps = {
8
8
  }
9
9
 
10
10
  const AnnotationList: React.FC<AnnotationListProps> = ({ useBootstrapVisibilityClasses = true }) => {
11
- const { state: config } = useContext(ConfigContext)
11
+ const { config } = useContext(ConfigContext)
12
12
  const annotations = config.annotations || []
13
13
 
14
14
  const ulClasses = () => {