@cdc/dashboard 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.
Files changed (76) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cdcdashboard-8NmHlKRI.es.js +15 -0
  3. package/dist/cdcdashboard-BPoPzKPz.es.js +6 -0
  4. package/dist/{cdcdashboard-dgT_1dIT.es.js → cdcdashboard-DQ00cQCm.es.js} +1 -20
  5. package/dist/cdcdashboard-jiQQPkty.es.js +6 -0
  6. package/dist/cdcdashboard-vr9HZwRt.es.js +6 -0
  7. package/dist/cdcdashboard.js +80971 -83096
  8. package/examples/custom/css/respiratory.css +1 -1
  9. package/examples/data/data-with-metadata.json +18 -0
  10. package/examples/default.json +492 -132
  11. package/examples/nested-dropdown.json +6985 -0
  12. package/examples/private/abc.json +467 -0
  13. package/examples/private/dash.json +12696 -0
  14. package/examples/private/inline-markup.json +775 -0
  15. package/examples/private/npcr.json +1 -0
  16. package/examples/private/recent-update.json +1456 -0
  17. package/examples/private/test.json +125407 -0
  18. package/examples/private/timeline-data.json +4994 -0
  19. package/examples/private/timeline.json +1708 -0
  20. package/examples/private/toggle.json +10137 -0
  21. package/examples/test-api-filter-reset.json +8 -4
  22. package/examples/tp5-gauges.json +196 -0
  23. package/examples/tp5-test.json +266 -0
  24. package/index.html +1 -29
  25. package/package.json +38 -40
  26. package/src/CdcDashboard.tsx +2 -1
  27. package/src/CdcDashboardComponent.tsx +47 -30
  28. package/src/_stories/Dashboard.DataSetup.stories.tsx +8 -2
  29. package/src/_stories/Dashboard.Pages.stories.tsx +22 -0
  30. package/src/_stories/Dashboard.stories.tsx +4501 -80
  31. package/src/_stories/_mock/dashboard-line-chart-angles.json +1030 -0
  32. package/src/_stories/_mock/tab-simple-filter.json +153 -0
  33. package/src/_stories/_mock/tp5-test.json +267 -0
  34. package/src/components/DashboardFilters/DashboardFilters.tsx +19 -3
  35. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +10 -4
  36. package/src/components/DashboardFilters/DashboardFiltersEditor/components/APIModal.tsx +1 -1
  37. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +6 -3
  38. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +13 -8
  39. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +8 -8
  40. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +1 -1
  41. package/src/components/DashboardFilters/dashboardfilter.styles.css +3 -3
  42. package/src/components/DataDesignerModal.tsx +2 -2
  43. package/src/components/Header/Header.tsx +27 -5
  44. package/src/components/Header/index.scss +1 -1
  45. package/src/components/MultiConfigTabs/multiconfigtabs.styles.css +6 -6
  46. package/src/components/Row.tsx +21 -0
  47. package/src/components/Toggle/toggle-style.css +7 -7
  48. package/src/components/VisualizationRow.tsx +42 -29
  49. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +1 -71
  50. package/src/components/VisualizationsPanel/visualizations-panel-styles.css +2 -2
  51. package/src/components/Widget/Widget.tsx +2 -2
  52. package/src/components/Widget/widget.styles.css +12 -12
  53. package/src/data/initial-state.js +1 -1
  54. package/src/helpers/addValuesToDashboardFilters.ts +17 -11
  55. package/src/helpers/addVisualization.ts +71 -0
  56. package/src/helpers/apiFilterHelpers.ts +28 -32
  57. package/src/helpers/formatConfigBeforeSave.ts +1 -1
  58. package/src/helpers/getVizConfig.ts +13 -3
  59. package/src/helpers/iconHash.tsx +45 -36
  60. package/src/helpers/processDataLegacy.ts +19 -14
  61. package/src/helpers/tests/addValuesToDashboardFilters.test.ts +141 -44
  62. package/src/helpers/tests/addVisualization.test.ts +52 -0
  63. package/src/helpers/tests/apiFilterHelpers.test.ts +523 -420
  64. package/src/helpers/tests/formatConfigBeforeSave.test.ts +81 -1
  65. package/src/scss/editor-panel.scss +1 -1
  66. package/src/scss/main.scss +169 -41
  67. package/src/store/dashboard.reducer.ts +1 -1
  68. package/src/test/CdcDashboard.test.jsx +2 -2
  69. package/src/test/CdcDashboardComponent.test.tsx +74 -0
  70. package/src/types/FilterStyles.ts +2 -1
  71. package/tests/fixtures/dashboard-config-with-metadata.json +89 -0
  72. package/vite.config.js +7 -1
  73. package/dist/cdcdashboard-BnB1QM5d.es.js +0 -361528
  74. package/dist/cdcdashboard-Ct2SB0vL.es.js +0 -231049
  75. package/dist/cdcdashboard-D6CG2-Hb.es.js +0 -39377
  76. package/dist/cdcdashboard-MXgURbdZ.es.js +0 -39194
