@cdc/dashboard 4.26.3 → 4.26.5

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 (151) hide show
  1. package/CONFIG.md +219 -0
  2. package/README.md +60 -20
  3. package/dist/cdcdashboard-CY9IcPSi.es.js +6 -0
  4. package/dist/cdcdashboard-DlpiY3fQ.es.js +4 -0
  5. package/dist/cdcdashboard.js +61559 -58048
  6. package/examples/__data__/data-2.json +6 -0
  7. package/examples/__data__/data.json +6 -0
  8. package/examples/dashboard-conditions-filters-incomplete.json +221 -0
  9. package/examples/dashboard-missing-datasets-multi.json +174 -0
  10. package/examples/dashboard-missing-datasets-single.json +121 -0
  11. package/examples/dashboard-multi-dashboard-version-regression.json +146 -0
  12. package/examples/dashboard-shared-filter-row-delete-cleanup.json +186 -0
  13. package/examples/dashboard-stale-dataset-keys.json +181 -0
  14. package/examples/dashboard-tiered-filter-regression.json +190 -0
  15. package/examples/legend-issue.json +1 -1
  16. package/examples/minimal-example.json +34 -0
  17. package/examples/private/cfa-dashboard.json +651 -0
  18. package/examples/private/data-bite-wrap.json +6936 -0
  19. package/examples/private/dengue.json +4640 -0
  20. package/examples/private/link_to_file.json +16662 -0
  21. package/examples/private/multi-dash-fix.json +16963 -0
  22. package/examples/private/versions.json +41612 -0
  23. package/examples/sankey.json +3 -3
  24. package/examples/test-api-filter-reset.json +4 -4
  25. package/examples/tp5-test.json +86 -4
  26. package/examples/us-map-filter-example.json +1074 -0
  27. package/package.json +9 -9
  28. package/src/CdcDashboard.tsx +6 -2
  29. package/src/CdcDashboardComponent.tsx +179 -88
  30. package/src/DashboardCopyPasteContext.test.tsx +33 -0
  31. package/src/DashboardCopyPasteContext.tsx +48 -0
  32. package/src/_stories/Dashboard.EditorRegression.stories.tsx +72 -0
  33. package/src/_stories/Dashboard.Regression.stories.tsx +196 -0
  34. package/src/_stories/Dashboard.Zoom.stories.tsx +88 -0
  35. package/src/_stories/Dashboard.smoke.stories.tsx +33 -0
  36. package/src/_stories/Dashboard.stories.tsx +337 -2
  37. package/src/_stories/FilteredTextMigrationComparison.stories.tsx +87 -0
  38. package/src/_stories/_mock/dashboard-data-driven-colors.json +171 -0
  39. package/src/_stories/_mock/tp5-test.json +86 -5
  40. package/src/components/Column.test.tsx +176 -0
  41. package/src/components/Column.tsx +214 -13
  42. package/src/components/DashboardConditionModal.test.tsx +420 -0
  43. package/src/components/DashboardConditionModal.tsx +367 -0
  44. package/src/components/DashboardConditionSummary.tsx +59 -0
  45. package/src/components/DashboardEditors.tsx +23 -0
  46. package/src/components/DashboardFilters/DashboardFilters.test.tsx +267 -0
  47. package/src/components/DashboardFilters/DashboardFilters.tsx +193 -172
  48. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.test.tsx +164 -0
  49. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +46 -6
  50. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +5 -3
  51. package/src/components/DashboardFilters/DashboardFiltersEditor/components/DeleteFilterModal.tsx +59 -58
  52. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.test.tsx +304 -0
  53. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +43 -36
  54. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +2 -2
  55. package/src/components/DashboardFilters/DashboardFiltersWrapper.test.tsx +142 -0
  56. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +32 -27
  57. package/src/components/DashboardFilters/dashboardfilter.styles.css +42 -27
  58. package/src/components/DataDesignerModal.tsx +2 -1
  59. package/src/components/ExpandCollapseButtons.tsx +6 -4
  60. package/src/components/Grid.tsx +12 -7
  61. package/src/components/Header/Header.tsx +36 -17
  62. package/src/components/MultiConfigTabs/MultiConfigTabs.tsx +141 -140
  63. package/src/components/Row.test.tsx +228 -0
  64. package/src/components/Row.tsx +104 -28
  65. package/src/components/VisualizationRow.test.tsx +396 -0
  66. package/src/components/VisualizationRow.tsx +177 -51
  67. package/src/components/VisualizationsPanel/VisualizationsPanel.test.tsx +49 -0
  68. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +14 -13
  69. package/src/components/Widget/Widget.test.tsx +218 -0
  70. package/src/components/Widget/Widget.tsx +123 -20
  71. package/src/components/Widget/widget.styles.css +58 -14
  72. package/src/components/dashboard-condition-modal.css +76 -0
  73. package/src/components/dashboard-condition-summary.css +87 -0
  74. package/src/data/initial-state.js +1 -0
  75. package/src/helpers/addValuesToDashboardFilters.ts +3 -5
  76. package/src/helpers/addVisualization.ts +17 -4
  77. package/src/helpers/cloneDashboardWidget.ts +127 -0
  78. package/src/helpers/dashboardColumnWidgets.ts +99 -0
  79. package/src/helpers/dashboardConditionUi.ts +47 -0
  80. package/src/helpers/dashboardConditions.ts +200 -0
  81. package/src/helpers/dashboardFilterTargets.ts +156 -0
  82. package/src/helpers/filterData.ts +4 -9
  83. package/src/helpers/filterVisibility.ts +20 -0
  84. package/src/helpers/formatConfigBeforeSave.ts +2 -2
  85. package/src/helpers/getFilteredData.ts +18 -5
  86. package/src/helpers/getUpdateConfig.ts +43 -12
  87. package/src/helpers/getVizRowColumnLocator.ts +11 -1
  88. package/src/helpers/iconHash.tsx +9 -3
  89. package/src/helpers/mapDataToConfig.ts +31 -29
  90. package/src/helpers/reloadURLHelpers.ts +25 -5
  91. package/src/helpers/removeDashboardFilter.ts +33 -33
  92. package/src/helpers/tests/addVisualization.test.ts +53 -9
  93. package/src/helpers/tests/cloneDashboardWidget.test.ts +136 -0
  94. package/src/helpers/tests/dashboardColumnWidgets.test.ts +99 -0
  95. package/src/helpers/tests/dashboardConditionUi.test.ts +41 -0
  96. package/src/helpers/tests/dashboardConditions.test.ts +428 -0
  97. package/src/helpers/tests/formatConfigBeforeSave.test.ts +51 -0
  98. package/src/helpers/tests/getFilteredData.test.ts +265 -86
  99. package/src/helpers/tests/getUpdateConfig.test.ts +338 -0
  100. package/src/helpers/tests/reloadURLHelpers.test.ts +394 -238
  101. package/src/index.tsx +6 -3
  102. package/src/scss/grid.scss +281 -22
  103. package/src/scss/main.scss +215 -64
  104. package/src/store/dashboard.actions.ts +17 -4
  105. package/src/store/dashboard.reducer.test.ts +538 -0
  106. package/src/store/dashboard.reducer.ts +136 -22
  107. package/src/test/CdcDashboard.test.jsx +24 -0
  108. package/src/test/CdcDashboard.test.tsx +148 -0
  109. package/src/test/CdcDashboardComponent.test.tsx +935 -2
  110. package/src/types/ConfigRow.ts +15 -0
  111. package/src/types/DashboardFilters.ts +4 -0
  112. package/src/types/SharedFilter.ts +2 -0
  113. package/tests/fixtures/dashboard-config-with-metadata.json +1 -1
  114. package/dist/cdcdashboard-vr9HZwRt.es.js +0 -6
  115. package/examples/DEV-6574.json +0 -2224
  116. package/examples/api-dashboard-data.json +0 -272
  117. package/examples/api-dashboard-years.json +0 -11
  118. package/examples/api-geographies-data.json +0 -11
  119. package/examples/chart-data.json +0 -5409
  120. package/examples/custom/css/respiratory.css +0 -236
  121. package/examples/custom/js/respiratory.js +0 -242
  122. package/examples/default-data.json +0 -368
  123. package/examples/default-filter-control.json +0 -209
  124. package/examples/default-multi-dataset-shared-filter.json +0 -1729
  125. package/examples/default-multi-dataset.json +0 -506
  126. package/examples/ed-visits-county-file.json +0 -402
  127. package/examples/filters/Alabama.json +0 -72
  128. package/examples/filters/Alaska.json +0 -1737
  129. package/examples/filters/Arkansas.json +0 -4713
  130. package/examples/filters/California.json +0 -212
  131. package/examples/filters/Colorado.json +0 -1500
  132. package/examples/filters/Connecticut.json +0 -559
  133. package/examples/filters/Delaware.json +0 -63
  134. package/examples/filters/DistrictofColumbia.json +0 -63
  135. package/examples/filters/Florida.json +0 -4217
  136. package/examples/filters/States.json +0 -146
  137. package/examples/state-level.json +0 -90136
  138. package/examples/state-points.json +0 -10474
  139. package/examples/temp-example-data.json +0 -130
  140. package/examples/test-dashboard-simple.json +0 -503
  141. package/examples/test-example.json +0 -752
  142. package/examples/test-file.json +0 -147
  143. package/examples/test.json +0 -752
  144. package/examples/testing.json +0 -94456
  145. /package/examples/{data → __data__}/data-with-metadata.json +0 -0
  146. /package/examples/{legend-issue-data.json → __data__/legend-issue-data.json} +0 -0
  147. /package/examples/api-test/{categories.json → __data__/categories.json} +0 -0
  148. /package/examples/api-test/{chart-data.json → __data__/chart-data.json} +0 -0
  149. /package/examples/api-test/{topics.json → __data__/topics.json} +0 -0
  150. /package/examples/api-test/{years.json → __data__/years.json} +0 -0
  151. /package/src/_stories/{Dashboard.Pages.stories.tsx → Dashboard.Pages.smoke.stories.tsx} +0 -0
