@cdc/core 4.25.11 → 4.26.2

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 (147) hide show
  1. package/.claude/agents/qa-test-developer.md +126 -0
  2. package/CLAUDE.local.md +67 -0
  3. package/_stories/Gallery.Charts.stories.tsx +300 -0
  4. package/_stories/Gallery.DataBite.stories.tsx +79 -0
  5. package/_stories/Gallery.Maps.stories.tsx +239 -0
  6. package/_stories/Gallery.WaffleChart.stories.tsx +187 -0
  7. package/_stories/PageART.stories.tsx +193 -0
  8. package/_stories/PageBRFSS.stories.tsx +294 -0
  9. package/_stories/PageCancerRegistries.stories.tsx +199 -0
  10. package/_stories/PageEasternEquineEncephalitis.stories.tsx +216 -0
  11. package/_stories/PageExcessiveAlcoholUse.stories.tsx +201 -0
  12. package/_stories/PageMaternalMortality.stories.tsx +193 -0
  13. package/_stories/PageOralHealth.stories.tsx +201 -0
  14. package/_stories/PageRespiratory.stories.tsx +332 -0
  15. package/_stories/PageSmokingTobacco.stories.tsx +200 -0
  16. package/_stories/PageStateDiabetesProfiles.stories.tsx +201 -0
  17. package/_stories/PageWastewater.stories.tsx +477 -0
  18. package/_stories/VegaImport.stories.tsx +401 -0
  19. package/_stories/vega-fixtures/bars-with-line.json +444 -0
  20. package/_stories/vega-fixtures/bars.json +58 -0
  21. package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
  22. package/_stories/vega-fixtures/combo.json +68 -0
  23. package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
  24. package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
  25. package/_stories/vega-fixtures/horizontal-bar.json +427 -0
  26. package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
  27. package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
  28. package/_stories/vega-fixtures/lines.json +227 -0
  29. package/_stories/vega-fixtures/measles-bars.json +348 -0
  30. package/_stories/vega-fixtures/measles-map.json +11101 -0
  31. package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
  32. package/_stories/vega-fixtures/multi-dataset.json +255 -0
  33. package/_stories/vega-fixtures/no-data.json +14 -0
  34. package/_stories/vega-fixtures/pie-chart.json +94 -0
  35. package/_stories/vega-fixtures/repeat-spec.json +47 -0
  36. package/_stories/vega-fixtures/stacked-area.json +222 -0
  37. package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
  38. package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
  39. package/_stories/vega-fixtures/stacked-bars.json +212 -0
  40. package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
  41. package/_stories/vega-fixtures/warning-combo.json +59 -0
  42. package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
  43. package/assets/icon-chart-area.svg +1 -0
  44. package/assets/icon-chart-radar.svg +23 -0
  45. package/assets/icon-magnifying-glass.svg +5 -0
  46. package/assets/icon-warming-stripes.svg +13 -0
  47. package/assets/logo2.svg +31 -0
  48. package/components/AdvancedEditor/AdvancedEditor.tsx +4 -0
  49. package/components/AdvancedEditor/EmbedEditor.tsx +513 -0
  50. package/components/ComboBox/ComboBox.tsx +345 -0
  51. package/components/ComboBox/combobox.styles.css +185 -0
  52. package/components/ComboBox/index.ts +1 -0
  53. package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
  54. package/components/DataTable/DataTable.tsx +132 -58
  55. package/components/DataTable/data-table.css +216 -215
  56. package/components/DataTable/helpers/getSeriesName.ts +6 -0
  57. package/components/DataTable/helpers/mapCellMatrix.tsx +14 -6
  58. package/components/EditorPanel/ColumnsEditor.tsx +37 -19
  59. package/components/EditorPanel/DataTableEditor.tsx +51 -25
  60. package/components/EditorPanel/EditorPanel.styles.css +16 -0
  61. package/components/EditorPanel/EditorPanel.tsx +144 -0
  62. package/components/EditorPanel/EditorPanelDispatch.tsx +75 -0
  63. package/components/EditorPanel/FieldSetWrapper.tsx +66 -23
  64. package/components/EditorPanel/Inputs.tsx +33 -7
  65. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +14 -6
  66. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +240 -175
  67. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
  68. package/components/EditorPanel/sections/VisualSection.tsx +169 -0
  69. package/components/Filters/Filters.tsx +31 -5
  70. package/components/Filters/helpers/getNestedOptions.ts +2 -1
  71. package/components/Filters/helpers/handleSorting.ts +1 -1
  72. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +84 -2
  73. package/components/Layout/components/Visualization/index.tsx +27 -1
  74. package/components/Layout/components/Visualization/visualizations.scss +7 -0
  75. package/components/Legend/Legend.Gradient.tsx +1 -1
  76. package/components/MediaControls.tsx +53 -28
  77. package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
  78. package/components/_stories/DataTable.stories.tsx +1 -0
  79. package/components/ui/Icon.tsx +3 -1
  80. package/components/ui/Title/index.tsx +30 -2
  81. package/components/ui/Title/title.styles.css +42 -0
  82. package/data/colorPalettes.ts +18 -5
  83. package/data/mapColorPalettes.ts +10 -0
  84. package/devTemplate/dev.js +235 -0
  85. package/devTemplate/index.html +30 -0
  86. package/devTemplate/preview.html +1503 -0
  87. package/devTemplate/sidebar.css +151 -0
  88. package/dist/cove-main.css +2803 -4448
  89. package/dist/cove-main.css.map +1 -1
  90. package/generateViteConfig.js +118 -2
  91. package/helpers/DataTransform.ts +1 -5
  92. package/helpers/addValuesToFilters.ts +6 -1
  93. package/helpers/cove/date.ts +33 -1
  94. package/helpers/cove/string.ts +29 -0
  95. package/helpers/coveUpdateWorker.ts +21 -12
  96. package/helpers/embed/embedCodeGenerator.ts +80 -0
  97. package/helpers/embed/embedHelper.js +158 -0
  98. package/helpers/embed/filterUtils.ts +121 -0
  99. package/helpers/embed/index.ts +21 -0
  100. package/helpers/embed/urlValidation.ts +119 -0
  101. package/helpers/filterVizData.ts +6 -1
  102. package/helpers/getFileExtension.ts +0 -6
  103. package/helpers/getUniqueValues.ts +19 -0
  104. package/helpers/hashObj.ts +25 -0
  105. package/helpers/isRightAlignedTableValue.js +5 -0
  106. package/helpers/metrics/helpers.ts +1 -0
  107. package/helpers/metrics/types.ts +3 -0
  108. package/helpers/palettes/colorDistributions.ts +1 -1
  109. package/helpers/palettes/utils.ts +12 -12
  110. package/helpers/parseCsvWithQuotes.ts +15 -14
  111. package/helpers/pivotData.ts +2 -2
  112. package/helpers/prepareScreenshot.ts +288 -0
  113. package/helpers/queryStringUtils.ts +29 -0
  114. package/helpers/testing.ts +44 -0
  115. package/helpers/tests/DataTransform.test.ts +125 -0
  116. package/helpers/tests/date.test.ts +64 -0
  117. package/helpers/tests/prepareScreenshot.test.ts +414 -0
  118. package/helpers/tests/queryStringUtils.test.ts +381 -0
  119. package/helpers/tests/testStandaloneBuild.ts +23 -5
  120. package/helpers/useDataVizClasses.ts +0 -1
  121. package/helpers/vegaConfig.ts +1 -1
  122. package/helpers/vegaConfigImport.ts +160 -0
  123. package/helpers/ver/4.26.1.ts +80 -0
  124. package/helpers/ver/4.26.2.ts +84 -0
  125. package/helpers/ver/tests/4.26.1.test.ts +105 -0
  126. package/helpers/ver/tests/4.26.2.test.ts +298 -0
  127. package/helpers/viewports.ts +2 -0
  128. package/hooks/useDataColumns.ts +63 -0
  129. package/hooks/useFilterManagement.ts +94 -0
  130. package/hooks/useLegendSeparators.ts +26 -0
  131. package/hooks/useListManagement.ts +192 -0
  132. package/package.json +29 -33
  133. package/styles/_button-section.scss +0 -3
  134. package/styles/v2/components/editor.scss +9 -9
  135. package/styles/v2/utils/_grid.scss +8 -3
  136. package/types/Annotation.ts +10 -11
  137. package/types/Axis.ts +1 -0
  138. package/types/ForecastingSeriesKey.ts +1 -0
  139. package/types/General.ts +2 -0
  140. package/types/MarkupInclude.ts +1 -0
  141. package/types/Palette.ts +21 -0
  142. package/types/Series.ts +3 -0
  143. package/types/Table.ts +1 -0
  144. package/types/Visualization.ts +7 -0
  145. package/types/VizFilter.ts +1 -0
  146. package/LICENSE +0 -201
  147. package/_stories/StoryRenderingTests.stories.tsx +0 -164
