@cdc/editor 4.26.3 → 4.26.4

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 (29) hide show
  1. package/dist/cdceditor-CY9IcPSi.es.js +6 -0
  2. package/dist/cdceditor-DlpiY3fQ.es.js +4 -0
  3. package/dist/cdceditor.js +74691 -71864
  4. package/package.json +9 -9
  5. package/src/_stories/Editor.stories.tsx +62 -0
  6. package/src/components/ChooseTab.tsx +5 -3
  7. package/src/components/DataImport/components/DataImport.tsx +114 -30
  8. package/src/components/DataImport/helpers/applyAutoDetectedDateParseFormat.ts +35 -0
  9. package/src/components/DataImport/tests/applyAutoDetectedDateParseFormat.test.ts +128 -0
  10. package/src/components/PreviewDataTable.test.tsx +184 -0
  11. package/src/components/PreviewDataTable.tsx +18 -7
  12. package/src/components/modal/Confirmation.jsx +5 -4
  13. package/src/scss/main.scss +14 -1
  14. package/LICENSE +0 -201
  15. package/dist/cdceditor-vr9HZwRt.es.js +0 -6
  16. package/example/data-horizontal-filters.json +0 -8
  17. package/example/data-horizontal-multiseries-filters.json +0 -18
  18. package/example/data-horizontal-multiseries.json +0 -6
  19. package/example/data-horizontal.json +0 -4
  20. package/example/data-vertical-filters.json +0 -10
  21. package/example/data-vertical-multiseries-filters.json +0 -18
  22. package/example/data-vertical-multiseries-multirow-filters.json +0 -50
  23. package/example/data-vertical-multiseries-multirow.json +0 -14
  24. package/example/data-vertical-multiseries.json +0 -6
  25. package/example/data-vertical.json +0 -6
  26. package/example/region-map.json +0 -33
  27. package/example/test.json +0 -110280
  28. package/example/valid-county-data.json +0 -3049
  29. package/example/valid-scatterplot.csv +0 -17
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@cdc/editor",
3
- "version": "4.26.3",
3
+ "version": "4.26.4",
4
4
  "description": "React component for generating a new component entry",
5
5
  "license": "Apache-2.0",
6
6
  "bugs": "https://github.com/CDCgov/cdc-open-viz/issues",
7
7
  "dependencies": {
8
- "@cdc/chart": "^4.26.3",
9
- "@cdc/core": "^4.26.3",
10
- "@cdc/dashboard": "^4.26.3",
11
- "@cdc/data-bite": "^4.26.3",
12
- "@cdc/map": "^4.26.3",
13
- "@cdc/markup-include": "^4.26.3",
14
- "@cdc/waffle-chart": "^4.26.3",
8
+ "@cdc/chart": "^4.26.4",
9
+ "@cdc/core": "^4.26.4",
10
+ "@cdc/dashboard": "^4.26.4",
11
+ "@cdc/data-bite": "^4.26.4",
12
+ "@cdc/map": "^4.26.4",
13
+ "@cdc/markup-include": "^4.26.4",
14
+ "@cdc/waffle-chart": "^4.26.4",
15
15
  "axios": "^1.13.2",
16
16
  "d3": "^7.9.0",
17
17
  "react-dropzone": "^14.3.8",
@@ -23,7 +23,7 @@
23
23
  "vite-plugin-css-injected-by-js": "^2.4.0",
24
24
  "vite-plugin-svgr": "^4.2.0"
25
25
  },
26
- "gitHead": "d50e45a074fbefa56cac904917e707d57f237737",
26
+ "gitHead": "6097de1ff814001880d9ac64bd66becdc092d63c",
27
27
  "main": "dist/cdceditor",
28
28
  "moduleName": "CdcEditor",
29
29
  "peerDependencies": {
@@ -103,6 +103,68 @@ export const LoadDataTableJsonConfig: Story = {
103
103
  }
104
104
  }
105
105
 