@@ -1,5 +1,6 @@
1
1
  import _ from 'lodash'
2
2
  import { getUpdateConfig } from '../helpers/getUpdateConfig'
3
+ import { getFilteredData } from '../helpers/getFilteredData'
3
4
  import { MultiDashboard, MultiDashboardConfig } from '../types/MultiDashboard'
4
5
  import DashboardActions from './dashboard.actions'
5
6
  import { devToolsWrapper } from '@cdc/core/helpers/withDevTools'
@@ -8,6 +9,8 @@ import { Dashboard } from '../types/Dashboard'
8
9
  import { ConfigRow } from '../types/ConfigRow'
9
10
  import { AnyVisualization } from '@cdc/core/types/Visualization'
10
11
  import { initialState } from '../DashboardContext'
12
+ import { hasConditionalWidgets, normalizeConditionalColumn } from '../helpers/dashboardColumnWidgets'
13
+ import { cloneDashboardWidget } from '../helpers/cloneDashboardWidget'
11
14
 
12
15
  type BlankMultiConfig = {
13
16
  dashboard: Partial<Dashboard>
@@ -24,6 +27,7 @@ const createBlankDashboard: () => BlankMultiConfig = () => ({
24
27
  label: 'Data Table',
25
28
  show: false,
26
29
  showDownloadUrl: false,
30
+ downloadUrlLabel: '',
27
31
  showVertical: true
28
32
  }
29
33
  })
