@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,14 +1,18 @@
1
- import { describe, expect, it, vi } from 'vitest'
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
2
  import { addVisualization } from '../addVisualization'
3
3
 
4
4
  describe('addVisualization', () => {
5
+ afterEach(() => {
6
+ vi.restoreAllMocks()
7
+ })
8
+
5
9
  it('creates chart visual settings with extra theme toggles disabled by default', () => {
6
- vi.spyOn(Date, 'now').mockReturnValue(12345)
10
+ vi.spyOn(Math, 'random').mockReturnValue(0.123456789)
7
11
 
8
12
  const visualization = addVisualization('chart', 'Bar')
9
13
 
10
14
  expect(visualization).toMatchObject({
11
- uid: 'chart12345',
15
+ uid: 'chart-4fzzzxjy',
12
16
  type: 'chart',
13
17
  visualizationType: 'Bar',
14
18
  visual: {
@@ -22,12 +26,12 @@ describe('addVisualization', () => {
22
26
  })
23
27
 
24
28
  it('creates map visual settings with extra theme toggles disabled by default', () => {
25
- vi.spyOn(Date, 'now').mockReturnValue(12345)
29
+ vi.spyOn(Math, 'random').mockReturnValue(0.23456789)
26
30
 
27
31
  const visualization = addVisualization('map', 'single-state')
28
32
 
29
33
  expect(visualization).toMatchObject({
30
- uid: 'map12345',
34
+ uid: 'map-8fzzzbjm',
31
35
  type: 'map',
32
36
  general: {
33
37
  geoType: 'single-state'
@@ -42,11 +46,51 @@ describe('addVisualization', () => {
42
46
  })
43
47
  })
44
48
 
45
- it('preserves visualizationType for data-bite family visualizations', () => {
46
- vi.spyOn(Date, 'now').mockReturnValue(12345)
49
+ it('uses TP5 defaults for new dashboard data bites and waffle charts', () => {
50
+ vi.spyOn(Math, 'random').mockReturnValue(0.123456789)
51
+
52
+ expect(addVisualization('data-bite')).toMatchObject({ biteStyle: 'tp5', visualizationType: 'data-bite' })
53
+ expect(addVisualization('waffle-chart', 'Waffle')).toMatchObject({ visualizationType: 'TP5 Waffle' })
54
+ })
55
+
56
+ it('preserves other visualizationType defaults for related visualizations', () => {
57
+ vi.spyOn(Math, 'random').mockReturnValue(0.123456789)
58
+
59
+ expect(addVisualization('waffle-chart', 'Gauge')).toMatchObject({ visualizationType: 'Gauge' })
60
+ })
61
+
62
+ it('preserves visualizationType for current lightweight visualizations', () => {
63
+ vi.spyOn(Math, 'random').mockReturnValue(0.123456789)
47
64
 
48
65
  expect(addVisualization('data-bite')).toMatchObject({ visualizationType: 'data-bite' })
49
- expect(addVisualization('waffle-chart')).toMatchObject({ visualizationType: 'waffle-chart' })
50
- expect(addVisualization('filtered-text')).toMatchObject({ visualizationType: 'filtered-text' })
66
+ expect(addVisualization('markup-include')).toMatchObject({ visualizationType: 'markup-include' })
67
+ })
68
+
69
+ it('throws when asked to create deprecated filtered-text visualizations', () => {
70
+ expect(() => addVisualization('filtered-text')).toThrow(
71
+ 'Cannot create new filtered-text visualizations. filtered-text is deprecated; use markup-include instead.'
72
+ )
73
+ })
74
+
75
+ it('creates dashboard filters with grey background disabled by default', () => {
76
+ vi.spyOn(Math, 'random').mockReturnValue(0.3456789)
77
+
78
+ expect(addVisualization('dashboardFilters', '')).toMatchObject({
79
+ uid: 'dashboardFilters-cfzzt7g4',
80
+ type: 'dashboardFilters',
81
+ sharedFilterIndexes: [],
82
+ visualizationType: 'dashboardFilters',
83
+ visual: {
84
+ grayBackground: false
85
+ }
86
+ })
87
+ })
88
+
89
+ it('avoids existing visualization ids when caller provides a uniqueness scope', () => {
90
+ vi.spyOn(Math, 'random').mockReturnValueOnce(0.123456789).mockReturnValueOnce(0.23456789)
91
+
92
+ const visualization = addVisualization('chart', 'Bar', { existingIds: ['chart-4fzzzxjy'] })
93
+
94
+ expect(visualization.uid).toBe('chart-8fzzzbjm')
51
95
  })
52
96
  })
@@ -0,0 +1,136 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+ import { cloneDashboardWidget } from '../cloneDashboardWidget'
3
+
4
+ const makeConfig = () =>
5
+ ({
6
+ dashboard: {
7
+ sharedFilters: [
8
+ { key: 'scoped-to-source', usedBy: ['source-widget'], setBy: 'source-widget' },
9
+ { key: 'unknown-target', usedBy: ['legacy-footnote-target'] },
10
+ { key: 'unscoped' },
11
+ { key: 'empty-used-by', usedBy: [] },
12
+ { key: 'row-target', usedBy: [0] }
13
+ ]
14
+ },
15
+ rows: [
16
+ {
17
+ dashboardCondition: { id: 'row-condition', operator: 'hasData', datasetKey: 'row-condition-data' },
18
+ columns: [
19
+ {
20
+ width: 4,
21
+ conditionalWidgets: [
22
+ {
23
+ widget: 'source-widget',
24
+ dashboardCondition: {
25
+ id: 'source-condition',
26
+ operator: 'columnHasAnyValue',
27
+ datasetKey: 'condition-data',
28
+ columnName: 'state',
29
+ values: ['CA']
30
+ }
31
+ }
32
+ ]
33
+ },
34
+ { width: 4 },
35
+ {
36
+ width: 4,
37
+ conditionalWidgets: [
38
+ {
39
+ widget: 'existing-widget',
40
+ dashboardCondition: { id: 'existing-condition', operator: 'hasData', datasetKey: 'condition-data' }
41
+ }
42
+ ]
43
+ }
44
+ ],
45
+ expandCollapseAllButtons: false
46
+ }
47
+ ],
48
+ visualizations: {
49
+ 'source-widget': {
50
+ uid: 'source-widget',
51
+ type: 'markup-include',
52
+ visualizationType: 'markup-include',
53
+ contentEditor: { title: 'Source' }
54
+ },
55
+ 'existing-widget': {
56
+ uid: 'existing-widget',
57
+ type: 'markup-include',
58
+ visualizationType: 'markup-include',
59
+ contentEditor: { title: 'Existing' }
60
+ }
61
+ }
62
+ } as any)
63
+
64
+ describe('cloneDashboardWidget', () => {
65
+ afterEach(() => {
66
+ vi.restoreAllMocks()
67
+ })
68
+
69
+ it('clones a simple component into an empty simple column with a fresh key and uid', () => {
70
+ vi.spyOn(Math, 'random').mockReturnValue(0.123456789)
71
+ const config = makeConfig()
72
+ delete config.rows[0].columns[0].conditionalWidgets[0].dashboardCondition
73
+
74
+ const result = cloneDashboardWidget(config, 'source-widget', { rowIdx: 0, colIdx: 1 })
75
+ const clonedWidgetKey = result.rows[0].columns[1].widget
76
+
77
+ expect(clonedWidgetKey).toBeTruthy()
78
+ expect(clonedWidgetKey).toMatch(/^markup-include-[a-z0-9]{8}$/)
79
+ expect(clonedWidgetKey).not.toContain('copy')
80
+ expect(result.visualizations[clonedWidgetKey].uid).toBe(clonedWidgetKey)
81
+ expect(result.visualizations[clonedWidgetKey].contentEditor.title).toBe('Source')
82
+ expect(result.rows[0].columns[0].conditionalWidgets[0].widget).toBe('source-widget')
83
+ expect(config.rows[0].columns[1].widget).toBeUndefined()
84
+ })
85
+
86
+ it('clones a component into an empty conditional slot and copies its component condition with a fresh id', () => {
87
+ vi.spyOn(Math, 'random').mockReturnValueOnce(0.123456789).mockReturnValueOnce(0.23456789)
88
+ const config = makeConfig()
89
+
90
+ const result = cloneDashboardWidget(config, 'source-widget', { rowIdx: 0, colIdx: 2, entryIdx: 1 })
91
+ const clonedEntry = result.rows[0].columns[2].conditionalWidgets[1]
92
+
93
+ expect(clonedEntry.widget).toBeTruthy()
94
+ expect(clonedEntry.widget).toMatch(/^markup-include-[a-z0-9]{8}$/)
95
+ expect(clonedEntry.widget).not.toContain('copy')
96
+ expect(clonedEntry.dashboardCondition).toMatchObject({
97
+ operator: 'columnHasAnyValue',
98
+ datasetKey: 'condition-data',
99
+ columnName: 'state',
100
+ values: ['CA']
101
+ })
102
+ expect(clonedEntry.dashboardCondition.id).toMatch(/^condition-[a-z0-9]{8}$/)
103
+ expect(clonedEntry.dashboardCondition.id).not.toBe('source-condition')
104
+ expect(clonedEntry.dashboardCondition.id).not.toBe('row-condition')
105
+ })
106
+
107
+ it('avoids existing visualization keys when cloning', () => {
108
+ vi.spyOn(Math, 'random').mockReturnValueOnce(0.123456789).mockReturnValueOnce(0.23456789)
109
+ const config = makeConfig()
110
+ delete config.rows[0].columns[0].conditionalWidgets[0].dashboardCondition
111
+ config.visualizations['markup-include-4fzzzxjy'] = {
112
+ uid: 'markup-include-4fzzzxjy',
113
+ type: 'markup-include',
114
+ visualizationType: 'markup-include'
115
+ }
116
+
117
+ const result = cloneDashboardWidget(config, 'source-widget', { rowIdx: 0, colIdx: 1 })
118
+
119
+ expect(result.rows[0].columns[1].widget).toBe('markup-include-8fzzzbjm')
120
+ })
121
+
122
+ it('clones widget-scoped shared filter targets while leaving unknown and unscoped filters unchanged', () => {
123
+ vi.spyOn(Math, 'random').mockReturnValue(0.123456789)
124
+ const config = makeConfig()
125
+
126
+ const result = cloneDashboardWidget(config, 'source-widget', { rowIdx: 0, colIdx: 2, entryIdx: 1 })
127
+ const clonedEntry = result.rows[0].columns[2].conditionalWidgets[1]
128
+
129
+ expect(result.dashboard.sharedFilters[0].usedBy).toEqual(['source-widget', clonedEntry.widget])
130
+ expect(result.dashboard.sharedFilters[0].setBy).toBe('source-widget')
131
+ expect(result.dashboard.sharedFilters[1].usedBy).toEqual(['legacy-footnote-target'])
132
+ expect(result.dashboard.sharedFilters[2].usedBy).toBeUndefined()
133
+ expect(result.dashboard.sharedFilters[3].usedBy).toEqual([])
134
+ expect(result.dashboard.sharedFilters[4].usedBy).toEqual([0])
135
+ })
136
+ })
@@ -0,0 +1,99 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import {
3
+ getColumnWidgetEntries,
4
+ hasAuthoredWidgetEntries,
5
+ normalizeConditionalColumn,
6
+ resolveColumnWidgetEntry
7
+ } from '../dashboardColumnWidgets'
8
+
9
+ describe('dashboardColumnWidgets', () => {
10
+ it('resolves simple columns through column.widget', () => {
11
+ expect(
12
+ resolveColumnWidgetEntry({
13
+ width: 12,
14
+ widget: 'viz-1'
15
+ } as any)
16
+ ).toMatchObject({ widget: 'viz-1', matches: true })
17
+ })
18
+
19
+ it('resolves the first matching conditional widget in author order', () => {
20
+ const evaluateCondition = vi
21
+ .fn()
22
+ .mockImplementation(condition => ({ matches: condition?.id === 'condition-2', resolved: true }))
23
+
24
+ const result = resolveColumnWidgetEntry(
25
+ {
26
+ width: 12,
27
+ conditionalWidgets: [
28
+ { widget: 'viz-1', dashboardCondition: { id: 'condition-1', operator: 'hasData' } },
29
+ { widget: 'viz-2', dashboardCondition: { id: 'condition-2', operator: 'hasData' } },
30
+ { widget: 'viz-3', dashboardCondition: { id: 'condition-3', operator: 'hasData' } }
31
+ ]
32
+ } as any,
33
+ { evaluateCondition }
34
+ )
35
+
36
+ expect(result).toMatchObject({ widget: 'viz-2', matches: true })
37
+ expect(evaluateCondition).toHaveBeenCalledTimes(2)
38
+ })
39
+
40
+ it('returns an empty resolution when no conditional widget matches', () => {
41
+ const result = resolveColumnWidgetEntry(
42
+ {
43
+ width: 12,
44
+ conditionalWidgets: [
45
+ { widget: 'viz-1', dashboardCondition: { id: 'condition-1', operator: 'hasData' } },
46
+ { widget: 'viz-2', dashboardCondition: { id: 'condition-2', operator: 'hasData' } }
47
+ ]
48
+ } as any,
49
+ {
50
+ evaluateCondition: () => ({ matches: false, resolved: true })
51
+ }
52
+ )
53
+
54
+ expect(result).toEqual({ widget: undefined, matches: false, resolved: true })
55
+ })
56
+
57
+ it('collapses a one-entry conditional column back to simple mode when the condition is removed', () => {
58
+ expect(
59
+ normalizeConditionalColumn({
60
+ width: 12,
61
+ widget: undefined,
62
+ conditionalWidgets: [{ widget: 'viz-1' }]
63
+ } as any)
64
+ ).toMatchObject({
65
+ width: 12,
66
+ widget: 'viz-1',
67
+ conditionalWidgets: undefined
68
+ })
69
+ })
70
+
71
+ it('keeps one-entry conditional columns in conditional mode when the remaining entry still has a condition', () => {
72
+ expect(
73
+ normalizeConditionalColumn({
74
+ width: 12,
75
+ conditionalWidgets: [{ widget: 'viz-1', dashboardCondition: { id: 'condition-1', operator: 'hasData' } }]
76
+ } as any)
77
+ ).toMatchObject({
78
+ width: 12,
79
+ widget: undefined,
80
+ conditionalWidgets: [{ widget: 'viz-1', dashboardCondition: { id: 'condition-1', operator: 'hasData' } }]
81
+ })
82
+ })
83
+
84
+ it('ignores column.widget when conditional widgets are present', () => {
85
+ expect(
86
+ getColumnWidgetEntries({
87
+ width: 12,
88
+ widget: 'simple-viz',
89
+ conditionalWidgets: [{ widget: 'conditional-viz', dashboardCondition: { id: 'condition-1' } }]
90
+ } as any)
91
+ ).toEqual([{ widget: 'conditional-viz', dashboardCondition: { id: 'condition-1' } }])
92
+ })
93
+
94
+ it('reports whether a column has authored widget entries', () => {
95
+ expect(hasAuthoredWidgetEntries({ width: 12, widget: 'viz-1' } as any)).toBe(true)
96
+ expect(hasAuthoredWidgetEntries({ width: 12, conditionalWidgets: [{ widget: 'viz-2' }] } as any)).toBe(true)
97
+ expect(hasAuthoredWidgetEntries({ width: 12 } as any)).toBe(false)
98
+ })
99
+ })
@@ -0,0 +1,41 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import {
3
+ DASHBOARD_CONDITION_TYPE_LABELS,
4
+ getColumnHasAnyValueSummaryParts,
5
+ getDashboardConditionSummary
6
+ } from '../dashboardConditionUi'
7
+
8
+ describe('dashboardConditionUi', () => {
9
+ it('uses shared dropdown labels for simple condition summaries', () => {
10
+ expect(getDashboardConditionSummary({ operator: 'hasData' })).toBe(DASHBOARD_CONDITION_TYPE_LABELS.hasData)
11
+ expect(getDashboardConditionSummary({ operator: 'hasNoData' })).toBe(DASHBOARD_CONDITION_TYPE_LABELS.hasNoData)
12
+ expect(getDashboardConditionSummary({ operator: 'filtersIncomplete' })).toBe(
13
+ DASHBOARD_CONDITION_TYPE_LABELS.filtersIncomplete
14
+ )
15
+ })
16
+
17
+ it('summarizes columnHasAnyValue with only the inspected column name', () => {
18
+ expect(
19
+ getDashboardConditionSummary({
20
+ operator: 'columnHasAnyValue',
21
+ datasetKey: 'condition-data',
22
+ columnName: 'state',
23
+ values: ['Adams', 'Brown']
24
+ })
25
+ ).toBe('Show based on the value in the state column')
26
+ })
27
+
28
+ it('shares columnHasAnyValue summary parts for rich text rendering', () => {
29
+ expect(getColumnHasAnyValueSummaryParts('state')).toEqual({
30
+ prefix: 'Show based on the value in the ',
31
+ columnName: 'state',
32
+ suffix: ' column'
33
+ })
34
+ })
35
+
36
+ it('falls back safely for incomplete or unknown condition details', () => {
37
+ expect(getDashboardConditionSummary({ operator: 'columnHasAnyValue' })).toBe('Show when column has a value')
38
+ expect(getDashboardConditionSummary({ operator: 'unknownOperator' } as any)).toBe('Dashboard condition configured')
39
+ expect(getDashboardConditionSummary()).toBe('Dashboard condition configured')
40
+ })
41
+ })