@cdc/map 4.25.11 → 4.26.1
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/dist/cdcmap.js +29879 -29091
- package/examples/private/city_styles_variable.json +877 -0
- package/examples/private/map-filter-issue.json +2260 -0
- package/examples/private/map-legend.json +5303 -0
- package/index.html +27 -37
- package/package.json +5 -4
- package/src/CdcMapComponent.tsx +42 -6
- package/src/_stories/CdcMap.Editor.stories.tsx +92 -37
- package/src/_stories/CdcMap.stories.tsx +94 -0
- package/src/_stories/_mock/usa-state-gradient.json +1 -0
- package/src/components/CityList.tsx +24 -18
- package/src/components/EditorPanel/components/EditorPanel.tsx +2320 -2212
- package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +0 -19
- package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +14 -17
- package/src/components/Legend/components/Legend.tsx +24 -41
- package/src/components/Legend/components/LegendGroup/Legend.Group.tsx +1 -1
- package/src/components/Legend/components/index.scss +22 -5
- package/src/components/UsaMap/components/Territory/Territory.Rectangle.tsx +6 -5
- package/src/components/UsaMap/components/Territory/TerritoryShape.ts +1 -0
- package/src/components/UsaMap/components/UsaMap.County.tsx +2 -2
- package/src/components/UsaMap/components/UsaMap.SingleState.tsx +4 -7
- package/src/components/UsaMap/components/UsaMap.State.tsx +4 -2
- package/src/data/initial-state.js +1 -0
- package/src/helpers/applyLegendToRow.ts +5 -3
- package/src/helpers/constants.ts +2 -0
- package/src/helpers/displayGeoName.ts +8 -5
- package/src/helpers/generateRuntimeFilters.ts +1 -1
- package/src/helpers/generateRuntimeLegend.ts +1 -1
- package/src/helpers/generateRuntimeLegendHash.ts +1 -1
- package/src/helpers/index.ts +9 -3
- package/src/helpers/isLegendItemDisabled.ts +2 -2
- package/src/helpers/resetLegendToggles.ts +1 -0
- package/src/helpers/tests/hashObj.test.ts +1 -1
- package/src/helpers/toggleLegendActive.ts +76 -8
- package/src/hooks/useResizeObserver.ts +3 -0
- package/src/hooks/useStateZoom.tsx +2 -2
- package/src/test/CdcMap.test.jsx +1 -1
- package/src/types/MapConfig.ts +2 -0
- package/src/types/runtimeLegend.ts +1 -0
- package/LICENSE +0 -201
- package/src/components/MapControls.tsx +0 -44
- package/src/helpers/getUniqueValues.ts +0 -19
- package/src/helpers/hashObj.ts +0 -25
- package/src/hooks/useLegendSeparators.ts +0 -26
package/index.html
CHANGED
|
@@ -1,42 +1,33 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
|
6
|
+
<style type="text/css">
|
|
7
|
+
body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
border-top: none !important;
|
|
10
|
+
/* Force scrollbar so page stays consistent width */
|
|
11
|
+
min-height: calc(100vh + 1px);
|
|
12
|
+
}
|
|
3
13
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
14
|
+
/* Add 1rem padding to mimic DFE when editor is not visible */
|
|
15
|
+
.cdc-open-viz-module:not(.isEditor) {
|
|
16
|
+
padding: 1rem;
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
19
|
+
<link rel="stylesheet prefetch" href="https://www.cdc.gov/TemplatePackage/5.0/css/app.min.css?_=71669" />
|
|
20
|
+
</head>
|
|
12
21
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<link rel="stylesheet prefetch" href="https://www.cdc.gov/TemplatePackage/5.0/css/app.min.css?_=71669" />
|
|
22
|
+
<body>
|
|
23
|
+
<!-- DEFAULT EXAMPLES -->
|
|
24
|
+
<!-- <div class="react-container" data-config="/examples/example-city-state.json"></div> -->
|
|
25
|
+
<div class="react-container" data-config="/examples/default-single-state.json"></div>
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
font-family: 'Nunito';
|
|
24
|
-
font-weight: 900;
|
|
25
|
-
font-display: swap;
|
|
26
|
-
src: url('https://app.unpkg.com/@fontsource/nunito@5.0.18/files/files/nunito-latin-900-normal.woff2') format('woff2');
|
|
27
|
-
}
|
|
28
|
-
</style>
|
|
29
|
-
</head>
|
|
30
|
-
|
|
31
|
-
<body>
|
|
32
|
-
<!-- DEFAULT EXAMPLES -->
|
|
33
|
-
<!-- <div class="react-container" data-config="/examples/example-city-state.json"></div> -->
|
|
34
|
-
<div class="react-container" data-config="/examples/private/measles.json"></div>
|
|
35
|
-
|
|
36
|
-
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
37
|
-
<script type="module" src="./src/index.jsx"></script>
|
|
38
|
-
<!-- add cove_loaded listener -->
|
|
39
|
-
<!-- <script>
|
|
27
|
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
28
|
+
<script type="module" src="./src/index.jsx"></script>
|
|
29
|
+
<!-- add cove_loaded listener -->
|
|
30
|
+
<!-- <script>
|
|
40
31
|
document.addEventListener('cove_loaded', function () {
|
|
41
32
|
// This is a temporary fix to ensure the map loads after Cove has loaded
|
|
42
33
|
// and the cdc-map-outer-container is available.
|
|
@@ -44,6 +35,5 @@
|
|
|
44
35
|
console.log('Cove has loaded, initializing map...');
|
|
45
36
|
});
|
|
46
37
|
</script> -->
|
|
47
|
-
</body>
|
|
48
|
-
|
|
49
|
-
</html>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdc/map",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.26.1",
|
|
4
4
|
"description": "React component for visualizing tabular data on a map of the United States or the world.",
|
|
5
5
|
"moduleName": "CdcMap",
|
|
6
6
|
"main": "dist/cdcmap",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"license": "Apache-2.0",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@cdc/core": "^4.
|
|
28
|
+
"@cdc/core": "^4.26.1",
|
|
29
29
|
"@googlemaps/markerclusterer": "^2.5.3",
|
|
30
30
|
"@hello-pangea/dnd": "^16.2.0",
|
|
31
31
|
"@react-google-maps/api": "^2.20.6",
|
|
@@ -55,16 +55,17 @@
|
|
|
55
55
|
"react-icons": "5.5.0",
|
|
56
56
|
"react-tooltip": "5.8.2-beta.3",
|
|
57
57
|
"resize-observer-polyfill": "^1.5.1",
|
|
58
|
+
"sass": "^1.89.2",
|
|
58
59
|
"topojson-client": "^3.1.0",
|
|
59
60
|
"use-debounce": "^10.0.5",
|
|
60
61
|
"vite": "^5.4.21",
|
|
61
62
|
"vite-plugin-css-injected-by-js": "^2.4.0",
|
|
62
|
-
"vite-plugin-svgr": "^2.
|
|
63
|
+
"vite-plugin-svgr": "^4.2.0",
|
|
63
64
|
"whatwg-fetch": "3.6.20"
|
|
64
65
|
},
|
|
65
66
|
"peerDependencies": {
|
|
66
67
|
"react": "^18.2.0",
|
|
67
68
|
"react-dom": "^18.2.0"
|
|
68
69
|
},
|
|
69
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "7e3b27098c4eb7a24bc9c3654ad53f88d6419f16"
|
|
70
71
|
}
|
package/src/CdcMapComponent.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import 'react-tooltip/dist/react-tooltip.css'
|
|
|
9
9
|
import DataTable from '@cdc/core/components/DataTable'
|
|
10
10
|
import Filters from '@cdc/core/components/Filters'
|
|
11
11
|
import Layout from '@cdc/core/components/Layout'
|
|
12
|
+
import MediaControls from '@cdc/core/components/MediaControls'
|
|
12
13
|
import SkipTo from '@cdc/core/components/elements/SkipTo'
|
|
13
14
|
import Title from '@cdc/core/components/ui/Title'
|
|
14
15
|
import Waiting from '@cdc/core/components/Waiting'
|
|
@@ -23,7 +24,7 @@ import './scss/main.scss'
|
|
|
23
24
|
import './cdcMapComponent.styles.css'
|
|
24
25
|
|
|
25
26
|
// Core Helpers
|
|
26
|
-
import { getQueryStringFilterValue } from '@cdc/core/helpers/queryStringUtils'
|
|
27
|
+
import { getQueryStringFilterValue, isFilterHiddenByQuery } from '@cdc/core/helpers/queryStringUtils'
|
|
27
28
|
import { generateRuntimeFilters } from './helpers/generateRuntimeFilters'
|
|
28
29
|
import { type MapReducerType, MapState } from './store/map.reducer'
|
|
29
30
|
import { addValuesToFilters } from '@cdc/core/helpers/addValuesToFilters'
|
|
@@ -37,9 +38,11 @@ import {
|
|
|
37
38
|
getMapContainerClasses,
|
|
38
39
|
generateRuntimeLegendHash,
|
|
39
40
|
handleMapTabbing,
|
|
40
|
-
hashObj,
|
|
41
41
|
navigationHandler
|
|
42
42
|
} from './helpers'
|
|
43
|
+
import { hashObj } from '@cdc/core/helpers/hashObj'
|
|
44
|
+
import { applyLegendToRow } from './helpers/applyLegendToRow'
|
|
45
|
+
import { getPatternForRow } from './helpers/getPatternForRow'
|
|
43
46
|
import { generateRuntimeLegend } from './helpers/generateRuntimeLegend'
|
|
44
47
|
import generateRuntimeData from './helpers/generateRuntimeData'
|
|
45
48
|
import { reloadURLData } from './helpers/urlDataHelpers'
|
|
@@ -55,7 +58,6 @@ import EditorPanel from './components/EditorPanel'
|
|
|
55
58
|
import Error from './components/EditorPanel/components/Error'
|
|
56
59
|
import Legend from './components/Legend'
|
|
57
60
|
import MapContainer from './components/MapContainer'
|
|
58
|
-
import MapControls from './components/MapControls'
|
|
59
61
|
import NavigationMenu from './components/NavigationMenu'
|
|
60
62
|
|
|
61
63
|
// hooks
|
|
@@ -196,6 +198,9 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
196
198
|
if (queryStringFilterValue) {
|
|
197
199
|
filters[index].active = queryStringFilterValue
|
|
198
200
|
}
|
|
201
|
+
if (isFilterHiddenByQuery(filter)) {
|
|
202
|
+
filters[index].showDropdown = false
|
|
203
|
+
}
|
|
199
204
|
})
|
|
200
205
|
dispatch({ type: 'SET_RUNTIME_FILTERS', payload: filters })
|
|
201
206
|
}
|
|
@@ -430,6 +435,8 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
430
435
|
<Title
|
|
431
436
|
title={title}
|
|
432
437
|
superTitle={processedSuperTitle}
|
|
438
|
+
titleStyle={general.titleStyle}
|
|
439
|
+
showTitle={general.showTitle}
|
|
433
440
|
config={config}
|
|
434
441
|
classes={['map-title', general.showTitle === true ? 'visible' : 'hidden', `${headerColor}`]}
|
|
435
442
|
/>
|
|
@@ -503,9 +510,8 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
503
510
|
|
|
504
511
|
{processedSubtext.length > 0 && <p className='subtext mt-4'>{parse(processedSubtext)}</p>}
|
|
505
512
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
{shouldShowDataTable(config, table, general, loading) && (
|
|
513
|
+
{/* Data Table or Download Links */}
|
|
514
|
+
{shouldShowDataTable(config, table, general, loading) ? (
|
|
509
515
|
<DataTable
|
|
510
516
|
columns={dataTableColumns}
|
|
511
517
|
config={dataTableConfig}
|
|
@@ -528,12 +534,42 @@ const CdcMapComponent: React.FC<CdcMapComponent> = ({
|
|
|
528
534
|
runtimeLegend={runtimeLegend}
|
|
529
535
|
showDownloadImgButton={showDownloadImgButton}
|
|
530
536
|
showDownloadPdfButton={showDownloadPdfButton}
|
|
537
|
+
includeContextInDownload={config.general?.includeContextInDownload}
|
|
531
538
|
tabbingId={tabId}
|
|
532
539
|
tableTitle={table.label}
|
|
533
540
|
vizTitle={general.title}
|
|
541
|
+
applyLegendToRow={applyLegendToRow}
|
|
542
|
+
getPatternForRow={getPatternForRow}
|
|
534
543
|
wrapColumns={table.wrapColumns}
|
|
535
544
|
interactionLabel={interactionLabel}
|
|
536
545
|
/>
|
|
546
|
+
) : (
|
|
547
|
+
(showDownloadImgButton || showDownloadPdfButton) && (
|
|
548
|
+
<div className='w-100 d-flex justify-content-end'>
|
|
549
|
+
<MediaControls.Section classes={['download-links', 'mt-4', 'mb-2']}>
|
|
550
|
+
{showDownloadImgButton && (
|
|
551
|
+
<MediaControls.DownloadLink
|
|
552
|
+
type='image'
|
|
553
|
+
title='Download Map as Image'
|
|
554
|
+
state={config}
|
|
555
|
+
elementToCapture={imageId}
|
|
556
|
+
interactionLabel={interactionLabel}
|
|
557
|
+
includeContextInDownload={config.general?.includeContextInDownload}
|
|
558
|
+
/>
|
|
559
|
+
)}
|
|
560
|
+
{showDownloadPdfButton && (
|
|
561
|
+
<MediaControls.DownloadLink
|
|
562
|
+
type='pdf'
|
|
563
|
+
title='Download Map as PDF'
|
|
564
|
+
state={config}
|
|
565
|
+
elementToCapture={imageId}
|
|
566
|
+
interactionLabel={interactionLabel}
|
|
567
|
+
includeContextInDownload={config.general?.includeContextInDownload}
|
|
568
|
+
/>
|
|
569
|
+
)}
|
|
570
|
+
</MediaControls.Section>
|
|
571
|
+
</div>
|
|
572
|
+
)
|
|
537
573
|
)}
|
|
538
574
|
|
|
539
575
|
{config.annotations?.length > 0 && <Annotation.Dropdown />}
|
|
@@ -119,7 +119,10 @@ export const TypeSectionTests: Story = {
|
|
|
119
119
|
// ==========================================================================
|
|
120
120
|
// TEST: Map Type select toggles classes/data representation
|
|
121
121
|
// ==========================================================================
|
|
122
|
-
|
|
122
|
+
// Use getAllByLabelText to avoid multiple elements error
|
|
123
|
+
const typeSelects = canvas.getAllByLabelText(/Map Type/i, { selector: 'select' }) as HTMLSelectElement[]
|
|
124
|
+
// Assume the first select is the correct one for the Type section
|
|
125
|
+
const typeSelect = typeSelects[0]
|
|
123
126
|
const initialType = typeSelect.value
|
|
124
127
|
const mapTypeOptions = Array.from(typeSelect.options).map(option => option.value)
|
|
125
128
|
expect(mapTypeOptions).toContain('navigation')
|
|
@@ -324,7 +327,7 @@ export const GeneralSectionTests: Story = {
|
|
|
324
327
|
expect(titleInput).toBeTruthy()
|
|
325
328
|
|
|
326
329
|
const getTitleVisual = () => {
|
|
327
|
-
const titleElement = canvasElement.querySelector('.map-title')
|
|
330
|
+
const titleElement = canvasElement.querySelector('.cove-title, .map-title')
|
|
328
331
|
return {
|
|
329
332
|
titleText: titleElement?.textContent || '',
|
|
330
333
|
hasTitleElement: Boolean(titleElement)
|
|
@@ -343,7 +346,7 @@ export const GeneralSectionTests: Story = {
|
|
|
343
346
|
|
|
344
347
|
// ==========================================================================
|
|
345
348
|
// TEST: Show Title checkbox
|
|
346
|
-
// Verifies: Title element visibility
|
|
349
|
+
// Verifies: Title element visibility is controlled by showTitle
|
|
347
350
|
// ==========================================================================
|
|
348
351
|
const generalAccordion = canvasElement.querySelector('[aria-expanded="true"]')?.closest('.accordion__item')
|
|
349
352
|
const showTitleLabel = Array.from(generalAccordion?.querySelectorAll('label') || []).find(label =>
|
|
@@ -353,22 +356,20 @@ export const GeneralSectionTests: Story = {
|
|
|
353
356
|
expect(showTitleCheckbox).toBeTruthy()
|
|
354
357
|
|
|
355
358
|
const getTitleVisibility = () => {
|
|
356
|
-
const titleElement = canvasElement.querySelector('.
|
|
357
|
-
const classes = titleElement ? Array.from(titleElement.classList) : []
|
|
359
|
+
const titleElement = canvasElement.querySelector('.cove-title, header.cove-component__header')
|
|
358
360
|
return {
|
|
359
|
-
|
|
360
|
-
isHidden: classes.includes('hidden')
|
|
361
|
+
isPresent: Boolean(titleElement)
|
|
361
362
|
}
|
|
362
363
|
}
|
|
363
364
|
|
|
364
|
-
// Test config has showTitle: true, so title starts visible
|
|
365
|
+
// Test config has showTitle: true, so title starts visible (present in DOM)
|
|
365
366
|
await performAndAssert(
|
|
366
367
|
'Show Title → Hide',
|
|
367
368
|
getTitleVisibility,
|
|
368
369
|
async () => {
|
|
369
370
|
await userEvent.click(showTitleCheckbox)
|
|
370
371
|
},
|
|
371
|
-
(before, after) => before.
|
|
372
|
+
(before, after) => before.isPresent && !after.isPresent
|
|
372
373
|
)
|
|
373
374
|
|
|
374
375
|
await performAndAssert(
|
|
@@ -377,7 +378,7 @@ export const GeneralSectionTests: Story = {
|
|
|
377
378
|
async () => {
|
|
378
379
|
await userEvent.click(showTitleCheckbox)
|
|
379
380
|
},
|
|
380
|
-
(before, after) => !before.
|
|
381
|
+
(before, after) => !before.isPresent && after.isPresent
|
|
381
382
|
)
|
|
382
383
|
|
|
383
384
|
// ==========================================================================
|
|
@@ -388,7 +389,7 @@ export const GeneralSectionTests: Story = {
|
|
|
388
389
|
expect(superTitleInput).toBeTruthy()
|
|
389
390
|
|
|
390
391
|
const getSuperTitleVisual = () => {
|
|
391
|
-
const titleElement = canvasElement.querySelector('.map-title')
|
|
392
|
+
const titleElement = canvasElement.querySelector('.cove-title, .map-title')
|
|
392
393
|
return {
|
|
393
394
|
titleText: titleElement?.textContent || ''
|
|
394
395
|
}
|
|
@@ -404,6 +405,60 @@ export const GeneralSectionTests: Story = {
|
|
|
404
405
|
(before, after) => !before.titleText.includes('Super Title Text') && after.titleText.includes('Super Title Text')
|
|
405
406
|
)
|
|
406
407
|
|
|
408
|
+
// ==========================================================================
|
|
409
|
+
// TEST: Title Style dropdown
|
|
410
|
+
// Verifies: Changing title style changes the heading element (h2/h3) used
|
|
411
|
+
// ==========================================================================
|
|
412
|
+
const titleStyleSelect = canvas.getByLabelText(/Title Style/i) as HTMLSelectElement
|
|
413
|
+
expect(titleStyleSelect).toBeTruthy()
|
|
414
|
+
|
|
415
|
+
const getTitleStyleVisual = () => {
|
|
416
|
+
const coveTitleElement = canvasElement.querySelector('.cove-title')
|
|
417
|
+
const legacyTitleElement = canvasElement.querySelector('header.cove-component__header')
|
|
418
|
+
|
|
419
|
+
// For modern titles, check for h2 (large) or h3 (small) elements
|
|
420
|
+
const hasH2 = Boolean(coveTitleElement?.querySelector('h2'))
|
|
421
|
+
const hasH3 = Boolean(coveTitleElement?.querySelector('h3'))
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
hasCoveTitle: Boolean(coveTitleElement),
|
|
425
|
+
hasLegacyTitle: Boolean(legacyTitleElement),
|
|
426
|
+
isSmall: hasH3,
|
|
427
|
+
isLarge: hasH2
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Current config has titleStyle: 'small'
|
|
432
|
+
// Test: Change to 'large'
|
|
433
|
+
await performAndAssert(
|
|
434
|
+
'Title Style → Change to Large',
|
|
435
|
+
getTitleStyleVisual,
|
|
436
|
+
async () => {
|
|
437
|
+
await userEvent.selectOptions(titleStyleSelect, 'large')
|
|
438
|
+
},
|
|
439
|
+
(before, after) => before.isSmall && after.isLarge && after.hasCoveTitle && !after.hasLegacyTitle
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
// Test: Change to 'legacy'
|
|
443
|
+
await performAndAssert(
|
|
444
|
+
'Title Style → Change to Legacy',
|
|
445
|
+
getTitleStyleVisual,
|
|
446
|
+
async () => {
|
|
447
|
+
await userEvent.selectOptions(titleStyleSelect, 'legacy')
|
|
448
|
+
},
|
|
449
|
+
(before, after) => before.hasCoveTitle && !after.hasCoveTitle && after.hasLegacyTitle
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
// Test: Change back to 'small'
|
|
453
|
+
await performAndAssert(
|
|
454
|
+
'Title Style → Change back to Small',
|
|
455
|
+
getTitleStyleVisual,
|
|
456
|
+
async () => {
|
|
457
|
+
await userEvent.selectOptions(titleStyleSelect, 'small')
|
|
458
|
+
},
|
|
459
|
+
(before, after) => before.hasLegacyTitle && !after.hasLegacyTitle && after.isSmall && after.hasCoveTitle
|
|
460
|
+
)
|
|
461
|
+
|
|
407
462
|
// ==========================================================================
|
|
408
463
|
// TEST: Message/Intro Text field
|
|
409
464
|
// Verifies: Intro text appears in section with class 'introText'
|
|
@@ -1573,10 +1628,8 @@ export const FiltersSectionTests: Story = {
|
|
|
1573
1628
|
await performAndAssert(
|
|
1574
1629
|
'Add Filter → Click button',
|
|
1575
1630
|
() => {
|
|
1576
|
-
const filtersList = canvasElement.querySelector('.
|
|
1577
|
-
|
|
1578
|
-
const allFilterItems = Array.from(filtersList?.querySelectorAll('li, .edit-block, .mb-1') || [])
|
|
1579
|
-
const collapsedFilters = filtersList?.querySelectorAll('.mb-1:has(button)') || []
|
|
1631
|
+
const filtersList = canvasElement.querySelector('.draggable-field-list')
|
|
1632
|
+
const collapsedFilters = filtersList?.querySelectorAll('.editor-field-item') || []
|
|
1580
1633
|
return {
|
|
1581
1634
|
hasFiltersList: Boolean(filtersList),
|
|
1582
1635
|
hasCollapsedFilter: collapsedFilters.length > 0
|
|
@@ -1591,16 +1644,16 @@ export const FiltersSectionTests: Story = {
|
|
|
1591
1644
|
}
|
|
1592
1645
|
)
|
|
1593
1646
|
|
|
1594
|
-
// Find and expand the collapsed filter
|
|
1595
|
-
const filtersList = canvasElement.querySelector('.
|
|
1596
|
-
const expandButton = filtersList?.querySelector('.
|
|
1647
|
+
// Find and expand the collapsed filter (click the header expand button)
|
|
1648
|
+
const filtersList = canvasElement.querySelector('.draggable-field-list')
|
|
1649
|
+
const expandButton = filtersList?.querySelector('.editor-field-item__header button') as HTMLButtonElement
|
|
1597
1650
|
await userEvent.click(expandButton)
|
|
1598
1651
|
|
|
1599
|
-
// Wait for the expanded filter
|
|
1600
|
-
await waitForPresence('.
|
|
1652
|
+
// Wait for the expanded filter content
|
|
1653
|
+
await waitForPresence('.draggable-field-list .editor-field-item__content', canvasElement)
|
|
1601
1654
|
|
|
1602
|
-
// Find the newly added filter section
|
|
1603
|
-
const filterBlock = filtersList?.querySelector('.
|
|
1655
|
+
// Find the newly added filter section content
|
|
1656
|
+
const filterBlock = filtersList?.querySelector('.editor-field-item__content') as HTMLElement
|
|
1604
1657
|
|
|
1605
1658
|
// ==========================================================================
|
|
1606
1659
|
// TEST: Select STATE as the filter column
|
|
@@ -1612,7 +1665,7 @@ export const FiltersSectionTests: Story = {
|
|
|
1612
1665
|
}) as HTMLSelectElement
|
|
1613
1666
|
|
|
1614
1667
|
const getDefaultValueState = () => {
|
|
1615
|
-
const updatedFilterBlock = filtersList?.querySelector('.
|
|
1668
|
+
const updatedFilterBlock = filtersList?.querySelector('.editor-field-item__content') as HTMLElement
|
|
1616
1669
|
const defaultValueSelect = Array.from(updatedFilterBlock?.querySelectorAll('select') || []).find(select => {
|
|
1617
1670
|
const label = select.closest('label')
|
|
1618
1671
|
const labelSpan = label?.querySelector('.edit-label')
|
|
@@ -1668,7 +1721,7 @@ export const FiltersSectionTests: Story = {
|
|
|
1668
1721
|
// ==========================================================================
|
|
1669
1722
|
// TEST: Select Alabama as the default filter value
|
|
1670
1723
|
// ==========================================================================
|
|
1671
|
-
const updatedFilterBlock = filtersList?.querySelector('.
|
|
1724
|
+
const updatedFilterBlock = filtersList?.querySelector('.editor-field-item__content') as HTMLElement
|
|
1672
1725
|
const defaultValueSelect = Array.from(updatedFilterBlock?.querySelectorAll('select') || []).find(select => {
|
|
1673
1726
|
const label = select.closest('label')
|
|
1674
1727
|
const labelSpan = label?.querySelector('.edit-label')
|
|
@@ -2091,9 +2144,13 @@ export const DataTableSectionTests: Story = {
|
|
|
2091
2144
|
await performAndAssert(
|
|
2092
2145
|
'Enable Image Download → Enable button',
|
|
2093
2146
|
() => {
|
|
2094
|
-
const downloadImgButton =
|
|
2095
|
-
|
|
2096
|
-
|
|
2147
|
+
const downloadImgButton =
|
|
2148
|
+
Array.from(canvasElement.querySelectorAll('button')).find(
|
|
2149
|
+
btn => btn.textContent?.includes('Download Image') || btn.classList.contains('download-image')
|
|
2150
|
+
) ||
|
|
2151
|
+
Array.from(canvasElement.querySelectorAll('a[role="button"]')).find(
|
|
2152
|
+
link => link.textContent?.includes('Download Map') && link.textContent?.includes('PNG')
|
|
2153
|
+
)
|
|
2097
2154
|
return {
|
|
2098
2155
|
hasDownloadImgButton: Boolean(downloadImgButton)
|
|
2099
2156
|
}
|
|
@@ -2163,36 +2220,34 @@ export const VisualSectionTests: StoryObj<typeof CdcMap> = {
|
|
|
2163
2220
|
await performAndAssert(
|
|
2164
2221
|
'Show Title → Toggle off',
|
|
2165
2222
|
() => {
|
|
2166
|
-
const
|
|
2167
|
-
const isVisible = title?.classList.contains('visible')
|
|
2223
|
+
const titleElement = canvasElement.querySelector('.cove-title, header.cove-component__header')
|
|
2168
2224
|
return {
|
|
2169
|
-
|
|
2225
|
+
isPresent: Boolean(titleElement)
|
|
2170
2226
|
}
|
|
2171
2227
|
},
|
|
2172
2228
|
async () => {
|
|
2173
2229
|
await userEvent.click(showTitleCheckbox)
|
|
2174
2230
|
},
|
|
2175
2231
|
(before, after) => {
|
|
2176
|
-
// After toggling off, title should be
|
|
2177
|
-
return before.
|
|
2232
|
+
// After toggling off, title should be removed from DOM
|
|
2233
|
+
return before.isPresent && !after.isPresent
|
|
2178
2234
|
}
|
|
2179
2235
|
)
|
|
2180
2236
|
|
|
2181
2237
|
await performAndAssert(
|
|
2182
2238
|
'Show Title → Toggle back on',
|
|
2183
2239
|
() => {
|
|
2184
|
-
const
|
|
2185
|
-
const isVisible = title?.classList.contains('visible')
|
|
2240
|
+
const titleElement = canvasElement.querySelector('.cove-title, header.cove-component__header')
|
|
2186
2241
|
return {
|
|
2187
|
-
|
|
2242
|
+
isPresent: Boolean(titleElement)
|
|
2188
2243
|
}
|
|
2189
2244
|
},
|
|
2190
2245
|
async () => {
|
|
2191
2246
|
await userEvent.click(showTitleCheckbox)
|
|
2192
2247
|
},
|
|
2193
2248
|
(before, after) => {
|
|
2194
|
-
// After toggling back on, title should be
|
|
2195
|
-
return !before.
|
|
2249
|
+
// After toggling back on, title should be present in DOM
|
|
2250
|
+
return !before.isPresent && after.isPresent
|
|
2196
2251
|
}
|
|
2197
2252
|
)
|
|
2198
2253
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { within, expect } from 'storybook/test'
|
|
2
3
|
import CdcMap from '../CdcMap'
|
|
3
4
|
import EqualNumberOptInExample from './_mock/DEV-7286.json'
|
|
4
5
|
import EqualNumberMap from './_mock/equal-number.json'
|
|
@@ -11,6 +12,11 @@ import USBubbleCities from './_mock/us-bubble-cities.json'
|
|
|
11
12
|
import { editConfigKeys } from '@cdc/core/helpers/configHelpers'
|
|
12
13
|
import exampleLegendBins from './_mock/legend-bins.json'
|
|
13
14
|
|
|
15
|
+
// Fallback step function for test descriptions
|
|
16
|
+
const step = async (description: string, fn: () => Promise<void> | void) => {
|
|
17
|
+
await fn()
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
const meta: Meta<typeof CdcMap> = {
|
|
15
21
|
title: 'Components/Templates/Map',
|
|
16
22
|
component: CdcMap
|
|
@@ -18,10 +24,33 @@ const meta: Meta<typeof CdcMap> = {
|
|
|
18
24
|
|
|
19
25
|
type Story = StoryObj<typeof CdcMap>
|
|
20
26
|
|
|
27
|
+
// Helper function to test map rendering
|
|
28
|
+
const testMapRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
29
|
+
const canvas = within(canvasElement)
|
|
30
|
+
|
|
31
|
+
await step('Wait for map to render', async () => {
|
|
32
|
+
const mapElement = await canvas.findByRole('img', { hidden: true }, { timeout: 10000 })
|
|
33
|
+
expect(mapElement).toBeInTheDocument()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
await step('Verify SVG element is present', async () => {
|
|
37
|
+
const svgElement = canvasElement.querySelector('svg')
|
|
38
|
+
expect(svgElement).toBeInTheDocument()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
await step('Verify COVE module wrapper is present', async () => {
|
|
42
|
+
const coveModule = canvasElement.querySelector('.cdc-open-viz-module')
|
|
43
|
+
expect(coveModule).toBeInTheDocument()
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
21
47
|
export const Equal_Interval_Map: Story = {
|
|
22
48
|
args: {
|
|
23
49
|
isEditor: true,
|
|
24
50
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/page-elements/equal-interval-map.json'
|
|
51
|
+
},
|
|
52
|
+
play: async ({ canvasElement }) => {
|
|
53
|
+
await testMapRendering(canvasElement, 'Equal Interval Map')
|
|
25
54
|
}
|
|
26
55
|
}
|
|
27
56
|
|
|
@@ -43,41 +72,62 @@ export const Scale_Based: Story = {
|
|
|
43
72
|
configUrl:
|
|
44
73
|
'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Scale-Based-Categorical-Map-With-Special-Classes.json',
|
|
45
74
|
isEditor: true
|
|
75
|
+
},
|
|
76
|
+
play: async ({ canvasElement }) => {
|
|
77
|
+
await testMapRendering(canvasElement, 'Scale Based')
|
|
46
78
|
}
|
|
47
79
|
}
|
|
48
80
|
export const Qualitative: Story = {
|
|
49
81
|
args: {
|
|
50
82
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/page-elements/qualitative-map.json'
|
|
83
|
+
},
|
|
84
|
+
play: async ({ canvasElement }) => {
|
|
85
|
+
await testMapRendering(canvasElement, 'Qualitative')
|
|
51
86
|
}
|
|
52
87
|
}
|
|
53
88
|
|
|
54
89
|
export const World_Map: Story = {
|
|
55
90
|
args: {
|
|
56
91
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/world-data-map-example.json'
|
|
92
|
+
},
|
|
93
|
+
play: async ({ canvasElement }) => {
|
|
94
|
+
await testMapRendering(canvasElement, 'World Map')
|
|
57
95
|
}
|
|
58
96
|
}
|
|
59
97
|
|
|
60
98
|
export const Filterable_Map: Story = {
|
|
61
99
|
args: {
|
|
62
100
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/page-elements/gender-rate-map.json'
|
|
101
|
+
},
|
|
102
|
+
play: async ({ canvasElement }) => {
|
|
103
|
+
await testMapRendering(canvasElement, 'Filterable Map')
|
|
63
104
|
}
|
|
64
105
|
}
|
|
65
106
|
|
|
66
107
|
export const Hex_Map: Story = {
|
|
67
108
|
args: {
|
|
68
109
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/Hex_Map.json'
|
|
110
|
+
},
|
|
111
|
+
play: async ({ canvasElement }) => {
|
|
112
|
+
await testMapRendering(canvasElement, 'Hex Map')
|
|
69
113
|
}
|
|
70
114
|
}
|
|
71
115
|
|
|
72
116
|
export const County_Map: Story = {
|
|
73
117
|
args: {
|
|
74
118
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/US-County-Level-Map.json'
|
|
119
|
+
},
|
|
120
|
+
play: async ({ canvasElement }) => {
|
|
121
|
+
await testMapRendering(canvasElement, 'County Map')
|
|
75
122
|
}
|
|
76
123
|
}
|
|
77
124
|
|
|
78
125
|
export const Single_State: Story = {
|
|
79
126
|
args: {
|
|
80
127
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/example-data-map-counties.json'
|
|
128
|
+
},
|
|
129
|
+
play: async ({ canvasElement }) => {
|
|
130
|
+
await testMapRendering(canvasElement, 'Single State')
|
|
81
131
|
}
|
|
82
132
|
}
|
|
83
133
|
|
|
@@ -102,18 +152,27 @@ export const Multi_Country_Hide_Mode: Story = {
|
|
|
102
152
|
export const Bubble_Map: Story = {
|
|
103
153
|
args: {
|
|
104
154
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/example-Bubble-Map-world.json'
|
|
155
|
+
},
|
|
156
|
+
play: async ({ canvasElement }) => {
|
|
157
|
+
await testMapRendering(canvasElement, 'Bubble Map')
|
|
105
158
|
}
|
|
106
159
|
}
|
|
107
160
|
|
|
108
161
|
export const HHS_Region_Map: Story = {
|
|
109
162
|
args: {
|
|
110
163
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/example-hhs-regions-data.json'
|
|
164
|
+
},
|
|
165
|
+
play: async ({ canvasElement }) => {
|
|
166
|
+
await testMapRendering(canvasElement, 'HHS Region Map')
|
|
111
167
|
}
|
|
112
168
|
}
|
|
113
169
|
|
|
114
170
|
export const Custom_Map_Layers: Story = {
|
|
115
171
|
args: {
|
|
116
172
|
configUrl: 'https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/custom-layer-map.json'
|
|
173
|
+
},
|
|
174
|
+
play: async ({ canvasElement }) => {
|
|
175
|
+
await testMapRendering(canvasElement, 'Custom Map Layers')
|
|
117
176
|
}
|
|
118
177
|
}
|
|
119
178
|
|
|
@@ -199,4 +258,39 @@ export const US_Bubble_Cities_Test: Story = {
|
|
|
199
258
|
}
|
|
200
259
|
}
|
|
201
260
|
|
|
261
|
+
export const City_Styles_By_Variable: Story = {
|
|
262
|
+
args: {
|
|
263
|
+
config: editConfigKeys(exampleCityState, [
|
|
264
|
+
{
|
|
265
|
+
path: ['visual', 'additionalCityStyles'],
|
|
266
|
+
value: [
|
|
267
|
+
{
|
|
268
|
+
label: 'High Risk (Rate > 20)',
|
|
269
|
+
column: 'Rate',
|
|
270
|
+
value: '22',
|
|
271
|
+
shape: 'Star'
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
label: 'School Location',
|
|
275
|
+
column: 'Location',
|
|
276
|
+
value: 'School',
|
|
277
|
+
shape: 'Triangle'
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
label: 'Vehicle Location',
|
|
281
|
+
column: 'Location',
|
|
282
|
+
value: 'Vehicle',
|
|
283
|
+
shape: 'Diamond'
|
|
284
|
+
}
|
|
285
|
+
]
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
path: ['visual', 'cityStyle'],
|
|
289
|
+
value: 'circle'
|
|
290
|
+
}
|
|
291
|
+
]),
|
|
292
|
+
isEditor: true
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
202
296
|
export default meta
|