@cdc/chart 4.26.5 → 4.26.6

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 (91) hide show
  1. package/CONFIG.md +12 -8
  2. package/dist/{cdcchart-DQ00cQCm.es.js → cdcchart-BWVIUcAF.es.js} +5 -55
  3. package/dist/cdcchart.js +34093 -32767
  4. package/examples/feature/heatmap/average-age-categorical.json +1 -1
  5. package/examples/feature/heatmap/calendar-heatmap.json +1 -1
  6. package/examples/feature/heatmap/sparse-aggregation.json +1 -1
  7. package/examples/private/bad-y-axis.json +543 -0
  8. package/examples/private/bar-chart-negative-value.json +455 -0
  9. package/package.json +3 -3
  10. package/src/CdcChart.tsx +5 -4
  11. package/src/CdcChartComponent.tsx +32 -9
  12. package/src/_stories/Chart.SeriesColorAssignments.stories.tsx +183 -0
  13. package/src/_stories/Chart.YAxisAutoMaxStrategy.stories.tsx +52 -0
  14. package/src/_stories/Chart.YAxisFilterDomain.stories.tsx +90 -0
  15. package/src/_stories/ChartAnnotation.smoke.stories.tsx +10 -0
  16. package/src/_stories/_mock/annotation_event-line_mock.json +1064 -0
  17. package/src/_stories/_mock/heatmap-average-age-categorical.json +1 -1
  18. package/src/_stories/_mock/heatmap.json +1 -1
  19. package/src/components/Annotations/components/AnnotationDraggable.styles.css +22 -1
  20. package/src/components/Annotations/components/AnnotationDraggable.tsx +219 -119
  21. package/src/components/Annotations/tests/AnnotationDraggable.test.tsx +198 -0
  22. package/src/components/Axis/LeftAxis.tsx +3 -0
  23. package/src/components/Axis/LeftAxisGridlines.tsx +8 -1
  24. package/src/components/Axis/RightAxis.tsx +3 -0
  25. package/src/components/Axis/tests/AxisTickValues.test.tsx +86 -0
  26. package/src/components/EditorPanel/EditorPanel.tsx +367 -136
  27. package/src/components/EditorPanel/components/Panels/Panel.Annotate.tsx +101 -52
  28. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.test.tsx +98 -0
  29. package/src/components/EditorPanel/components/Panels/Panel.SmallMultiples.tsx +10 -0
  30. package/src/components/EditorPanel/components/Panels/Panel.Visual.test.tsx +484 -0
  31. package/src/components/EditorPanel/components/Panels/Panel.Visual.tsx +3 -0
  32. package/src/components/EditorPanel/components/Panels/SeriesColorAssignments.tsx +262 -0
  33. package/src/components/EditorPanel/components/Panels/panelVisual.styles.css +32 -0
  34. package/src/components/EditorPanel/components/panels.scss +10 -0
  35. package/src/components/EditorPanel/editor-panel.scss +47 -0
  36. package/src/components/EditorPanel/helpers/getVisualizationTypeConfigUpdate.ts +6 -2
  37. package/src/components/EditorPanel/helpers/tests/getVisualizationTypeConfigUpdate.test.ts +20 -2
  38. package/src/components/EditorPanel/useEditorPermissions.test.tsx +256 -11
  39. package/src/components/EditorPanel/useEditorPermissions.ts +41 -5
  40. package/src/components/HeatMap/components/HeatMap.tsx +22 -17
  41. package/src/components/HeatMap/components/HeatMapGradientLegend.tsx +29 -15
  42. package/src/components/HeatMap/heatmap.css +15 -1
  43. package/src/components/HeatMap/tests/HeatMap.test.tsx +39 -7
  44. package/src/components/Legend/Legend.Component.tsx +2 -2
  45. package/src/components/Legend/helpers/createFormatLabels.test.ts +88 -0
  46. package/src/components/Legend/helpers/createFormatLabels.tsx +6 -1
  47. package/src/components/LineChart/helpers.test.ts +230 -1
  48. package/src/components/LineChart/helpers.ts +26 -13
  49. package/src/components/LineChart/index.tsx +21 -4
  50. package/src/components/LinearChart/tests/LinearChart.test.tsx +30 -0
  51. package/src/components/LinearChart.tsx +50 -15
  52. package/src/components/ScatterPlot/ScatterPlot.jsx +3 -3
  53. package/src/components/SmallMultiples/SmallMultipleTile.tsx +3 -0
  54. package/src/components/SmallMultiples/SmallMultiples.tsx +38 -9
  55. package/src/components/SmallMultiples/tests/SmallMultiples.test.tsx +170 -0
  56. package/src/data/initial-state.js +1 -0
  57. package/src/helpers/calculateLeftYAxisWidth.ts +9 -3
  58. package/src/helpers/colorAssignmentHelpers.ts +102 -0
  59. package/src/helpers/dataHelpers.ts +11 -1
  60. package/src/helpers/getAxisMaxOverride.ts +16 -0
  61. package/src/helpers/getCleanTopTickMax.ts +64 -0
  62. package/src/helpers/getColorScale.ts +18 -9
  63. package/src/helpers/getHasBoundarySuppression.ts +26 -0
  64. package/src/helpers/getMinMax.ts +24 -22
  65. package/src/helpers/getYAxisDomainData.ts +38 -0
  66. package/src/helpers/getYAxisFinalizationEligibility.ts +40 -0
  67. package/src/helpers/hasSpacedInlineLabel.ts +10 -0
  68. package/src/helpers/seriesColumnSettings.ts +5 -4
  69. package/src/helpers/smallMultiplesHelpers.ts +5 -0
  70. package/src/helpers/tests/calculateLeftYAxisWidth.test.ts +38 -0
  71. package/src/helpers/tests/dataHelpers.test.ts +135 -0
  72. package/src/helpers/tests/getAxisMaxOverride.test.ts +36 -0
  73. package/src/helpers/tests/getCleanTopTickMax.test.ts +165 -0
  74. package/src/helpers/tests/getColorScale.test.ts +197 -0
  75. package/src/helpers/tests/getMinMax.test.ts +211 -0
  76. package/src/helpers/tests/getYAxisDomainData.test.ts +119 -0
  77. package/src/helpers/tests/getYAxisFinalizationEligibility.test.ts +185 -0
  78. package/src/helpers/tests/{getYAxisAutoPaddingMode.test.ts → hasSpacedInlineLabel.test.ts} +17 -32
  79. package/src/helpers/tests/seriesColumnSettings.test.ts +16 -2
  80. package/src/helpers/tests/smallMultiplesHelpers.test.ts +99 -0
  81. package/src/hooks/tests/useRightAxis.test.tsx +127 -0
  82. package/src/hooks/tests/useScales.test.tsx +307 -29
  83. package/src/hooks/useRightAxis.ts +30 -13
  84. package/src/hooks/useScales.ts +45 -28
  85. package/src/hooks/useTooltip.tsx +3 -1
  86. package/src/test/CdcChart.dataTable.test.tsx +122 -0
  87. package/src/types/ChartConfig.ts +6 -1
  88. package/src/types/ChartContext.ts +3 -0
  89. package/src/helpers/getYAxisAutoPadding.ts +0 -55
  90. package/src/helpers/getYAxisAutoPaddingMode.ts +0 -17
  91. package/src/helpers/tests/getYAxisAutoPadding.test.ts +0 -37
