@cdc/core 4.26.1 → 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.
- package/.claude/agents/qa-test-developer.md +126 -0
- package/CLAUDE.local.md +67 -0
- package/_stories/Gallery.Charts.stories.tsx +34 -41
- package/_stories/Gallery.DataBite.stories.tsx +14 -7
- package/_stories/Gallery.Maps.stories.tsx +36 -27
- package/_stories/Gallery.WaffleChart.stories.tsx +1 -1
- package/_stories/PageART.stories.tsx +4 -3
- package/_stories/PageBRFSS.stories.tsx +20 -15
- package/_stories/PageCancerRegistries.stories.tsx +14 -14
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +30 -16
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +148 -143
- package/_stories/PageMaternalMortality.stories.tsx +4 -3
- package/_stories/PageOralHealth.stories.tsx +14 -9
- package/_stories/PageSmokingTobacco.stories.tsx +14 -9
- package/_stories/PageStateDiabetesProfiles.stories.tsx +14 -9
- package/_stories/PageWastewater.stories.tsx +40 -26
- package/_stories/VegaImport.stories.tsx +401 -0
- package/_stories/vega-fixtures/bars-with-line.json +444 -0
- package/_stories/vega-fixtures/bars.json +58 -0
- package/_stories/vega-fixtures/combo-bar-rolling-mean.json +88 -0
- package/_stories/vega-fixtures/combo.json +68 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars.json +83 -0
- package/_stories/vega-fixtures/grouped-horizontal-bars2.json +231 -0
- package/_stories/vega-fixtures/horizontal-bar.json +427 -0
- package/_stories/vega-fixtures/horizontal-bars-with-bad-colors.json +197 -0
- package/_stories/vega-fixtures/horizontal-bars2.json +58 -0
- package/_stories/vega-fixtures/lines.json +227 -0
- package/_stories/vega-fixtures/measles-bars.json +348 -0
- package/_stories/vega-fixtures/measles-map.json +11101 -0
- package/_stories/vega-fixtures/measles-stacked-bars.json +2147 -0
- package/_stories/vega-fixtures/multi-dataset.json +255 -0
- package/_stories/vega-fixtures/no-data.json +14 -0
- package/_stories/vega-fixtures/pie-chart.json +94 -0
- package/_stories/vega-fixtures/repeat-spec.json +47 -0
- package/_stories/vega-fixtures/stacked-area.json +222 -0
- package/_stories/vega-fixtures/stacked-bar-with-rect.json +3412 -0
- package/_stories/vega-fixtures/stacked-bars-with-line.json +364 -0
- package/_stories/vega-fixtures/stacked-bars.json +212 -0
- package/_stories/vega-fixtures/stacked-horizontal-bars.json +140 -0
- package/_stories/vega-fixtures/warning-combo.json +59 -0
- package/_stories/vega-fixtures/warning-scatter-and-line.json +1182 -0
- package/assets/icon-chart-area.svg +1 -0
- package/assets/icon-chart-radar.svg +23 -0
- package/assets/logo2.svg +31 -0
- package/components/AdvancedEditor/EmbedEditor.tsx +270 -38
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
- package/components/DataTable/helpers/getSeriesName.ts +6 -0
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +14 -6
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +4 -0
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +2 -2
- package/components/Layout/components/Visualization/index.tsx +11 -0
- package/components/MediaControls.tsx +0 -1
- package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
- package/components/_stories/DataTable.stories.tsx +1 -0
- package/data/colorPalettes.ts +18 -5
- package/data/mapColorPalettes.ts +10 -0
- package/devTemplate/dev.js +235 -0
- package/devTemplate/index.html +30 -0
- package/devTemplate/preview.html +1503 -0
- package/devTemplate/sidebar.css +151 -0
- package/dist/cove-main.css +2803 -4471
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +111 -2
- package/helpers/DataTransform.ts +1 -5
- package/helpers/cove/date.ts +33 -1
- package/helpers/cove/string.ts +29 -0
- package/helpers/coveUpdateWorker.ts +3 -1
- package/helpers/embed/embedCodeGenerator.ts +80 -0
- package/helpers/embed/embedHelper.js +158 -0
- package/helpers/embed/filterUtils.ts +121 -0
- package/helpers/embed/index.ts +21 -0
- package/helpers/embed/urlValidation.ts +119 -0
- package/helpers/filterVizData.ts +6 -1
- package/helpers/getFileExtension.ts +0 -6
- package/helpers/metrics/types.ts +3 -0
- package/helpers/palettes/colorDistributions.ts +1 -1
- package/helpers/palettes/utils.ts +12 -12
- package/helpers/parseCsvWithQuotes.ts +15 -14
- package/helpers/prepareScreenshot.ts +27 -7
- package/helpers/testing.ts +44 -0
- package/helpers/tests/DataTransform.test.ts +125 -0
- package/helpers/tests/date.test.ts +64 -0
- package/helpers/vegaConfig.ts +1 -1
- package/helpers/vegaConfigImport.ts +160 -0
- package/helpers/ver/4.26.1.ts +1 -1
- package/helpers/ver/4.26.2.ts +84 -0
- package/helpers/ver/tests/4.26.1.test.ts +105 -0
- package/helpers/ver/tests/4.26.2.test.ts +298 -0
- package/helpers/viewports.ts +2 -0
- package/package.json +27 -32
- package/styles/v2/components/editor.scss +9 -9
- package/styles/v2/utils/_grid.scss +8 -3
- package/types/Annotation.ts +10 -11
- package/types/General.ts +2 -0
- package/types/Palette.ts +21 -0
- package/types/Visualization.ts +6 -0
- package/_stories/StoryRenderingTests.stories.tsx +0 -164
- package/helpers/embedCodeGenerator.ts +0 -109
|
@@ -115,18 +115,32 @@ type MapStory = StoryObj<typeof CdcMap>
|
|
|
115
115
|
type ChartStory = StoryObj<typeof Chart>
|
|
116
116
|
type DashboardStory = StoryObj<typeof Dashboard>
|
|
117
117
|
|
|
118
|
-
// Helper function to test map rendering
|
|
118
|
+
// Helper function to test map rendering (supports both SVG and canvas-based maps)
|
|
119
119
|
const testMapRendering = async (canvasElement: HTMLElement, storyName: string) => {
|
|
120
|
-
const canvas = within(canvasElement)
|
|
121
|
-
|
|
122
120
|
await step('Wait for map to render', async () => {
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
await new Promise<void>((resolve, reject) => {
|
|
122
|
+
const startTime = Date.now()
|
|
123
|
+
const timeout = 15000
|
|
124
|
+
|
|
125
|
+
const checkMap = () => {
|
|
126
|
+
const svgMap = canvasElement.querySelector('svg[role="img"]')
|
|
127
|
+
const canvasMap = canvasElement.querySelector('canvas')
|
|
128
|
+
if (svgMap || canvasMap) {
|
|
129
|
+
resolve()
|
|
130
|
+
} else if (Date.now() - startTime > timeout) {
|
|
131
|
+
reject(new Error(`Timeout: No map element (svg or canvas) found after ${timeout}ms`))
|
|
132
|
+
} else {
|
|
133
|
+
setTimeout(checkMap, 100)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
checkMap()
|
|
137
|
+
})
|
|
125
138
|
})
|
|
126
139
|
|
|
127
|
-
await step('Verify
|
|
128
|
-
const
|
|
129
|
-
|
|
140
|
+
await step('Verify map visualization is present', async () => {
|
|
141
|
+
const svgMap = canvasElement.querySelector('svg[role="img"]')
|
|
142
|
+
const canvasMap = canvasElement.querySelector('canvas')
|
|
143
|
+
expect(svgMap || canvasMap).toBeTruthy()
|
|
130
144
|
})
|
|
131
145
|
|
|
132
146
|
await step('Verify COVE module wrapper is present', async () => {
|
|
@@ -167,7 +181,7 @@ const testDashboardRendering = async (canvasElement: HTMLElement, storyName: str
|
|
|
167
181
|
const timeout = 15000
|
|
168
182
|
|
|
169
183
|
const checkDashboard = () => {
|
|
170
|
-
const dashboardElement = canvasElement.querySelector('.
|
|
184
|
+
const dashboardElement = canvasElement.querySelector('.type-dashboard')
|
|
171
185
|
if (dashboardElement) {
|
|
172
186
|
resolve()
|
|
173
187
|
} else if (Date.now() - startTime > timeout) {
|
|
@@ -181,7 +195,7 @@ const testDashboardRendering = async (canvasElement: HTMLElement, storyName: str
|
|
|
181
195
|
})
|
|
182
196
|
|
|
183
197
|
await step('Verify dashboard wrapper is present', async () => {
|
|
184
|
-
const dashboard = canvasElement.querySelector('.
|
|
198
|
+
const dashboard = canvasElement.querySelector('.type-dashboard')
|
|
185
199
|
expect(dashboard).toBeInTheDocument()
|
|
186
200
|
})
|
|
187
201
|
|
|
@@ -289,14 +303,14 @@ export const COVID_Time_Period_Map: MapStory = {
|
|
|
289
303
|
*
|
|
290
304
|
* COVID-19 wastewater data visualization by state.
|
|
291
305
|
*/
|
|
292
|
-
export const COVID_State_Level:
|
|
306
|
+
export const COVID_State_Level: MapStory = {
|
|
293
307
|
render: () => {
|
|
294
308
|
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidStateLevel)
|
|
295
309
|
if (!config) return <div>Loading...</div>
|
|
296
|
-
return <
|
|
310
|
+
return <CdcMap config={config} />
|
|
297
311
|
},
|
|
298
312
|
play: async ({ canvasElement }) => {
|
|
299
|
-
await
|
|
313
|
+
await testMapRendering(canvasElement, 'COVID State Level')
|
|
300
314
|
}
|
|
301
315
|
}
|
|
302
316
|
|
|
@@ -325,10 +339,10 @@ export const COVID_State_Level_Rest: ChartStory = {
|
|
|
325
339
|
render: () => {
|
|
326
340
|
const config = useConfigWithAbsoluteDataUrl(CONFIG_URLS.covidStateLevelRest)
|
|
327
341
|
if (!config) return <div>Loading...</div>
|
|
328
|
-
return <
|
|
342
|
+
return <Dashboard config={config} />
|
|
329
343
|
},
|
|
330
344
|
play: async ({ canvasElement }) => {
|
|
331
|
-
await
|
|
345
|
+
await testDashboardRendering(canvasElement, 'COVID State Level Rest')
|
|
332
346
|
}
|
|
333
347
|
}
|
|
334
348
|
|
|
@@ -365,50 +379,50 @@ export const All_Wastewater_Visualizations: StoryObj = {
|
|
|
365
379
|
}
|
|
366
380
|
|
|
367
381
|
return (
|
|
368
|
-
<div className=
|
|
369
|
-
<h1 className=
|
|
382
|
+
<div className='container-fluid p-4'>
|
|
383
|
+
<h1 className='mb-4'>NWSS - All Wastewater Visualizations</h1>
|
|
370
384
|
|
|
371
|
-
<section className=
|
|
385
|
+
<section className='mb-5'>
|
|
372
386
|
<h2>NWSS Home Page</h2>
|
|
373
387
|
<Dashboard config={homePageConfig} />
|
|
374
388
|
</section>
|
|
375
389
|
|
|
376
|
-
<section className=
|
|
390
|
+
<section className='mb-5'>
|
|
377
391
|
<h2>Measles - Summary Modules</h2>
|
|
378
392
|
<Dashboard config={measlesTopConfig} />
|
|
379
393
|
</section>
|
|
380
394
|
|
|
381
|
-
<section className=
|
|
395
|
+
<section className='mb-5'>
|
|
382
396
|
<h2>Measles - US Map</h2>
|
|
383
397
|
<CdcMap config={measlesMapConfig} />
|
|
384
398
|
</section>
|
|
385
399
|
|
|
386
|
-
<section className=
|
|
400
|
+
<section className='mb-5'>
|
|
387
401
|
<h2>Measles - Time Period</h2>
|
|
388
402
|
<Dashboard config={measlesTimePeriodConfig} />
|
|
389
403
|
</section>
|
|
390
404
|
|
|
391
|
-
<section className=
|
|
405
|
+
<section className='mb-5'>
|
|
392
406
|
<h2>COVID-19 - Summary Modules</h2>
|
|
393
407
|
<Dashboard config={covidTopConfig} />
|
|
394
408
|
</section>
|
|
395
409
|
|
|
396
|
-
<section className=
|
|
410
|
+
<section className='mb-5'>
|
|
397
411
|
<h2>COVID-19 - State Map</h2>
|
|
398
412
|
<CdcMap config={covidMapConfig} />
|
|
399
413
|
</section>
|
|
400
414
|
|
|
401
|
-
<section className=
|
|
415
|
+
<section className='mb-5'>
|
|
402
416
|
<h2>COVID-19 - State Level Data</h2>
|
|
403
417
|
<Chart config={covidStateLevelConfig} />
|
|
404
418
|
</section>
|
|
405
419
|
|
|
406
|
-
<section className=
|
|
420
|
+
<section className='mb-5'>
|
|
407
421
|
<h2>COVID-19 - National and Regional Trends</h2>
|
|
408
422
|
<Chart config={covidNationalRegionalConfig} />
|
|
409
423
|
</section>
|
|
410
424
|
|
|
411
|
-
<section className=
|
|
425
|
+
<section className='mb-5'>
|
|
412
426
|
<h2>COVID-19 - State Trends</h2>
|
|
413
427
|
<Chart config={covidStateRestConfig} />
|
|
414
428
|
</section>
|
|
@@ -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('.cdc-open-viz-module')
|
|
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('.cdc-open-viz-module')
|
|
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('.cdc-open-viz-module')
|
|
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
|
+
)
|