@@ -75,10 +79,22 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
75
79
  } else return state // ignore SET_CONFIG calls that have the wrong activeDashboard due to async api fetching
76
80
  }
77
81
  case 'SET_DATA': {
78
- return { ...state, data: action.payload }
82
+ if (
83
+ action.payload.activeDashboard !== undefined &&
84
+ state.config.activeDashboard !== action.payload.activeDashboard
85
+ ) {
86
+ return state
87
+ }
88
+ return { ...state, data: action.payload.data }
79
89
  }
80
90
  case 'SET_FILTERED_DATA': {
81
- return { ...state, filteredData: action.payload }
91
+ if (
92
+ action.payload.activeDashboard !== undefined &&
93
+ state.config.activeDashboard !== action.payload.activeDashboard
94
+ ) {
95
+ return state
96
+ }
97
+ return { ...state, filteredData: action.payload.filteredData }
82
98
  }
83
99
  case 'SET_LOADING': {
84
100
  return { ...state, loading: action.payload }
@@ -89,9 +105,15 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
89
105
  case 'SET_SHARED_FILTERS': {
90
106
  const newSharedFilters = action.payload
91
107
  const newDashboardConfig = { ...state.config.dashboard, sharedFilters: newSharedFilters }
108
+ const nextConfig = saveMultiChanges(
109
+ { ...state.config, dashboard: newDashboardConfig },
110
+ state.config.activeDashboard
111
+ )
112
+ const filteredData = getFilteredData({ ...state, config: nextConfig })
92
113
  return {
93
114
  ...state,
94
- config: saveMultiChanges({ ...state.config, dashboard: newDashboardConfig }, state.config.activeDashboard)
115
+ config: nextConfig,
116
+ filteredData
95
117
  }
96
118
  }
97
119
  case 'SET_TAB_SELECTED': {
@@ -114,12 +136,12 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
114
136
  case 'RENAME_DASHBOARD_TAB': {
115
137
  const newMultiDashboards = state.config.multiDashboards.map(dashboard => {
116
138
  if (dashboard.label === action.payload.current) {
117
- dashboard.label = action.payload.new
139
+ return { ...dashboard, label: action.payload.new }
118
140
  }
119
141
  return dashboard
120
142
  })
121
143
  const newConfig = { ...state.config, label: action.payload.new } // make sure active label is updated
122
- return applyMultiDashboards({ ...state, newConfig }, newMultiDashboards)
144
+ return applyMultiDashboards({ ...state, config: newConfig }, newMultiDashboards)
123
145
  }
124
146
  case 'REORDER_MULTIDASHBOARDS': {
125
147
  const { newIndex, currentIndex } = action.payload
@@ -163,10 +185,24 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
163
185
  return { ...state, config: { ...state.config, rows: newRows } }
164
186
  }
165
187
  case 'ADD_VISUALIZATION': {
166
- const { newViz, rowIdx, colIdx } = action.payload
188
+ const { newViz, rowIdx, colIdx, entryIdx } = action.payload
167
189
  const vizKey = newViz.uid
168
190
  const newRows = _.cloneDeep(state.config.rows)
169
- newRows[rowIdx].columns[colIdx].widget = vizKey
191
+ const column = newRows[rowIdx].columns[colIdx]
192
+
193
+ if (entryIdx !== undefined || hasConditionalWidgets(column)) {
194
+ const nextConditionalWidgets = [...(column.conditionalWidgets || [])]
195
+ const targetEntryIndex = entryIdx ?? nextConditionalWidgets.length
196
+ nextConditionalWidgets[targetEntryIndex] = { widget: vizKey }
197
+ newRows[rowIdx].columns[colIdx] = normalizeConditionalColumn({
198
+ ...column,
199
+ widget: undefined,
200
+ conditionalWidgets: nextConditionalWidgets.filter(entry => !!entry?.widget)
201
+ })
202
+ } else {
203
+ newRows[rowIdx].columns[colIdx].widget = vizKey
204
+ }
205
+
170
206
  return {
171
207
  ...state,
172
208
  config: saveMultiChanges(
@@ -175,14 +211,73 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
175
211
  )
176
212
  }
177
213
  }
214
+ case 'CLONE_VISUALIZATION': {
215
+ const { sourceWidgetKey, rowIdx, colIdx, entryIdx } = action.payload
216
+ const nextConfig = cloneDashboardWidget(state.config, sourceWidgetKey, { rowIdx, colIdx, entryIdx })
217
+
218
+ if (nextConfig === state.config) return state
219
+
220
+ const config = saveMultiChanges(nextConfig, state.config.activeDashboard)
221
+ const filteredData = getFilteredData({ ...state, config })
222
+
223
+ return {
224
+ ...state,
225
+ config,
226
+ filteredData
227
+ }
228
+ }
178
229
  case 'MOVE_VISUALIZATION': {
179
- const { rowIdx, colIdx, widget } = action.payload
230
+ const { rowIdx, colIdx, entryIdx, widget } = action.payload
180
231
  const newRows = _.cloneDeep(state.config.rows)
181
- newRows[widget.rowIdx].columns[widget.colIdx].widget = null
182
- newRows[rowIdx].columns[colIdx].widget = widget.uid
232
+ const sourceColumn = newRows[widget.rowIdx].columns[widget.colIdx]
233
+ let widgetEntry
234
+
235
+ if (hasConditionalWidgets(sourceColumn)) {
236
+ widgetEntry =
237
+ widget.entryIdx !== undefined
238
+ ? sourceColumn.conditionalWidgets[widget.entryIdx]
239
+ : sourceColumn.conditionalWidgets.find(entry => entry.widget === widget.uid)
240
+ } else if (sourceColumn.widget === widget.uid) {
241
+ widgetEntry = { widget: widget.uid }
242
+ }
243
+
244
+ if (!widgetEntry) {
245
+ return state
246
+ }
247
+
248
+ if (hasConditionalWidgets(sourceColumn)) {
249
+ newRows[widget.rowIdx].columns[widget.colIdx] = normalizeConditionalColumn({
250
+ ...sourceColumn,
251
+ conditionalWidgets: sourceColumn.conditionalWidgets.filter((entry, index) => {
252
+ if (widget.entryIdx !== undefined) return index !== widget.entryIdx
253
+ return entry.widget !== widget.uid
254
+ })
255
+ })
256
+ } else {
257
+ newRows[widget.rowIdx].columns[widget.colIdx].widget = undefined
258
+ }
259
+
260
+ const targetColumn = newRows[rowIdx].columns[colIdx]
261
+ if (entryIdx !== undefined || hasConditionalWidgets(targetColumn)) {
262
+ const nextConditionalWidgets = [...(targetColumn.conditionalWidgets || [])]
263
+ const targetEntryIndex = entryIdx ?? nextConditionalWidgets.length
264
+ nextConditionalWidgets[targetEntryIndex] = widgetEntry
265
+ newRows[rowIdx].columns[colIdx] = normalizeConditionalColumn({
266
+ ...targetColumn,
267
+ widget: undefined,
268
+ conditionalWidgets: nextConditionalWidgets.filter(entry => !!entry?.widget)
269
+ })
270
+ } else {
271
+ newRows[rowIdx].columns[colIdx].widget = widgetEntry.widget
272
+ }
273
+
274
+ const nextConfig = saveMultiChanges({ ...state.config, rows: newRows }, state.config.activeDashboard)
275
+ const filteredData = getFilteredData({ ...state, config: nextConfig })
276
+
183
277
  return {
184
278
  ...state,
185
- config: saveMultiChanges({ ...state.config, rows: newRows }, state.config.activeDashboard)
279
+ config: nextConfig,
280
+ filteredData
186
281
  }
187
282
  }
188
283
  case 'RESET_VISUALIZATION': {
@@ -219,7 +314,15 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
219
314
  }
220
315
  return row
221
316
  })
