@cdc/core 4.26.1 → 4.26.3

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 (249) hide show
  1. package/.claude/agents/qa-test-developer.md +126 -0
  2. package/CLAUDE.local.md +67 -0
  3. package/LICENSE +201 -0
  4. package/_stories/Gallery.Charts.stories.tsx +35 -42
  5. package/_stories/Gallery.DataBite.stories.tsx +15 -8
  6. package/_stories/Gallery.Maps.stories.tsx +37 -28
  7. package/_stories/Gallery.WaffleChart.stories.tsx +1 -1
  8. package/_stories/PageART.stories.tsx +5 -4
  9. package/_stories/PageBRFSS.stories.tsx +21 -16
  10. package/_stories/PageCancerRegistries.stories.tsx +15 -15
  11. package/_stories/PageEasternEquineEncephalitis.stories.tsx +33 -19
  12. package/_stories/PageExcessiveAlcoholUse.stories.tsx +148 -143
  13. package/_stories/PageMaternalMortality.stories.tsx +5 -4
  14. package/_stories/PageOralHealth.stories.tsx +15 -10
  15. package/_stories/PageRespiratory.stories.tsx +4 -4
  16. package/_stories/PageSmokingTobacco.stories.tsx +15 -10
  17. package/_stories/PageStateDiabetesProfiles.stories.tsx +15 -10
  18. package/_stories/PageWastewater.stories.tsx +44 -30
  19. package/_stories/VegaImport.stories.tsx +401 -0
  20. package/_stories/vega-fixtures/bars-with-line.json +444 -0
  21. package/_stories/vega-fixtures/bars.json +58 -0
  22. package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
  23. package/_stories/vega-fixtures/combo.json +68 -0
  24. package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
  25. package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
  26. package/_stories/vega-fixtures/horizontal-bar.json +427 -0
  27. package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
  28. package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
  29. package/_stories/vega-fixtures/lines.json +227 -0
  30. package/_stories/vega-fixtures/measles-bars.json +348 -0
  31. package/_stories/vega-fixtures/measles-map.json +11101 -0
  32. package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
  33. package/_stories/vega-fixtures/multi-dataset.json +255 -0
  34. package/_stories/vega-fixtures/no-data.json +14 -0
  35. package/_stories/vega-fixtures/pie-chart.json +94 -0
  36. package/_stories/vega-fixtures/repeat-spec.json +47 -0
  37. package/_stories/vega-fixtures/stacked-area.json +222 -0
  38. package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
  39. package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
  40. package/_stories/vega-fixtures/stacked-bars.json +212 -0
  41. package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
  42. package/_stories/vega-fixtures/warning-combo.json +59 -0
  43. package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
  44. package/assets/callout-flag.svg +7 -0
  45. package/assets/icon-chart-area.svg +1 -0
  46. package/assets/icon-chart-radar.svg +23 -0
  47. package/assets/logo2.svg +31 -0
  48. package/components/AdvancedEditor/EmbedEditor.tsx +270 -38
  49. package/components/Alert/components/Alert.styles.css +2 -2
  50. package/components/ComboBox/combobox.styles.css +48 -48
  51. package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
  52. package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
  53. package/components/DataTable/DataTable.tsx +46 -18
  54. package/components/DataTable/DataTableStandAlone.tsx +1 -0
  55. package/components/DataTable/components/ChartHeader.tsx +21 -12
  56. package/components/DataTable/components/MapHeader.tsx +34 -28
  57. package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
  58. package/components/DataTable/data-table.css +50 -52
  59. package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
  60. package/components/DataTable/helpers/getChartCellValue.ts +10 -7
  61. package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
  62. package/components/DataTable/helpers/getSeriesName.ts +6 -0
  63. package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
  64. package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
  65. package/components/DownloadButton.tsx +14 -6
  66. package/components/EditorPanel/ColumnsEditor.tsx +38 -31
  67. package/components/EditorPanel/CustomSortOrder.tsx +94 -0
  68. package/components/EditorPanel/DataTableEditor.tsx +139 -23
  69. package/components/EditorPanel/EditorPanel.styles.css +71 -71
  70. package/components/EditorPanel/EditorPanel.tsx +3 -8
  71. package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
  72. package/components/EditorPanel/FootnotesEditor.tsx +2 -2
  73. package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +21 -12
  74. package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +16 -10
  75. package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
  76. package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
  77. package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
  78. package/{styles/v2/components → components/EditorPanel}/editor.scss +76 -22
  79. package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
  80. package/components/EditorPanel/sections/VisualSection.tsx +11 -0
  81. package/components/EditorWrapper/editor-wrapper.style.css +1 -1
  82. package/components/Filters/Filters.tsx +3 -5
  83. package/components/Filters/components/Tabs.tsx +19 -7
  84. package/{styles → components/Filters}/filters.scss +3 -3
  85. package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
  86. package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
  87. package/components/Layout/components/Responsive.tsx +14 -6
  88. package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
  89. package/components/Layout/components/Sidebar/components/sidebar.styles.scss +14 -20
  90. package/components/Layout/components/Visualization/index.tsx +50 -38
  91. package/components/Layout/components/Visualization/visualizations.scss +232 -15
  92. package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
  93. package/components/Layout/components/VisualizationContainer.tsx +37 -0
  94. package/components/Layout/components/VisualizationContent.test.tsx +182 -0
  95. package/components/Layout/components/VisualizationContent.tsx +75 -0
  96. package/components/Layout/index.tsx +5 -5
  97. package/components/Layout/styles/editor-utils.scss +3 -3
  98. package/components/Layout/styles/editor.scss +4 -4
  99. package/components/Legend/Legend.Gradient.tsx +7 -1
  100. package/components/Loader/loader.styles.css +2 -2
  101. package/components/Loading.jsx +1 -1
  102. package/components/MediaControls.tsx +10 -3
  103. package/components/MultiSelect/multiselect.styles.css +19 -19
  104. package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
  105. package/components/PaletteSelector/PaletteSelector.css +15 -15
  106. package/components/RichTooltip/richTooltip.css +6 -6
  107. package/components/Table/table.styles.css +2 -2
  108. package/components/Waiting.tsx +1 -1
  109. package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
  110. package/components/_stories/DataTable.stories.tsx +1 -0
  111. package/components/_stories/Filters.stories.tsx +1 -1
  112. package/components/_stories/styles.scss +0 -1
  113. package/components/elements/Button.jsx +1 -1
  114. package/components/elements/Card.jsx +1 -1
  115. package/{styles/v2/components → components/elements}/button.scss +9 -8
  116. package/components/inputs/InputCheckbox.jsx +1 -1
  117. package/components/inputs/InputSelect.tsx +1 -1
  118. package/components/inputs/InputText.jsx +1 -1
  119. package/components/inputs/InputToggle.tsx +1 -1
  120. package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
  121. package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
  122. package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
  123. package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
  124. package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
  125. package/{styles → components}/loading.scss +1 -1
  126. package/components/managers/DataDesigner.tsx +1 -1
  127. package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
  128. package/components/ui/Accordion.jsx +1 -1
  129. package/components/ui/Icon.tsx +1 -1
  130. package/components/ui/LoadSpin.jsx +1 -1
  131. package/components/ui/Modal.jsx +1 -1
  132. package/components/ui/Overlay.jsx +1 -1
  133. package/components/ui/Title/index.test.tsx +34 -0
  134. package/components/ui/Title/index.tsx +24 -7
  135. package/components/ui/Title/title.styles.css +119 -25
  136. package/components/ui/Tooltip.tsx +1 -1
  137. package/components/ui/_stories/Title.stories.tsx +1 -1
  138. package/{styles/v2/components → components/ui}/accordion.scss +3 -3
  139. package/components/ui/accordion.styles.css +11 -11
  140. package/{styles/v2/components → components/ui}/modal.scss +2 -2
  141. package/{styles/v2/components → components/ui}/overlay.scss +6 -6
  142. package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
  143. package/{styles → components}/waiting.scss +9 -3
  144. package/data/colorPalettes.ts +18 -5
  145. package/data/mapColorPalettes.ts +10 -0
  146. package/devTemplate/dev.js +285 -0
  147. package/devTemplate/index.html +30 -0
  148. package/devTemplate/preview.html +1503 -0
  149. package/devTemplate/sidebar.css +151 -0
  150. package/dist/cove-main.css +2530 -3901
  151. package/dist/cove-main.css.map +1 -1
  152. package/generateViteConfig.js +111 -2
  153. package/helpers/DataTransform.ts +1 -5
  154. package/helpers/backfillDefaults.ts +35 -0
  155. package/helpers/constants.ts +12 -0
  156. package/helpers/cove/date.ts +64 -3
  157. package/helpers/cove/number.ts +29 -15
  158. package/helpers/cove/string.ts +29 -0
  159. package/helpers/coveUpdateWorker.ts +14 -8
  160. package/helpers/displayDataAsText.ts +1 -1
  161. package/helpers/embed/embedCodeGenerator.ts +80 -0
  162. package/helpers/embed/embedHelper.js +169 -0
  163. package/helpers/embed/filterUtils.ts +121 -0
  164. package/helpers/embed/index.ts +17 -0
  165. package/helpers/embed/urlValidation.ts +119 -0
  166. package/helpers/extractDataAndMetadata.ts +20 -0
  167. package/helpers/fetchRemoteData.ts +14 -8
  168. package/helpers/filterVizData.ts +6 -1
  169. package/helpers/getFileExtension.ts +0 -6
  170. package/helpers/labelHash.ts +9 -0
  171. package/helpers/markupProcessor.ts +56 -38
  172. package/helpers/metrics/types.ts +3 -0
  173. package/helpers/palettes/colorDistributions.ts +1 -1
  174. package/helpers/palettes/utils.ts +12 -12
  175. package/helpers/parseCsvWithQuotes.ts +15 -14
  176. package/helpers/prepareScreenshot.ts +33 -10
  177. package/helpers/testing.ts +44 -0
  178. package/helpers/tests/DataTransform.test.ts +125 -0
  179. package/helpers/tests/abbreviateNumber.test.ts +59 -0
  180. package/helpers/tests/backfillDefaults.test.ts +253 -0
  181. package/helpers/tests/date.test.ts +110 -0
  182. package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
  183. package/helpers/tests/markupProcessor.test.ts +315 -124
  184. package/helpers/tests/number.test.ts +42 -0
  185. package/helpers/tests/prepareScreenshot.test.ts +28 -28
  186. package/helpers/tests/testStandaloneBuild.ts +36 -26
  187. package/helpers/tests/useDataVizClasses.test.ts +66 -0
  188. package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
  189. package/helpers/useDataVizClasses.ts +13 -7
  190. package/helpers/vegaConfig.ts +1 -1
  191. package/helpers/vegaConfigImport.ts +160 -0
  192. package/helpers/ver/4.24.4.ts +24 -0
  193. package/helpers/ver/4.26.1.ts +1 -1
  194. package/helpers/ver/4.26.2.ts +84 -0
  195. package/helpers/ver/4.26.3.ts +44 -0
  196. package/helpers/ver/4.26.4.ts +31 -0
  197. package/helpers/ver/tests/4.26.1.test.ts +105 -0
  198. package/helpers/ver/tests/4.26.2.test.ts +298 -0
  199. package/helpers/ver/tests/4.26.3.test.ts +168 -0
  200. package/helpers/ver/tests/4.26.4.test.ts +88 -0
  201. package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
  202. package/helpers/viewports.ts +2 -0
  203. package/package.json +27 -32
  204. package/styles/_global.scss +7 -7
  205. package/styles/_reset.scss +2 -2
  206. package/styles/{v2/base → base}/_file-selector.scss +4 -4
  207. package/styles/{v2/base → base}/_general.scss +2 -4
  208. package/styles/{v2/base → base}/index.scss +1 -1
  209. package/styles/base.scss +107 -165
  210. package/styles/cove-main.scss +3 -6
  211. package/styles/layout/_component.scss +110 -0
  212. package/styles/{v2/layout → layout}/_data-table.scss +7 -7
  213. package/styles/layout/_wrapper-padding.scss +27 -0
  214. package/styles/{v2/main.scss → main.scss} +3 -1
  215. package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
  216. package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
  217. package/styles/{v2/utils → utils}/_grid.scss +8 -3
  218. package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
  219. package/styles/{v2/utils → utils}/index.scss +2 -1
  220. package/types/Annotation.ts +10 -11
  221. package/types/Axis.ts +2 -0
  222. package/types/ComponentStyles.ts +1 -0
  223. package/types/ConfigureData.ts +1 -0
  224. package/types/General.ts +2 -0
  225. package/types/MarkupInclude.ts +1 -0
  226. package/types/MarkupVariable.ts +2 -1
  227. package/types/Palette.ts +22 -0
  228. package/types/Table.ts +9 -0
  229. package/types/Visualization.ts +7 -0
  230. package/_stories/StoryRenderingTests.stories.tsx +0 -164
  231. package/helpers/embedCodeGenerator.ts +0 -109
  232. package/styles/_common-components.css +0 -73
  233. package/styles/_variables.scss +0 -63
  234. package/styles/v2/layout/_component.scss +0 -21
  235. package/styles/v2/utils/_variables.scss +0 -9
  236. package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
  237. /package/{styles/v2/components → components/ui}/icon.scss +0 -0
  238. /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
  239. /package/styles/{v2/base → base}/_heading.scss +0 -0
  240. /package/styles/{v2/base → base}/_reset.scss +0 -0
  241. /package/styles/{v2/layout → layout}/_alert.scss +0 -0
  242. /package/styles/{v2/layout → layout}/_progression.scss +0 -0
  243. /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
  244. /package/styles/{v2/layout → layout}/index.scss +0 -0
  245. /package/styles/{v2/themes → themes}/index.scss +0 -0
  246. /package/styles/{v2/utils → utils}/_align.scss +0 -0
  247. /package/styles/{v2/utils → utils}/_animations.scss +0 -0
  248. /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
  249. /package/styles/{v2/utils → utils}/_mixins.scss +0 -0
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Vega Import Stories
3
+ *
4
+ * Each story imports a Vega/Vega-Lite JSON config, converts it to a COVE config
5
+ * via the Vega importer pipeline, and renders the resulting chart or map.
6
+ *
7
+ * Play functions verify that each conversion renders without errors.
8
+ */
9
+ import React from 'react'
10
+ import type { Meta, StoryObj } from '@storybook/react-vite'
11
+ import { within, expect, waitFor } from 'storybook/test'
12
+
13
+ import Chart from '@cdc/chart/src/CdcChart'
14
+ import CdcMap from '@cdc/map/src/CdcMap'
15
+ import { maybeConvertVega } from '@cdc/core/helpers/vegaConfigImport'
16
+
17
+ // --- Import Vega fixture JSON files (alphabetical by filename) ---
18
+ import vegaBarsWithLine from './vega-fixtures/bars-with-line.json'
19
+ import vegaBars from './vega-fixtures/bars.json'
20
+ import vegaComboBarRollingMean from './vega-fixtures/combo-bar-rolling-mean.json'
21
+ import vegaCombo from './vega-fixtures/combo.json'
22
+ import vegaGroupedHorizontalBars from './vega-fixtures/grouped-horizontal-bars.json'
23
+ import vegaGroupedHorizontalBars2 from './vega-fixtures/grouped-horizontal-bars2.json'
24
+ import vegaHorizontalBar from './vega-fixtures/horizontal-bar.json'
25
+ import vegaHorizontalBarsWithBadColors from './vega-fixtures/horizontal-bars-with-bad-colors.json'
26
+ import vegaHorizontalBars2 from './vega-fixtures/horizontal-bars2.json'
27
+ import vegaLines from './vega-fixtures/lines.json'
28
+ import vegaMeaslesBars from './vega-fixtures/measles-bars.json'
29
+ import vegaMeaslesMap from './vega-fixtures/measles-map.json'
30
+ import vegaMeaslesStackedBars from './vega-fixtures/measles-stacked-bars.json'
31
+ import vegaMultiDataset from './vega-fixtures/multi-dataset.json'
32
+ import vegaNoData from './vega-fixtures/no-data.json'
33
+ import vegaPieChart from './vega-fixtures/pie-chart.json'
34
+ import vegaRepeatSpec from './vega-fixtures/repeat-spec.json'
35
+ import vegaStackedArea from './vega-fixtures/stacked-area.json'
36
+ import vegaStackedBarWithRect from './vega-fixtures/stacked-bar-with-rect.json'
37
+ import vegaStackedBars from './vega-fixtures/stacked-bars.json'
38
+ import vegaStackedBarsWithLine from './vega-fixtures/stacked-bars-with-line.json'
39
+ import vegaStackedHorizontalBars from './vega-fixtures/stacked-horizontal-bars.json'
40
+ import vegaWarningCombo from './vega-fixtures/warning-combo.json'
41
+ import vegaWarningScatterAndLine from './vega-fixtures/warning-scatter-and-line.json'
42
+
43
+ // --- Convert all configs at module evaluation time (top-level await) ---
44
+ // Ordered alphabetically by original filename
45
+ const fixtures: Record<string, any> = {
46
+ barsWithLine: vegaBarsWithLine,
47
+ bars: vegaBars,
48
+ comboBarRollingMean: vegaComboBarRollingMean,
49
+ combo: vegaCombo,
50
+ groupedHorizontalBars: vegaGroupedHorizontalBars,
51
+ groupedHorizontalBars2: vegaGroupedHorizontalBars2,
52
+ horizontalBar: vegaHorizontalBar,
53
+ horizontalBarsWithBadColors: vegaHorizontalBarsWithBadColors,
54
+ horizontalBars2: vegaHorizontalBars2,
55
+ lines: vegaLines,
56
+ measlesBars: vegaMeaslesBars,
57
+ measlesMap: vegaMeaslesMap,
58
+ measlesStackedBars: vegaMeaslesStackedBars,
59
+ multiDataset: vegaMultiDataset,
60
+ noData: vegaNoData,
61
+ pieChart: vegaPieChart,
62
+ repeatSpec: vegaRepeatSpec,
63
+ stackedArea: vegaStackedArea,
64
+ stackedBarWithRect: vegaStackedBarWithRect,
65
+ stackedBars: vegaStackedBars,
66
+ stackedBarsWithLine: vegaStackedBarsWithLine,
67
+ stackedHorizontalBars: vegaStackedHorizontalBars,
68
+ warningCombo: vegaWarningCombo,
69
+ warningScatterAndLine: vegaWarningScatterAndLine
70
+ }
71
+
72
+ const convertedConfigs: Record<string, any> = {}
73
+ const conversionErrors: Record<string, string[]> = {}
74
+ const conversionWarnings: Record<string, string[]> = {}
75
+
76
+ for (const [key, vegaJson] of Object.entries(fixtures)) {
77
+ // Capture console warnings/errors during conversion
78
+ const warnings: string[] = []
79
+ const errors: string[] = []
80
+
81
+ const originalWarn = console.warn
82
+ const originalError = console.error
83
+
84
+ console.warn = (...args: any[]) => {
85
+ const message = args.map(arg => (typeof arg === 'string' ? arg : JSON.stringify(arg))).join(' ')
86
+ warnings.push(message)
87
+ }
88
+
89
+ console.error = (...args: any[]) => {
90
+ const message = args.map(arg => (typeof arg === 'string' ? arg : JSON.stringify(arg))).join(' ')
91
+ errors.push(message)
92
+ }
93
+
94
+ try {
95
+ convertedConfigs[key] = await maybeConvertVega({ ...vegaJson })
96
+ } catch (err) {
97
+ console.error(`Failed to convert ${key}:`, err)
98
+ convertedConfigs[key] = null
99
+ }
100
+
101
+ console.warn = originalWarn
102
+ console.error = originalError
103
+
104
+ conversionErrors[key] = [...warnings, ...errors]
105
+ conversionWarnings[key] = warnings
106
+ }
107
+
108
+ // --- Storybook meta ---
109
+ const meta: Meta = {
110
+ title: 'Regression Tests/Vega Importer',
111
+ parameters: {
112
+ layout: 'fullscreen'
113
+ },
114
+ tags: ['!autodocs']
115
+ }
116
+
117
+ export default meta
118
+
119
+ // --- Shared play function: verify the chart/map rendered ---
120
+ const assertRendered = async ({ canvasElement }: { canvasElement: HTMLElement }) => {
121
+ await waitFor(
122
+ () => {
123
+ const coveModule = canvasElement.querySelector('.cove-visualization')
124
+ expect(coveModule).toBeTruthy()
125
+ },
126
+ { timeout: 15000 }
127
+ )
128
+
129
+ // Verify an SVG was rendered
130
+ const svgs = canvasElement.querySelectorAll('svg')
131
+ expect(svgs.length).toBeGreaterThan(0)
132
+ }
133
+
134
+ // --- Shared play function: verify map rendered ---
135
+ const assertMapRendered = async ({ canvasElement }: { canvasElement: HTMLElement }) => {
136
+ await waitFor(
137
+ () => {
138
+ const coveModule = canvasElement.querySelector('.cove-visualization')
139
+ expect(coveModule).toBeTruthy()
140
+ },
141
+ { timeout: 15000 }
142
+ )
143
+
144
+ // For maps, check for either SVG or canvas elements
145
+ await waitFor(
146
+ () => {
147
+ const svgs = canvasElement.querySelectorAll('svg')
148
+ const canvases = canvasElement.querySelectorAll('canvas')
149
+ const hasRenderedContent = svgs.length > 0 || canvases.length > 0
150
+ expect(hasRenderedContent).toBe(true)
151
+ },
152
+ { timeout: 15000 }
153
+ )
154
+ }
155
+
156
+ // --- Shared play function: verify error configs failed properly ---
157
+ const assertConversionFailed =
158
+ (expectedErrorPattern: string | RegExp) =>
159
+ async ({ canvasElement }: { canvasElement: HTMLElement }) => {
160
+ // Wait a bit for any async operations to complete
161
+ await new Promise(resolve => setTimeout(resolve, 500))
162
+
163
+ // Verify error message is displayed
164
+ const errorElement = canvasElement.querySelector('[data-testid="error"]')
165
+ expect(errorElement).toBeTruthy()
166
+ expect(errorElement?.textContent).toContain('failed to convert')
167
+
168
+ // Verify the specific error message appears
169
+ const errorMessages = canvasElement.querySelector('[data-testid="error-messages"]')
170
+ expect(errorMessages).toBeTruthy()
171
+
172
+ const errorText = errorMessages?.textContent || ''
173
+ if (typeof expectedErrorPattern === 'string') {
174
+ expect(errorText).toContain(expectedErrorPattern)
175
+ } else {
176
+ expect(expectedErrorPattern.test(errorText)).toBe(true)
177
+ }
178
+
179
+ // Verify no visualization rendered
180
+ const coveModule = canvasElement.querySelector('.cove-visualization')
181
+ expect(coveModule).toBeNull()
182
+ }
183
+
184
+ // --- Helper to build a chart story ---
185
+ const chartStory = (key: string): StoryObj => ({
186
+ render: () => {
187
+ const config = convertedConfigs[key]
188
+ if (!config) return <div data-testid='error'>Config "{key}" failed to convert</div>
189
+ return <Chart config={{ ...config }} isEditor={false} />
190
+ },
191
+ play: assertRendered
192
+ })
193
+
194
+ // --- Helper to build a map story ---
195
+ const mapStory = (key: string): StoryObj => ({
196
+ render: () => {
197
+ const config = convertedConfigs[key]
198
+ if (!config) return <div data-testid='error'>Config "{key}" failed to convert</div>
199
+ return <CdcMap config={{ ...config }} navigationHandler={() => {}} setConfig={() => {}} />
200
+ },
201
+ play: assertMapRendered
202
+ })
203
+
204
+ // --- Helper to build an error story (expects conversion to fail) ---
205
+ const errorStory = (key: string, expectedErrorPattern: string | RegExp): StoryObj => ({
206
+ render: () => {
207
+ const config = convertedConfigs[key]
208
+ const errorMessages = conversionErrors[key] || []
209
+
210
+ if (!config) {
211
+ return (
212
+ <div style={{ padding: '20px', fontFamily: 'monospace' }}>
213
+ <div data-testid='error' style={{ color: '#28a745', fontWeight: 'bold', marginBottom: '10px' }}>
214
+ ✓ Config "{key}" failed to convert (expected)
215
+ </div>
216
+ <div
217
+ data-testid='error-messages'
218
+ style={{
219
+ backgroundColor: '#f5f5f5',
220
+ padding: '15px',
221
+ borderRadius: '4px',
222
+ border: '1px solid #ddd'
223
+ }}
224
+ >
225
+ <strong>Error Messages:</strong>
226
+ <ul style={{ marginTop: '10px', paddingLeft: '20px' }}>
227
+ {errorMessages.map((msg, i) => (
228
+ <li key={i} style={{ marginBottom: '5px' }}>
229
+ {msg}
230
+ </li>
231
+ ))}
232
+ </ul>
233
+ </div>
234
+ </div>
235
+ )
236
+ }
237
+
238
+ return (
239
+ <div data-testid='unexpected-success' style={{ padding: '20px', color: '#c41e3a', fontWeight: 'bold' }}>
240
+ ⚠️ Config "{key}" unexpectedly succeeded - this should have failed!
241
+ </div>
242
+ )
243
+ },
244
+ play: assertConversionFailed(expectedErrorPattern)
245
+ })
246
+
247
+ // --- Helper to build a warning story (renders with warnings) ---
248
+ const warningStory = (
249
+ key: string,
250
+ Component: typeof Chart | typeof CdcMap,
251
+ expectedWarningPattern: string | RegExp
252
+ ): StoryObj => ({
253
+ render: () => {
254
+ const config = convertedConfigs[key]
255
+ const warnings = conversionWarnings[key] || []
256
+
257
+ if (!config) {
258
+ return <div data-testid='error'>Config "{key}" failed to convert</div>
259
+ }
260
+
261
+ return (
262
+ <div>
263
+ {warnings.length > 0 && (
264
+ <div
265
+ data-testid='warnings'
266
+ style={{
267
+ backgroundColor: '#fff3cd',
268
+ border: '1px solid #ffc107',
269
+ borderRadius: '4px',
270
+ padding: '15px',
271
+ margin: '20px',
272
+ fontFamily: 'monospace',
273
+ fontSize: '14px'
274
+ }}
275
+ >
276
+ <div style={{ color: '#856404', fontWeight: 'bold', marginBottom: '10px' }}>⚠️ Conversion Warnings:</div>
277
+ <ul style={{ margin: '0', paddingLeft: '20px', color: '#856404' }}>
278
+ {warnings.map((msg, i) => (
279
+ <li key={i} style={{ marginBottom: '5px' }}>
280
+ {msg}
281
+ </li>
282
+ ))}
283
+ </ul>
284
+ </div>
285
+ )}
286
+ <Component config={{ ...config }} isEditor={false} />
287
+ </div>
288
+ )
289
+ },
290
+ play: async ({ canvasElement }: { canvasElement: HTMLElement }) => {
291
+ // Verify warnings were captured
292
+ const warningsElement = canvasElement.querySelector('[data-testid="warnings"]')
293
+ expect(warningsElement).toBeTruthy()
294
+
295
+ // Verify the warning contains the expected text
296
+ const warningText = warningsElement?.textContent || ''
297
+
298
+ if (typeof expectedWarningPattern === 'string') {
299
+ expect(warningText).toContain(expectedWarningPattern)
300
+ } else {
301
+ expect(expectedWarningPattern.test(warningText)).toBe(true)
302
+ }
303
+
304
+ // Verify the visualization still rendered
305
+ await assertRendered({ canvasElement })
306
+ }
307
+ })
308
+
309
+ // ============================================================================
310
+ // Stories — alphabetical by original filename
311
+ // ============================================================================
312
+
313
+ // bars-with-line.json
314
+ export const BarsWithLineTests: StoryObj = chartStory('barsWithLine')
315
+
316
+ // bars.json
317
+ export const BarsTests: StoryObj = chartStory('bars')
318
+
319
+ // combo-bar-rolling-mean.json
320
+ export const ComboBarRollingMeanTests: StoryObj = chartStory('comboBarRollingMean')
321
+
322
+ // combo.json
323
+ export const ComboTests: StoryObj = chartStory('combo')
324
+
325
+ // grouped-horizontal-bars.json
326
+ export const GroupedHorizontalBarsTests: StoryObj = chartStory('groupedHorizontalBars')
327
+
328
+ // grouped-horizontal-bars2.json
329
+ export const GroupedHorizontalBars2Tests: StoryObj = chartStory('groupedHorizontalBars2')
330
+
331
+ // horizontal-bar.json
332
+ export const HorizontalBarTests: StoryObj = chartStory('horizontalBar')
333
+
334
+ // horizontal-bars-with-bad-colors.json
335
+ export const HorizontalBarsWithBadColorsTests: StoryObj = chartStory('horizontalBarsWithBadColors')
336
+
337
+ // horizontal-bars2.json
338
+ export const HorizontalBars2Tests: StoryObj = chartStory('horizontalBars2')
339
+
340
+ // lines.json
341
+ export const LinesTests: StoryObj = chartStory('lines')
342
+
343
+ // measles-bars.json
344
+ export const MeaslesBarsTests: StoryObj = chartStory('measlesBars')
345
+
346
+ // measles-map.json
347
+ export const MeaslesMapTests: StoryObj = mapStory('measlesMap')
348
+
349
+ // measles-stacked-bars.json
350
+ export const MeaslesStackedBarsTests: StoryObj = chartStory('measlesStackedBars')
351
+
352
+ // multi-dataset.json
353
+ export const MultiDatasetTests: StoryObj = chartStory('multiDataset')
354
+
355
+ // stacked-area.json
356
+ export const StackedAreaTests: StoryObj = chartStory('stackedArea')
357
+
358
+ // stacked-bar-with-rect.json
359
+ export const StackedBarWithRectTests: StoryObj = chartStory('stackedBarWithRect')
360
+
361
+ // stacked-bars.json
362
+ export const StackedBarsTests: StoryObj = chartStory('stackedBars')
363
+
364
+ // stacked-horizontal-bars.json
365
+ export const StackedHorizontalBarsTests: StoryObj = chartStory('stackedHorizontalBars')
366
+
367
+ // ============================================================================
368
+ // Error Test Stories (Expected to Fail)
369
+ // ============================================================================
370
+
371
+ // no-data.json - Config references external data URL instead of embedded data
372
+ export const NoDataErrorTests: StoryObj = errorStory('noData', 'No data was found')
373
+
374
+ // pie-chart.json - Pie charts are not supported by COVE's Vega importer
375
+ export const PieChartErrorTests: StoryObj = errorStory(
376
+ 'pieChart',
377
+ /could not find a COVE chart type|Supported marks are/
378
+ )
379
+
380
+ // repeat-spec.json - Vega-Lite repeat/spec operators are not supported
381
+ export const RepeatSpecErrorTests: StoryObj = errorStory('repeatSpec', /does not support.*repeat\/spec operator/)
382
+
383
+ // stacked-bars-with-line.json - Complex combinations may not be supported
384
+ export const StackedBarsWithLineErrorTests: StoryObj = errorStory(
385
+ 'stackedBarsWithLine',
386
+ /one of them appears to be stacked/
387
+ )
388
+
389
+ // ============================================================================
390
+ // Warning Test Stories (Render with Warnings)
391
+ // ============================================================================
392
+
393
+ // warning-combo.json - Combo chart with multiple mark types
394
+ export const WarningComboTests: StoryObj = warningStory('warningCombo', Chart, 'only support these types of marks')
395
+
396
+ // warning-scatter-and-line.json - Scatter plot with line
397
+ export const WarningScatterAndLineTests: StoryObj = warningStory(
398
+ 'warningScatterAndLine',
399
+ Chart,
400
+ 'only support these types of marks'
401
+ )