@@ -1,420 +1,523 @@
1
- import {
2
- setAutoLoadDefaultValue,
3
- getToFetch,
4
- getFilterValues,
5
- getLoadingFilterMemo,
6
- getParentParams,
7
- setActiveNestedDropdown,
8
- setActiveMultiDropdown
9
- } from '../apiFilterHelpers'
10
- import _ from 'lodash'
11
- import type { APIFilterDropdowns } from '../../components/DashboardFilters'
12
- import { SharedFilter } from '../../types/SharedFilter'
13
- import { FILTER_STYLE } from '../../types/FilterStyles'
14
-
15
- describe('getLoadingFilterMemo', () => {
16
- it('should return correct APIFilterDropdowns for valid inputs', () => {
17
- const sharedAPIFilters = ['endpoint1', 'endpoint2']
18
- const apiFilterDropdowns: APIFilterDropdowns = {
19
- endpoint1: { text: 'text1', value: 'value1' }
20
- }
21
- const expectedOutput: APIFilterDropdowns = {
22
- endpoint1: { text: 'text1', value: 'value1' },
23
- endpoint2: undefined
24
- }
25
- expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(expectedOutput)
26
- })
27
-
28
- it('should return an empty object for empty sharedAPIFilters', () => {
29
- const sharedAPIFilters: string[] = []
30
- const apiFilterDropdowns: APIFilterDropdowns = {
31
- endpoint1: { text: 'text1', value: 'value1' }
32
- }
33
- const expectedOutput: APIFilterDropdowns = {}
34
- expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(expectedOutput)
35
- })
36
-
37
- it('should return APIFilterDropdowns with null values for empty apiFilterDropdowns', () => {
38
- const sharedAPIFilters = ['endpoint1', 'endpoint2']
39
- const apiFilterDropdowns: APIFilterDropdowns = {}
40
- const expectedOutput: APIFilterDropdowns = {
41
- endpoint1: undefined,
42
- endpoint2: undefined
43
- }
44
- expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(expectedOutput)
45
- })
46
-
47
- it('should not overwrite existing data in apiFilterDropdowns, so return original dropdowns', () => {
48
- const sharedAPIFilters = ['endpoint1', 'endpoint2']
49
- const apiFilterDropdowns: APIFilterDropdowns = {
50
- endpoint1: { text: 'text1', value: 'value1' },
51
- endpoint2: { text: 'text2', value: 'value2' }
52
- }
53
- expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(apiFilterDropdowns)
54
- })
55
- })
56
-
57
- describe('getParentParams', () => {
58
- it('should return null when there are no parent filters', () => {
59
- const childFilter: SharedFilter = {
60
- key: 'child',
61
- parents: [],
62
- apiFilter: {}
63
- }
64
- const sharedFilters: SharedFilter[] = []
65
-
66
- const result = getParentParams(childFilter, sharedFilters)
67
- expect(result).toBeNull()
68
- })
69
-
70
- it('should return key-value pairs for nestedDropdown parent filters', () => {
71
- const childFilter: SharedFilter = {
72
- key: 'child',
73
- parents: ['parent1'],
74
- apiFilter: {}
75
- }
76
- const sharedFilters: SharedFilter[] = [
77
- {
78
- key: 'parent1',
79
- filterStyle: FILTER_STYLE.nestedDropdown,
80
- apiFilter: {
81
- valueSelector: 'parent1Value',
82
- subgroupValueSelector: 'parent1SubValue'
83
- },
84
- active: 'activeValue1',
85
- subGrouping: {
86
- active: 'activeSubValue1'
87
- }
88
- }
89
- ]
90
-
91
- const expectedOutput = [
92
- { key: 'parent1Value', value: 'activeValue1' },
93
- { key: 'parent1SubValue', value: 'activeSubValue1' }
94
- ]
95
-
96
- const result = getParentParams(childFilter, sharedFilters)
97
- expect(result).toEqual(expectedOutput)
98
- })
99
-
100
- it('should return key-value pairs for non-nestedDropdown parent filters', () => {
101
- const childFilter: SharedFilter = {
102
- key: 'child',
103
- parents: ['parent1'],
104
- apiFilter: {}
105
- }
106
- const sharedFilters: SharedFilter[] = [
107
- {
108
- key: 'parent1',
109
- filterStyle: 'someOtherStyle',
110
- apiFilter: {
111
- valueSelector: 'parent1Value'
112
- },
113
- active: 'activeValue1'
114
- }
115
- ]
116
-
117
- const expectedOutput = [{ key: 'parent1Value', value: 'activeValue1' }]
118
-
119
- const result = getParentParams(childFilter, sharedFilters)
120
- expect(result).toEqual(expectedOutput)
121
- })
122
-
123
- it('should handle queuedActive values for nestedDropdown parent filters', () => {
124
- const childFilter: SharedFilter = {
125
- key: 'child',
126
- parents: ['parent1'],
127
- apiFilter: {}
128
- }
129
- const sharedFilters: SharedFilter[] = [
130
- {
131
- key: 'parent1',
132
- filterStyle: FILTER_STYLE.nestedDropdown,
133
- apiFilter: {
134
- valueSelector: 'parent1Value',
135
- subgroupValueSelector: 'parent1SubValue'
136
- },
137
- queuedActive: ['queuedActiveValue1', 'queuedActiveSubValue1']
138
- }
139
- ]
140
-
141
- const expectedOutput = [
142
- { key: 'parent1Value', value: 'queuedActiveValue1' },
143
- { key: 'parent1SubValue', value: 'queuedActiveSubValue1' }
144
- ]
145
-
146
- const result = getParentParams(childFilter, sharedFilters)
147
- expect(result).toEqual(expectedOutput)
148
- })
149
-
150
- it('should handle queuedActive values for non-nestedDropdown parent filters', () => {
151
- const childFilter: SharedFilter = {
152
- key: 'child',
153
- parents: ['parent1'],
154
- apiFilter: {}
155
- }
156
- const sharedFilters: SharedFilter[] = [
157
- {
158
- key: 'parent1',
159
- filterStyle: 'someOtherStyle',
160
- apiFilter: {
161
- valueSelector: 'parent1Value'
162
- },
163
- queuedActive: 'queuedActiveValue1'
164
- }
165
- ]
166
-
167
- const expectedOutput = [{ key: 'parent1Value', value: 'queuedActiveValue1' }]
168
-
169
- const result = getParentParams(childFilter, sharedFilters)
170
- expect(result).toEqual(expectedOutput)
171
- })
172
-
173
- it('should handle array values for non-nestedDropdown parent filters', () => {
174
- const childFilter: SharedFilter = {
175
- key: 'child',
176
- parents: ['parent1'],
177
- apiFilter: {}
178
- }
179
- const sharedFilters: SharedFilter[] = [
180
- {
181
- key: 'parent1',
182
- filterStyle: 'someOtherStyle',
183
- apiFilter: {
184
- valueSelector: 'parent1Value'
185
- },
186
- active: ['activeValue1', 'activeValue2']
187
- }
188
- ]
189
-
190
- const expectedOutput = [
191
- { key: 'parent1Value', value: 'activeValue1' },
192
- { key: 'parent1Value', value: 'activeValue2' }
193
- ]
194
-
195
- const result = getParentParams(childFilter, sharedFilters)
196
- expect(result).toEqual(expectedOutput)
197
- })
198
- })
199
-
200
- describe('getFilterValues', () => {
201
- it('should return correct filter values for valid inputs', () => {
202
- const data = [{ key1: 'value1', key2: 'value2' }]
203
- const apiFilter = { textSelector: 'key1', valueSelector: 'key2' }
204
- const expectedOutput = [{ text: 'value1', value: 'value2' }]
205
- expect(getFilterValues(data, apiFilter)).toEqual(expectedOutput)
206
- delete apiFilter.textSelector
207
- const expectedOutput2 = [{ text: 'value2', value: 'value2' }]
208
- expect(getFilterValues(data, apiFilter)).toEqual(expectedOutput2)
209
- })
210
-
211
- it('should return nested dropdown options when subgroupValueSelector is provided', () => {
212
- const data = [
213
- { id: 1, name: 'Group 1', subId: 101, subName: 'Subgroup 1-1' },
214
- { id: 1, name: 'Group 1', subId: 102, subName: 'Subgroup 1-2' },
215
- { id: 2, name: 'Group 2', subId: 201, subName: 'Subgroup 2-1' }
216
- ]
217
-
218
- const apiFilter = {
219
- textSelector: 'name',
220
- valueSelector: 'id',
221
- subgroupTextSelector: 'subName',
222
- subgroupValueSelector: 'subId'
223
- }
224
-
225
- const expectedOutput = [
226
- {
227
- text: 'Group 1',
228
- value: 1,
229
- subOptions: [
230
- { text: 'Subgroup 1-1', value: 101 },
231
- { text: 'Subgroup 1-2', value: 102 }
232
- ]
233
- },
234
- {
235
- text: 'Group 2',
236
- value: 2,
237
- subOptions: [{ text: 'Subgroup 2-1', value: 201 }]
238
- }
239
- ]
240
-
241
- expect(getFilterValues(data, apiFilter)).toEqual(expectedOutput)
242
- })
243
- })
244
-
245
- describe('getToFetch', () => {
246
- it('should return an empty object when sharedAPIFilters is empty', () => {
247
- const result = getToFetch([], {})
248
- expect(result).toEqual({})
249
- })
250
-
251
- it('should return an object with endpoints when apiFilterDropdowns is empty', () => {
252
- const sharedAPIFilters = [{ apiFilter: { apiEndpoint: '/endpoint1' }, parents: [] }]
253
- const result = getToFetch(sharedAPIFilters, {})
254
- expect(result).toEqual({ '/endpoint1': ['/endpoint1', 0] })
255
- })
256
-
257
- it('should return and empty object when sharedAPIFilters contains filters with no parents', () => {
258
- const sharedAPIFilters = [{ apiFilter: { apiEndpoint: '/endpoint1' }, parents: [] }]
259
- const apiFilterDropdowns = { '/endpoint1': true }
260
- const result = getToFetch(sharedAPIFilters, apiFilterDropdowns)
261
- expect(result).toEqual({})
262
- })
263
-
264
- it('should return an empty object when parentParams contains an empty value', () => {
265
- const sharedAPIFilters = [
266
- { key: 'parent1', apiFilter: { apiEndpoint: '/endpoint1' }, parents: [] },
267
- { apiFilter: { apiEndpoint: '/endpoint1' }, parents: ['parent1'] }
268
- ]
269
- const apiFilterDropdowns = { '/endpoint1': true }
270
- const result = getToFetch(sharedAPIFilters, apiFilterDropdowns)
271
- expect(result).toEqual({})
272
- })
273
-
274
- it('should return an empty object when parentParams contains an empty value', () => {
275
- const sharedAPIFilters = [
276
- { key: 'parent1', value: '', apiFilter: { apiEndpoint: '/endpoint1' }, parents: [] },
277
- { apiFilter: { apiEndpoint: '/endpoint1' }, parents: ['parent1'] }
278
- ]
279
- const apiFilterDropdowns = { '/endpoint1': true }
280
- const result = getToFetch(sharedAPIFilters, apiFilterDropdowns)
281
- expect(result).toEqual({})
282
- })
283
- })
284
-
285
- describe('setActiveNestedDropdown', () => {
286
- const dropdownOptions = [
287
- { value: 'option1', subOptions: [{ value: 'subOption1' }], label: 'Option 1' },
288
- { value: 'option2', subOptions: [{ value: 'subOption2' }], label: 'Option 2' }
289
- ]
290
-
291
- const sharedFilters = [
292
- {
293
- key: 'filter1',
294
- active: null,
295
- filterStyle: FILTER_STYLE.nestedDropdown,
296
- subGrouping: {},
297
- queuedActive: null,
298
- parents: []
299
- },
300
- {
301
- key: 'filter2',
302
- active: null,
303
- setByQueryParameter: 'group',
304
- filterStyle: FILTER_STYLE.nestedDropdown,
305
- subGrouping: { setByQueryParameter: 'subgroup' },
306
- queuedActive: null,
307
- parents: ['filter1']
308
- }
309
- ] as SharedFilter[]
310
-
311
- it('should set the active value for a nested dropdown', () => {
312
- setActiveNestedDropdown(dropdownOptions, sharedFilters[0])
313
- expect(sharedFilters[0].active).toEqual('option1')
314
- expect(sharedFilters[0].subGrouping.active).toEqual('subOption1')
315
- })
316
- it('should set the active value for nested dropdown with query parameters', () => {
317
- delete window.location
318
- window.location = new URL('https://www.example.com?group=option2&subgroup=subOption2')
319
- setActiveNestedDropdown(dropdownOptions, sharedFilters[1])
320
- expect(sharedFilters[1].active).toEqual('option2')
321
- expect(sharedFilters[1].subGrouping.active).toEqual('subOption2')
322
- })
323
- })
324
-
325
- describe('setActiveMultiDropdown', () => {
326
- const dropdownOptions = [
327
- { value: 'option1', label: 'Option 1' },
328
- { value: 'option2', label: 'Option 2' }
329
- ]
330
-
331
- const sharedFilters = [
332
- {
333
- key: 'filter1',
334
- active: null,
335
- filterStyle: FILTER_STYLE.multiSelect,
336
- queuedActive: null,
337
- parents: []
338
- },
339
- {
340
- key: 'filter2',
341
- active: null,
342
- filterStyle: FILTER_STYLE.multiSelect,
343
- setByQueryParameter: 'group',
344
- queuedActive: null,
345
- parents: ['filter1']
346
- }
347
- ] as SharedFilter[]
348
- it('should set the active value for a multi dropdown', () => {
349
- setActiveMultiDropdown(dropdownOptions, sharedFilters[0])
350
- expect(sharedFilters[0].active).toEqual(['option1'])
351
- })
352
- it('should set the active value for a multi dropdown with queryParameters', () => {
353
- delete window.location
354
- window.location = new URL('https://www.example.com?group=option1&group=option2')
355
- setActiveMultiDropdown(dropdownOptions, sharedFilters[1])
356
- expect(sharedFilters[1].active).toEqual(['option1', 'option2'])
357
- delete window.location
358
- window.location = new URL('https://www.example.com?group=option1,option2')
359
- setActiveMultiDropdown(dropdownOptions, sharedFilters[1])
360
- expect(sharedFilters[1].active).toEqual(['option1', 'option2'])
361
- })
362
- })
363
-
364
- describe('setAutoLoadDefaultValue', () => {
365
- const dropdownOptions = [
366
- { value: 'option1', label: 'Option 1' },
367
- { value: 'option2', label: 'Option 2' }
368
- ]
369
-
370
- const sharedFilters = [
371
- { key: 'filter1', active: null, queuedActive: null, parents: [] },
372
- { key: 'filter2', active: null, queuedActive: null, parents: ['filter1'] }
373
- ]
374
-
375
- it('should return the original filter when autoLoadFilterIndexes is empty', () => {
376
- const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFilters, [])
377
- expect(result).toEqual(sharedFilters[0])
378
- })
379
-
380
- it('should return the original filter when dropdownOptions is empty', () => {
381
- const result = setAutoLoadDefaultValue(0, [], sharedFilters, [0])
382
- expect(result).toEqual(sharedFilters[0])
383
- })
384
-
385
- it('should return the original filter when dropdownOptions is undefined', () => {
386
- const result = setAutoLoadDefaultValue(0, undefined, sharedFilters, [0])
387
- expect(result).toEqual(sharedFilters[0])
388
- })
389
-
390
- it('should return the original filter when sharedFilterIndex is not in autoLoadFilterIndexes', () => {
391
- const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFilters, [1])
392
- expect(result).toEqual(sharedFilters[0])
393
- })
394
-
395
- it('should return the original filter when not all parent filters are selected', () => {
396
- const result = setAutoLoadDefaultValue(1, dropdownOptions, sharedFilters, [1])
397
- expect(result).toEqual(sharedFilters[1])
398
- })
399
-
400
- it('should assign the default value from dropdownOptions when no active value is set', () => {
401
- const sharedFiltersCopy = _.cloneDeep(sharedFilters)
402
- sharedFiltersCopy[0].active = null
403
- const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFiltersCopy, [0])
404
- expect(result.active).toEqual('option1')
405
- })
406
-
407
- it('should retain the current active value if it exists in dropdownOptions', () => {
408
- const sharedFiltersCopy = _.cloneDeep(sharedFilters)
409
- sharedFiltersCopy[0].active = 'option1'
410
- const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFiltersCopy, [0])
411
- expect(result.active).toEqual('option1')
412
- })
413
-
414
- it('should assign the default value if the current active value does not exist in dropdownOptions', () => {
415
- const sharedFiltersCopy = _.cloneDeep(sharedFilters)
416
- sharedFiltersCopy[0].active = 'nonexistent'
417
- const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFiltersCopy, [0])
418
- expect(result.active).toEqual('option1')
419
- })
420
- })
1
+ import {
2
+ setAutoLoadDefaultValue,
3
+ getToFetch,
4
+ getFilterValues,
5
+ getLoadingFilterMemo,
6
+ getParentParams,
7
+ setActiveNestedDropdown,
8
+ setActiveMultiDropdown
9
+ } from '../apiFilterHelpers'
10
+ import _ from 'lodash'
11
+ import type { APIFilterDropdowns } from '../../components/DashboardFilters'
12
+ import { SharedFilter } from '../../types/SharedFilter'
13
+ import { FILTER_STYLE } from '../../types/FilterStyles'
14
+
15
+ describe('getLoadingFilterMemo', () => {
16
+ it('should return correct APIFilterDropdowns for valid inputs', () => {
17
+ const sharedAPIFilters = ['endpoint1', 'endpoint2']
18
+ const apiFilterDropdowns: APIFilterDropdowns = {
19
+ endpoint1: { text: 'text1', value: 'value1' }
20
+ }
21
+ const expectedOutput: APIFilterDropdowns = {
22
+ endpoint1: { text: 'text1', value: 'value1' },
23
+ endpoint2: undefined
24
+ }
25
+ expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(expectedOutput)
26
+ })
27
+
28
+ it('should return an empty object for empty sharedAPIFilters', () => {
29
+ const sharedAPIFilters: string[] = []
30
+ const apiFilterDropdowns: APIFilterDropdowns = {
31
+ endpoint1: { text: 'text1', value: 'value1' }
32
+ }
33
+ const expectedOutput: APIFilterDropdowns = {}
34
+ expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(expectedOutput)
35
+ })
36
+
37
+ it('should return APIFilterDropdowns with null values for empty apiFilterDropdowns', () => {
38
+ const sharedAPIFilters = ['endpoint1', 'endpoint2']
39
+ const apiFilterDropdowns: APIFilterDropdowns = {}
40
+ const expectedOutput: APIFilterDropdowns = {
41
+ endpoint1: undefined,
42
+ endpoint2: undefined
43
+ }
44
+ expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(expectedOutput)
45
+ })
46
+
47
+ it('should not overwrite existing data in apiFilterDropdowns, so return original dropdowns', () => {
48
+ const sharedAPIFilters = ['endpoint1', 'endpoint2']
49
+ const apiFilterDropdowns: APIFilterDropdowns = {
50
+ endpoint1: { text: 'text1', value: 'value1' },
51
+ endpoint2: { text: 'text2', value: 'value2' }
52
+ }
53
+ expect(getLoadingFilterMemo(sharedAPIFilters, apiFilterDropdowns)).toEqual(apiFilterDropdowns)
54
+ })
55
+ })
56
+
57
+ describe('getParentParams', () => {
58
+ it('should return null when there are no parent filters', () => {
59
+ const childFilter: SharedFilter = {
60
+ key: 'child',
61
+ parents: [],
62
+ apiFilter: {}
63
+ }
64
+ const sharedFilters: SharedFilter[] = []
65
+
66
+ const result = getParentParams(childFilter, sharedFilters)
67
+ expect(result).toBeNull()
68
+ })
69
+
70
+ it('should return key-value pairs for nestedDropdown parent filters', () => {
71
+ const childFilter: SharedFilter = {
72
+ key: 'child',
73
+ parents: ['parent1'],
74
+ apiFilter: {}
75
+ }
76
+ const sharedFilters: SharedFilter[] = [
77
+ {
78
+ key: 'parent1',
79
+ filterStyle: FILTER_STYLE.nestedDropdown,
80
+ apiFilter: {
81
+ valueSelector: 'parent1Value',
82
+ subgroupValueSelector: 'parent1SubValue'
83
+ },
84
+ active: 'activeValue1',
85
+ subGrouping: {
86
+ active: 'activeSubValue1'
87
+ }
88
+ }
89
+ ]
90
+
91
+ const expectedOutput = [
92
+ { key: 'parent1Value', value: 'activeValue1' },
93
+ { key: 'parent1SubValue', value: 'activeSubValue1' }
94
+ ]
95
+
96
+ const result = getParentParams(childFilter, sharedFilters)
97
+ expect(result).toEqual(expectedOutput)
98
+ })
99
+
100
+ it('should return key-value pairs for non-nestedDropdown parent filters', () => {
101
+ const childFilter: SharedFilter = {
102
+ key: 'child',
103
+ parents: ['parent1'],
104
+ apiFilter: {}
105
+ }
106
+ const sharedFilters: SharedFilter[] = [
107
+ {
108
+ key: 'parent1',
109
+ filterStyle: 'someOtherStyle',
110
+ apiFilter: {
111
+ valueSelector: 'parent1Value'
112
+ },
113
+ active: 'activeValue1'
114
+ }
115
+ ]
116
+
117
+ const expectedOutput = [{ key: 'parent1Value', value: 'activeValue1' }]
118
+
119
+ const result = getParentParams(childFilter, sharedFilters)
120
+ expect(result).toEqual(expectedOutput)
121
+ })
122
+
123
+ it('should handle queuedActive values for nestedDropdown parent filters', () => {
124
+ const childFilter: SharedFilter = {
125
+ key: 'child',
126
+ parents: ['parent1'],
127
+ apiFilter: {}
128
+ }
129
+ const sharedFilters: SharedFilter[] = [
130
+ {
131
+ key: 'parent1',
132
+ filterStyle: FILTER_STYLE.nestedDropdown,
133
+ apiFilter: {
134
+ valueSelector: 'parent1Value',
135
+ subgroupValueSelector: 'parent1SubValue'
136
+ },
137
+ queuedActive: ['queuedActiveValue1', 'queuedActiveSubValue1']
138
+ }
139
+ ]
140
+
141
+ const expectedOutput = [
142
+ { key: 'parent1Value', value: 'queuedActiveValue1' },
143
+ { key: 'parent1SubValue', value: 'queuedActiveSubValue1' }
144
+ ]
145
+
146
+ const result = getParentParams(childFilter, sharedFilters)
147
+ expect(result).toEqual(expectedOutput)
148
+ })
149
+
150
+ it('should handle queuedActive values for non-nestedDropdown parent filters', () => {
151
+ const childFilter: SharedFilter = {
152
+ key: 'child',
153
+ parents: ['parent1'],
154
+ apiFilter: {}
155
+ }
156
+ const sharedFilters: SharedFilter[] = [
157
+ {
158
+ key: 'parent1',
159
+ filterStyle: 'someOtherStyle',
160
+ apiFilter: {
161
+ valueSelector: 'parent1Value'
162
+ },
163
+ queuedActive: 'queuedActiveValue1'
164
+ }
165
+ ]
166
+
167
+ const expectedOutput = [{ key: 'parent1Value', value: 'queuedActiveValue1' }]
168
+
169
+ const result = getParentParams(childFilter, sharedFilters)
170
+ expect(result).toEqual(expectedOutput)
171
+ })
172
+
173
+ it('should handle array values for non-nestedDropdown parent filters', () => {
174
+ const childFilter: SharedFilter = {
175
+ key: 'child',
176
+ parents: ['parent1'],
177
+ apiFilter: {}
178
+ }
179
+ const sharedFilters: SharedFilter[] = [
180
+ {
181
+ key: 'parent1',
182
+ filterStyle: 'someOtherStyle',
183
+ apiFilter: {
184
+ valueSelector: 'parent1Value'
185
+ },
186
+ active: ['activeValue1', 'activeValue2']
187
+ }
188
+ ]
189
+
190
+ const expectedOutput = [
191
+ { key: 'parent1Value', value: 'activeValue1' },
192
+ { key: 'parent1Value', value: 'activeValue2' }
193
+ ]
194
+
195
+ const result = getParentParams(childFilter, sharedFilters)
196
+ expect(result).toEqual(expectedOutput)
197
+ })
198
+ })
199
+
200
+ describe('getFilterValues', () => {
201
+ it('should return correct filter values for valid inputs', () => {
202
+ const data = [{ key1: 'value1', key2: 'value2' }]
203
+ const apiFilter = { textSelector: 'key1', valueSelector: 'key2' }
204
+ const expectedOutput = [{ text: 'value1', value: 'value2' }]
205
+ expect(getFilterValues(data, apiFilter)).toEqual(expectedOutput)
206
+ delete apiFilter.textSelector
207
+ const expectedOutput2 = [{ text: 'value2', value: 'value2' }]
208
+ expect(getFilterValues(data, apiFilter)).toEqual(expectedOutput2)
209
+ })
210
+
211
+ it('should return nested dropdown options when subgroupValueSelector is provided', () => {
212
+ const data = [
213
+ { id: 1, name: 'Group 1', subId: 101, subName: 'Subgroup 1-1' },
214
+ { id: 1, name: 'Group 1', subId: 102, subName: 'Subgroup 1-2' },
215
+ { id: 2, name: 'Group 2', subId: 201, subName: 'Subgroup 2-1' }
216
+ ]
217
+
218
+ const apiFilter = {
219
+ textSelector: 'name',
220
+ valueSelector: 'id',
221
+ subgroupTextSelector: 'subName',
222
+ subgroupValueSelector: 'subId'
223
+ }
224
+
225
+ const expectedOutput = [
226
+ {
227
+ text: 'Group 1',
228
+ value: 1,
229
+ subOptions: [
230
+ { text: 'Subgroup 1-1', value: 101 },
231
+ { text: 'Subgroup 1-2', value: 102 }
232
+ ]
233
+ },
234
+ {
235
+ text: 'Group 2',
236
+ value: 2,
237
+ subOptions: [{ text: 'Subgroup 2-1', value: 201 }]
238
+ }
239
+ ]
240
+
241
+ expect(getFilterValues(data, apiFilter)).toEqual(expectedOutput)
242
+ })
243
+ })
244
+
245
+ describe('getToFetch', () => {
246
+ it('should return an empty object when sharedAPIFilters is empty', () => {
247
+ const result = getToFetch([], {})
248
+ expect(result).toEqual({})
249
+ })
250
+
251
+ it('should return an object with endpoints when apiFilterDropdowns is empty', () => {
252
+ const sharedAPIFilters = [{ apiFilter: { apiEndpoint: '/endpoint1' }, parents: [] }]
253
+ const result = getToFetch(sharedAPIFilters, {})
254
+ expect(result).toEqual({ '/endpoint1': ['/endpoint1', 0] })
255
+ })
256
+
257
+ it('should return and empty object when sharedAPIFilters contains filters with no parents', () => {
258
+ const sharedAPIFilters = [{ apiFilter: { apiEndpoint: '/endpoint1' }, parents: [] }]
259
+ const apiFilterDropdowns = { '/endpoint1': true }
260
+ const result = getToFetch(sharedAPIFilters, apiFilterDropdowns)
261
+ expect(result).toEqual({})
262
+ })
263
+
264
+ it('should return an empty object when parentParams contains an empty value', () => {
265
+ const sharedAPIFilters = [
266
+ { key: 'parent1', apiFilter: { apiEndpoint: '/endpoint1' }, parents: [] },
267
+ { apiFilter: { apiEndpoint: '/endpoint1' }, parents: ['parent1'] }
268
+ ]
269
+ const apiFilterDropdowns = { '/endpoint1': true }
270
+ const result = getToFetch(sharedAPIFilters, apiFilterDropdowns)
271
+ expect(result).toEqual({})
272
+ })
273
+
274
+ it('should return an empty object when parentParams contains an empty value', () => {
275
+ const sharedAPIFilters = [
276
+ { key: 'parent1', value: '', apiFilter: { apiEndpoint: '/endpoint1' }, parents: [] },
277
+ { apiFilter: { apiEndpoint: '/endpoint1' }, parents: ['parent1'] }
278
+ ]
279
+ const apiFilterDropdowns = { '/endpoint1': true }
280
+ const result = getToFetch(sharedAPIFilters, apiFilterDropdowns)
281
+ expect(result).toEqual({})
282
+ })
283
+ })
284
+
285
+ describe('setActiveNestedDropdown', () => {
286
+ const dropdownOptions = [
287
+ { value: 'option1', subOptions: [{ value: 'subOption1' }], label: 'Option 1' },
288
+ { value: 'option2', subOptions: [{ value: 'subOption2' }], label: 'Option 2' }
289
+ ]
290
+
291
+ const sharedFilters = [
292
+ {
293
+ key: 'filter1',
294
+ active: null,
295
+ filterStyle: FILTER_STYLE.nestedDropdown,
296
+ subGrouping: {},
297
+ queuedActive: null,
298
+ parents: []
299
+ },
300
+ {
301
+ key: 'filter2',
302
+ active: null,
303
+ setByQueryParameter: 'group',
304
+ filterStyle: FILTER_STYLE.nestedDropdown,
305
+ subGrouping: { setByQueryParameter: 'subgroup' },
306
+ queuedActive: null,
307
+ parents: ['filter1']
308
+ }
309
+ ] as SharedFilter[]
310
+
311
+ it('should set the active value for a nested dropdown', () => {
312
+ setActiveNestedDropdown(dropdownOptions, sharedFilters[0])
313
+ expect(sharedFilters[0].active).toEqual('option1')
314
+ expect(sharedFilters[0].subGrouping.active).toEqual('subOption1')
315
+ })
316
+ it('should set the active value for nested dropdown with query parameters', () => {
317
+ delete window.location
318
+ window.location = new URL('https://www.example.com?group=option2&subgroup=subOption2')
319
+ setActiveNestedDropdown(dropdownOptions, sharedFilters[1])
320
+ expect(sharedFilters[1].active).toEqual('option2')
321
+ expect(sharedFilters[1].subGrouping.active).toEqual('subOption2')
322
+ })
323
+
324
+ it('should respect configured defaultValue for nested dropdown group', () => {
325
+ const filter = {
326
+ key: 'filter1',
327
+ active: null,
328
+ defaultValue: 'option2',
329
+ filterStyle: FILTER_STYLE.nestedDropdown,
330
+ subGrouping: { active: null },
331
+ queuedActive: null,
332
+ parents: []
333
+ } as SharedFilter
334
+
335
+ setActiveNestedDropdown(dropdownOptions, filter)
336
+ expect(filter.active).toEqual('option2')
337
+ expect(filter.subGrouping.active).toEqual('subOption2')
338
+ })
339
+
340
+ it('should respect configured defaultValue for nested dropdown subgroup', () => {
341
+ const options = [
342
+ {
343
+ value: 'option1',
344
+ subOptions: [{ value: 'subA' }, { value: 'subB' }, { value: 'subC' }]
345
+ }
346
+ ]
347
+
348
+ const filter = {
349
+ key: 'filter1',
350
+ active: null,
351
+ defaultValue: 'option1',
352
+ filterStyle: FILTER_STYLE.nestedDropdown,
353
+ subGrouping: { active: null, defaultValue: 'subC' },
354
+ queuedActive: null,
355
+ parents: []
356
+ } as SharedFilter
357
+
358
+ setActiveNestedDropdown(options, filter)
359
+ expect(filter.active).toEqual('option1')
360
+ expect(filter.subGrouping.active).toEqual('subC')
361
+ })
362
+
363
+ it('should prioritize query parameter over defaultValue', () => {
364
+ delete window.location
365
+ window.location = new URL('https://www.example.com?year=option2&quarter=subOption2')
366
+
367
+ const filter = {
368
+ key: 'filter1',
369
+ active: null,
370
+ defaultValue: 'option1',
371
+ setByQueryParameter: 'year',
372
+ filterStyle: FILTER_STYLE.nestedDropdown,
373
+ subGrouping: { active: null, defaultValue: 'subOption1', setByQueryParameter: 'quarter' },
374
+ queuedActive: null,
375
+ parents: []
376
+ } as SharedFilter
377
+
378
+ setActiveNestedDropdown(dropdownOptions, filter)
379
+ expect(filter.active).toEqual('option2')
380
+ expect(filter.subGrouping.active).toEqual('subOption2')
381
+ })
382
+
383
+ it('should handle type coercion for number and string values (loose equality)', () => {
384
+ // Simulate dropdown with numeric values but string query params
385
+ const numericOptions = [
386
+ { value: 2023, subOptions: [{ value: 1 }, { value: 2 }] },
387
+ { value: 2024, subOptions: [{ value: 1 }, { value: 2 }] }
388
+ ]
389
+
390
+ delete window.location
391
+ window.location = new URL('https://www.example.com?year=2023&quarter=2')
392
+
393
+ const filter = {
394
+ key: 'filter1',
395
+ active: null,
396
+ setByQueryParameter: 'year',
397
+ filterStyle: FILTER_STYLE.nestedDropdown,
398
+ subGrouping: { active: null, setByQueryParameter: 'quarter' },
399
+ queuedActive: null,
400
+ parents: []
401
+ } as SharedFilter
402
+
403
+ setActiveNestedDropdown(numericOptions, filter)
404
+ // Should match '2023' (string) with 2023 (number) using loose equality
405
+ expect(filter.active).toEqual('2023')
406
+ // Should match '2' (string) with 2 (number) using loose equality
407
+ expect(filter.subGrouping.active).toEqual('2')
408
+ })
409
+ })
410
+
411
+ describe('setActiveMultiDropdown', () => {
412
+ const dropdownOptions = [
413
+ { value: 'option1', label: 'Option 1' },
414
+ { value: 'option2', label: 'Option 2' }
415
+ ]
416
+
417
+ const sharedFilters = [
418
+ {
419
+ key: 'filter1',
420
+ active: null,
421
+ filterStyle: FILTER_STYLE.multiSelect,
422
+ queuedActive: null,
423
+ parents: []
424
+ },
425
+ {
426
+ key: 'filter2',
427
+ active: null,
428
+ filterStyle: FILTER_STYLE.multiSelect,
429
+ setByQueryParameter: 'group',
430
+ queuedActive: null,
431
+ parents: ['filter1']
432
+ }
433
+ ] as SharedFilter[]
434
+ it('should set the active value for a multi dropdown', () => {
435
+ setActiveMultiDropdown(dropdownOptions, sharedFilters[0])
436
+ expect(sharedFilters[0].active).toEqual(['option1'])
437
+ })
438
+ it('should set the active value for a multi dropdown with queryParameters', () => {
439
+ delete window.location
440
+ window.location = new URL('https://www.example.com?group=option1&group=option2')
441
+ setActiveMultiDropdown(dropdownOptions, sharedFilters[1])
442
+ expect(sharedFilters[1].active).toEqual(['option1', 'option2'])
443
+ delete window.location
444
+ window.location = new URL('https://www.example.com?group=option1,option2')
445
+ setActiveMultiDropdown(dropdownOptions, sharedFilters[1])
446
+ expect(sharedFilters[1].active).toEqual(['option1', 'option2'])
447
+ })
448
+
449
+ it('should fallback to first option when query parameter is empty array', () => {
450
+ const filter = {
451
+ key: 'filter',
452
+ active: null,
453
+ filterStyle: FILTER_STYLE.multiSelect,
454
+ setByQueryParameter: 'group',
455
+ queuedActive: null,
456
+ parents: []
457
+ } as SharedFilter
458
+
459
+ // Mock getQueryParam to return empty array
460
+ delete window.location
461
+ window.location = new URL('https://www.example.com?group=')
462
+ setActiveMultiDropdown(dropdownOptions, filter)
463
+ expect(filter.active).toEqual(['option1'])
464
+ })
465
+ })
466
+
467
+ describe('setAutoLoadDefaultValue', () => {
468
+ const dropdownOptions = [
469
+ { value: 'option1', label: 'Option 1' },
470
+ { value: 'option2', label: 'Option 2' }
471
+ ]
472
+
473
+ const sharedFilters = [
474
+ { key: 'filter1', active: null, queuedActive: null, parents: [] },
475
+ { key: 'filter2', active: null, queuedActive: null, parents: ['filter1'] }
476
+ ]
477
+
478
+ it('should return the original filter when autoLoadFilterIndexes is empty', () => {
479
+ const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFilters, [])
480
+ expect(result).toEqual(sharedFilters[0])
481
+ })
482
+
483
+ it('should return the original filter when dropdownOptions is empty', () => {
484
+ const result = setAutoLoadDefaultValue(0, [], sharedFilters, [0])
485
+ expect(result).toEqual(sharedFilters[0])
486
+ })
487
+
488
+ it('should return the original filter when dropdownOptions is undefined', () => {
489
+ const result = setAutoLoadDefaultValue(0, undefined, sharedFilters, [0])
490
+ expect(result).toEqual(sharedFilters[0])
491
+ })
492
+
493
+ it('should return the original filter when sharedFilterIndex is not in autoLoadFilterIndexes', () => {
494
+ const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFilters, [1])
495
+ expect(result).toEqual(sharedFilters[0])
496
+ })
497
+
498
+ it('should return the original filter when not all parent filters are selected', () => {
499
+ const result = setAutoLoadDefaultValue(1, dropdownOptions, sharedFilters, [1])
500
+ expect(result).toEqual(sharedFilters[1])
501
+ })
502
+
503
+ it('should assign the default value from dropdownOptions when no active value is set', () => {
504
+ const sharedFiltersCopy = _.cloneDeep(sharedFilters)
505
+ sharedFiltersCopy[0].active = null
506
+ const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFiltersCopy, [0])
507
+ expect(result.active).toEqual('option1')
508
+ })
509
+
510
+ it('should retain the current active value if it exists in dropdownOptions', () => {
511
+ const sharedFiltersCopy = _.cloneDeep(sharedFilters)
512
+ sharedFiltersCopy[0].active = 'option1'
513
+ const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFiltersCopy, [0])
514
+ expect(result.active).toEqual('option1')
515
+ })
516
+
517
+ it('should assign the default value if the current active value does not exist in dropdownOptions', () => {
518
+ const sharedFiltersCopy = _.cloneDeep(sharedFilters)
519
+ sharedFiltersCopy[0].active = 'nonexistent'
520
+ const result = setAutoLoadDefaultValue(0, dropdownOptions, sharedFiltersCopy, [0])
521
+ expect(result.active).toEqual('option1')
522
+ })
523
+ })