@@ -0,0 +1,239 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { within, expect } from 'storybook/test'
3
+ import CdcMap from '@cdc/map'
4
+
5
+ // Fallback step function for test descriptions
6
+ const step = async (description: string, fn: () => Promise<void> | void) => {
7
+ console.log(`▶ ${description}`)
8
+ await fn()
9
+ console.log(`✓ ${description}`)
10
+ }
11
+
12
+ const meta: Meta = {
13
+ title: 'Regression Tests/Gallery/Maps',
14
+ parameters: {
15
+ layout: 'fullscreen',
16
+ docs: {
17
+ description: {
18
+ component: 'Map visualization examples from the CDC COVE Gallery'
19
+ }
20
+ }
21
+ },
22
+ tags: ['autodocs']
23
+ }
24
+
25
+ export default meta
26
+
27
+ type Story = StoryObj<typeof CdcMap>
28
+
29
+ // Helper function to test map rendering
30
+ const testMapRendering = async (canvasElement: HTMLElement, storyName: string) => {
31
+ const canvas = within(canvasElement)
32
+
33
+ await step('Wait for map to render', async () => {
34
+ const mapElement = await canvas
35
+ .findByRole('img', { hidden: true }, { timeout: 10000 })
36
+ .catch(() => canvasElement.querySelector('canvas'))
37
+ expect(mapElement).toBeInTheDocument()
38
+ })
39
+
40
+ await step('Verify SVG or canvas element is present', async () => {
41
+ const vizElement = canvasElement.querySelector('svg, canvas')
42
+ expect(vizElement).toBeInTheDocument()
43
+ })
44
+
45
+ await step('Verify COVE module wrapper is present', async () => {
46
+ const coveModule = canvasElement.querySelector('.cdc-open-viz-module')
47
+ expect(coveModule).toBeInTheDocument()
48
+ })
49
+
50
+ console.log(` ${storyName} map rendered successfully`)
51
+ }
52
+
53
+ // Bubble Maps
54
+ export const Bubble_Map_World: Story = {
55
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/example-Bubble-Map-world.json' />,
56
+ play: async ({ canvasElement }) => {
57
+ await testMapRendering(canvasElement, 'Bubble Map World')
58
+ }
59
+ }
60
+
61
+ export const Bubble_Map_US: Story = {
62
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/Example-Bubble-US-Data-Map.json' />,
63
+ play: async ({ canvasElement }) => {
64
+ await testMapRendering(canvasElement, 'Bubble Map US')
65
+ }
66
+ }
67
+
68
+ // Categorical Maps
69
+ export const Categorical_Map_With_Special_Classes: Story = {
70
+ render: () => (
71
+ <CdcMap configUrl='https://www.cdc.gov/cove/data-files/Scale-Based-Categorical-Map-With-Special-Classes.json' />
72
+ ),
73
+ play: async ({ canvasElement }) => {
74
+ await testMapRendering(canvasElement, 'Categorical Map With Special Classes')
75
+ }
76
+ }
77
+
78
+ export const Qualitative_Categorical_Map: Story = {
79
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/data-files/qualitative-map.json' />,
80
+ play: async ({ canvasElement }) => {
81
+ await testMapRendering(canvasElement, 'Qualitative Categorical Map')
82
+ }
83
+ }
84
+
85
+ // Custom Layer Map
86
+ export const Custom_Layer_Map: Story = {
87
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/custom-layer-map.json' />,
88
+ play: async ({ canvasElement }) => {
89
+ await testMapRendering(canvasElement, 'Custom Layer Map')
90
+ }
91
+ }
92
+
93
+ // Filterable Data Maps
94
+ export const Map_Multiple_Filters: Story = {
95
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/map-multiple-filters.json' />,
96
+ play: async ({ canvasElement }) => {
97
+ await testMapRendering(canvasElement, 'Map Multiple Filters')
98
+ }
99
+ }
100
+
101
+ export const Map_Tab_Filters: Story = {
102
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/map-tab-filters.json' />,
103
+ play: async ({ canvasElement }) => {
104
+ await testMapRendering(canvasElement, 'Map Tab Filters')
105
+ }
106
+ }
107
+
108
+ // Geocode Map
109
+ export const US_Geocode_Map: Story = {
110
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/example-u-s-geo-code-dat.json' />,
111
+ play: async ({ canvasElement }) => {
112
+ await testMapRendering(canvasElement, 'US Geocode Map')
113
+ }
114
+ }
115
+
116
+ // HHS Region Map
117
+ export const HHS_Regions_Map: Story = {
118
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/example-hhs-regions-data.json' />,
119
+ play: async ({ canvasElement }) => {
120
+ await testMapRendering(canvasElement, 'HHS Regions Map')
121
+ }
122
+ }
123
+
124
+ // Hex Maps
125
+ export const Standard_Hex_Map: Story = {
126
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/Hex_Map.json' />,
127
+ play: async ({ canvasElement }) => {
128
+ await testMapRendering(canvasElement, 'Standard Hex Map')
129
+ }
130
+ }
131
+
132
+ export const Hex_Map_With_Filter: Story = {
133
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/example-hex-map-with-filter.json' />,
134
+ play: async ({ canvasElement }) => {
135
+ await testMapRendering(canvasElement, 'Hex Map With Filter')
136
+ }
137
+ }
138
+
139
+ export const Hex_Map_With_Arrows: Story = {
140
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/Hex_Map_Arrows.json' />,
141
+ play: async ({ canvasElement }) => {
142
+ await testMapRendering(canvasElement, 'Hex Map With Arrows')
143
+ }
144
+ }
145
+
146
+ // Map with Cities
147
+ export const US_Map_With_Cities: Story = {
148
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/city-data-map-example.json' />,
149
+ play: async ({ canvasElement }) => {
150
+ await testMapRendering(canvasElement, 'US Map With Cities')
151
+ }
152
+ }
153
+
154
+ // Navigation-Only Maps
155
+ export const US_Navigation_Map: Story = {
156
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/us-nav-map.json' />,
157
+ play: async ({ canvasElement }) => {
158
+ await testMapRendering(canvasElement, 'US Navigation Map')
159
+ }
160
+ }
161
+
162
+ export const World_Navigation_Map: Story = {
163
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/world-nav-map-example.json' />,
164
+ play: async ({ canvasElement }) => {
165
+ await testMapRendering(canvasElement, 'World Navigation Map')
166
+ }
167
+ }
168
+
169
+ // Numeric Maps
170
+ export const Equal_Interval_Map: Story = {
171
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/equal-interval-map.json' />,
172
+ play: async ({ canvasElement }) => {
173
+ await testMapRendering(canvasElement, 'Equal Interval Map')
174
+ }
175
+ }
176
+
177
+ export const Equal_Number_Map: Story = {
178
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/equal-number-map.json' />,
179
+ play: async ({ canvasElement }) => {
180
+ await testMapRendering(canvasElement, 'Equal Number Map')
181
+ }
182
+ }
183
+
184
+ export const Equal_Number_Map_New_Quantile: Story = {
185
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/equal-number-map_new_quantile.json' />,
186
+ play: async ({ canvasElement }) => {
187
+ await testMapRendering(canvasElement, 'Equal Number Map New Quantile')
188
+ }
189
+ }
190
+
191
+ // Single State Maps
192
+ export const Single_State_Map_Counties: Story = {
193
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/example-data-map-counties.json' />,
194
+ play: async ({ canvasElement }) => {
195
+ await testMapRendering(canvasElement, 'Single State Map Counties')
196
+ }
197
+ }
198
+
199
+ /*
200
+ Commented out because the config is pointing at a data file on wcms-wp.cdc.gov which is not accessible.
201
+ export const Single_State_With_Census_Toggle: Story = {
202
+ render: () => <CdcMap configUrl="https://www.cdc.gov/cove/examples/single-state-map-with-census-toggle.json" />,
203
+ play: async ({ canvasElement }) => {
204
+ await testMapRendering(canvasElement, 'Single State With Census Toggle')
205
+ }
206
+ }
207
+ */
208
+
209
+ export const Single_State_Filter_With_Zoom: Story = {
210
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/single-state-filter-change-with-zoom.json' />,
211
+ play: async ({ canvasElement }) => {
212
+ await testMapRendering(canvasElement, 'Single State Filter With Zoom')
213
+ }
214
+ }
215
+
216
+ export const Multi_State_County_Map: Story = {
217
+ render: () => (
218
+ <CdcMap configUrl='https://www.cdc.gov/wcms/4.0/cdc-wp/data-presentation/examples/multistate-county-map.json' />
219
+ ),
220
+ play: async ({ canvasElement }) => {
221
+ await testMapRendering(canvasElement, 'Multi State County Map')
222
+ }
223
+ }
224
+
225
+ // US County Map
226
+ export const US_County_Level_Map: Story = {
227
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/US-County-Level-Map.json' />,
228
+ play: async ({ canvasElement }) => {
229
+ await testMapRendering(canvasElement, 'US County Level Map')
230
+ }
231
+ }
232
+
233
+ // World Map
234
+ export const World_Data_Map: Story = {
235
+ render: () => <CdcMap configUrl='https://www.cdc.gov/cove/examples/world-data-map-example.json' />,
236
+ play: async ({ canvasElement }) => {
237
+ await testMapRendering(canvasElement, 'World Data Map')
238
+ }
239
+ }
@@ -0,0 +1,187 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { within, expect } from 'storybook/test'
3
+ import WaffleChart from '@cdc/waffle-chart'
4
+
5
+ // Fallback step function for test descriptions
6
+ const step = async (description: string, fn: () => Promise<void> | void) => {
7
+ console.log(`▶ ${description}`)
8
+ await fn()
9
+ console.log(`✓ ${description}`)
10
+ }
11
+
12
+ const meta: Meta = {
13
+ title: 'Regression Tests/Gallery/Waffle Charts',
14
+ parameters: {
15
+ layout: 'fullscreen',
16
+ docs: {
17
+ description: {
18
+ component: 'Waffle Chart and Gauge visualization examples from the CDC COVE Gallery'
19
+ }
20
+ }
21
+ },
22
+ tags: ['autodocs']
23
+ }
24
+
25
+ export default meta
26
+
27
+ type Story = StoryObj<typeof WaffleChart>
28
+
29
+ // Helper function to test waffle chart rendering
30
+ const testWaffleChartRendering = async (canvasElement: HTMLElement, storyName: string) => {
31
+ await step('Wait for waffle chart to render', async () => {
32
+ // Wait for SVG element to appear
33
+ await new Promise<void>((resolve, reject) => {
34
+ const startTime = Date.now()
35
+ const timeout = 10000
36
+
37
+ const checkSvg = () => {
38
+ const svgElement = canvasElement.querySelector('svg')
39
+ if (svgElement) {
40
+ resolve()
41
+ } else if (Date.now() - startTime > timeout) {
42
+ reject(new Error(`Timeout: SVG element not found after ${timeout}ms`))
43
+ } else {
44
+ setTimeout(checkSvg, 100)
45
+ }
46
+ }
47
+ checkSvg()
48
+ })
49
+ })
50
+
51
+ await step('Verify SVG element is present', async () => {
52
+ const svgElement = canvasElement.querySelector('svg')
53
+ expect(svgElement).toBeInTheDocument()
54
+ })
55
+
56
+ await step('Verify waffle chart container is present', async () => {
57
+ const waffleContainer = canvasElement.querySelector('.cove-waffle-chart, .cove-gauge-chart')
58
+ expect(waffleContainer).toBeInTheDocument()
59
+ })
60
+
61
+ console.log(` ${storyName} waffle chart rendered successfully`)
62
+ }
63
+
64
+ // Base config for examples
65
+ const baseWaffleConfig = {
66
+ type: 'waffle-chart',
67
+ shape: 'person',
68
+ title: 'Overdose Mortality Rates',
69
+ content: 'of overdoses resulted in death.',
70
+ subtext: 'This data is an example and does not reflect actual averages',
71
+ orientation: 'horizontal',
72
+ data: [
73
+ {
74
+ 'Resulted in Death': 250,
75
+ 'Number of Overdoses': 600,
76
+ state: 'Alabama',
77
+ Year: '2010'
78
+ },
79
+ {
80
+ 'Resulted in Death': 16,
81
+ 'Number of Overdoses': 80,
82
+ state: 'Alaska',
83
+ Year: '2008'
84
+ },
85
+ {
86
+ 'Resulted in Death': 19,
87
+ 'Number of Overdoses': 100,
88
+ state: 'Alaska',
89
+ Year: '2010'
90
+ },
91
+ {
92
+ 'Resulted in Death': 93,
93
+ 'Number of Overdoses': 400,
94
+ state: 'Alaska',
95
+ Year: '2012'
96
+ },
97
+ {
98
+ 'Resulted in Death': 82,
99
+ 'Number of Overdoses': 400,
100
+ state: 'Arizona',
101
+ Year: '2010'
102
+ }
103
+ ],
104
+ filters: [],
105
+ fontSize: null,
106
+ overallFontSize: 'medium',
107
+ dataColumn: 'Resulted in Death',
108
+ dataFunction: 'Sum',
109
+ dataConditionalColumn: '',
110
+ dataConditionalOperator: null,
111
+ dataConditionalComparate: '',
112
+ customDenom: true,
113
+ dataDenom: null,
114
+ dataDenomColumn: 'Number of Overdoses',
115
+ dataDenomFunction: 'Sum',
116
+ prefix: '',
117
+ suffix: '%',
118
+ roundToPlace: 0,
119
+ nodeWidth: 10,
120
+ nodeSpacer: 2,
121
+ theme: 'theme-blue',
122
+ invalidComparate: false,
123
+ showDenominator: false,
124
+ showPercent: true,
125
+ valueDescription: 'testing',
126
+ visual: {
127
+ border: true,
128
+ accent: true,
129
+ background: true,
130
+ hideBackgroundColor: true,
131
+ borderColorTheme: true
132
+ }
133
+ }
134
+
135
+ export const Waffle_Chart_Person: Story = {
136
+ render: () => (
137
+ <WaffleChart
138
+ config={{
139
+ ...baseWaffleConfig,
140
+ visualizationType: 'Waffle',
141
+ visualizationSubType: '',
142
+ gauge: {
143
+ height: 35,
144
+ width: 400
145
+ },
146
+ visual: {
147
+ border: true,
148
+ accent: true,
149
+ background: true,
150
+ hideBackgroundColor: true,
151
+ borderColorTheme: true,
152
+ colors: {
153
+ 'theme-blue': '#005eaa',
154
+ 'theme-purple': '#712177',
155
+ 'theme-brown': '#705043',
156
+ 'theme-teal': '#00695c',
157
+ 'theme-pink': '#af4448',
158
+ 'theme-orange': '#bb4d00',
159
+ 'theme-slate': '#29434e',
160
+ 'theme-indigo': '#26418f',
161
+ 'theme-cyan': '#006778',
162
+ 'theme-green': '#4b830d',
163
+ 'theme-amber': '#fbab18'
164
+ }
165
+ }
166
+ }}
167
+ />
168
+ ),
169
+ play: async ({ canvasElement }) => {
170
+ await testWaffleChartRendering(canvasElement, 'Waffle Chart Person')
171
+ }
172
+ }
173
+
174
+ export const Gauge_Chart: Story = {
175
+ render: () => (
176
+ <WaffleChart
177
+ config={{
178
+ ...baseWaffleConfig,
179
+ visualizationType: 'Gauge',
180
+ visualizationSubType: ''
181
+ }}
182
+ />
183
+ ),
184
+ play: async ({ canvasElement }) => {
185
+ await testWaffleChartRendering(canvasElement, 'Gauge Chart')
186
+ }
187
+ }
@@ -0,0 +1,193 @@
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/ART',
15
+ parameters: {
16
+ layout: 'fullscreen',
17
+ docs: {
18
+ description: {
19
+ component:
20
+ 'Stories for visualizations from the CDC ART (Assisted Reproductive Technology) Surveillance State-Specific page (https://www.cdc.gov/art/php/surveillance-state-specific/index.html)'
21
+ }
22
+ }
23
+ },
24
+ tags: ['autodocs']
25
+ }
26
+
27
+ export default meta
28
+
29
+ // Config URL from the ART surveillance state-specific page
30
+ const CONFIG_URLS = {
31
+ stateSpecificDashboard: 'https://www.cdc.gov/art/dfe-modules/state-specific-dashboard.json'
32
+ }
33
+
34
+ // Helper to fetch config and update data URLs to use absolute cdc.gov paths
35
+ const useConfigWithAbsoluteDataUrl = (configUrl: string) => {
36
+ const [config, setConfig] = useState(null)
37
+
38
+ useEffect(() => {
39
+ fetch(configUrl)
40
+ .then(res => res.json())
41
+ .then(data => {
42
+ // Convert relative data URLs to absolute cdc.gov URLs
43
+ if (data.dataUrl) {
44
+ // Handle different relative path formats (../../path or /path)
45
+ const dataUrl = data.dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
46
+ data.dataUrl = `https://www.cdc.gov/${dataUrl}`
47
+ }
48
+ if (data.dataFileName) {
49
+ const dataFileName = data.dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
50
+ data.dataFileName = `https://www.cdc.gov/${dataFileName}`
51
+ }
52
+
53
+ // For dashboard configs with multiDashboards, convert dataKey references in visualizations
54
+ if (data.multiDashboards) {
55
+ data.multiDashboards.forEach((dashboard: any) => {
56
+ if (dashboard.visualizations) {
57
+ Object.values(dashboard.visualizations).forEach((viz: any) => {
58
+ // Only convert dataKey if it's a URL path (starts with / or ../)
59
+ if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
60
+ const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
61
+ viz.dataKey = `https://www.cdc.gov/${dataKey}`
62
+ }
63
+ })
64
+ }
65
+ })
66
+ }
67
+
68
+ // For dashboard configs, convert dataKey references in visualizations
69
+ if (data.visualizations) {
70
+ Object.values(data.visualizations).forEach((viz: any) => {
71
+ // Only convert dataKey if it's a URL path (starts with / or ../)
72
+ if (viz.dataKey && (viz.dataKey.startsWith('/') || viz.dataKey.startsWith('../'))) {
73
+ const dataKey = viz.dataKey.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
74
+ viz.dataKey = `https://www.cdc.gov/${dataKey}`
75
+ }
76
+ })
77
+ }
78
+
79
+ // For dashboard configs, convert datasets only if they reference external files
80
+ if (data.datasets) {
81
+ const newDatasets = {}
82
+ Object.entries(data.datasets).forEach(([key, dataset]: [string, any]) => {
83
+ // Check if dataset has embedded data
84
+ const hasEmbeddedData = (dataset as any).data && Array.isArray((dataset as any).data)
85
+
86
+ // If data is embedded, keep the original key
87
+ if (hasEmbeddedData) {
88
+ newDatasets[key] = dataset
89
+ } else {
90
+ // Otherwise, convert paths to absolute URLs
91
+ const newKey = key.replace(/^(\.\.\/)+/, '').replace(/^\//, '')
92
+ const absoluteKey = `https://www.cdc.gov/${newKey}`
93
+
94
+ newDatasets[absoluteKey] = {
95
+ ...dataset,
96
+ dataFileName: (dataset as any).dataFileName
97
+ ? `https://www.cdc.gov/${(dataset as any).dataFileName.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
98
+ : (dataset as any).dataFileName,
99
+ dataUrl: (dataset as any).dataUrl
100
+ ? `https://www.cdc.gov/${(dataset as any).dataUrl.replace(/^(\.\.\/)+/, '').replace(/^\//, '')}`
101
+ : (dataset as any).dataUrl
102
+ }
103
+ }
104
+ })
105
+ data.datasets = newDatasets
106
+ }
107
+
108
+ // Set activeDashboard to 0 if it's null and multiDashboards exist
109
+ if (data.multiDashboards && data.multiDashboards.length > 0 && data.activeDashboard === null) {
110
+ data.activeDashboard = 0
111
+ }
112
+
113
+ // Log config info for debugging
114
+ console.log('✓ Config loaded:', {
115
+ hasMultiDashboards: !!data.multiDashboards,
116
+ dashboardCount: data.multiDashboards?.length || 0,
117
+ activeDashboard: data.activeDashboard,
118
+ datasetCount: Object.keys(data.datasets || {}).length,
119
+ firstDashboardVizCount: data.multiDashboards?.[0]?.visualizations
120
+ ? Object.keys(data.multiDashboards[0].visualizations).length
121
+ : 0
122
+ })
123
+
124
+ setConfig(data)
125
+ })
126
+ .catch(err => {
127
+ console.error('Failed to fetch config:', configUrl, err)
128
+ })
129
+ }, [configUrl])
130
+
131
+ return config
132
+ }
133
+
134
+ type DashboardStory = StoryObj<typeof Dashboard>
135
+
136
+ // Helper function to test dashboard rendering
137
+ const testDashboardRendering = async (canvasElement: HTMLElement, storyName: string) => {
138
+ await step('Wait for dashboard to render', async () => {
139
+ await new Promise<void>((resolve, reject) => {
140
+ const startTime = Date.now()
141
+ const timeout = 20000
142
+
143
+ const checkDashboard = () => {
144
+ const dashboardElement = canvasElement.querySelector('.type-dashboard')
145
+ const loadingDiv = canvasElement.querySelector('div')
146
+
147
+ // Log current state for debugging
148
+ if (!dashboardElement && loadingDiv?.textContent?.includes('Loading')) {
149
+ console.log('Still loading config...')
150
+ }
151
+
152
+ if (dashboardElement) {
153
+ resolve()
154
+ } else if (Date.now() - startTime > timeout) {
155
+ reject(new Error(`Timeout: Dashboard element not found after ${timeout}ms`))
156
+ } else {
157
+ setTimeout(checkDashboard, 100)
158
+ }
159
+ }
160
+ checkDashboard()
161
+ })
162
+ })
163
+
164
+ await step('Verify dashboard wrapper is present', async () => {
165
+ const dashboard = canvasElement.querySelector('.type-dashboard')
166
+ expect(dashboard).toBeInTheDocument()
167
+ })
168
+
169
+ await step('Verify at least one visualization rendered', async () => {
170
+ const coveModules = canvasElement.querySelectorAll('.cdc-open-viz-module')
171
+ expect(coveModules.length).toBeGreaterThan(0)
172
+ })
173
+
174
+ console.log(` ${storyName} dashboard rendered successfully`)
175
+ }
176
+
177
+ /**
178
+ * ART State-Specific Surveillance Dashboard
179
+ *
180
+ * Interactive dashboard showing Assisted Reproductive Technology (ART) surveillance
181
+ * data for different states. This visualization allows users to explore state-specific
182
+ * ART outcomes and trends.
183
+ */
184
+ export const State_Specific_Dashboard: DashboardStory = {
185
+ render: () => {
186
+ const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.stateSpecificDashboard)
187
+ if (!config) return <div>Loading...</div>
188
+ return <Dashboard config={config} />
189
+ },
190
+ play: async ({ canvasElement }) => {
191
+ await testDashboardRendering(canvasElement, 'State Specific Dashboard')
192
+ }
193
+ }