@cdc/core 4.26.1 → 4.26.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/qa-test-developer.md +126 -0
- package/CLAUDE.local.md +67 -0
- package/LICENSE +201 -0
- package/_stories/Gallery.Charts.stories.tsx +35 -42
- package/_stories/Gallery.DataBite.stories.tsx +15 -8
- package/_stories/Gallery.Maps.stories.tsx +37 -28
- package/_stories/Gallery.WaffleChart.stories.tsx +1 -1
- package/_stories/PageART.stories.tsx +5 -4
- package/_stories/PageBRFSS.stories.tsx +21 -16
- package/_stories/PageCancerRegistries.stories.tsx +15 -15
- package/_stories/PageEasternEquineEncephalitis.stories.tsx +33 -19
- package/_stories/PageExcessiveAlcoholUse.stories.tsx +148 -143
- package/_stories/PageMaternalMortality.stories.tsx +5 -4
- package/_stories/PageOralHealth.stories.tsx +15 -10
- package/_stories/PageRespiratory.stories.tsx +4 -4
- package/_stories/PageSmokingTobacco.stories.tsx +15 -10
- package/_stories/PageStateDiabetesProfiles.stories.tsx +15 -10
- package/_stories/PageWastewater.stories.tsx +44 -30
- 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/callout-flag.svg +7 -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/Alert/components/Alert.styles.css +2 -2
- package/components/ComboBox/combobox.styles.css +48 -48
- package/components/CustomColorsEditor/CustomColorsEditor.css +53 -53
- package/components/CustomColorsEditor/CustomColorsEditor.tsx +3 -10
- package/components/DataTable/DataTable.tsx +46 -18
- package/components/DataTable/DataTableStandAlone.tsx +1 -0
- package/components/DataTable/components/ChartHeader.tsx +21 -12
- package/components/DataTable/components/MapHeader.tsx +34 -28
- package/components/DataTable/components/SortIcon/sort-icon.css +5 -5
- package/components/DataTable/data-table.css +50 -52
- package/components/DataTable/helpers/applyCustomOrder.ts +17 -0
- package/components/DataTable/helpers/getChartCellValue.ts +10 -7
- package/components/DataTable/helpers/getMapDataTableColumnKeys.ts +22 -0
- package/components/DataTable/helpers/getSeriesName.ts +6 -0
- package/components/DataTable/helpers/mapCellMatrix.tsx +33 -23
- package/components/DataTable/helpers/tests/mapCellMatrix.test.ts +33 -0
- package/components/DownloadButton.tsx +14 -6
- package/components/EditorPanel/ColumnsEditor.tsx +38 -31
- package/components/EditorPanel/CustomSortOrder.tsx +94 -0
- package/components/EditorPanel/DataTableEditor.tsx +139 -23
- package/components/EditorPanel/EditorPanel.styles.css +71 -71
- package/components/EditorPanel/EditorPanel.tsx +3 -8
- package/components/EditorPanel/EditorPanelDispatch.tsx +4 -4
- package/components/EditorPanel/FootnotesEditor.tsx +2 -2
- package/components/EditorPanel/VizFilterEditor/NestedDropdownEditor.tsx +21 -12
- package/components/EditorPanel/VizFilterEditor/VizFilterEditor.tsx +16 -10
- package/components/EditorPanel/VizFilterEditor/components/FilterOrder.tsx +33 -29
- package/components/EditorPanel/components/MarkupVariablesEditor.tsx +160 -106
- package/components/EditorPanel/components/PanelMarkup.tsx +5 -1
- package/{styles/v2/components → components/EditorPanel}/editor.scss +76 -22
- package/components/EditorPanel/sections/StyleTreatmentSection.tsx +99 -0
- package/components/EditorPanel/sections/VisualSection.tsx +11 -0
- package/components/EditorWrapper/editor-wrapper.style.css +1 -1
- package/components/Filters/Filters.tsx +3 -5
- package/components/Filters/components/Tabs.tsx +19 -7
- package/{styles → components/Filters}/filters.scss +3 -3
- package/components/Footnotes/FootnotesStandAlone.tsx +4 -2
- package/components/HeaderThemeSelector/HeaderThemeSelector.css +61 -5
- package/components/Layout/components/Responsive.tsx +14 -6
- package/components/Layout/components/Sidebar/components/Sidebar.tsx +1 -1
- package/components/Layout/components/Sidebar/components/sidebar.styles.scss +14 -20
- package/components/Layout/components/Visualization/index.tsx +50 -38
- package/components/Layout/components/Visualization/visualizations.scss +232 -15
- package/components/Layout/components/VisualizationContainer.test.tsx +67 -0
- package/components/Layout/components/VisualizationContainer.tsx +37 -0
- package/components/Layout/components/VisualizationContent.test.tsx +182 -0
- package/components/Layout/components/VisualizationContent.tsx +75 -0
- package/components/Layout/index.tsx +5 -5
- package/components/Layout/styles/editor-utils.scss +3 -3
- package/components/Layout/styles/editor.scss +4 -4
- package/components/Legend/Legend.Gradient.tsx +7 -1
- package/components/Loader/loader.styles.css +2 -2
- package/components/Loading.jsx +1 -1
- package/components/MediaControls.tsx +10 -3
- package/components/MultiSelect/multiselect.styles.css +19 -19
- package/components/NestedDropdown/nesteddropdown.styles.css +15 -15
- package/components/PaletteSelector/PaletteSelector.css +15 -15
- package/components/RichTooltip/richTooltip.css +6 -6
- package/components/Table/table.styles.css +2 -2
- package/components/Waiting.tsx +1 -1
- package/components/_stories/CustomColorsEditor.stories.tsx +37 -0
- package/components/_stories/DataTable.stories.tsx +1 -0
- package/components/_stories/Filters.stories.tsx +1 -1
- package/components/_stories/styles.scss +0 -1
- package/components/elements/Button.jsx +1 -1
- package/components/elements/Card.jsx +1 -1
- package/{styles/v2/components → components/elements}/button.scss +9 -8
- package/components/inputs/InputCheckbox.jsx +1 -1
- package/components/inputs/InputSelect.tsx +1 -1
- package/components/inputs/InputText.jsx +1 -1
- package/components/inputs/InputToggle.tsx +1 -1
- package/{styles/v2/components/input → components/inputs}/_input-check-radio.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input-group.scss +3 -3
- package/{styles/v2/components/input → components/inputs}/_input-slider.scss +2 -2
- package/{styles/v2/components/input → components/inputs}/_input.scss +5 -5
- package/{styles/v2/components/input → components/inputs}/index.scss +2 -2
- package/{styles → components}/loading.scss +1 -1
- package/components/managers/DataDesigner.tsx +1 -1
- package/{styles/v2/components → components/managers}/data-designer.scss +6 -7
- package/components/ui/Accordion.jsx +1 -1
- package/components/ui/Icon.tsx +1 -1
- package/components/ui/LoadSpin.jsx +1 -1
- package/components/ui/Modal.jsx +1 -1
- package/components/ui/Overlay.jsx +1 -1
- package/components/ui/Title/index.test.tsx +34 -0
- package/components/ui/Title/index.tsx +24 -7
- package/components/ui/Title/title.styles.css +119 -25
- package/components/ui/Tooltip.tsx +1 -1
- package/components/ui/_stories/Title.stories.tsx +1 -1
- package/{styles/v2/components → components/ui}/accordion.scss +3 -3
- package/components/ui/accordion.styles.css +11 -11
- package/{styles/v2/components → components/ui}/modal.scss +2 -2
- package/{styles/v2/components → components/ui}/overlay.scss +6 -6
- package/{styles/v2/components → components}/ui/tooltip.scss +1 -1
- package/{styles → components}/waiting.scss +9 -3
- package/data/colorPalettes.ts +18 -5
- package/data/mapColorPalettes.ts +10 -0
- package/devTemplate/dev.js +285 -0
- package/devTemplate/index.html +30 -0
- package/devTemplate/preview.html +1503 -0
- package/devTemplate/sidebar.css +151 -0
- package/dist/cove-main.css +2530 -3901
- package/dist/cove-main.css.map +1 -1
- package/generateViteConfig.js +111 -2
- package/helpers/DataTransform.ts +1 -5
- package/helpers/backfillDefaults.ts +35 -0
- package/helpers/constants.ts +12 -0
- package/helpers/cove/date.ts +64 -3
- package/helpers/cove/number.ts +29 -15
- package/helpers/cove/string.ts +29 -0
- package/helpers/coveUpdateWorker.ts +14 -8
- package/helpers/displayDataAsText.ts +1 -1
- package/helpers/embed/embedCodeGenerator.ts +80 -0
- package/helpers/embed/embedHelper.js +169 -0
- package/helpers/embed/filterUtils.ts +121 -0
- package/helpers/embed/index.ts +17 -0
- package/helpers/embed/urlValidation.ts +119 -0
- package/helpers/extractDataAndMetadata.ts +20 -0
- package/helpers/fetchRemoteData.ts +14 -8
- package/helpers/filterVizData.ts +6 -1
- package/helpers/getFileExtension.ts +0 -6
- package/helpers/labelHash.ts +9 -0
- package/helpers/markupProcessor.ts +56 -38
- 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 +33 -10
- package/helpers/testing.ts +44 -0
- package/helpers/tests/DataTransform.test.ts +125 -0
- package/helpers/tests/abbreviateNumber.test.ts +59 -0
- package/helpers/tests/backfillDefaults.test.ts +253 -0
- package/helpers/tests/date.test.ts +110 -0
- package/helpers/tests/extractDataAndMetadata.test.ts +93 -0
- package/helpers/tests/markupProcessor.test.ts +315 -124
- package/helpers/tests/number.test.ts +42 -0
- package/helpers/tests/prepareScreenshot.test.ts +28 -28
- package/helpers/tests/testStandaloneBuild.ts +36 -26
- package/helpers/tests/useDataVizClasses.test.ts +66 -0
- package/helpers/tests/visualizationWrapperUsage.test.ts +57 -0
- package/helpers/useDataVizClasses.ts +13 -7
- package/helpers/vegaConfig.ts +1 -1
- package/helpers/vegaConfigImport.ts +160 -0
- package/helpers/ver/4.24.4.ts +24 -0
- package/helpers/ver/4.26.1.ts +1 -1
- package/helpers/ver/4.26.2.ts +84 -0
- package/helpers/ver/4.26.3.ts +44 -0
- package/helpers/ver/4.26.4.ts +31 -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/ver/tests/4.26.3.test.ts +168 -0
- package/helpers/ver/tests/4.26.4.test.ts +88 -0
- package/helpers/ver/tests/coveUpdateWorker.test.ts +57 -0
- package/helpers/viewports.ts +2 -0
- package/package.json +27 -32
- package/styles/_global.scss +7 -7
- package/styles/_reset.scss +2 -2
- package/styles/{v2/base → base}/_file-selector.scss +4 -4
- package/styles/{v2/base → base}/_general.scss +2 -4
- package/styles/{v2/base → base}/index.scss +1 -1
- package/styles/base.scss +107 -165
- package/styles/cove-main.scss +3 -6
- package/styles/layout/_component.scss +110 -0
- package/styles/{v2/layout → layout}/_data-table.scss +7 -7
- package/styles/layout/_wrapper-padding.scss +27 -0
- package/styles/{v2/main.scss → main.scss} +3 -1
- package/styles/{v2/themes → themes}/_color-definitions.scss +46 -41
- package/styles/{_accessibility.scss → utils/_accessibility.scss} +1 -1
- package/styles/{v2/utils → utils}/_grid.scss +8 -3
- package/styles/{_global-variables.scss → utils/_properties.scss} +133 -112
- package/styles/{v2/utils → utils}/index.scss +2 -1
- package/types/Annotation.ts +10 -11
- package/types/Axis.ts +2 -0
- package/types/ComponentStyles.ts +1 -0
- package/types/ConfigureData.ts +1 -0
- package/types/General.ts +2 -0
- package/types/MarkupInclude.ts +1 -0
- package/types/MarkupVariable.ts +2 -1
- package/types/Palette.ts +22 -0
- package/types/Table.ts +9 -0
- package/types/Visualization.ts +7 -0
- package/_stories/StoryRenderingTests.stories.tsx +0 -164
- package/helpers/embedCodeGenerator.ts +0 -109
- package/styles/_common-components.css +0 -73
- package/styles/_variables.scss +0 -63
- package/styles/v2/layout/_component.scss +0 -21
- package/styles/v2/utils/_variables.scss +0 -9
- package/{styles/v2/components/card.scss → components/elements/card.css} +2 -2
- /package/{styles/v2/components → components/ui}/icon.scss +0 -0
- /package/{styles/v2/components → components/ui}/loadspin.scss +0 -0
- /package/styles/{v2/base → base}/_heading.scss +0 -0
- /package/styles/{v2/base → base}/_reset.scss +0 -0
- /package/styles/{v2/layout → layout}/_alert.scss +0 -0
- /package/styles/{v2/layout → layout}/_progression.scss +0 -0
- /package/styles/{v2/layout → layout}/_tooltip.scss +0 -0
- /package/styles/{v2/layout → layout}/index.scss +0 -0
- /package/styles/{v2/themes → themes}/index.scss +0 -0
- /package/styles/{v2/utils → utils}/_align.scss +0 -0
- /package/styles/{v2/utils → utils}/_animations.scss +0 -0
- /package/styles/{v2/utils → utils}/_breakpoints.scss +0 -0
- /package/styles/{v2/utils → utils}/_mixins.scss +0 -0
|
@@ -26,13 +26,23 @@ export const processMarkupVariables = (
|
|
|
26
26
|
filters?: VizFilter[]
|
|
27
27
|
datasets?: Datasets
|
|
28
28
|
configDataKey?: string // Add support for widget's assigned dataset
|
|
29
|
+
locale?: string
|
|
30
|
+
dataMetadata?: Record<string, string>
|
|
29
31
|
} = {}
|
|
30
32
|
): {
|
|
31
33
|
processedContent: string
|
|
32
34
|
shouldHideSection: boolean
|
|
33
35
|
shouldShowNoDataMessage: boolean
|
|
34
36
|
} => {
|
|
35
|
-
const {
|
|
37
|
+
const {
|
|
38
|
+
isEditor = false,
|
|
39
|
+
showNoDataMessage = false,
|
|
40
|
+
allowHideSection = false,
|
|
41
|
+
filters = [],
|
|
42
|
+
datasets,
|
|
43
|
+
configDataKey,
|
|
44
|
+
locale = 'en-US'
|
|
45
|
+
} = options
|
|
36
46
|
|
|
37
47
|
// Helper function to get data for a specific variable
|
|
38
48
|
const getDataForVariable = (variable: MarkupVariable): any[] => {
|
|
@@ -65,41 +75,51 @@ export const processMarkupVariables = (
|
|
|
65
75
|
const workingVariable = markupVariables.find(variable => variable.tag === variableTag)
|
|
66
76
|
if (!workingVariable) return variableTag
|
|
67
77
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
// Resolve the data source for this variable. Metadata-sourced variables
|
|
79
|
+
// (metadataKey) pull a single value from the data file's top-level fields,
|
|
80
|
+
// while column-sourced variables (columnName) pull from dataset rows.
|
|
81
|
+
// Both paths produce a `conditionFilteredData` array so the downstream
|
|
82
|
+
// extraction, formatting (addCommas), dedup, and hide-check logic is shared.
|
|
83
|
+
let effectiveColumnName: string
|
|
84
|
+
let conditionFilteredData: any[]
|
|
85
|
+
|
|
86
|
+
if (workingVariable.metadataKey) {
|
|
87
|
+
// Metadata path: synthesize a single-row array from the file-level metadata
|
|
88
|
+
// so it flows through the same formatting pipeline as column values.
|
|
89
|
+
effectiveColumnName = workingVariable.metadataKey
|
|
90
|
+
const metaValue = options.dataMetadata?.[effectiveColumnName] ?? ''
|
|
91
|
+
conditionFilteredData = metaValue ? [{ [effectiveColumnName]: metaValue }] : []
|
|
92
|
+
} else {
|
|
93
|
+
// Column path: pull values from the dataset, applying filters and conditions.
|
|
94
|
+
if (!workingVariable.columnName) {
|
|
95
|
+
console.warn(`Markup variable ${variableTag} has no columnName specified`)
|
|
96
|
+
return variableTag
|
|
97
|
+
}
|
|
98
|
+
effectiveColumnName = workingVariable.columnName
|
|
99
|
+
|
|
100
|
+
let variableData = getDataForVariable(workingVariable)
|
|
101
|
+
if (filters && filters.length > 0) {
|
|
102
|
+
variableData = filterVizData(filters, variableData)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
conditionFilteredData =
|
|
106
|
+
workingVariable.conditions.length === 0
|
|
107
|
+
? variableData
|
|
108
|
+
: filterDataByConditions(variableData, [...workingVariable.conditions])
|
|
72
109
|
}
|
|
73
110
|
|
|
74
|
-
// Get the appropriate dataset for this variable
|
|
75
|
-
let variableData = getDataForVariable(workingVariable)
|
|
76
|
-
|
|
77
|
-
// Apply global filters if present
|
|
78
|
-
if (filters && filters.length > 0) {
|
|
79
|
-
variableData = filterVizData(filters, variableData)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Filter data with error handling (apply conditions on top of already filtered data)
|
|
83
|
-
const conditionFilteredData =
|
|
84
|
-
workingVariable.conditions.length === 0
|
|
85
|
-
? variableData
|
|
86
|
-
: filterDataByConditions(variableData, [...workingVariable.conditions])
|
|
87
|
-
|
|
88
111
|
// Extract values with error handling
|
|
89
112
|
const variableValues: string[] = _.uniq(
|
|
90
113
|
(conditionFilteredData || []).map(dataObject => {
|
|
91
114
|
try {
|
|
92
|
-
const dataObjectValue = dataObject[
|
|
115
|
+
const dataObjectValue = dataObject[effectiveColumnName]
|
|
93
116
|
|
|
94
|
-
// Handle undefined column
|
|
95
117
|
if (dataObjectValue === undefined && isEditor) {
|
|
96
|
-
console.warn(
|
|
97
|
-
`Column "${workingVariable.columnName}" not found in data for variable ${variableTag}`
|
|
98
|
-
)
|
|
118
|
+
console.warn(`Column "${effectiveColumnName}" not found in data for variable ${variableTag}`)
|
|
99
119
|
}
|
|
100
120
|
|
|
101
121
|
return workingVariable.addCommas && !isNaN(parseFloat(dataObjectValue))
|
|
102
|
-
? parseFloat(dataObjectValue).toLocaleString(
|
|
122
|
+
? parseFloat(dataObjectValue).toLocaleString(locale, { useGrouping: true })
|
|
103
123
|
: String(dataObjectValue || '')
|
|
104
124
|
} catch (error) {
|
|
105
125
|
console.error(`Error processing data value for ${variableTag}:`, error)
|
|
@@ -153,13 +173,12 @@ const filterDataByConditions = (data: any[], conditions: MarkupCondition[]): any
|
|
|
153
173
|
const [currentCondition, ...remainingConditions] = conditions
|
|
154
174
|
const { columnName, isOrIsNotEqualTo, value } = currentCondition
|
|
155
175
|
|
|
156
|
-
const filteredData =
|
|
157
|
-
|
|
158
|
-
|
|
176
|
+
const filteredData =
|
|
177
|
+
isOrIsNotEqualTo === 'is'
|
|
178
|
+
? data.filter(dataObject => String(dataObject[columnName]) === value)
|
|
179
|
+
: data.filter(dataObject => String(dataObject[columnName]) !== value)
|
|
159
180
|
|
|
160
|
-
return remainingConditions.length === 0
|
|
161
|
-
? filteredData
|
|
162
|
-
: filterDataByConditions(filteredData, remainingConditions)
|
|
181
|
+
return remainingConditions.length === 0 ? filteredData : filterDataByConditions(filteredData, remainingConditions)
|
|
163
182
|
}
|
|
164
183
|
|
|
165
184
|
/**
|
|
@@ -180,10 +199,7 @@ const formatValuesList = (values: string[], conjunction: string): string[] => {
|
|
|
180
199
|
/**
|
|
181
200
|
* Validates markup variables configuration
|
|
182
201
|
*/
|
|
183
|
-
export const validateMarkupVariables = (
|
|
184
|
-
markupVariables: MarkupVariable[],
|
|
185
|
-
data: any[]
|
|
186
|
-
): string[] => {
|
|
202
|
+
export const validateMarkupVariables = (markupVariables: MarkupVariable[], data: any[]): string[] => {
|
|
187
203
|
const errors: string[] = []
|
|
188
204
|
|
|
189
205
|
if (!markupVariables || !Array.isArray(markupVariables)) {
|
|
@@ -197,9 +213,9 @@ export const validateMarkupVariables = (
|
|
|
197
213
|
errors.push(`Variable ${index + 1}: Tag must be in format {{tagName}}`)
|
|
198
214
|
}
|
|
199
215
|
|
|
200
|
-
if (!variable.columnName) {
|
|
216
|
+
if (!variable.metadataKey && !variable.columnName) {
|
|
201
217
|
errors.push(`Variable ${index + 1}: Column name is required`)
|
|
202
|
-
} else if (availableColumns.length > 0 && !availableColumns.includes(variable.columnName)) {
|
|
218
|
+
} else if (variable.columnName && availableColumns.length > 0 && !availableColumns.includes(variable.columnName)) {
|
|
203
219
|
errors.push(`Variable ${index + 1}: Column "${variable.columnName}" not found in data`)
|
|
204
220
|
}
|
|
205
221
|
|
|
@@ -207,7 +223,9 @@ export const validateMarkupVariables = (
|
|
|
207
223
|
if (!condition.columnName) {
|
|
208
224
|
errors.push(`Variable ${index + 1}, Condition ${condIndex + 1}: Column name is required`)
|
|
209
225
|
} else if (availableColumns.length > 0 && !availableColumns.includes(condition.columnName)) {
|
|
210
|
-
errors.push(
|
|
226
|
+
errors.push(
|
|
227
|
+
`Variable ${index + 1}, Condition ${condIndex + 1}: Column "${condition.columnName}" not found in data`
|
|
228
|
+
)
|
|
211
229
|
}
|
|
212
230
|
|
|
213
231
|
if (!condition.value) {
|
package/helpers/metrics/types.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { map } from 'lodash'
|
|
2
1
|
import { FALLBACK_COLOR_PALETTE_V1, FALLBACK_COLOR_PALETTE_V2, USE_V2_MIGRATION } from '../constants'
|
|
3
2
|
import { getColorPaletteVersion } from '../getColorPaletteVersion'
|
|
4
3
|
import { getPaletteAccessor } from '../getPaletteAccessor'
|
|
5
4
|
import { chartPaletteMigrationMap } from './migratePaletteName'
|
|
6
5
|
import { newMapPaletteNames } from './standardizePaletteNames'
|
|
6
|
+
import { Visualization } from '../../types/Visualization'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Gets the current palette name from a visualization config
|
|
10
10
|
* @param config - The visualization config object
|
|
11
11
|
* @returns The current palette name or empty string if not found
|
|
12
12
|
*/
|
|
13
|
-
export const getCurrentPaletteName = (config:
|
|
13
|
+
export const getCurrentPaletteName = (config: Partial<Visualization>): string => {
|
|
14
14
|
// Check new v2 format first
|
|
15
15
|
if (config?.general?.palette?.name) {
|
|
16
16
|
return config.general.palette.name
|
|
@@ -35,7 +35,7 @@ export const getCurrentPaletteName = (config: any): string => {
|
|
|
35
35
|
* @param colorPalettes - The color palettes object (e.g., mapColorPalettes, chartColorPalettes)
|
|
36
36
|
* @returns The palette colors array or empty array if not found
|
|
37
37
|
*/
|
|
38
|
-
export const getPaletteColors = (config:
|
|
38
|
+
export const getPaletteColors = (config: Partial<Visualization>, colorPalettes: Record<string, Record<string, string[]>>): string[] => {
|
|
39
39
|
// First check for custom colors (v2 format)
|
|
40
40
|
if (config?.general?.palette?.customColors) {
|
|
41
41
|
return config.general.palette.customColors
|
|
@@ -65,7 +65,7 @@ export const getPaletteColors = (config: any, colorPalettes: any): string[] => {
|
|
|
65
65
|
* @param config - The visualization config object
|
|
66
66
|
* @returns True if the config is using v1 palette configuration (which would show conversion modal)
|
|
67
67
|
*/
|
|
68
|
-
export const isV1Palette = (config:
|
|
68
|
+
export const isV1Palette = (config: Partial<Visualization>): boolean => {
|
|
69
69
|
// If v2 migration is disabled globally, don't treat as v1 (no conversion modal)
|
|
70
70
|
if (!USE_V2_MIGRATION) {
|
|
71
71
|
return false
|
|
@@ -84,7 +84,7 @@ export const isV1Palette = (config: any): boolean => {
|
|
|
84
84
|
* @param config - The visualization config object
|
|
85
85
|
* @returns The fallback palette name for the detected version
|
|
86
86
|
*/
|
|
87
|
-
export const getFallbackColorPalette = (config:
|
|
87
|
+
export const getFallbackColorPalette = (config: Partial<Visualization>): string => {
|
|
88
88
|
const paletteVersion = getColorPaletteVersion(config)
|
|
89
89
|
return paletteVersion === 1 ? FALLBACK_COLOR_PALETTE_V1 : FALLBACK_COLOR_PALETTE_V2
|
|
90
90
|
}
|
|
@@ -161,7 +161,7 @@ export const migratePaletteWithMap = (
|
|
|
161
161
|
* @param config - The visualization config object
|
|
162
162
|
* @returns True if backup data exists
|
|
163
163
|
*/
|
|
164
|
-
export const hasPaletteBackup = (config:
|
|
164
|
+
export const hasPaletteBackup = (config: Partial<Visualization>): boolean => {
|
|
165
165
|
return !!(config?.general?.palette?.backups?.length > 0)
|
|
166
166
|
}
|
|
167
167
|
|
|
@@ -170,7 +170,7 @@ export const hasPaletteBackup = (config: any): boolean => {
|
|
|
170
170
|
* @param config - The visualization config object
|
|
171
171
|
* @returns The original palette name or null if no backup exists
|
|
172
172
|
*/
|
|
173
|
-
export const getOriginalPaletteName = (config:
|
|
173
|
+
export const getOriginalPaletteName = (config: Partial<Visualization>): string | null => {
|
|
174
174
|
const backups = config?.general?.palette?.backups
|
|
175
175
|
if (!backups || backups.length === 0) return null
|
|
176
176
|
|
|
@@ -184,7 +184,7 @@ export const getOriginalPaletteName = (config: any): string | null => {
|
|
|
184
184
|
* @param config - The visualization config object
|
|
185
185
|
* @returns The original two-color palette name or null if no backup exists
|
|
186
186
|
*/
|
|
187
|
-
export const getOriginalTwoColorPaletteName = (config:
|
|
187
|
+
export const getOriginalTwoColorPaletteName = (config: Partial<Visualization>): string | null => {
|
|
188
188
|
const backups = config?.general?.palette?.backups
|
|
189
189
|
if (!backups || backups.length === 0) return null
|
|
190
190
|
|
|
@@ -198,7 +198,7 @@ export const getOriginalTwoColorPaletteName = (config: any): string | null => {
|
|
|
198
198
|
* @param config - The visualization config object
|
|
199
199
|
* @returns True if two-color backup data exists
|
|
200
200
|
*/
|
|
201
|
-
export const hasTwoColorPaletteBackup = (config:
|
|
201
|
+
export const hasTwoColorPaletteBackup = (config: Partial<Visualization>): boolean => {
|
|
202
202
|
const backups = config?.general?.palette?.backups
|
|
203
203
|
if (!backups || backups.length === 0) return false
|
|
204
204
|
return backups.some((backup: any) => backup.type === 'twoColor')
|
|
@@ -209,7 +209,7 @@ export const hasTwoColorPaletteBackup = (config: any): boolean => {
|
|
|
209
209
|
* @param config - The visualization config object to modify
|
|
210
210
|
* @returns True if rollback was successful, false if no backup available
|
|
211
211
|
*/
|
|
212
|
-
export const rollbackPaletteToOriginal = (config:
|
|
212
|
+
export const rollbackPaletteToOriginal = (config: Partial<Visualization>): boolean => {
|
|
213
213
|
const backups = config?.general?.palette?.backups
|
|
214
214
|
if (!backups || backups.length === 0) {
|
|
215
215
|
return false
|
|
@@ -230,7 +230,7 @@ export const rollbackPaletteToOriginal = (config: any): boolean => {
|
|
|
230
230
|
config.general.palette.version = '1.0' // Reset to v1
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
return
|
|
233
|
+
return true
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
/**
|
|
@@ -238,7 +238,7 @@ export const rollbackPaletteToOriginal = (config: any): boolean => {
|
|
|
238
238
|
* @param config - The visualization config object to modify
|
|
239
239
|
* @returns True if rollback was successful, false if no backup available
|
|
240
240
|
*/
|
|
241
|
-
export const rollbackTwoColorPaletteToOriginal = (config:
|
|
241
|
+
export const rollbackTwoColorPaletteToOriginal = (config: Partial<Visualization>): boolean => {
|
|
242
242
|
const backups = config?.general?.palette?.backups
|
|
243
243
|
if (!backups || backups.length === 0) {
|
|
244
244
|
return false
|
|
@@ -2,7 +2,7 @@ import Papa from 'papaparse'
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Parses CSV text while preserving newlines and commas within quoted fields.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* @param responseText - The raw CSV text to parse
|
|
7
7
|
* @param options - Parsing options
|
|
8
8
|
* @param options.delimiter - The delimiter to use after processing (default: '|')
|
|
@@ -17,27 +17,28 @@ export function parseCsvWithQuotes(
|
|
|
17
17
|
} = {}
|
|
18
18
|
): any[] {
|
|
19
19
|
const { delimiter = '|', dynamicTyping = false } = options
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
const NEWLINE_PLACEHOLDER = '__COVE_NEWLINE__'
|
|
22
|
-
|
|
22
|
+
const newlinePlaceholderRegex = new RegExp(NEWLINE_PLACEHOLDER, 'g')
|
|
23
|
+
|
|
23
24
|
// Preserve newlines in quoted fields by replacing with placeholder
|
|
24
25
|
const quotedFields: string[] = []
|
|
25
26
|
let placeholderIndex = 0
|
|
26
|
-
let sanitizedText = responseText.replace(/("(?:[^"\\]|\\.|[\s\S])*?")/g,
|
|
27
|
+
let sanitizedText = responseText.replace(/("(?:[^"\\]|\\.|[\s\S])*?")/g, match => {
|
|
27
28
|
const preserved = match.replace(/\n/g, NEWLINE_PLACEHOLDER)
|
|
28
29
|
quotedFields.push(preserved)
|
|
29
30
|
return `__QUOTED_FIELD_${placeholderIndex++}__`
|
|
30
31
|
})
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
// Replace commas outside quoted fields with pipe delimiter
|
|
33
34
|
sanitizedText = sanitizedText.replace(/(__QUOTED_FIELD_\d+__)|,/g, (...m) => m[1] || delimiter)
|
|
34
|
-
|
|
35
|
-
// Restore quoted fields without outer quotes
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
|
|
35
|
+
|
|
36
|
+
// Restore quoted fields without outer quotes (single pass instead of N passes)
|
|
37
|
+
sanitizedText = sanitizedText.replace(/__QUOTED_FIELD_(\d+)__/g, (_, idx) => {
|
|
38
|
+
const field = quotedFields[parseInt(idx, 10)]
|
|
39
|
+
return field.slice(1, -1).replace(newlinePlaceholderRegex, '\n')
|
|
39
40
|
})
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
// Parse with Papa.parse
|
|
42
43
|
const parsedCsv = Papa.parse(sanitizedText, {
|
|
43
44
|
header: true,
|
|
@@ -45,21 +46,21 @@ export function parseCsvWithQuotes(
|
|
|
45
46
|
delimiter,
|
|
46
47
|
dynamicTyping
|
|
47
48
|
})
|
|
48
|
-
|
|
49
|
+
|
|
49
50
|
// Restore newlines in parsed data
|
|
50
51
|
const restoredData = parsedCsv.data.map((row: any) => {
|
|
51
52
|
const restoredRow: any = {}
|
|
52
53
|
Object.keys(row).forEach(key => {
|
|
53
54
|
const value = row[key]
|
|
54
55
|
if (typeof value === 'string') {
|
|
55
|
-
restoredRow[key] = value.replace(
|
|
56
|
+
restoredRow[key] = value.replace(newlinePlaceholderRegex, '\n')
|
|
56
57
|
} else {
|
|
57
58
|
restoredRow[key] = value
|
|
58
59
|
}
|
|
59
60
|
})
|
|
60
61
|
return restoredRow
|
|
61
62
|
})
|
|
62
|
-
|
|
63
|
+
|
|
63
64
|
return restoredData
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -80,7 +80,7 @@ function findNearestHeadingIndex(parentChildren: Element[], vizWrapperIndex: num
|
|
|
80
80
|
const child = parentChildren[i] as HTMLElement
|
|
81
81
|
|
|
82
82
|
// STOP: Another visualization found - don't include its content
|
|
83
|
-
if (child.classList.contains('
|
|
83
|
+
if (child.classList.contains('cove-visualization') || child.querySelector('.cove-visualization')) {
|
|
84
84
|
return -1
|
|
85
85
|
}
|
|
86
86
|
|
|
@@ -135,7 +135,7 @@ function buildContextClone(
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
function isInEditorMode(element: HTMLElement): boolean {
|
|
138
|
-
return element.closest('.
|
|
138
|
+
return element.closest('.cove-visualization.is-editor') !== null
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
/**
|
|
@@ -208,6 +208,23 @@ function convertCanvasToImages(baseSvg: HTMLElement, clonedViz: HTMLElement): vo
|
|
|
208
208
|
})
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Remove width classes that interfere with screenshot sizing
|
|
213
|
+
*/
|
|
214
|
+
function removeWidthClasses(clonedTree: HTMLElement): void {
|
|
215
|
+
const classesToRemove = ['dfe-block--width-wide', 'dfe-block--width-full_width']
|
|
216
|
+
|
|
217
|
+
classesToRemove.forEach(className => {
|
|
218
|
+
const elements = clonedTree.querySelectorAll(`.${className}`)
|
|
219
|
+
elements.forEach(el => el.classList.remove(className))
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// Also check the root element itself
|
|
223
|
+
classesToRemove.forEach(className => {
|
|
224
|
+
clonedTree.classList.remove(className)
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
211
228
|
/**
|
|
212
229
|
* Expand SVG widths and remove animation classes
|
|
213
230
|
*/
|
|
@@ -222,7 +239,10 @@ function expandSvgWidths(clonedViz: HTMLElement): void {
|
|
|
222
239
|
}
|
|
223
240
|
|
|
224
241
|
// Remove animation classes to show final state immediately
|
|
225
|
-
svg.classList.
|
|
242
|
+
const isAnimatedPie = svg.classList.contains('animated-pie')
|
|
243
|
+
if (!isAnimatedPie) {
|
|
244
|
+
svg.classList.remove('animated', 'animate')
|
|
245
|
+
}
|
|
226
246
|
})
|
|
227
247
|
}
|
|
228
248
|
|
|
@@ -237,31 +257,34 @@ export function prepareScreenshotContainer(
|
|
|
237
257
|
// 1. Clone elements (with or without context)
|
|
238
258
|
const { clonedTree, clonedViz } = prepareClonedElements(baseSvg, includeContextInDownload, elementToCapture)
|
|
239
259
|
|
|
240
|
-
// 2.
|
|
260
|
+
// 2. Remove width classes that interfere with screenshot sizing
|
|
261
|
+
removeWidthClasses(clonedTree)
|
|
262
|
+
|
|
263
|
+
// 3. Strip all links (not clickable in static image)
|
|
241
264
|
stripLinks(clonedTree)
|
|
242
265
|
|
|
243
|
-
//
|
|
266
|
+
// 4. Convert canvas elements to images
|
|
244
267
|
convertCanvasToImages(baseSvg, clonedViz)
|
|
245
268
|
|
|
246
|
-
//
|
|
269
|
+
// 5. Expand SVG widths to prevent clipping
|
|
247
270
|
expandSvgWidths(clonedViz)
|
|
248
271
|
|
|
249
|
-
//
|
|
272
|
+
// 6. Calculate viz dimensions
|
|
250
273
|
const computedStyle = getComputedStyle(baseSvg)
|
|
251
274
|
const vizWidth =
|
|
252
275
|
parseFloat(computedStyle.width) -
|
|
253
276
|
(parseFloat(computedStyle.paddingLeft) || 0) -
|
|
254
277
|
(parseFloat(computedStyle.paddingRight) || 0)
|
|
255
278
|
|
|
256
|
-
//
|
|
279
|
+
// 7. Create and style container
|
|
257
280
|
const container = document.createElement('div')
|
|
258
281
|
container.style.width = `${vizWidth + 36}px`
|
|
259
282
|
container.style.padding = '18px'
|
|
260
283
|
|
|
261
|
-
//
|
|
284
|
+
// 8. Reset viz padding
|
|
262
285
|
clonedViz.style.padding = '0'
|
|
263
286
|
|
|
264
|
-
//
|
|
287
|
+
// 9. Append cloned tree to container
|
|
265
288
|
container.appendChild(clonedTree)
|
|
266
289
|
|
|
267
290
|
return container
|
package/helpers/testing.ts
CHANGED
|
@@ -313,6 +313,50 @@ export const testBooleanControl = async (checkbox: HTMLInputElement, getVisualSt
|
|
|
313
313
|
expect(secondToggleVisualState).toEqual(initialVisualState)
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
// ============================================================================
|
|
317
|
+
// VISUALIZATION RENDERING ASSERTIONS
|
|
318
|
+
// ============================================================================
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Assert that a visualization has rendered successfully.
|
|
322
|
+
* Uses performAndAssert to poll until one of these conditions is met:
|
|
323
|
+
* ((svgCount > 0 || canvasCount > 0) && hasCoveModule) || isDataBite || isDataTable
|
|
324
|
+
*
|
|
325
|
+
* Handles all visualization types:
|
|
326
|
+
* - Charts (SVG-based)
|
|
327
|
+
* - Maps with SVG rendering (US state, world, hex, etc.)
|
|
328
|
+
* - Maps with canvas rendering (county maps via UsaMap.County)
|
|
329
|
+
* - Data bites (.bite-content)
|
|
330
|
+
* - Data tables (.data-table)
|
|
331
|
+
* - Waffle charts (SVG-based)
|
|
332
|
+
*
|
|
333
|
+
* Use as a play function in any Components/Templates/ story:
|
|
334
|
+
* play: async ({ canvasElement }) => {
|
|
335
|
+
* await assertVisualizationRendered(canvasElement)
|
|
336
|
+
* }
|
|
337
|
+
*
|
|
338
|
+
* @param vizElement The story's canvas element (Storybook's canvasElement)
|
|
339
|
+
*/
|
|
340
|
+
export const assertVisualizationRendered = async (vizElement: HTMLElement) => {
|
|
341
|
+
await performAndAssert(
|
|
342
|
+
'Wait for visualization to render',
|
|
343
|
+
() => {
|
|
344
|
+
const svgCount = vizElement.querySelectorAll('svg').length
|
|
345
|
+
const canvasCount = vizElement.querySelectorAll('canvas').length
|
|
346
|
+
const hasCoveModule = !!vizElement.querySelector('.cove-visualization')
|
|
347
|
+
const isDataBite = !!vizElement.querySelector('.bite-content')
|
|
348
|
+
const isDataTable = !!vizElement.querySelector('.type-data-table')
|
|
349
|
+
return { svgCount, canvasCount, hasCoveModule, isDataBite, isDataTable }
|
|
350
|
+
},
|
|
351
|
+
async () => {},
|
|
352
|
+
(_before, after) => {
|
|
353
|
+
return (
|
|
354
|
+
((after.svgCount > 0 || after.canvasCount > 0) && after.hasCoveModule) || after.isDataBite || after.isDataTable
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
316
360
|
// ============================================================================
|
|
317
361
|
// DATA EXTRACTION HELPERS
|
|
318
362
|
// ============================================================================
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import DataTransform from '../DataTransform'
|
|
3
|
+
|
|
4
|
+
describe('DataTransform', () => {
|
|
5
|
+
let transformer: DataTransform
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
transformer = new DataTransform()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
describe('autoStandardize', () => {
|
|
12
|
+
it('returns undefined for empty data', () => {
|
|
13
|
+
expect(transformer.autoStandardize([])).toBeUndefined()
|
|
14
|
+
expect(transformer.autoStandardize(null)).toBeUndefined()
|
|
15
|
+
expect(transformer.autoStandardize(undefined)).toBeUndefined()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('returns undefined for invalid data format', () => {
|
|
19
|
+
expect(transformer.autoStandardize(['string', 'array'])).toBeUndefined()
|
|
20
|
+
expect(transformer.autoStandardize([1, 2, 3])).toBeUndefined()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns data unchanged when already in object format', () => {
|
|
24
|
+
const data = [
|
|
25
|
+
{ name: 'Alice', value: 10 },
|
|
26
|
+
{ name: 'Bob', value: 20 }
|
|
27
|
+
]
|
|
28
|
+
const result = transformer.autoStandardize(data)
|
|
29
|
+
expect(result).toEqual(data)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('converts array of arrays to array of objects', () => {
|
|
33
|
+
const data = [
|
|
34
|
+
['name', 'value'],
|
|
35
|
+
['Alice', 10],
|
|
36
|
+
['Bob', 20]
|
|
37
|
+
]
|
|
38
|
+
const result = transformer.autoStandardize(data)
|
|
39
|
+
expect(result).toEqual([
|
|
40
|
+
{ name: 'Alice', value: 10 },
|
|
41
|
+
{ name: 'Bob', value: 20 }
|
|
42
|
+
])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('handles array of arrays with multiple columns', () => {
|
|
46
|
+
const data = [
|
|
47
|
+
['state', 'year', 'population'],
|
|
48
|
+
['CA', 2020, 39538223],
|
|
49
|
+
['TX', 2020, 29145505]
|
|
50
|
+
]
|
|
51
|
+
const result = transformer.autoStandardize(data)
|
|
52
|
+
expect(result).toEqual([
|
|
53
|
+
{ state: 'CA', year: 2020, population: 39538223 },
|
|
54
|
+
{ state: 'TX', year: 2020, population: 29145505 }
|
|
55
|
+
])
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('cleanDataPoint', () => {
|
|
60
|
+
it('removes commas from numbers', () => {
|
|
61
|
+
expect(transformer.cleanDataPoint('1,234')).toBe('1234')
|
|
62
|
+
expect(transformer.cleanDataPoint('1,234,567')).toBe('1234567')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('removes dollar signs', () => {
|
|
66
|
+
expect(transformer.cleanDataPoint('$100')).toBe('100')
|
|
67
|
+
expect(transformer.cleanDataPoint('$1,234')).toBe('1234')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('removes percent signs', () => {
|
|
71
|
+
expect(transformer.cleanDataPoint('50%')).toBe('50')
|
|
72
|
+
expect(transformer.cleanDataPoint('99.9%')).toBe('99.9')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('handles combined formatting', () => {
|
|
76
|
+
expect(transformer.cleanDataPoint('$1,234,567')).toBe('1234567')
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('handles null and empty values', () => {
|
|
80
|
+
expect(transformer.cleanDataPoint(null)).toBe('')
|
|
81
|
+
expect(transformer.cleanDataPoint('')).toBe('')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('handles non-string values', () => {
|
|
85
|
+
expect(transformer.cleanDataPoint(123)).toBe(123)
|
|
86
|
+
expect(transformer.cleanDataPoint(0)).toBe(0)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('returns plain numbers unchanged', () => {
|
|
90
|
+
expect(transformer.cleanDataPoint('123')).toBe('123')
|
|
91
|
+
expect(transformer.cleanDataPoint('45.67')).toBe('45.67')
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('developerStandardize', () => {
|
|
96
|
+
it('returns empty array when data is falsy', () => {
|
|
97
|
+
expect(transformer.developerStandardize(null, {})).toEqual([])
|
|
98
|
+
expect(transformer.developerStandardize(undefined, {})).toEqual([])
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('returns undefined when description is missing', () => {
|
|
102
|
+
const data = [{ a: 1 }]
|
|
103
|
+
expect(transformer.developerStandardize(data, null)).toBeUndefined()
|
|
104
|
+
expect(transformer.developerStandardize(data, undefined)).toBeUndefined()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('returns undefined when description is incomplete', () => {
|
|
108
|
+
const data = [{ a: 1 }]
|
|
109
|
+
expect(transformer.developerStandardize(data, {})).toBeUndefined()
|
|
110
|
+
expect(transformer.developerStandardize(data, { horizontal: true })).toBeUndefined()
|
|
111
|
+
expect(transformer.developerStandardize(data, { series: true })).toBeUndefined()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('handles horizontal data without series', () => {
|
|
115
|
+
const data = [
|
|
116
|
+
{ category: 'A', val1: 10, val2: 20 },
|
|
117
|
+
{ category: 'B', val1: 30, val2: 40 }
|
|
118
|
+
]
|
|
119
|
+
const description = { horizontal: true, series: false }
|
|
120
|
+
const result = transformer.developerStandardize(data, description)
|
|
121
|
+
expect(result).toBeDefined()
|
|
122
|
+
expect(Array.isArray(result)).toBe(true)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
})
|