@@ -0,0 +1,211 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import getMinMax from '../getMinMax'
3
+ import { createMockConfig } from '../../components/LinearChart/tests/mockConfigContext'
4
+ import { ChartConfig } from '../../types/ChartConfig'
5
+
6
+ const leftSeries = { dataKey: 'Cases', type: 'Line', axis: 'Left', tooltip: true }
7
+ const rightSeries = { dataKey: 'Rate', type: 'Line', axis: 'Right', tooltip: true }
8
+
9
+ const createConfig = (overrides: Partial<ChartConfig> = {}) =>
10
+ createMockConfig({
11
+ visualizationType: 'Line',
12
+ visualizationSubType: 'regular',
13
+ yAxis: {
14
+ ...createMockConfig().yAxis,
15
+ type: 'linear',
16
+ enablePadding: false,
17
+ scalePadding: 0,
18
+ min: '',
19
+ max: '',
20
+ autoMaxStrategy: 'clean-top-tick'
21
+ },
22
+ runtime: {
23
+ ...createMockConfig().runtime,
24
+ yAxis: {
25
+ ...createMockConfig().runtime.yAxis,
26
+ min: '',
27
+ max: ''
28
+ },
29
+ series: [leftSeries],
30
+ seriesKeys: ['Cases']
31
+ } as any,
32
+ series: [leftSeries],
33
+ ...overrides
34
+ })
35
+
36
+ const getResult = (config: ChartConfig, maxValue = 25) =>
37
+ getMinMax({
38
+ config,
39
+ minValue: 0,
40
+ maxValue,
41
+ existPositiveValue: maxValue > 0,
42
+ data: [{ Cases: maxValue }],
43
+ tableData: [{ Cases: maxValue }],
44
+ isAllLine: true
45
+ })
46
+
47
+ describe('getMinMax automatic max strategy', () => {
48
+ it('uses clean-top-tick for automatic left-axis max when enabled', () => {
49
+ expect(getResult(createConfig(), 25).max).toBe(25)
50
+ expect(getResult(createConfig(), 101).max).toBe(120)
51
+ expect(getResult(createConfig(), 1434).max).toBe(1500)
52
+ })
53
+
54
+ it('uses the raw automatic max when strategy is omitted or default', () => {
55
+ expect(getResult(createConfig({ yAxis: { ...createConfig().yAxis, autoMaxStrategy: undefined } }), 101).max).toBe(
56
+ 101
57
+ )
58
+ expect(getResult(createConfig({ yAxis: { ...createConfig().yAxis, autoMaxStrategy: 'default' } }), 101).max).toBe(
59
+ 101
60
+ )
61
+ })
62
+
63
+ it('bypasses clean-top-tick when the left-axis max is explicit', () => {
64
+ const config = createConfig({
65
+ yAxis: {
66
+ ...createConfig().yAxis,
67
+ max: '100'
68
+ },
69
+ runtime: {
70
+ ...createConfig().runtime,
71
+ yAxis: {
72
+ ...createConfig().runtime.yAxis,
73
+ min: '',
74
+ max: '100'
75
+ }
76
+ } as any
77
+ })
78
+
79
+ expect(getResult(config, 25).max).toBe(100)
80
+ })
81
+
82
+ it('uses clean-top-tick when the entered left-axis max is invalid', () => {
83
+ const createConfigWithRuntimeMax = (max: string) =>
84
+ createConfig({
85
+ yAxis: {
86
+ ...createConfig().yAxis,
87
+ max
88
+ },
89
+ runtime: {
90
+ ...createConfig().runtime,
91
+ yAxis: {
92
+ ...createConfig().runtime.yAxis,
93
+ min: '',
94
+ max
95
+ }
96
+ } as any
97
+ })
98
+
99
+ expect(getResult(createConfigWithRuntimeMax('50'), 101).max).toBe(120)
100
+ expect(getResult(createConfigWithRuntimeMax('not-a-number'), 101).max).toBe(120)
101
+ })
102
+
103
+ it('applies smallestLeftAxisMax as a final author-provided floor without cleaning it', () => {
104
+ const config = createConfig({
105
+ yAxis: {
106
+ ...createConfig().yAxis,
107
+ autoMaxStrategy: 'clean-top-tick',
108
+ smallestLeftAxisMax: 12
109
+ }
110
+ })
111
+
112
+ expect(getResult(config, 3).max).toBe(12)
113
+ })
114
+
115
+ it('allows axis padding to increase the domain after the automatic max strategy', () => {
116
+ const config = createConfig({
117
+ yAxis: {
118
+ ...createConfig().yAxis,
119
+ autoMaxStrategy: 'clean-top-tick',
120
+ enablePadding: true,
121
+ scalePadding: 10
122
+ }
123
+ })
124
+
125
+ expect(getResult(config, 25).max).toBeCloseTo(27.5)
126
+ })
127
+
128
+ it('uses clean-top-tick for combo left-axis max while leaving right-axis strategy to useRightAxis', () => {
129
+ const config = createConfig({
130
+ visualizationType: 'Combo',
131
+ series: [leftSeries, rightSeries],
132
+ runtime: {
133
+ ...createConfig().runtime,
134
+ yAxis: {
135
+ ...createConfig().runtime.yAxis,
136
+ min: '',
137
+ max: ''
138
+ },
139
+ series: [leftSeries, rightSeries],
140
+ seriesKeys: ['Cases', 'Rate']
141
+ } as any
142
+ })
143
+
144
+ const result = getMinMax({
145
+ config,
146
+ minValue: 0,
147
+ maxValue: 101,
148
+ existPositiveValue: true,
149
+ data: [{ Cases: 101, Rate: 89 }],
150
+ tableData: [{ Cases: 101, Rate: 89 }],
151
+ isAllLine: false
152
+ })
153
+
154
+ expect(result.leftMax).toBe(120)
155
+ expect(result.rightMax).toBe(89)
156
+ })
157
+
158
+ it('forces a zero min for line charts when boundary suppression is present', () => {
159
+ const config = createConfig({
160
+ visualizationType: 'Line',
161
+ runtime: {
162
+ ...createConfig().runtime,
163
+ yAxis: {
164
+ ...createConfig().runtime.yAxis,
165
+ min: '',
166
+ max: ''
167
+ }
168
+ } as any
169
+ })
170
+
171
+ const result = getMinMax({
172
+ config,
173
+ minValue: 70,
174
+ maxValue: 120,
175
+ existPositiveValue: true,
176
+ data: [{ Cases: 70 }, { Cases: 120 }],
177
+ tableData: [{ Cases: 70 }, { Cases: 120 }],
178
+ isAllLine: true,
179
+ hasBoundarySuppression: true
180
+ })
181
+
182
+ expect(result.min).toBe(0)
183
+ })
184
+
185
+ it('keeps data min for line charts when boundary suppression is not present', () => {
186
+ const config = createConfig({
187
+ visualizationType: 'Line',
188
+ runtime: {
189
+ ...createConfig().runtime,
190
+ yAxis: {
191
+ ...createConfig().runtime.yAxis,
192
+ min: '',
193
+ max: ''
194
+ }
195
+ } as any
196
+ })
197
+
198
+ const result = getMinMax({
199
+ config,
200
+ minValue: 70,
201
+ maxValue: 120,
202
+ existPositiveValue: true,
203
+ data: [{ Cases: 70 }, { Cases: 120 }],
204
+ tableData: [{ Cases: 70 }, { Cases: 120 }],
205
+ isAllLine: true,
206
+ hasBoundarySuppression: false
207
+ })
208
+
209
+ expect(result.min).toBe(70)
210
+ })
211
+ })
@@ -0,0 +1,119 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getYAxisDomainData, getYAxisFilterDomainBehavior } from '../getYAxisDomainData'
3
+ import { createMockConfig } from '../../components/LinearChart/tests/mockConfigContext'
4
+
5
+ describe('getYAxisDomainData', () => {
6
+ const filteredRows = [{ value: 3 }]
7
+ const tableRows = [{ value: 3 }, { value: 9 }]
8
+ const postExclusionRows = [{ value: 3 }, { value: 12 }]
9
+
10
+ const buildConfig = ({
11
+ brushActive = false,
12
+ brushDynamicYAxis = false,
13
+ filterDomainBehavior
14
+ }: {
15
+ brushActive?: boolean
16
+ brushDynamicYAxis?: boolean
17
+ filterDomainBehavior?: 'dynamic' | 'stable'
18
+ }) => {
19
+ const baseConfig = createMockConfig()
20
+ return createMockConfig({
21
+ xAxis: {
22
+ ...baseConfig.xAxis,
23
+ brushActive,
24
+ brushDynamicYAxis
25
+ },
26
+ yAxis: {
27
+ ...baseConfig.yAxis,
28
+ filterDomainBehavior
29
+ }
30
+ })
31
+ }
32
+
33
+ it('defaults omitted filterDomainBehavior to dynamic', () => {
34
+ const config = createMockConfig()
35
+
36
+ expect(getYAxisFilterDomainBehavior(config)).toBe('dynamic')
37
+ })
38
+
39
+ it.each([
40
+ {
41
+ name: 'brush off with dynamic filter domains',
42
+ configOptions: { brushActive: false, filterDomainBehavior: 'dynamic' as const },
43
+ expectedRows: filteredRows
44
+ },
45
+ {
46
+ name: 'brush off with stable filter domains',
47
+ configOptions: { brushActive: false, filterDomainBehavior: 'stable' as const },
48
+ expectedRows: postExclusionRows
49
+ },
50
+ {
51
+ name: 'dynamic brush Y-axis with dynamic filter domains',
52
+ configOptions: { brushActive: true, brushDynamicYAxis: true, filterDomainBehavior: 'dynamic' as const },
53
+ expectedRows: filteredRows
54
+ },
55
+ {
56
+ name: 'dynamic brush Y-axis with stable filter domains',
57
+ configOptions: { brushActive: true, brushDynamicYAxis: true, filterDomainBehavior: 'stable' as const },
58
+ expectedRows: filteredRows
59
+ },
60
+ {
61
+ name: 'non-dynamic brush Y-axis with dynamic filter domains',
62
+ configOptions: { brushActive: true, brushDynamicYAxis: false, filterDomainBehavior: 'dynamic' as const },
63
+ expectedRows: tableRows
64
+ },
65
+ {
66
+ name: 'non-dynamic brush Y-axis with stable filter domains',
67
+ configOptions: { brushActive: true, brushDynamicYAxis: false, filterDomainBehavior: 'stable' as const },
68
+ expectedRows: postExclusionRows
69
+ }
70
+ ])('selects the expected domain data for $name', ({ configOptions, expectedRows }) => {
71
+ expect(
72
+ getYAxisDomainData({
73
+ config: buildConfig(configOptions),
74
+ data: filteredRows,
75
+ tableData: tableRows,
76
+ fullEligibleDomainData: postExclusionRows
77
+ })
78
+ ).toBe(expectedRows)
79
+ })
80
+
81
+ it('falls back to rendered data for non-dynamic brush domains when table data is unavailable', () => {
82
+ expect(
83
+ getYAxisDomainData({
84
+ config: buildConfig({
85
+ brushActive: true,
86
+ brushDynamicYAxis: false,
87
+ filterDomainBehavior: 'dynamic'
88
+ }),
89
+ data: filteredRows
90
+ })
91
+ ).toBe(filteredRows)
92
+ })
93
+
94
+ it('falls back to rendered data for stable filter domains when full eligible data is unavailable', () => {
95
+ expect(
96
+ getYAxisDomainData({
97
+ config: buildConfig({
98
+ filterDomainBehavior: 'stable'
99
+ }),
100
+ data: filteredRows,
101
+ tableData: tableRows
102
+ })
103
+ ).toBe(filteredRows)
104
+ })
105
+
106
+ it('falls back to table data for non-dynamic brush stable domains when full eligible data is unavailable', () => {
107
+ expect(
108
+ getYAxisDomainData({
109
+ config: buildConfig({
110
+ brushActive: true,
111
+ brushDynamicYAxis: false,
112
+ filterDomainBehavior: 'stable'
113
+ }),
114
+ data: filteredRows,
115
+ tableData: tableRows
116
+ })
117
+ ).toBe(tableRows)
118
+ })
119
+ })
@@ -0,0 +1,185 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { createMockConfig } from '../../components/LinearChart/tests/mockConfigContext'
3
+ import { getYAxisFinalizationEligibility } from '../getYAxisFinalizationEligibility'
4
+
5
+ const getEligibility = (overrides = {}) =>
6
+ getYAxisFinalizationEligibility({
7
+ config: createMockConfig(overrides),
8
+ hasSpacedInlineLabel: false,
9
+ hasValidExplicitLeftMax: false,
10
+ isHorizontal: false
11
+ })
12
+
13
+ describe('getYAxisFinalizationEligibility', () => {
14
+ it('finalizes automatic clean-top-tick linear Y axes', () => {
15
+ const result = getEligibility({
16
+ yAxis: {
17
+ ...createMockConfig().yAxis,
18
+ type: 'linear',
19
+ autoMaxStrategy: 'clean-top-tick'
20
+ }
21
+ })
22
+
23
+ expect(result.shouldUseCleanTopTick).toBe(true)
24
+ expect(result.shouldFinalizePrimaryYAxis).toBe(true)
25
+ expect(result.shouldUseInlineLabelHeadroom).toBe(false)
26
+ })
27
+
28
+ it('uses spaced inline labels as headroom for standard non-Combo Y axes', () => {
29
+ const result = getYAxisFinalizationEligibility({
30
+ config: createMockConfig({
31
+ yAxis: {
32
+ ...createMockConfig().yAxis,
33
+ type: 'linear',
34
+ autoMaxStrategy: 'default',
35
+ inlineLabel: ' percent'
36
+ }
37
+ }),
38
+ hasSpacedInlineLabel: true,
39
+ hasValidExplicitLeftMax: false,
40
+ isHorizontal: false
41
+ })
42
+
43
+ expect(result.shouldUseInlineLabelHeadroom).toBe(true)
44
+ expect(result.shouldUseCleanTopTick).toBe(true)
45
+ expect(result.shouldFinalizePrimaryYAxis).toBe(true)
46
+ })
47
+
48
+ it('does not use spaced inline labels as headroom for Combo charts', () => {
49
+ const result = getYAxisFinalizationEligibility({
50
+ config: createMockConfig({
51
+ visualizationType: 'Combo',
52
+ yAxis: {
53
+ ...createMockConfig().yAxis,
54
+ type: 'linear',
55
+ autoMaxStrategy: 'default',
56
+ inlineLabel: ' percent'
57
+ }
58
+ }),
59
+ hasSpacedInlineLabel: true,
60
+ hasValidExplicitLeftMax: false,
61
+ isHorizontal: false
62
+ })
63
+
64
+ expect(result.supportsInlineLabelHeadroom).toBe(false)
65
+ expect(result.shouldUseInlineLabelHeadroom).toBe(false)
66
+ expect(result.shouldFinalizePrimaryYAxis).toBe(false)
67
+ })
68
+
69
+ it('still finalizes Combo charts when clean-top-tick is explicitly enabled', () => {
70
+ const result = getEligibility({
71
+ visualizationType: 'Combo',
72
+ yAxis: {
73
+ ...createMockConfig().yAxis,
74
+ type: 'linear',
75
+ autoMaxStrategy: 'clean-top-tick'
76
+ }
77
+ })
78
+
79
+ expect(result.shouldUseCleanTopTick).toBe(true)
80
+ expect(result.shouldFinalizePrimaryYAxis).toBe(true)
81
+ expect(result.shouldUseInlineLabelHeadroom).toBe(false)
82
+ })
83
+
84
+ it('treats a valid explicit left max as a full finalization bypass', () => {
85
+ const result = getYAxisFinalizationEligibility({
86
+ config: createMockConfig({
87
+ yAxis: {
88
+ ...createMockConfig().yAxis,
89
+ type: 'linear',
90
+ autoMaxStrategy: 'clean-top-tick',
91
+ inlineLabel: ' percent'
92
+ }
93
+ }),
94
+ hasSpacedInlineLabel: true,
95
+ hasValidExplicitLeftMax: true,
96
+ isHorizontal: false
97
+ })
98
+
99
+ expect(result.shouldUseCleanTopTick).toBe(false)
100
+ expect(result.shouldFinalizePrimaryYAxis).toBe(false)
101
+ })
102
+
103
+ it('does not finalize horizontal, categorical, logarithmic, or special chart Y axes', () => {
104
+ expect(
105
+ getYAxisFinalizationEligibility({
106
+ config: createMockConfig({
107
+ yAxis: {
108
+ ...createMockConfig().yAxis,
109
+ type: 'linear',
110
+ autoMaxStrategy: 'clean-top-tick'
111
+ }
112
+ }),
113
+ hasSpacedInlineLabel: false,
114
+ hasValidExplicitLeftMax: false,
115
+ isHorizontal: true
116
+ }).shouldFinalizePrimaryYAxis
117
+ ).toBe(false)
118
+
119
+ expect(
120
+ getEligibility({
121
+ yAxis: {
122
+ ...createMockConfig().yAxis,
123
+ type: 'categorical',
124
+ autoMaxStrategy: 'clean-top-tick'
125
+ }
126
+ }).shouldFinalizePrimaryYAxis
127
+ ).toBe(false)
128
+
129
+ expect(
130
+ getEligibility({
131
+ yAxis: {
132
+ ...createMockConfig().yAxis,
133
+ type: 'logarithmic',
134
+ autoMaxStrategy: 'clean-top-tick'
135
+ }
136
+ }).shouldFinalizePrimaryYAxis
137
+ ).toBe(false)
138
+
139
+ expect(
140
+ getEligibility({
141
+ visualizationType: 'Box Plot',
142
+ yAxis: {
143
+ ...createMockConfig().yAxis,
144
+ type: 'linear',
145
+ autoMaxStrategy: 'clean-top-tick'
146
+ }
147
+ }).shouldFinalizePrimaryYAxis
148
+ ).toBe(false)
149
+
150
+ expect(
151
+ getEligibility({
152
+ visualizationType: 'Bump Chart',
153
+ yAxis: {
154
+ ...createMockConfig().yAxis,
155
+ type: 'linear',
156
+ autoMaxStrategy: 'clean-top-tick'
157
+ }
158
+ }).shouldFinalizePrimaryYAxis
159
+ ).toBe(false)
160
+
161
+ expect(
162
+ getEligibility({
163
+ visualizationType: 'Forest Plot',
164
+ yAxis: {
165
+ ...createMockConfig().yAxis,
166
+ type: 'linear',
167
+ autoMaxStrategy: 'clean-top-tick'
168
+ }
169
+ }).shouldFinalizePrimaryYAxis
170
+ ).toBe(false)
171
+ })
172
+
173
+ it('treats omitted legacy Y-axis type as linear', () => {
174
+ const result = getEligibility({
175
+ yAxis: {
176
+ ...createMockConfig().yAxis,
177
+ type: undefined,
178
+ autoMaxStrategy: 'clean-top-tick'
179
+ }
180
+ })
181
+
182
+ expect(result.hasStandardLinearPrimaryYAxis).toBe(true)
183
+ expect(result.shouldFinalizePrimaryYAxis).toBe(true)
184
+ })
185
+ })
@@ -1,11 +1,11 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { getYAxisAutoPaddingMode } from '../getYAxisAutoPaddingMode'
2
+ import { hasSpacedInlineLabel } from '../hasSpacedInlineLabel'
3
3
  import { createMockConfig } from '../../components/LinearChart/tests/mockConfigContext'