106
+ export const DownloadDashboardDatasetCSV: Story = {
107
+ args: { config: {} },
108
+ play: async ({ canvasElement }) => {
109
+ const canvas = within(canvasElement)
110
+ const user = userEvent.setup()
111
+
112
+ await loadConfigFromTextArea(canvasElement, DashboardConfig)
113
+ await user.click(canvas.getByText('2. Import Data'))
114
+ await expect(canvas.findByText('Data Sources')).resolves.toBeTruthy()
115
+
116
+ const originalCreateObjectURL = URL.createObjectURL
117
+ let capturedBlob: Blob | null = null
118
+ URL.createObjectURL = (blob: Blob) => {
119
+ capturedBlob = blob
120
+ return 'blob:test-mock'
121
+ }
122
+
123
+ try {
124
+ const downloadBtn = await canvas.findByRole('button', { name: 'Download' })
125
+ await user.click(downloadBtn)
126
+
127
+ expect(capturedBlob).toBeTruthy()
128
+ const text = await capturedBlob!.text()
129
+ expect(text).toContain('Location')
130
+ expect(text).toContain('Rate')
131
+ } finally {
132
+ URL.createObjectURL = originalCreateObjectURL
133
+ }
134
+ }
135
+ }
136
+
137
+ export const DownloadSingleVizCSV: Story = {
138
+ args: { config: {} },
139
+ play: async ({ canvasElement }) => {
140
+ const canvas = within(canvasElement)
141
+ const user = userEvent.setup()
142
+
143
+ await loadConfigFromTextArea(canvasElement, ChartEditorConfig)
144
+ await user.click(canvas.getByText('2. Import Data'))
145
+ await expect(canvas.findByText('Data Preview')).resolves.toBeTruthy()
146
+
147
+ const originalCreateObjectURL = URL.createObjectURL
148
+ let capturedBlob: Blob | null = null
149
+ URL.createObjectURL = (blob: Blob) => {
150
+ capturedBlob = blob
151
+ return 'blob:test-mock'
152
+ }
153
+
154
+ try {
155
+ const downloadBtn = await canvas.findByRole('button', { name: 'Download CSV' })
156
+ await user.click(downloadBtn)
157
+
158
+ expect(capturedBlob).toBeTruthy()
159
+ const text = await capturedBlob!.text()
160
+ expect(text).toContain('Year')
161
+ expect(text).toContain('Category')
162
+ } finally {
163
+ URL.createObjectURL = originalCreateObjectURL
164
+ }
165
+ }
166
+ }
167
+
106
168
  export const InvalidJsonShowsValidationAlert: Story = {
107
169
  args: {
108
170
  config: {}
@@ -3,6 +3,7 @@ import '../scss/choose-vis-tab.scss'
3
3
 
4
4
  import ConfigContext, { EditorDispatchContext } from '@cdc/core/contexts/EditorContext'
5
5
  import Tooltip from '@cdc/core/components/ui/Tooltip'
6
+ import Button from '@cdc/core/components/elements/Button'
6
7
 
7
8
  import AlabamaGraphic from '@cdc/core/assets/icon-map-alabama.svg'
8
9
  import AreaChartIcon from '@cdc/core/assets/icon-area-chart.svg'
@@ -274,15 +275,16 @@ const ChooseTab: React.FC = (): JSX.Element => {
274
275
  placeholder='{ }'
275
276
  value={pastedConfig}
276
277
  />
277
- <button
278
- className='btn btn-primary px-4 ms-2'
278
+ <Button
279
+ variant='primary'
280
+ className='px-4 ms-2'
279
281
  type='submit'
280
282
  id='load-data'
281
283
  disabled={!pastedConfig}
282
284
  onClick={() => importConfig(pastedConfig)}
283
285
  >
284
286
  Load
285
- </button>
287
+ </Button>
286
288
  </div>
287
289
  </div>
288
290
  </div>
@@ -1,6 +1,7 @@
1
1
  import React, { useState, useContext, useEffect } from 'react'
2
2
  import { useDropzone } from 'react-dropzone'
3
3
  import axios from 'axios'
4
+ import { csvFormat } from 'd3'
4
5
 
5
6
  import { DataTransform } from '@cdc/core/helpers/DataTransform'
6
7
 
@@ -19,6 +20,7 @@ import CloseIcon from '@cdc/core/assets/icon-close.svg'
19
20
  import DataDesigner from '@cdc/core/components/managers/DataDesigner'
20
21
  import Tooltip from '@cdc/core/components/ui/Tooltip'
21
22
  import Icon from '@cdc/core/components/ui/Icon'
23
+ import Button from '@cdc/core/components/elements/Button'
22
24
  import { isSolrCsv, isSolrJson } from '@cdc/core/helpers/isSolr'
23
25
  import { type Visualization } from '@cdc/core/types/Visualization'
24
26
  import { type DataSet } from '@cdc/core/types/DataSet'
@@ -32,6 +34,7 @@ import { supportedDataTypes } from '../helpers/supportedDataTypes'
32
34
  import { getFileExtension } from '../helpers/getFileExtension'
33
35
  import { parseTextByMimeType } from '../helpers/parseTextByMimeType'
34
36
  import { getMimeType } from '../helpers/getMimeType'
37
+ import { applyAutoDetectedDateParseFormat } from '../helpers/applyAutoDetectedDateParseFormat'
35
38
  import {
36
39
  extractCoveData,
37
40
  getSampleVegaJson,
@@ -208,14 +211,21 @@ const DataImport = () => {
208
211
  payload: { datasetKey: newDatasetName || fileSource, dataset, oldDatasetKey }
209
212
  })
210
213
  } else {
214
+ const configWithAutoDetectedDateFormat = applyAutoDetectedDateParseFormat(
215
+ {
216
+ ...config,
217
+ ...tempConfig
218
+ },
219
+ newData as Record<string, unknown>[]
220
+ )
221
+
211
222
  let newConfig = {
212
- ...config,
213
- ...tempConfig,
223
+ ...configWithAutoDetectedDateFormat,
214
224
  data: newData,
215
225
  dataMetadata,
216
226
  dataFileName: fileSource, // new file source
217
227
  dataFileSourceType: fileSourceType, // new file source type
218
- formattedData: transform.developerStandardize(newData, config.dataDescription)
228
+ formattedData: transform.developerStandardize(newData, configWithAutoDetectedDateFormat.dataDescription)
219
229
  }
220
230
  if (setDataURL) {
221
231
  newConfig.dataUrl = fileSource
@@ -359,7 +369,7 @@ const DataImport = () => {
359
369
  Always load from URL (normally will only pull once)
360
370
  </label>
361
371
  <div className='d-flex justify-content-end mt-2 mb-3'>
362
- <button
372
+ <Button
363
373
  className='btn btn-primary px-4'
364
374
  type='submit'
365
375
  id='load-data'
@@ -367,7 +377,7 @@ const DataImport = () => {
367
377
  onClick={() => loadData(null, externalURL, editingDataset)}
368
378
  >
369
379
  Save & Load
370
- </button>
380
+ </Button>
371
381
  </div>
372
382
  </>
373
383
  )
@@ -388,7 +398,7 @@ const DataImport = () => {
388
398
  return (
389
399
  //todo convert to modal
390
400
  <>
391
- <button
401
+ <Button
392
402
  className='btn danger'
393
403
  onClick={() =>
394
404
  resetEditor(
@@ -399,7 +409,7 @@ const DataImport = () => {
399
409
  >
400
410
  Clear
401
411
  <CloseIcon />
402
- </button>
412
+ </Button>
403
413
  {/* DEV-851 link to replace file should pop file dialog */}
404
414
  {config.dataFileSourceType === 'file' && (
405
415
  <div className='link link-replace' {...getRootProps2()}>
@@ -435,6 +445,49 @@ const DataImport = () => {
435
445
  dispatch({ type: 'DELETE_DASHBOARD_DATASET', payload: { datasetKey } })
436
446
  }
437
447
 
448
+ const downloadCSV = (data: Array<Record<string, unknown>>, filename: string) => {
449
+ // Normalize filename: strip query/hash, remove path, replace extension with .csv
450
+ let baseName = filename || ''
451
+
452
+ const queryIndex = baseName.indexOf('?')
453
+ const hashIndex = baseName.indexOf('#')
454
+ const cutoffIndex = queryIndex === -1 ? hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex)
455
+ if (cutoffIndex !== -1) {
456
+ baseName = baseName.substring(0, cutoffIndex)
457
+ }
458
+
459
+ const lastSlash = Math.max(baseName.lastIndexOf('/'), baseName.lastIndexOf('\\'))
460
+ if (lastSlash !== -1) {
461
+ baseName = baseName.substring(lastSlash + 1)
462
+ }
463
+
464
+ const lastDot = baseName.lastIndexOf('.')
465
+ if (lastDot > 0) {
466
+ baseName = baseName.substring(0, lastDot)
467
+ }
468
+
469
+ if (!baseName) {
470
+ baseName = 'data'
471
+ }
472
+
473
+ const name = `${baseName}.csv`
474
+ const blob = new Blob(['\uFEFF' + csvFormat(data)], { type: 'text/csv;charset=utf-8;' })
475
+ // @ts-ignore
476
+ if (typeof window.navigator.msSaveBlob === 'function') {
477
+ // @ts-ignore
478
+ navigator.msSaveBlob(blob, name)
479
+ } else {
480
+ const url = URL.createObjectURL(blob)
481
+ const link = document.createElement('a')
482
+ link.href = url
483
+ link.download = name
484
+ document.body.appendChild(link)
485
+ link.click()
486
+ document.body.removeChild(link)
487
+ URL.revokeObjectURL(url)
488
+ }
489
+ }
490
+
438
491
  const updateDataFromVegaConfig = pastedConfig => {
439
492
  const vegaConfig = parseVegaConfig(JSON.parse(pastedConfig))
440
493
  const newData = extractCoveData(vegaConfig)
@@ -609,7 +662,7 @@ const DataImport = () => {
609
662
  </fieldset>
610
663
  )
611
664
  )}
612
- <button
665
+ <Button
613
666
  className='btn full-width btn-primary'
614
667
  onClick={() => {
615
668
  setConfig({
@@ -621,7 +674,7 @@ const DataImport = () => {
621
674
  }}
622
675
  >
623
676
  Add New URL Filter
624
- </button>
677
+ </Button>
625
678
  </>
626
679
  )
627
680
 
@@ -639,7 +692,7 @@ const DataImport = () => {
639
692
  <th>Name</th>
640
693
  <th>Size</th>
641
694
  <th>Type</th>
642
- <th colSpan={3}>Actions</th>
695
+ <th colSpan={4}>Actions</th>
643
696
  </tr>
644
697
  </thead>
645
698
  <tbody>
@@ -651,17 +704,33 @@ const DataImport = () => {
651
704
  <td className='p-1'>{displaySize(config.datasets[datasetKey].dataFileSize)}</td>
652
705
  <td className='p-1'>{config.datasets[datasetKey].dataFileFormat}</td>
653
706
  <td>
654
- <button
655
- className='btn btn-link p-1'
707
+ <Button
708
+ variant='link'
709
+ className='p-1'
656
710
  onClick={() => setGlobalDatasetProp(datasetKey, 'preview', true)}
657
711
  >
658
712
  Preview
713
+ </Button>
714
+ </td>
715
+ <td>
716
+ <button
717
+ className='btn btn-link p-1'
718
+ disabled={!config.datasets[datasetKey].data?.length}
719
+ onClick={() =>
720
+ downloadCSV(
721
+ config.datasets[datasetKey].data,
722
+ config.datasets[datasetKey].dataFileName || datasetKey
723
+ )
724
+ }
725
+ >
726
+ Download
659
727
  </button>
660
728
  </td>
661
729
  <td>
662
730
  {config.datasets[datasetKey].dataFileSourceType === 'url' && (
663
- <button
664
- className='btn btn-link p-1'
731
+ <Button
732
+ variant='link'
733
+ className='p-1'
665
734
  onClick={() => {
666
735
  if (editingDataset === datasetKey) {
667
736
  setEditingDataset(undefined)
@@ -677,13 +746,13 @@ const DataImport = () => {
677
746
  }}
678
747
  >
679
748
  Edit
680
- </button>
749
+ </Button>
681
750
  )}
682
751
  </td>
683
752
  <td>
684
- <button className='btn btn-danger' onClick={() => removeDataset(datasetKey)}>
753
+ <Button variant='danger' onClick={() => removeDataset(datasetKey)}>
685
754
  X
686
- </button>
755
+ </Button>
687
756
  </td>
688
757
  </tr>
689
758
  )
@@ -719,6 +788,14 @@ const DataImport = () => {
719
788
  )}
720
789
  </div>
721
790
  <div>{resetButton()}</div>
791
+ {config.data?.length > 0 && (
792
+ <button
793
+ className='btn btn-link p-1'
794
+ onClick={() => downloadCSV(config.data, config.dataFileName || 'data')}
795
+ >
796
+ Download CSV
797
+ </button>
798
+ )}
722
799
  </div>
723
800
  )}
724
801
 
@@ -727,6 +804,14 @@ const DataImport = () => {
727
804
  <div className='url-source-options'>
728
805
  <div>{loadDataFromUrl()}</div>
729
806
  <div>{resetButton()}</div>
807
+ {config.data?.length > 0 && (
808
+ <button
809
+ className='btn btn-link p-1'
810
+ onClick={() => downloadCSV(config.data, config.dataFileName || 'data')}
811
+ >
812
+ Download CSV
813
+ </button>
814
+ )}
730
815
  </div>
731
816
  {config.dataUrl && (config.type === 'chart' || config.type === 'map') && urlFilters}
732
817
  </>
@@ -750,15 +835,16 @@ const DataImport = () => {
750
835
  placeholder='{ }'
751
836
  />
752
837
  <div className='mb-3 d-flex justify-content-end'>
753
- <button
754
- className='btn btn-primary px-4'
838
+ <Button
839
+ variant='primary'
840
+ className='px-4'
755
841
  type='submit'
756
842
  id='load-data'
757
843
  disabled={!pastedConfig}
758
844
  onClick={() => updateDataFromVegaConfig(pastedConfig)}
759
845
  >
760
846
  Save & Load
761
- </button>
847
+ </Button>
762
848
  </div>
763
849
  </div>
764
850
  </TabPane>
@@ -812,15 +898,16 @@ const DataImport = () => {
812
898
  />
813
899
  </label>
814
900
  <div className='d-flex justify-content-end mt-2 mb-3'>
815
- <button
816
- className='btn btn-primary px-4'
901
+ <Button
902
+ variant='primary'
903
+ className='px-4'
817
904
  type='submit'
818
905
  id='load-data'
819
906
  disabled={!newDatasetName || !externalURL}
820
907
  onClick={() => loadData(null, externalURL, editingDataset)}
821
908
  >
822
909
  Save & Load
823
- </button>
910
+ </Button>
824
911
  </div>
825
912
  </TabPane>
826
913
  </Tabs>
@@ -895,20 +982,17 @@ const DataImport = () => {
895
982
 
896
983
  {config.type === 'dashboard' && !addingDataset && (
897
984
  <div className='mt-2'>
898
- <button className='btn btn-primary' onClick={() => setAddingDataset(true)}>
985
+ <Button variant='primary' onClick={() => setAddingDataset(true)}>
899
986
  + Add More Files
900
- </button>
987
+ </Button>
901
988
  </div>
902
989
  )}
903
990
 
904
991
  {readyToConfigure && (
905
992
  <div className='mt-2'>
906
- <button
907
- className='btn btn-primary'
908
- onClick={() => dispatch({ type: 'EDITOR_SET_GLOBALACTIVE', payload: 2 })}
909
- >
993
+ <Button variant='primary' onClick={() => dispatch({ type: 'EDITOR_SET_GLOBALACTIVE', payload: 2 })}>
910
994
  Configure your visualization
911
- </button>
995
+ </Button>
912
996
  </div>
913
997
  )}
914
998
 
@@ -0,0 +1,35 @@
1
+ import { getAutoDetectedDateParseFormat, isDateScale } from '@cdc/core/helpers/cove/date'
2
+ import { type Visualization } from '@cdc/core/types/Visualization'
3
+
4
+ const isChartWithDateAxis = (config: Visualization) =>
5
+ config?.type === 'chart' && !!config?.xAxis && isDateScale(config.xAxis)
6
+
7
+ export const applyAutoDetectedDateParseFormat = (
8
+ config: Visualization,
9
+ importedData: Record<string, unknown>[]
10
+ ): Visualization => {
11
+ if (!isChartWithDateAxis(config) || !config?.xAxis?.dataKey || !importedData?.length) {
12
+ return config
13
+ }
14
+
15
+ const existingDateParseFormat = config.xAxis.dateParseFormat
16
+
17
+ if (typeof existingDateParseFormat === 'string' && existingDateParseFormat.trim() !== '') {
18
+ return config
19
+ }
20
+ const xAxisKey = config.xAxis.dataKey
21
+
22
+ const autoDetectedDateParseFormat = getAutoDetectedDateParseFormat(importedData, xAxisKey)
23
+
24
+ if (!autoDetectedDateParseFormat) {
25
+ return config
26
+ }
27
+
28
+ return {
29
+ ...config,
30
+ xAxis: {
31
+ ...config.xAxis,
32
+ dateParseFormat: autoDetectedDateParseFormat
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,128 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { applyAutoDetectedDateParseFormat } from '../helpers/applyAutoDetectedDateParseFormat'
3
+
4
+ describe('applyAutoDetectedDateParseFormat', () => {
5
+ const baseChartConfig: any = {
6
+ type: 'chart',
7
+ xAxis: {
8
+ type: 'date',
9
+ dataKey: 'date',
10
+ dateParseFormat: ''
11
+ },
12
+ tooltips: {
13
+ dateDisplayFormat: '%B %-d, %Y'
14
+ },
15
+ table: {
16
+ dateDisplayFormat: '%b %-d %Y'
17
+ }
18
+ }
19
+
20
+ const reliableSlashDateRows = [
21
+ { date: '2024/03/15', value: 10 },
22
+ { date: '2025/11/09', value: 12 }
23
+ ]
24
+
25
+ const ambiguousUsOrEuDateRows = [
26
+ { date: '01/02/2024', value: 10 },
27
+ { date: '02/03/2024', value: 12 }
28
+ ]
29
+
30
+ const missingXAxisDateRows = [{ otherDate: '2024/03/15', value: 10 }]
31
+ const sparseFirstRowReliableDateRows = [
32
+ { otherDate: 'ignore-me', value: 8 },
33
+ { date: '2024/03/15', value: 10 },
34
+ { date: '2025/11/09', value: 12 }
35
+ ]
36
+
37
+ it('auto-fills the chart xAxis date parse format when detection is reliable', () => {
38
+ const result: any = applyAutoDetectedDateParseFormat(baseChartConfig, reliableSlashDateRows)
39
+
40
+ expect(result.xAxis.dateParseFormat).toBe('%Y/%m/%d')
41
+ expect(result.tooltips.dateDisplayFormat).toBe('%B %-d, %Y')
42
+ expect(result.table.dateDisplayFormat).toBe('%b %-d %Y')
43
+ })
44
+
45
+ it('auto-fills the chart xAxis date parse format for date-time axes when detection is reliable', () => {
46
+ const result: any = applyAutoDetectedDateParseFormat(
47
+ {
48
+ ...baseChartConfig,
49
+ xAxis: {
50
+ ...baseChartConfig.xAxis,
51
+ type: 'date-time'
52
+ }
53
+ },
54
+ reliableSlashDateRows
55
+ )
56
+
57
+ expect(result.xAxis.dateParseFormat).toBe('%Y/%m/%d')
58
+ expect(result.tooltips.dateDisplayFormat).toBe('%B %-d, %Y')
59
+ expect(result.table.dateDisplayFormat).toBe('%b %-d %Y')
60
+ })
61
+
62
+ it('leaves the config unchanged when the sample is ambiguous', () => {
63
+ const result: any = applyAutoDetectedDateParseFormat(baseChartConfig, ambiguousUsOrEuDateRows)
64
+
65
+ expect(result).toEqual(baseChartConfig)
66
+ })
67
+
68
+ it('leaves the config unchanged when xAxis.dateParseFormat is already configured', () => {
69
+ const preconfiguredChartConfig: any = {
70
+ ...baseChartConfig,
71
+ xAxis: {
72
+ ...baseChartConfig.xAxis,
73
+ dateParseFormat: '%d/%m/%Y'
74
+ }
75
+ }
76
+
77
+ const result: any = applyAutoDetectedDateParseFormat(preconfiguredChartConfig, reliableSlashDateRows)
78
+
79
+ expect(result).toEqual(preconfiguredChartConfig)
80
+ })
81
+ it('does nothing for non-date chart axes', () => {
82
+ const result: any = applyAutoDetectedDateParseFormat(
83
+ {
84
+ ...baseChartConfig,
85
+ xAxis: {
86
+ ...baseChartConfig.xAxis,
87
+ type: 'categorical'
88
+ }
89
+ },
90
+ [{ date: '2024/03/15', value: 10 }]
91
+ )
92
+
93
+ expect(result).toEqual({
94
+ ...baseChartConfig,
95
+ xAxis: {
96
+ ...baseChartConfig.xAxis,
97
+ type: 'categorical'
98
+ }
99
+ })
100
+ })
101
+
102
+ it('does nothing for non-chart visualizations', () => {
103
+ const result: any = applyAutoDetectedDateParseFormat(
104
+ {
105
+ ...baseChartConfig,
106
+ type: 'map'
107
+ },
108
+ [{ date: '2024/03/15', value: 10 }]
109
+ )
110
+
111
+ expect(result).toEqual({
112
+ ...baseChartConfig,
113
+ type: 'map'
114
+ })
115
+ })
116
+
117
+ it('does nothing when the configured x-axis column is missing', () => {
118
+ const result: any = applyAutoDetectedDateParseFormat(baseChartConfig, missingXAxisDateRows)
119
+
120
+ expect(result).toEqual(baseChartConfig)
121
+ })
122
+
123
+ it('still detects the date parse format when the first row is missing the x-axis key', () => {
124
+ const result: any = applyAutoDetectedDateParseFormat(baseChartConfig, sparseFirstRowReliableDateRows)
125
+
126
+ expect(result.xAxis.dateParseFormat).toBe('%Y/%m/%d')
127
+ })
128
+ })