222
- return { ...state, config: saveMultiChanges({ ...state.config, rows: newRows }, state.config.activeDashboard) }
317
+ const nextConfig = {
318
+ ...state.config,
319
+ rows: newRows
320
+ }
321
+
322
+ return {
323
+ ...state,
324
+ config: saveMultiChanges(nextConfig, state.config.activeDashboard)
325
+ }
223
326
  }
224
327
  case 'DELETE_WIDGET': {
225
328
  const { uid } = action.payload
@@ -237,20 +340,31 @@ const reducer = (state: DashboardState, action: DashboardActions): DashboardStat
237
340
 
238
341
  const filteredRows = _.map(newRows, row => ({
239
342
  ...row,
240
- columns: row.columns.map(column => (column.widget === uid ? _.omit(column, 'widget') : column))
343
+ columns: row.columns.map(column => {
344
+ if (hasConditionalWidgets(column)) {
345
+ return normalizeConditionalColumn({
346
+ ...column,
347
+ conditionalWidgets: column.conditionalWidgets.filter(entry => entry.widget !== uid)
348
+ })
349
+ }
350
+
351
+ return column.widget === uid ? _.omit(column, 'widget') : column
352
+ })
241
353
  }))
242
354
 
355
+ const nextConfig = {
356
+ ...state.config,
357
+ dashboard: {
358
+ ...state.config.dashboard,
359
+ sharedFilters: newSharedFilters
360
+ },
361
+ visualizations: newVisualizations,
362
+ rows: filteredRows
363
+ }
364
+
243
365
  return {
244
366
  ...state,
245
- config: saveMultiChanges(
246
- {
247
- ...state.config,
248
- dashboard: { ...state.config.dashboard, sharedFilters: newSharedFilters },
249
- visualizations: newVisualizations,
250
- rows: filteredRows
251
- },
252
- state.config.activeDashboard
253
- )
367
+ config: saveMultiChanges(nextConfig, state.config.activeDashboard)
254
368
  }
255
369
  }
