@cdc/core 4.25.10 → 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/_stories/Gallery.Charts.stories.tsx +307 -0
- package/_stories/Gallery.DataBite.stories.tsx +72 -0
- package/_stories/Gallery.Maps.stories.tsx +230 -0
- package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
- package/_stories/PageART.stories.tsx +192 -0
- package/_stories/PageBRFSS.stories.tsx +289 -0
- package/_stories/PageCancerRegistries.stories.tsx +199 -0
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +202 -0
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +196 -0
- package/_stories/PageMaternalMortality.stories.tsx +192 -0
- package/_stories/PageOralHealth.stories.tsx +196 -0
- package/_stories/PageRespiratory.stories.tsx +332 -0
- package/_stories/PageSmokingTobacco.stories.tsx +195 -0
- package/_stories/PageStateDiabetesProfiles.stories.tsx +196 -0
- package/_stories/PageWastewater.stories.tsx +463 -0
- package/_stories/StoryRenderingTests.stories.tsx +164 -0
- package/assets/icon-magnifying-glass.svg +5 -0
- package/assets/icon-warming-stripes.svg +13 -0
- package/components/AdvancedEditor/AdvancedEditor.tsx +7 -1
- package/components/AdvancedEditor/EmbedEditor.tsx +281 -0
- package/components/ComboBox/ComboBox.tsx +345 -0
- package/components/ComboBox/combobox.styles.css +185 -0
- package/components/ComboBox/index.ts +1 -0
- package/components/CustomColorsEditor/CustomColorsEditor.css +299 -0
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +209 -0
- package/components/CustomColorsEditor/index.ts +1 -0
- package/components/DataTable/DataTable.tsx +132 -58
- package/components/DataTable/DataTableStandAlone.tsx +8 -3
- package/components/DataTable/components/DataTableEditorPanel.tsx +12 -2
- package/components/DataTable/data-table.css +217 -210
- package/components/DataTable/helpers/mapCellMatrix.tsx +28 -9
- package/components/DataTable/helpers/standardizeState.js +2 -2
- package/components/DataTable/helpers/tests/standardizeState.test.js +54 -0
- package/components/EditorPanel/ColumnsEditor.tsx +37 -19
- package/components/EditorPanel/DataTableEditor.tsx +54 -28
- package/components/EditorPanel/EditorPanel.styles.css +439 -0
- package/components/EditorPanel/EditorPanel.tsx +144 -0
- package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
- package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
- package/components/EditorPanel/FootnotesEditor.tsx +44 -37
- package/components/EditorPanel/Inputs.tsx +44 -8
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +35 -62
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +246 -175
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +61 -22
- package/components/EditorPanel/sections/VisualSection.tsx +169 -0
- package/components/Filters/Filters.tsx +57 -10
- package/components/Filters/components/Dropdown.tsx +6 -1
- package/components/Filters/helpers/getNestedOptions.ts +2 -1
- package/components/Filters/helpers/handleSorting.ts +1 -1
- package/components/Footnotes/Footnotes.tsx +35 -25
- package/components/Footnotes/FootnotesStandAlone.tsx +42 -6
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +43 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.stories.tsx +74 -0
- package/components/HeaderThemeSelector/HeaderThemeSelector.tsx +61 -0
- package/components/HeaderThemeSelector/index.ts +2 -0
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +82 -0
- package/components/Layout/components/Visualization/index.tsx +16 -1
- package/components/Layout/components/Visualization/visualizations.scss +7 -0
- package/components/Layout/styles/editor.scss +2 -1
- package/components/Legend/Legend.Gradient.tsx +1 -1
- package/components/Loader/Loader.tsx +1 -1
- package/components/MediaControls.tsx +63 -34
- package/components/PaletteConversionModal.tsx +7 -4
- package/components/PaletteSelector/PaletteSelector.css +49 -6
- package/components/Table/components/Cell.tsx +23 -2
- package/components/Table/components/Row.tsx +5 -3
- package/components/_stories/Filters.stories.tsx +20 -1
- package/components/_stories/Footnotes.CSV.stories.tsx +247 -0
- package/components/_stories/Footnotes.stories.tsx +768 -3
- package/components/_stories/Inputs.stories.tsx +2 -2
- package/components/_stories/styles.scss +0 -1
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/Icon.tsx +3 -1
- package/components/ui/Title/index.tsx +30 -2
- package/components/ui/Title/title.styles.css +42 -0
- package/components/ui/accordion.styles.css +57 -0
- package/data/chartColorPalettes.ts +1 -1
- package/dist/cove-main.css +75 -6
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +8 -1
- package/helpers/addValuesToFilters.ts +11 -1
- package/helpers/constants.ts +37 -0
- package/helpers/cove/number.ts +33 -12
- package/helpers/coveUpdateWorker.ts +20 -11
- package/helpers/embedCodeGenerator.ts +109 -0
- package/helpers/fetchRemoteData.ts +3 -15
- package/helpers/getUniqueValues.ts +19 -0
- package/helpers/hashObj.ts +25 -0
- package/helpers/isRightAlignedTableValue.js +5 -0
- package/helpers/markupProcessor.ts +27 -12
- package/helpers/mergeCustomOrderValues.ts +37 -0
- package/helpers/metrics/helpers.ts +1 -0
- package/helpers/parseCsvWithQuotes.ts +65 -0
- package/helpers/pivotData.ts +2 -2
- package/helpers/prepareScreenshot.ts +268 -0
- package/helpers/queryStringUtils.ts +29 -0
- package/helpers/testing.ts +17 -4
- package/helpers/tests/prepareScreenshot.test.ts +414 -0
- package/helpers/tests/queryStringUtils.test.ts +381 -0
- package/helpers/tests/testStandaloneBuild.ts +23 -5
- package/helpers/useDataVizClasses.ts +0 -1
- package/helpers/ver/4.25.11.ts +13 -0
- package/helpers/ver/4.26.1.ts +80 -0
- package/helpers/viewports.ts +2 -0
- package/hooks/useDataColumns.ts +63 -0
- package/hooks/useFilterManagement.ts +94 -0
- package/hooks/useLegendSeparators.ts +26 -0
- package/hooks/useListManagement.ts +192 -0
- package/package.json +6 -4
- package/styles/_button-section.scss +0 -3
- package/styles/_common-components.css +73 -0
- package/styles/_global.scss +25 -5
- package/styles/base.scss +0 -50
- package/styles/cove-main.scss +3 -1
- package/styles/filters.scss +10 -3
- package/styles/v2/base/index.scss +0 -1
- package/styles/v2/components/editor.scss +14 -6
- package/styles/v2/utils/_breakpoints.scss +1 -1
- package/styles/v2/utils/index.scss +0 -1
- package/styles/waiting.scss +1 -1
- package/types/Axis.ts +1 -0
- package/types/ForecastingSeriesKey.ts +1 -0
- package/types/MarkupInclude.ts +5 -3
- package/types/MarkupVariable.ts +1 -1
- package/types/Series.ts +3 -0
- package/types/Table.ts +1 -0
- package/types/Visualization.ts +1 -0
- package/types/VizFilter.ts +2 -0
- package/LICENSE +0 -201
- package/styles/_mixins.scss +0 -13
- package/styles/_typography.scss +0 -0
- package/styles/v2/base/_typography.scss +0 -0
- package/styles/v2/components/guidance-block.scss +0 -74
- package/styles/v2/utils/_functions.scss +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { within, expect } from 'storybook/test'
|
|
3
|
+
import Dashboard from '@cdc/dashboard'
|
|
4
|
+
import { useEffect, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
// Fallback step function for test descriptions
|
|
7
|
+
const step = async (description: string, fn: () => Promise<void> | void) => {
|
|
8
|
+
console.log(`▶ ${description}`)
|
|
9
|
+
await fn()
|
|
10
|
+
console.log(`✓ ${description}`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const meta: Meta = {
|
|
14
|
+
title: 'Regression Tests/Pages/Cancer Registries',
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: 'fullscreen',
|
|
17
|
+
docs: {
|
|
18
|
+
description: {
|
|
19
|
+
component: 'Stories for visualizations from the CDC National Program of Cancer Registries (NPCR) Contact page (https://www.cdc.gov/national-program-cancer-registries/contact/index.html)'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
tags: ['autodocs']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
|
|
28
|
+
// Config URL from the cancer registries contact page
|
|
29
|
+
const CONFIG_URLS = {
|
|
30
|
+
npcrPrograms: 'https://www.cdc.gov/cancer/npcr/modules/npcr-programs.json'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper to fetch config and update data URLs to use absolute cdc.gov paths
|
|
34
|
+
const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
|
|
35
|
+
const [config, setConfig] = useState(null)
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
fetch(configUrl)
|
|
39
|
+
.then(res => res.json())
|
|
40
|
+
.then(data => {
|
|
41
|
+
// Convert relative data URLs to absolute cdc.gov URLs
|
|
42
|
+
if (data.dataUrl) {
|
|
43
|
+
// Handle different relative path formats (../../path or /path)
|
|
44
|
+
const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
45
|
+
data.dataUrl = `https://www.cdc.gov/${dataUrl}`
|
|
46
|
+
}
|
|
47
|
+
if (data.dataFileName) {
|
|
48
|
+
const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
49
|
+
data.dataFileName = `https://www.cdc.gov/${dataFileName}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// For dashboard configs with multiDashboards, convert dataKey references in visualizations
|
|
53
|
+
if (data.multiDashboards) {
|
|
54
|
+
data.multiDashboards.forEach((dashboard: any) => {
|
|
55
|
+
if (dashboard.visualizations) {
|
|
56
|
+
Object.values(dashboard.visualizations).forEach((viz: any) => {
|
|
57
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
58
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
59
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
60
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// For dashboard configs, convert dataKey references in visualizations
|
|
68
|
+
if (data.visualizations) {
|
|
69
|
+
Object.values(data.visualizations).forEach((viz: any) => {
|
|
70
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
71
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
72
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
73
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// For dashboard configs, convert datasets only if they reference external files
|
|
79
|
+
if (data.datasets) {
|
|
80
|
+
const newDatasets = {}
|
|
81
|
+
Object.entries(data.datasets).forEach(([key, dataset]: [string, any]) => {
|
|
82
|
+
// Check if dataset has embedded data
|
|
83
|
+
const hasEmbeddedData = (dataset as any).data && Array.isArray((dataset as any).data)
|
|
84
|
+
|
|
85
|
+
// If data is embedded, keep the original key
|
|
86
|
+
if (hasEmbeddedData) {
|
|
87
|
+
newDatasets[key] = dataset
|
|
88
|
+
} else {
|
|
89
|
+
// Otherwise, convert paths to absolute URLs (but keep absolute URLs as-is)
|
|
90
|
+
const newKey = key.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
91
|
+
const absoluteKey = key.startsWith('http') ? key : `https://www.cdc.gov/${newKey}`
|
|
92
|
+
|
|
93
|
+
newDatasets[absoluteKey] = {
|
|
94
|
+
...dataset,
|
|
95
|
+
dataFileName: (dataset as any).dataFileName && !(dataset as any).dataFileName.startsWith('http')
|
|
96
|
+
? `https://www.cdc.gov/${(dataset as any).dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
97
|
+
: (dataset as any).dataFileName,
|
|
98
|
+
dataUrl: (dataset as any).dataUrl && !(dataset as any).dataUrl.startsWith('http')
|
|
99
|
+
? `https://www.cdc.gov/${(dataset as any).dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
100
|
+
: (dataset as any).dataUrl
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
data.datasets = newDatasets
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Set activeDashboard to 0 if it's null and multiDashboards exist
|
|
108
|
+
if (data.multiDashboards && data.multiDashboards.length > 0 && data.activeDashboard === null) {
|
|
109
|
+
data.activeDashboard = 0
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Log config info for debugging
|
|
113
|
+
console.log('✓ Config loaded:', {
|
|
114
|
+
type: data.type,
|
|
115
|
+
hasMultiDashboards: !!data.multiDashboards,
|
|
116
|
+
dashboardCount: data.multiDashboards?.length || 0,
|
|
117
|
+
datasetCount: Object.keys(data.datasets || {}).length,
|
|
118
|
+
vizCount: data.visualizations ? Object.keys(data.visualizations).length : 0,
|
|
119
|
+
vizTypes: data.visualizations ? Object.values(data.visualizations).map((v: any) => v.type) : []
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
setConfig(data)
|
|
123
|
+
})
|
|
124
|
+
.catch(err => {
|
|
125
|
+
console.error('Failed to fetch config:', configUrl, err)
|
|
126
|
+
})
|
|
127
|
+
}, [configUrl])
|
|
128
|
+
|
|
129
|
+
return config
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
type DashboardStory = StoryObj<typeof Dashboard>
|
|
133
|
+
|
|
134
|
+
// Helper function to test dashboard rendering
|
|
135
|
+
const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
136
|
+
await step('Wait for dashboard to render', async () => {
|
|
137
|
+
await new Promise<void>((resolve, reject) => {
|
|
138
|
+
const startTime = Date.now()
|
|
139
|
+
const timeout = 20000
|
|
140
|
+
|
|
141
|
+
const checkDashboard = () => {
|
|
142
|
+
const dashboardElement = canvasElement.querySelector('.cove-dashboard')
|
|
143
|
+
const loadingDiv = canvasElement.querySelector('div')
|
|
144
|
+
|
|
145
|
+
// Log current state for debugging
|
|
146
|
+
if (!dashboardElement && loadingDiv?.textContent?.includes('Loading')) {
|
|
147
|
+
console.log('Still loading config...')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (dashboardElement) {
|
|
151
|
+
resolve()
|
|
152
|
+
} else if (Date.now() - startTime > timeout) {
|
|
153
|
+
reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
|
|
154
|
+
} else {
|
|
155
|
+
setTimeout(checkDashboard, 100)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
checkDashboard()
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
await step('Verify dashboard wrapper is present', async () => {
|
|
163
|
+
const dashboard = canvasElement.querySelector('.cove-dashboard')
|
|
164
|
+
expect(dashboard).toBeInTheDocument()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
await step('Verify at least one visualization rendered', async () => {
|
|
168
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
169
|
+
expect(coveModules.length).toBeGreaterThan(0)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
await step('Verify map visualization is present', async () => {
|
|
173
|
+
const mapElement = canvasElement.querySelector('svg')
|
|
174
|
+
expect(mapElement).toBeInTheDocument()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
console.log(` ${storyName} dashboard rendered successfully`)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* NPCR Programs Contact Map
|
|
182
|
+
*
|
|
183
|
+
* Interactive navigation map showing National Program of Cancer Registries (NPCR) programs
|
|
184
|
+
* across the United States, territories, and U.S.-affiliated Pacific Islands. Users can
|
|
185
|
+
* click on states to access contact information for their local cancer registry program.
|
|
186
|
+
*
|
|
187
|
+
* The NPCR supports central cancer registries in all 50 states, the District of Columbia,
|
|
188
|
+
* Puerto Rico, the U.S. Pacific Island Jurisdictions, and the U.S. Virgin Islands.
|
|
189
|
+
*/
|
|
190
|
+
export const NPCR_Programs_Map: DashboardStory = {
|
|
191
|
+
render: () => {
|
|
192
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.npcrPrograms)
|
|
193
|
+
if (!config) return <div>Loading...</div>
|
|
194
|
+
return <Dashboard config={config} />
|
|
195
|
+
},
|
|
196
|
+
play: async ({ canvasElement }) => {
|
|
197
|
+
await testDashboardRendering(canvasElement, 'NPCR Programs Map')
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { within, expect } from 'storybook/test'
|
|
3
|
+
import Chart from '@cdc/chart'
|
|
4
|
+
import CdcMap from '@cdc/map'
|
|
5
|
+
import Dashboard from '@cdc/dashboard'
|
|
6
|
+
|
|
7
|
+
// Fallback step function for test descriptions
|
|
8
|
+
const step = async (description: string, fn: () => Promise<void> | void) => {
|
|
9
|
+
console.log(`▶ ${description}`)
|
|
10
|
+
await fn()
|
|
11
|
+
console.log(`✓ ${description}`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const meta: Meta = {
|
|
15
|
+
title: 'Regression Tests/Pages/Eastern Equine Encephalitis',
|
|
16
|
+
parameters: {
|
|
17
|
+
layout: 'fullscreen',
|
|
18
|
+
docs: {
|
|
19
|
+
description: {
|
|
20
|
+
component: 'Stories for all visualizations from the CDC Eastern Equine Encephalitis historic data page'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
tags: ['autodocs']
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default meta
|
|
28
|
+
|
|
29
|
+
// Config URLs from the EEE historic data page
|
|
30
|
+
const CONFIG_URLS = {
|
|
31
|
+
cumulativeData: 'https://www.cdc.gov/eastern-equine-encephalitis/data-maps/cumulative-data_1.json',
|
|
32
|
+
exploreHumanData: 'https://www.cdc.gov/eastern-equine-encephalitis/data-maps/explore-human-data.json',
|
|
33
|
+
exploreCountyData: 'https://www.cdc.gov/eastern-equine-encephalitis/data-maps/explore-county-data.json',
|
|
34
|
+
ct2022: 'https://www.cdc.gov/eastern-equine-encephalitis/data-maps/ct-2022.json'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type MapStory = StoryObj<typeof CdcMap>
|
|
38
|
+
type DashboardStory = StoryObj<typeof Dashboard>
|
|
39
|
+
|
|
40
|
+
// Helper function to test map rendering
|
|
41
|
+
const testMapRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
42
|
+
const canvas = within(canvasElement)
|
|
43
|
+
|
|
44
|
+
await step('Wait for map to render', async () => {
|
|
45
|
+
const mapElement = await canvas.findByRole('img', { hidden: true }, { timeout: 10000 })
|
|
46
|
+
expect(mapElement).toBeInTheDocument()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
await step('Verify SVG element is present', async () => {
|
|
50
|
+
const svgElement = canvasElement.querySelector('svg')
|
|
51
|
+
expect(svgElement).toBeInTheDocument()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
await step('Verify COVE module wrapper is present', async () => {
|
|
55
|
+
const coveModule = canvasElement.querySelector('.cdc-open-viz-module')
|
|
56
|
+
expect(coveModule).toBeInTheDocument()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
console.log(`✅ ${storyName} map rendered successfully`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Helper function to test dashboard rendering
|
|
63
|
+
const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
64
|
+
await step('Wait for dashboard to render', async () => {
|
|
65
|
+
await new Promise<void>((resolve, reject) => {
|
|
66
|
+
const startTime = Date.now()
|
|
67
|
+
const timeout = 15000
|
|
68
|
+
|
|
69
|
+
const checkDashboard = () => {
|
|
70
|
+
const dashboardElement = canvasElement.querySelector('.cove-dashboard')
|
|
71
|
+
if (dashboardElement) {
|
|
72
|
+
resolve()
|
|
73
|
+
} else if (Date.now() - startTime > timeout) {
|
|
74
|
+
reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
|
|
75
|
+
} else {
|
|
76
|
+
setTimeout(checkDashboard, 100)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
checkDashboard()
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
await step('Verify dashboard wrapper is present', async () => {
|
|
84
|
+
const dashboard = canvasElement.querySelector('.cove-dashboard')
|
|
85
|
+
expect(dashboard).toBeInTheDocument()
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
console.log(`✅ ${storyName} dashboard rendered successfully`)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Cumulative Data for 2003–2024
|
|
93
|
+
*
|
|
94
|
+
* Summary dashboard showing cumulative Eastern Equine Encephalitis cases.
|
|
95
|
+
*/
|
|
96
|
+
export const Cumulative_Data: DashboardStory = {
|
|
97
|
+
render: () => <Dashboard configUrl={CONFIG_URLS.cumulativeData} />,
|
|
98
|
+
play: async ({ canvasElement }) => {
|
|
99
|
+
await testDashboardRendering(canvasElement, 'Cumulative Data')
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Explore Human Data for 2003–2024
|
|
105
|
+
*
|
|
106
|
+
* Interactive dashboard allowing exploration of human EEE cases with filters.
|
|
107
|
+
* Note: May experience slow load times after selecting filters.
|
|
108
|
+
*/
|
|
109
|
+
export const Explore_Human_Data: DashboardStory = {
|
|
110
|
+
render: () => <Dashboard configUrl={CONFIG_URLS.exploreHumanData} />,
|
|
111
|
+
play: async ({ canvasElement }) => {
|
|
112
|
+
await testDashboardRendering(canvasElement, 'Explore Human Data')
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Explore County Level Data for 2003–2024
|
|
118
|
+
*
|
|
119
|
+
* Interactive map showing county-level EEE data across the United States.
|
|
120
|
+
*/
|
|
121
|
+
export const Explore_County_Data: MapStory = {
|
|
122
|
+
render: () => <CdcMap configUrl={CONFIG_URLS.exploreCountyData} />,
|
|
123
|
+
play: async ({ canvasElement }) => {
|
|
124
|
+
await testMapRendering(canvasElement, 'Explore County Data')
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Connecticut 2022 Data
|
|
130
|
+
*
|
|
131
|
+
* Map visualization showing EEE data specific to Connecticut for 2022.
|
|
132
|
+
*/
|
|
133
|
+
export const Connecticut_2022: MapStory = {
|
|
134
|
+
render: () => <CdcMap configUrl={CONFIG_URLS.ct2022} />,
|
|
135
|
+
play: async ({ canvasElement }) => {
|
|
136
|
+
await testMapRendering(canvasElement, 'Connecticut 2022')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* All EEE Visualizations - Combined Test
|
|
142
|
+
*
|
|
143
|
+
* Tests all visualizations from the Eastern Equine Encephalitis historic data page.
|
|
144
|
+
*/
|
|
145
|
+
export const All_EEE_Visualizations: StoryObj = {
|
|
146
|
+
render: () => (
|
|
147
|
+
<div className="container-fluid p-4">
|
|
148
|
+
<h1 className="mb-4">Eastern Equine Encephalitis - Historic Data</h1>
|
|
149
|
+
|
|
150
|
+
<section className="mb-5">
|
|
151
|
+
<h2>Cumulative Data for 2003–2024</h2>
|
|
152
|
+
<Dashboard configUrl={CONFIG_URLS.cumulativeData} />
|
|
153
|
+
</section>
|
|
154
|
+
|
|
155
|
+
<section className="mb-5">
|
|
156
|
+
<h2>Explore Human Data for 2003–2024</h2>
|
|
157
|
+
<Dashboard configUrl={CONFIG_URLS.exploreHumanData} />
|
|
158
|
+
</section>
|
|
159
|
+
|
|
160
|
+
<section className="mb-5">
|
|
161
|
+
<h2>Explore County Level Data for 2003–2024</h2>
|
|
162
|
+
<CdcMap configUrl={CONFIG_URLS.exploreCountyData} />
|
|
163
|
+
</section>
|
|
164
|
+
|
|
165
|
+
<section className="mb-5">
|
|
166
|
+
<h2>Connecticut 2022 Data</h2>
|
|
167
|
+
<CdcMap configUrl={CONFIG_URLS.ct2022} />
|
|
168
|
+
</section>
|
|
169
|
+
</div>
|
|
170
|
+
),
|
|
171
|
+
play: async ({ canvasElement }) => {
|
|
172
|
+
await step('Wait for all visualizations to start rendering', async () => {
|
|
173
|
+
await new Promise<void>(resolve => setTimeout(resolve, 2000))
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
await step('Wait for all 4 COVE modules to render', async () => {
|
|
177
|
+
await new Promise<void>((resolve, reject) => {
|
|
178
|
+
const startTime = Date.now()
|
|
179
|
+
const timeout = 30000
|
|
180
|
+
|
|
181
|
+
const checkModules = () => {
|
|
182
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
183
|
+
if (coveModules.length >= 4) {
|
|
184
|
+
resolve()
|
|
185
|
+
} else if (Date.now() - startTime > timeout) {
|
|
186
|
+
reject(new Error(`Timeout: Only ${coveModules.length}/4 COVE modules found after ${timeout}ms`))
|
|
187
|
+
} else {
|
|
188
|
+
setTimeout(checkModules, 200)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
checkModules()
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
await step('Verify at least 4 visualizations are present', async () => {
|
|
196
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
197
|
+
expect(coveModules.length).toBeGreaterThanOrEqual(4)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
console.log(`✅ All 4+ EEE visualizations rendered successfully`)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite'
|
|
2
|
+
import { within, expect } from 'storybook/test'
|
|
3
|
+
import Dashboard from '@cdc/dashboard'
|
|
4
|
+
import { useEffect, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
// Fallback step function for test descriptions
|
|
7
|
+
const step = async (description: string, fn: () => Promise<void> | void) => {
|
|
8
|
+
console.log(`▶ ${description}`)
|
|
9
|
+
await fn()
|
|
10
|
+
console.log(`✓ ${description}`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const meta: Meta = {
|
|
14
|
+
title: 'Regression Tests/Pages/Excessive Alcohol Use',
|
|
15
|
+
parameters: {
|
|
16
|
+
layout: 'fullscreen',
|
|
17
|
+
docs: {
|
|
18
|
+
description: {
|
|
19
|
+
component: 'Stories for visualizations from the CDC Excessive Alcohol Use page (https://www.cdc.gov/alcohol/fact-sheets/states/excessive-alcohol-use-united-states.html)'
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
tags: ['autodocs']
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default meta
|
|
27
|
+
|
|
28
|
+
// Config URL from the excessive alcohol use page
|
|
29
|
+
const CONFIG_URLS = {
|
|
30
|
+
excessiveAlcoholUse: 'https://www.cdc.gov/alcohol/fact-sheets/states/alcohol-data-for-50-states.json'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Helper to fetch config and update data URLs to use absolute cdc.gov paths
|
|
34
|
+
const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
|
|
35
|
+
const [config, setConfig] = useState(null)
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
fetch(configUrl)
|
|
39
|
+
.then(res => res.json())
|
|
40
|
+
.then(data => {
|
|
41
|
+
// Convert relative data URLs to absolute cdc.gov URLs
|
|
42
|
+
if (data.dataUrl) {
|
|
43
|
+
// Handle different relative path formats (../../path or /path)
|
|
44
|
+
const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
45
|
+
data.dataUrl = `https://www.cdc.gov/${dataUrl}`
|
|
46
|
+
}
|
|
47
|
+
if (data.dataFileName) {
|
|
48
|
+
const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
49
|
+
data.dataFileName = `https://www.cdc.gov/${dataFileName}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// For dashboard configs with multiDashboards, convert dataKey references in visualizations
|
|
53
|
+
if (data.multiDashboards) {
|
|
54
|
+
data.multiDashboards.forEach((dashboard: any) => {
|
|
55
|
+
if (dashboard.visualizations) {
|
|
56
|
+
Object.values(dashboard.visualizations).forEach((viz: any) => {
|
|
57
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
58
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
59
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
60
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// For dashboard configs, convert dataKey references in visualizations
|
|
68
|
+
if (data.visualizations) {
|
|
69
|
+
Object.values(data.visualizations).forEach((viz: any) => {
|
|
70
|
+
// Only convert dataKey if it's a URL path (starts with / or ../)
|
|
71
|
+
if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
|
|
72
|
+
const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
73
|
+
viz.dataKey = `https://www.cdc.gov/${dataKey}`
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// For dashboard configs, convert datasets only if they reference external files
|
|
79
|
+
if (data.datasets) {
|
|
80
|
+
const newDatasets = {}
|
|
81
|
+
Object.entries(data.datasets).forEach(([key, dataset]: [string, any]) => {
|
|
82
|
+
// Check if dataset has embedded data
|
|
83
|
+
const hasEmbeddedData = (dataset as any).data && Array.isArray((dataset as any).data)
|
|
84
|
+
|
|
85
|
+
// If data is embedded, keep the original key
|
|
86
|
+
if (hasEmbeddedData) {
|
|
87
|
+
newDatasets[key] = dataset
|
|
88
|
+
} else {
|
|
89
|
+
// Otherwise, convert paths to absolute URLs (but keep absolute URLs as-is)
|
|
90
|
+
const newKey = key.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
|
|
91
|
+
const absoluteKey = key.startsWith('http') ? key : `https://www.cdc.gov/${newKey}`
|
|
92
|
+
|
|
93
|
+
newDatasets[absoluteKey] = {
|
|
94
|
+
...dataset,
|
|
95
|
+
dataFileName: (dataset as any).dataFileName && !(dataset as any).dataFileName.startsWith('http')
|
|
96
|
+
? `https://www.cdc.gov/${(dataset as any).dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
97
|
+
: (dataset as any).dataFileName,
|
|
98
|
+
dataUrl: (dataset as any).dataUrl && !(dataset as any).dataUrl.startsWith('http')
|
|
99
|
+
? `https://www.cdc.gov/${(dataset as any).dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
|
|
100
|
+
: (dataset as any).dataUrl
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
data.datasets = newDatasets
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Set activeDashboard to 0 if it's null and multiDashboards exist
|
|
108
|
+
if (data.multiDashboards && data.multiDashboards.length > 0 && data.activeDashboard === null) {
|
|
109
|
+
data.activeDashboard = 0
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Log config info for debugging
|
|
113
|
+
console.log('✓ Config loaded:', {
|
|
114
|
+
type: data.type,
|
|
115
|
+
hasMultiDashboards: !!data.multiDashboards,
|
|
116
|
+
dashboardCount: data.multiDashboards?.length || 0,
|
|
117
|
+
activeDashboard: data.activeDashboard,
|
|
118
|
+
datasetCount: Object.keys(data.datasets || {}).length,
|
|
119
|
+
dashboardLabels: data.multiDashboards?.map((d: any) => d.label)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
setConfig(data)
|
|
123
|
+
})
|
|
124
|
+
.catch(err => {
|
|
125
|
+
console.error('Failed to fetch config:', configUrl, err)
|
|
126
|
+
})
|
|
127
|
+
}, [configUrl])
|
|
128
|
+
|
|
129
|
+
return config
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
type DashboardStory = StoryObj<typeof Dashboard>
|
|
133
|
+
|
|
134
|
+
// Helper function to test dashboard rendering
|
|
135
|
+
const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
136
|
+
await step('Wait for dashboard to render', async () => {
|
|
137
|
+
await new Promise<void>((resolve, reject) => {
|
|
138
|
+
const startTime = Date.now()
|
|
139
|
+
const timeout = 30000 // Longer timeout for external data loading
|
|
140
|
+
|
|
141
|
+
const checkDashboard = () => {
|
|
142
|
+
const dashboardElement = canvasElement.querySelector('.cove-dashboard')
|
|
143
|
+
const loadingDiv = canvasElement.querySelector('div')
|
|
144
|
+
|
|
145
|
+
// Log current state for debugging
|
|
146
|
+
if (!dashboardElement && loadingDiv?.textContent?.includes('Loading')) {
|
|
147
|
+
console.log('Still loading config...')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (dashboardElement) {
|
|
151
|
+
resolve()
|
|
152
|
+
} else if (Date.now() - startTime > timeout) {
|
|
153
|
+
reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
|
|
154
|
+
} else {
|
|
155
|
+
setTimeout(checkDashboard, 100)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
checkDashboard()
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
await step('Verify dashboard wrapper is present', async () => {
|
|
163
|
+
const dashboard = canvasElement.querySelector('.cove-dashboard')
|
|
164
|
+
expect(dashboard).toBeInTheDocument()
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
await step('Verify at least one visualization rendered', async () => {
|
|
168
|
+
const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
|
|
169
|
+
expect(coveModules.length).toBeGreaterThan(0)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
console.log(`✓ ${storyName} dashboard rendered successfully`)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Excessive Alcohol Use Dashboard
|
|
177
|
+
*
|
|
178
|
+
* Interactive dashboard showing state-level data on excessive alcohol use in the United States.
|
|
179
|
+
*
|
|
180
|
+
* The dashboard displays data for all 50 states including:
|
|
181
|
+
* - Prevalence of excessive drinking among adults
|
|
182
|
+
* - Binge drinking statistics
|
|
183
|
+
* - Economic costs of excessive alcohol use
|
|
184
|
+
* - Alcohol-related deaths and health impacts
|
|
185
|
+
* - State-specific alcohol consumption trends
|
|
186
|
+
*/
|
|
187
|
+
export const Excessive_Alcohol_Use_Dashboard: DashboardStory = {
|
|
188
|
+
render: () => {
|
|
189
|
+
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.excessiveAlcoholUse)
|
|
190
|
+
if (!config) return <div>Loading...</div>
|
|
191
|
+
return <Dashboard config={config} />
|
|
192
|
+
},
|
|
193
|
+
play: async ({ canvasElement }) => {
|
|
194
|
+
await testDashboardRendering(canvasElement, 'Excessive Alcohol Use Dashboard')
|
|
195
|
+
}
|
|
196
|
+
}
|