4
4
 
5
- describe('getYAxisAutoPaddingMode', () => {
6
- it('returns inline-label for multi-word inline labels', () => {
5
+ describe('hasSpacedInlineLabel', () => {
6
+ it('returns true for multi-word inline labels', () => {
7
7
  expect(
8
- getYAxisAutoPaddingMode(
8
+ hasSpacedInlineLabel(
9
9
  createMockConfig({
10
10
  yAxis: {
11
11
  ...createMockConfig().yAxis,
@@ -14,12 +14,12 @@ describe('getYAxisAutoPaddingMode', () => {
14
14
  }
15
15
  })
16
16
  )
17
- ).toBe('inline-label')
17
+ ).toBe(true)
18
18
  })
19
19
 
20
- it('returns inline-label for inline labels with leading space', () => {
20
+ it('returns true for inline labels with leading space', () => {
21
21
  expect(
22
- getYAxisAutoPaddingMode(
22
+ hasSpacedInlineLabel(
23
23
  createMockConfig({
24
24
  yAxis: {
25
25
  ...createMockConfig().yAxis,
@@ -28,12 +28,12 @@ describe('getYAxisAutoPaddingMode', () => {
28
28
  }
29
29
  })
30
30
  )
31
- ).toBe('inline-label')
31
+ ).toBe(true)
32
32
  })
33
33
 
34
- it('returns top-title for top y-axis titles with label content', () => {
34
+ it('returns false for top y-axis titles with empty inline labels', () => {
35
35
  expect(
36
- getYAxisAutoPaddingMode(
36
+ hasSpacedInlineLabel(
37
37
  createMockConfig({
38
38
  yAxis: {
39
39
  ...createMockConfig().yAxis,
@@ -42,27 +42,12 @@ describe('getYAxisAutoPaddingMode', () => {
42
42
  }
43
43
  })
44
44
  )
45
- ).toBe('top-title')
45
+ ).toBe(false)
46
46
  })
47
47
 
48
- it('returns none for hidden top y-axis titles', () => {
48
+ it('returns false when the inline label has no spaces', () => {
49
49
  expect(
50
- getYAxisAutoPaddingMode(
51
- createMockConfig({
52
- hideYAxisLabel: true,
53
- yAxis: {
54
- ...createMockConfig().yAxis,
55
- titlePlacement: 'top',
56
- inlineLabel: ''
57
- }
58
- })
59
- )
60
- ).toBe('none')
61
- })
62
-
63
- it('returns none when neither trigger applies', () => {
64
- expect(
65
- getYAxisAutoPaddingMode(
50
+ hasSpacedInlineLabel(
66
51
  createMockConfig({
67
52
  yAxis: {
68
53
  ...createMockConfig().yAxis,
@@ -71,12 +56,12 @@ describe('getYAxisAutoPaddingMode', () => {
71
56
  }
72
57
  })
73
58
  )
74
- ).toBe('none')
59
+ ).toBe(false)
75
60
  })
76
61
 
77
- it('returns inline-label when both triggers are present', () => {
62
+ it('returns true for spaced inline labels regardless of title placement', () => {
78
63
  expect(
79
- getYAxisAutoPaddingMode(
64
+ hasSpacedInlineLabel(
80
65
  createMockConfig({
81
66
  yAxis: {
82
67
  ...createMockConfig().yAxis,
@@ -85,6 +70,6 @@ describe('getYAxisAutoPaddingMode', () => {
85
70
  }
86
71
  })
87
72
  )
88
- ).toBe('inline-label')
73
+ ).toBe(true)
89
74
  })
90
75
  })
@@ -65,19 +65,33 @@ describe('seriesColumnSettings', () => {
65
65
  it('only returns explicit formatting overrides and preserves falsey values', () => {
66
66
  expect(
67
67
  getSeriesColumnFormattingParams({
68
- prefix: '',
68
+ prefix: '$',
69
69
  suffix: ' units',
70
70
  roundToPlace: 0,
71
71
  commas: false
72
72
  })
73
73
  ).toEqual({
74
- addColPrefix: '',
74
+ addColPrefix: '$',
75
75
  addColSuffix: ' units',
76
76
  addColRoundTo: 0,
77
77
  addColCommas: false
78
78
  })
79
79
  })
80
80
 
81
+ it('ignores blank prefix and suffix overrides so global formatting can apply', () => {
82
+ expect(
83
+ getSeriesColumnFormattingParams({
84
+ prefix: '',
85
+ suffix: '',
86
+ roundToPlace: 0,
87
+ commas: false
88
+ })
89
+ ).toEqual({
90
+ addColRoundTo: 0,
91
+ addColCommas: false
92
+ })
93
+ })
94
+
81
95
  it('returns undefined when no formatting overrides were explicitly configured', () => {
82
96
  expect(getSeriesColumnFormattingParams({ label: 'Cases' })).toBeUndefined()
83
97
  })