256
370
  case 'UPDATE_TOGGLE_NAME': {
@@ -1,11 +1,35 @@
1
1
  import path from 'node:path'
2
+ import fs from 'node:fs'
3
+ import vm from 'node:vm'
2
4
  import { testStandaloneBuild } from '@cdc/core/helpers/tests/testStandaloneBuild.ts'
3
5
  import { describe, it, expect } from 'vitest'
4
6
 
7
+ const extractMarkedExampleConfig = (content, label) => {
8
+ const match = content.match(
9
+ /<!-- README_EXAMPLE_CONFIG_START -->\s*```jsx\s*([\s\S]*?)\s*```\s*<!-- README_EXAMPLE_CONFIG_END -->/
10
+ )
11
+ expect(match, `${label} should contain a marked README example block`).toBeTruthy()
12
+ const configMatch = match[1].match(/const config = (\{[\s\S]*?\})\n\nfunction App\(\)/)
13
+ expect(configMatch, `${label} should define const config before function App()`).toBeTruthy()
14
+ return vm.runInNewContext(`(${configMatch[1]})`)
15
+ }
16
+
5
17
  describe('Dashboard', () => {
6
18
  it('Can be built in isolation', async () => {
7
19
  const pkgDir = path.join(__dirname, '..')
8
20
  const result = await testStandaloneBuild(pkgDir)
9
21
  expect(result).toBe(true)
10
22
  }, 300000)
23
+
24
+ it('keeps the minimal example in sync with the README docs', () => {
25
+ const pkgRoot = path.join(__dirname, '..', '..')
26
+ const minimalExamplePath = path.join(pkgRoot, 'examples', 'minimal-example.json')
27
+ const readmePath = path.join(pkgRoot, 'README.md')
28
+
29
+ const minimalExample = JSON.parse(fs.readFileSync(minimalExamplePath, 'utf8'))
30
+ const readmeBlock = extractMarkedExampleConfig(fs.readFileSync(readmePath, 'utf8'), 'README.md')
31
+
32
+ expect(readmeBlock).toEqual(minimalExample)
33
+ expect(minimalExample.version).toBeTruthy()
34
+ })
11
35
  })
@@ -0,0 +1,148 @@
1
+ import React from 'react'
2
+ import { render, screen, waitFor } from '@testing-library/react'
3
+ import userEvent from '@testing-library/user-event'
4
+ import { afterEach, describe, expect, it, vi } from 'vitest'
5
+ import CdcDashboard from '../CdcDashboard'
6
+
7
+ vi.mock('resize-observer-polyfill', () => ({
8
+ default: vi.fn(() => ({
9
+ observe: vi.fn(),
10
+ unobserve: vi.fn(),
11
+ disconnect: vi.fn()
12
+ }))
13
+ }))
14
+
15
+ const createDashboardConfig = () => ({
16
+ type: 'dashboard',
17
+ dashboard: {
18
+ theme: 'theme-blue',
19
+ titleStyle: 'small',
20
+ sharedFilters: [
21
+ {
22
+ key: 'Year',
23
+ showDropdown: true,
24
+ values: ['2024', '2025'],
25
+ orderedValues: ['2024', '2025'],
26
+ type: 'datafilter',
27
+ filterStyle: 'dropdown',
28
+ columnName: 'year',
29
+ defaultValue: '2025',
30
+ active: '2025',
31
+ usedBy: ['waffle']
32
+ }
33
+ ]
34
+ },
35
+ rows: [{ columns: [{ width: 12, widget: 'filters' }] }, { columns: [{ width: 12, widget: 'waffle' }] }],
36
+ visualizations: {
37
+ filters: {
38
+ type: 'dashboardFilters',
39
+ visualizationType: 'dashboardFilters',
40
+ filterBehavior: 'Filter Change',
41
+ sharedFilterIndexes: [0],
42
+ uid: 'filters'
43
+ },
44
+ waffle: {
45
+ type: 'waffle-chart',
46
+ uid: 'waffle',
47
+ title: 'Year Waffle',
48
+ showTitle: false,
49
+ visualizationType: 'Waffle',
50
+ visualizationSubType: 'linear',
51
+ showPercent: false,
52
+ showDenominator: false,
53
+ valueDescription: 'out of 100',
54
+ content: 'during {{year}}',
55
+ subtext: '',
56
+ orientation: 'horizontal',
57
+ filters: [],
58
+ fontSize: '',
59
+ overallFontSize: 'medium',
60
+ dataColumn: 'value',
61
+ dataFunction: 'Max',
62
+ dataConditionalColumn: '',
63
+ dataConditionalOperator: '',
64
+ dataConditionalComparate: '',
65
+ invalidComparate: false,
66
+ customDenom: false,
67
+ dataDenom: '100',
68
+ dataDenomColumn: '',
69
+ dataDenomFunction: '',
70
+ suffix: '',
71
+ roundToPlace: '0',
72
+ shape: 'circle',
73
+ nodeWidth: '10',
74
+ nodeSpacer: '2',
75
+ theme: 'theme-blue',
76
+ gauge: {
77
+ height: 35,
78
+ width: '100%'
79
+ },
80
+ visual: {
81
+ border: true,
82
+ accent: false,
83
+ background: false,
84
+ useWrap: false,
85
+ hideBackgroundColor: false,
86
+ borderColorTheme: false,
87
+ colors: {
88
+ 'theme-blue': '#005eaa'
89
+ }
90
+ },
91
+ markupVariables: [
92
+ {
93
+ sourceType: 'column',
94
+ name: 'year',
95
+ tag: '{{year}}',
96
+ columnName: 'year',
97
+ conditions: [],
98
+ addCommas: false,
99
+ hideOnNull: false,
100
+ outputType: 'value'
101
+ }
102
+ ],
103
+ enableMarkupVariables: true,
104
+ filterBehavior: 'Filter Change',
105
+ dataKey: 'waffle-data.json',
106
+ version: '4.26.4-1'
107
+ }
108
+ },
109
+ datasets: {
110
+ 'waffle-data.json': {
111
+ data: [
112
+ { year: '2024', value: 24 },
113
+ { year: '2025', value: 25 }
114
+ ]
115
+ }
116
+ },
117
+ table: {
118
+ show: false
119
+ }
120
+ })
121
+
122
+ afterEach(() => {
123
+ vi.restoreAllMocks()
124
+ vi.unstubAllGlobals()
125
+ })
126
+
127
+ describe('CdcDashboard', () => {
128
+ it('updates waffle chart markup when a dashboard loaded through configUrl changes data filters', async () => {
129
+ const fetchMock = vi.fn().mockResolvedValue({
130
+ json: vi.fn().mockResolvedValue(createDashboardConfig())
131
+ })
132
+ vi.stubGlobal('fetch', fetchMock)
133
+
134
+ render(<CdcDashboard configUrl='/dashboard-with-waffle.json' interactionLabel='dashboard-test' />)
135
+
136
+ await waitFor(() => {
137
+ expect(screen.getByText(/during 2025/)).toBeInTheDocument()
138
+ })
139
+
140
+ await userEvent.selectOptions(screen.getByLabelText('Year'), '2024')
141
+
142
+ await waitFor(() => {
143
+ expect(screen.getByText(/during 2024/)).toBeInTheDocument()
144
+ })
145
+
146
+ expect(screen.queryByText(/during 2025/)).not.toBeInTheDocument()
147
+ })
148
+ })