@cdc/dashboard 4.25.10 → 4.26.1

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 (86) hide show
  1. package/Dynamic_Data.md +66 -0
  2. package/dist/{cdcdashboard-fce76882.es.js → cdcdashboard-BnB1QM5d.es.js} +6 -13
  3. package/dist/{cdcdashboard-c55ac1ea.es.js → cdcdashboard-D6CG2-Hb.es.js} +5 -12
  4. package/dist/{cdcdashboard-31a33da1.es.js → cdcdashboard-MXgURbdZ.es.js} +6 -13
  5. package/dist/{cdcdashboard-1a1724a1.es.js → cdcdashboard-dgT_1dIT.es.js} +136 -151
  6. package/dist/cdcdashboard.js +84214 -79641
  7. package/examples/api-dashboard-data.json +272 -0
  8. package/examples/api-dashboard-years.json +11 -0
  9. package/examples/api-geographies-data.json +11 -0
  10. package/examples/api-test/categories.json +18 -0
  11. package/examples/api-test/chart-data.json +602 -0
  12. package/examples/api-test/topics.json +47 -0
  13. package/examples/api-test/years.json +22 -0
  14. package/examples/markup-axis-label.json +4167 -0
  15. package/examples/private/big-dashboard.json +39095 -39077
  16. package/examples/private/cat-y.json +1235 -0
  17. package/examples/private/chronic-dash.json +1584 -0
  18. package/examples/private/clade-2.json +430 -0
  19. package/examples/private/diabetes.json +546 -196
  20. package/examples/private/map-issue.json +2260 -0
  21. package/examples/private/markup-footer/mortality-deaths-footnotes-age.csv +3 -0
  22. package/examples/private/mpinc-state-reports.json +2260 -0
  23. package/examples/private/mpox.json +38128 -0
  24. package/examples/private/nwss/rsv.json +1240 -0
  25. package/examples/private/reset.json +32920 -0
  26. package/examples/private/simple-dash.json +490 -0
  27. package/examples/private/test-dash.json +0 -0
  28. package/examples/private/test123.json +491 -0
  29. package/examples/test-api-filter-reset.json +132 -0
  30. package/examples/test-dashboard-simple.json +503 -0
  31. package/index.html +25 -26
  32. package/package.json +11 -11
  33. package/src/CdcDashboardComponent.tsx +35 -10
  34. package/src/DashboardContext.tsx +3 -1
  35. package/src/_stories/Dashboard.DataSetup.stories.tsx +203 -0
  36. package/src/_stories/Dashboard.stories.tsx +402 -1
  37. package/src/_stories/_mock/custom-order-new-values.json +116 -0
  38. package/src/_stories/_mock/filter-cascade.json +3350 -0
  39. package/src/_stories/_mock/gallery-data-bite-dashboard.json +3500 -0
  40. package/src/_stories/_mock/nested-parent-child-filters.json +392 -0
  41. package/src/_stories/_mock/parent-child-filters.json +233 -0
  42. package/src/components/DashboardFilters/DashboardFilters.tsx +54 -31
  43. package/src/components/DashboardFilters/DashboardFiltersEditor/DashboardFiltersEditor.tsx +118 -50
  44. package/src/components/DashboardFilters/DashboardFiltersEditor/components/FilterEditor.tsx +96 -108
  45. package/src/components/DashboardFilters/DashboardFiltersEditor/components/NestedDropDownDashboard.tsx +196 -59
  46. package/src/components/DashboardFilters/DashboardFiltersWrapper.tsx +129 -29
  47. package/src/components/DashboardFilters/_stories/DashboardFilters.stories.tsx +62 -3
  48. package/src/components/DataDesignerModal.tsx +18 -6
  49. package/src/components/Header/Header.tsx +53 -21
  50. package/src/components/Toggle/Toggle.tsx +48 -48
  51. package/src/components/VisualizationRow.tsx +73 -6
  52. package/src/components/VisualizationsPanel/VisualizationsPanel.tsx +2 -3
  53. package/src/components/Widget/Widget.tsx +1 -1
  54. package/src/data/initial-state.js +1 -0
  55. package/src/helpers/addValuesToDashboardFilters.ts +24 -6
  56. package/src/helpers/apiFilterHelpers.ts +26 -2
  57. package/src/helpers/changeFilterActive.ts +67 -65
  58. package/src/helpers/filterData.ts +52 -7
  59. package/src/helpers/filterResetHelpers.ts +102 -0
  60. package/src/helpers/formatConfigBeforeSave.ts +6 -5
  61. package/src/helpers/getUpdateConfig.ts +91 -91
  62. package/src/helpers/getVizConfig.ts +2 -2
  63. package/src/helpers/loadAPIFilters.ts +109 -99
  64. package/src/helpers/tests/filterResetHelpers.test.ts +532 -0
  65. package/src/helpers/tests/updatesChildFilters.test.ts +53 -22
  66. package/src/helpers/updateChildFilters.ts +50 -27
  67. package/src/index.tsx +1 -0
  68. package/src/scss/editor-panel.scss +3 -431
  69. package/src/scss/main.scss +142 -25
  70. package/src/store/errorMessage/errorMessage.reducer.ts +1 -1
  71. package/src/test/CdcDashboard.test.jsx +9 -4
  72. package/src/types/Dashboard.ts +1 -0
  73. package/src/types/DashboardFilters.ts +9 -8
  74. package/src/types/FilterStyles.ts +8 -7
  75. package/src/types/SharedFilter.ts +13 -0
  76. package/LICENSE +0 -201
  77. package/examples/private/DEV-11072.json +0 -7591
  78. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_data.csv +0 -14041
  79. package/examples/private/burden_toolkit_mortality_diabetes_attributable_deaths_per_100000_data.csv +0 -14041
  80. package/examples/private/burden_toolkit_mortality_qaly_data.csv +0 -18721
  81. package/examples/private/burden_toolkit_mortality_yll_data.csv +0 -18721
  82. package/examples/private/pedro.json +0 -1
  83. package/src/helpers/getAutoLoadVisualization.ts +0 -11
  84. package/src/scss/mixins.scss +0 -47
  85. package/src/scss/variables.scss +0 -5
  86. /package/dist/{cdcdashboard-548642e6.es.js → cdcdashboard-Ct2SB0vL.es.js} +0 -0
@@ -0,0 +1,532 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { getFilterResetValue, resetFilterToValue, clearChildFilterDropdowns } from '../filterResetHelpers'
3
+ import { SharedFilter } from '../../types/SharedFilter'
4
+ import { APIFilterDropdowns } from '../../components/DashboardFilters'
5
+ import { FILTER_STYLE } from '../../types/FilterStyles'
6
+ import _ from 'lodash'
7
+
8
+ describe('getFilterResetValue', () => {
9
+ const mockAPIFilterDropdowns: APIFilterDropdowns = {
10
+ 'api.example.com/filter1': [
11
+ { value: 'option1', text: 'Option 1' },
12
+ { value: 'option2', text: 'Option 2' }
13
+ ],
14
+ 'api.example.com/filter2': []
15
+ }
16
+
17
+ describe('when forceEmpty is true', () => {
18
+ it('should return empty string for API filters without resetLabel', () => {
19
+ const filter: SharedFilter = {
20
+ key: 'testFilter',
21
+ apiFilter: {
22
+ apiEndpoint: 'api.example.com/filter1',
23
+ valueSelector: 'value',
24
+ textSelector: 'text'
25
+ }
26
+ }
27
+
28
+ const result = getFilterResetValue(filter, mockAPIFilterDropdowns, true)
29
+ expect(result).toBe('')
30
+ })
31
+
32
+ it('should return empty string for API filters with resetLabel', () => {
33
+ const filter: SharedFilter = {
34
+ key: 'testFilter',
35
+ resetLabel: 'Select One',
36
+ apiFilter: {
37
+ apiEndpoint: 'api.example.com/filter1',
38
+ valueSelector: 'value',
39
+ textSelector: 'text'
40
+ }
41
+ }
42
+
43
+ const result = getFilterResetValue(filter, mockAPIFilterDropdowns, true)
44
+ expect(result).toBe('')
45
+ })
46
+
47
+ it('should return empty string for data filters with resetLabel', () => {
48
+ const filter: SharedFilter = {
49
+ key: 'testFilter',
50
+ resetLabel: 'Select One',
51
+ values: ['value1', 'value2']
52
+ }
53
+
54
+ const result = getFilterResetValue(filter, mockAPIFilterDropdowns, true)
55
+ expect(result).toBe('')
56
+ })
57
+
58
+ it('should return undefined for data filters without resetLabel', () => {
59
+ const filter: SharedFilter = {
60
+ key: 'testFilter',
61
+ values: ['value1', 'value2']
62
+ }
63
+
64
+ const result = getFilterResetValue(filter, mockAPIFilterDropdowns, true)
65
+ expect(result).toBe('')
66
+ })
67
+ })
68
+
69
+ describe('when forceEmpty is false', () => {
70
+ it('should return defaultValue if it exists', () => {
71
+ const filter: SharedFilter = {
72
+ key: 'testFilter',
73
+ defaultValue: 'defaultVal',
74
+ resetLabel: 'Select One',
75
+ apiFilter: {
76
+ apiEndpoint: 'api.example.com/filter1',
77
+ valueSelector: 'value',
78
+ textSelector: 'text'
79
+ }
80
+ }
81
+
82
+ const result = getFilterResetValue(filter, mockAPIFilterDropdowns, false)
83
+ expect(result).toBe('defaultVal')
84
+ })
85
+
86
+ it('should return empty string if resetLabel exists (no defaultValue)', () => {
87
+ const filter: SharedFilter = {
88
+ key: 'testFilter',
89
+ resetLabel: 'Select One',
90
+ apiFilter: {
91
+ apiEndpoint: 'api.example.com/filter1',
92
+ valueSelector: 'value',
93
+ textSelector: 'text'
94
+ }
95
+ }
96
+
97
+ const result = getFilterResetValue(filter, mockAPIFilterDropdowns, false)
98
+ expect(result).toBe('')
99
+ })
100
+
101
+ it('should return first API option if no defaultValue or resetLabel', () => {
102
+ const filter: SharedFilter = {
103
+ key: 'testFilter',
104
+ apiFilter: {
105
+ apiEndpoint: 'api.example.com/filter1',
106
+ valueSelector: 'value',
107
+ textSelector: 'text'
108
+ }
109
+ }
110
+
111
+ const result = getFilterResetValue(filter, mockAPIFilterDropdowns, false)
112
+ expect(result).toBe('option1')
113
+ })
114
+
115
+ it('should return undefined if API filter has no options', () => {
116
+ const filter: SharedFilter = {
117
+ key: 'testFilter',
118
+ apiFilter: {
119
+ apiEndpoint: 'api.example.com/filter2',
120
+ valueSelector: 'value',
121
+ textSelector: 'text'
122
+ }
123
+ }
124
+
125
+ const result = getFilterResetValue(filter, mockAPIFilterDropdowns, false)
126
+ expect(result).toBeUndefined()
127
+ })
128
+
129
+ it('should return undefined if no API filter, defaultValue, or resetLabel', () => {
130
+ const filter: SharedFilter = {
131
+ key: 'testFilter',
132
+ values: ['value1', 'value2']
133
+ }
134
+
135
+ const result = getFilterResetValue(filter, mockAPIFilterDropdowns, false)
136
+ expect(result).toBeUndefined()
137
+ })
138
+ })
139
+ })
140
+
141
+ describe('resetFilterToValue', () => {
142
+ const mockAPIFilterDropdowns: APIFilterDropdowns = {
143
+ 'api.example.com/nested': [
144
+ {
145
+ value: 'group1',
146
+ text: 'Group 1',
147
+ subOptions: [
148
+ { value: 'sub1', text: 'Sub 1' },
149
+ { value: 'sub2', text: 'Sub 2' }
150
+ ]
151
+ },
152
+ {
153
+ value: 'group2',
154
+ text: 'Group 2',
155
+ subOptions: [{ value: 'sub3', text: 'Sub 3' }]
156
+ }
157
+ ]
158
+ }
159
+
160
+ describe('multi-select filters', () => {
161
+ it('should reset to array with single value when resetValue is provided', () => {
162
+ const filter: SharedFilter = {
163
+ key: 'multiFilter',
164
+ filterStyle: FILTER_STYLE.multiSelect,
165
+ active: ['value1', 'value2'],
166
+ queuedActive: ['value3']
167
+ }
168
+
169
+ resetFilterToValue(filter, 'value1', mockAPIFilterDropdowns)
170
+
171
+ expect(filter.active).toEqual(['value1'])
172
+ expect(filter.queuedActive).toBeUndefined()
173
+ })
174
+
175
+ it('should reset to empty array when resetValue is undefined', () => {
176
+ const filter: SharedFilter = {
177
+ key: 'multiFilter',
178
+ filterStyle: FILTER_STYLE.multiSelect,
179
+ active: ['value1', 'value2']
180
+ }
181
+
182
+ resetFilterToValue(filter, undefined, mockAPIFilterDropdowns)
183
+
184
+ expect(filter.active).toEqual([])
185
+ expect(filter.queuedActive).toBeUndefined()
186
+ })
187
+
188
+ it('should reset to empty array when resetValue is empty string', () => {
189
+ const filter: SharedFilter = {
190
+ key: 'multiFilter',
191
+ filterStyle: FILTER_STYLE.multiSelect,
192
+ active: ['value1', 'value2']
193
+ }
194
+
195
+ resetFilterToValue(filter, '', mockAPIFilterDropdowns)
196
+
197
+ expect(filter.active).toEqual([])
198
+ expect(filter.queuedActive).toBeUndefined()
199
+ })
200
+ })
201
+
202
+ describe('nested dropdown filters', () => {
203
+ it('should clear both group and subgroup when resetValue is empty string', () => {
204
+ const filter: SharedFilter = {
205
+ key: 'nestedFilter',
206
+ filterStyle: FILTER_STYLE.nestedDropdown,
207
+ active: 'group1',
208
+ subGrouping: {
209
+ active: 'sub1'
210
+ },
211
+ apiFilter: {
212
+ apiEndpoint: 'api.example.com/nested',
213
+ valueSelector: 'value',
214
+ textSelector: 'text'
215
+ }
216
+ }
217
+
218
+ resetFilterToValue(filter, '', mockAPIFilterDropdowns)
219
+
220
+ expect(filter.active).toBe('')
221
+ expect(filter.subGrouping.active).toBe('')
222
+ expect(filter.queuedActive).toBeUndefined()
223
+ })
224
+
225
+ it('should clear both group and subgroup when resetValue is undefined', () => {
226
+ const filter: SharedFilter = {
227
+ key: 'nestedFilter',
228
+ filterStyle: FILTER_STYLE.nestedDropdown,
229
+ active: 'group1',
230
+ subGrouping: {
231
+ active: 'sub1'
232
+ },
233
+ apiFilter: {
234
+ apiEndpoint: 'api.example.com/nested',
235
+ valueSelector: 'value',
236
+ textSelector: 'text'
237
+ }
238
+ }
239
+
240
+ resetFilterToValue(filter, undefined, mockAPIFilterDropdowns)
241
+
242
+ expect(filter.active).toBe('')
243
+ expect(filter.subGrouping.active).toBe('')
244
+ expect(filter.queuedActive).toBeUndefined()
245
+ })
246
+
247
+ it('should set to resetValue and first suboption when resetValue is provided', () => {
248
+ const filter: SharedFilter = {
249
+ key: 'nestedFilter',
250
+ filterStyle: FILTER_STYLE.nestedDropdown,
251
+ active: 'oldValue',
252
+ subGrouping: {
253
+ active: 'oldSub'
254
+ },
255
+ apiFilter: {
256
+ apiEndpoint: 'api.example.com/nested',
257
+ valueSelector: 'value',
258
+ textSelector: 'text'
259
+ }
260
+ }
261
+
262
+ resetFilterToValue(filter, 'group2', mockAPIFilterDropdowns)
263
+
264
+ expect(filter.active).toBe('group2')
265
+ expect(filter.subGrouping.active).toBe('sub3')
266
+ expect(filter.queuedActive).toBeUndefined()
267
+ })
268
+
269
+ it('should fallback to first option when no resetValue and options exist', () => {
270
+ const filter: SharedFilter = {
271
+ key: 'nestedFilter',
272
+ filterStyle: FILTER_STYLE.nestedDropdown,
273
+ active: 'oldValue',
274
+ subGrouping: {
275
+ active: 'oldSub'
276
+ },
277
+ apiFilter: {
278
+ apiEndpoint: 'api.example.com/nested',
279
+ valueSelector: 'value',
280
+ textSelector: 'text'
281
+ }
282
+ }
283
+
284
+ resetFilterToValue(filter, undefined, mockAPIFilterDropdowns)
285
+
286
+ expect(filter.active).toBe('')
287
+ expect(filter.subGrouping.active).toBe('')
288
+ expect(filter.queuedActive).toBeUndefined()
289
+ })
290
+ })
291
+
292
+ describe('standard dropdown filters', () => {
293
+ it('should reset to provided resetValue', () => {
294
+ const filter: SharedFilter = {
295
+ key: 'standardFilter',
296
+ active: 'oldValue',
297
+ queuedActive: 'queuedValue'
298
+ }
299
+
300
+ resetFilterToValue(filter, 'newValue', mockAPIFilterDropdowns)
301
+
302
+ expect(filter.active).toBe('newValue')
303
+ expect(filter.queuedActive).toBeUndefined()
304
+ })
305
+
306
+ it('should reset to empty string when resetValue is empty string', () => {
307
+ const filter: SharedFilter = {
308
+ key: 'standardFilter',
309
+ active: 'oldValue'
310
+ }
311
+
312
+ resetFilterToValue(filter, '', mockAPIFilterDropdowns)
313
+
314
+ expect(filter.active).toBe('')
315
+ expect(filter.queuedActive).toBeUndefined()
316
+ })
317
+
318
+ it('should reset to undefined when resetValue is undefined', () => {
319
+ const filter: SharedFilter = {
320
+ key: 'standardFilter',
321
+ active: 'oldValue'
322
+ }
323
+
324
+ resetFilterToValue(filter, undefined, mockAPIFilterDropdowns)
325
+
326
+ expect(filter.active).toBeUndefined()
327
+ expect(filter.queuedActive).toBeUndefined()
328
+ })
329
+ })
330
+ })
331
+
332
+ describe('clearChildFilterDropdowns', () => {
333
+ it('should clear dropdowns for filters with parents', () => {
334
+ const sharedFilters: SharedFilter[] = [
335
+ {
336
+ key: 'parentFilter',
337
+ apiFilter: {
338
+ apiEndpoint: 'api.example.com/parent',
339
+ valueSelector: 'value',
340
+ textSelector: 'text'
341
+ }
342
+ },
343
+ {
344
+ key: 'childFilter',
345
+ parents: ['parentFilter'],
346
+ apiFilter: {
347
+ apiEndpoint: 'api.example.com/child',
348
+ valueSelector: 'value',
349
+ textSelector: 'text'
350
+ }
351
+ }
352
+ ]
353
+
354
+ const apiFilterDropdowns: APIFilterDropdowns = {
355
+ 'api.example.com/parent': [
356
+ { value: 'parent1', text: 'Parent 1' },
357
+ { value: 'parent2', text: 'Parent 2' }
358
+ ],
359
+ 'api.example.com/child': [
360
+ { value: 'child1', text: 'Child 1' },
361
+ { value: 'child2', text: 'Child 2' }
362
+ ]
363
+ }
364
+
365
+ const result = clearChildFilterDropdowns(sharedFilters, apiFilterDropdowns)
366
+
367
+ // Parent filter dropdown should remain unchanged
368
+ expect(result['api.example.com/parent']).toEqual([
369
+ { value: 'parent1', text: 'Parent 1' },
370
+ { value: 'parent2', text: 'Parent 2' }
371
+ ])
372
+
373
+ // Child filter dropdown should be cleared to empty array
374
+ expect(result['api.example.com/child']).toEqual([])
375
+ })
376
+
377
+ it('should not clear dropdowns for filters without parents', () => {
378
+ const sharedFilters: SharedFilter[] = [
379
+ {
380
+ key: 'filter1',
381
+ apiFilter: {
382
+ apiEndpoint: 'api.example.com/filter1',
383
+ valueSelector: 'value',
384
+ textSelector: 'text'
385
+ }
386
+ },
387
+ {
388
+ key: 'filter2',
389
+ apiFilter: {
390
+ apiEndpoint: 'api.example.com/filter2',
391
+ valueSelector: 'value',
392
+ textSelector: 'text'
393
+ }
394
+ }
395
+ ]
396
+
397
+ const apiFilterDropdowns: APIFilterDropdowns = {
398
+ 'api.example.com/filter1': [{ value: 'val1', text: 'Value 1' }],
399
+ 'api.example.com/filter2': [{ value: 'val2', text: 'Value 2' }]
400
+ }
401
+
402
+ const result = clearChildFilterDropdowns(sharedFilters, apiFilterDropdowns)
403
+
404
+ // Both should remain unchanged since neither has parents
405
+ expect(result['api.example.com/filter1']).toEqual([{ value: 'val1', text: 'Value 1' }])
406
+ expect(result['api.example.com/filter2']).toEqual([{ value: 'val2', text: 'Value 2' }])
407
+ })
408
+
409
+ it('should handle filters with empty parents array', () => {
410
+ const sharedFilters: SharedFilter[] = [
411
+ {
412
+ key: 'filter1',
413
+ parents: [],
414
+ apiFilter: {
415
+ apiEndpoint: 'api.example.com/filter1',
416
+ valueSelector: 'value',
417
+ textSelector: 'text'
418
+ }
419
+ }
420
+ ]
421
+
422
+ const apiFilterDropdowns: APIFilterDropdowns = {
423
+ 'api.example.com/filter1': [{ value: 'val1', text: 'Value 1' }]
424
+ }
425
+
426
+ const result = clearChildFilterDropdowns(sharedFilters, apiFilterDropdowns)
427
+
428
+ // Should remain unchanged since parents array is empty
429
+ expect(result['api.example.com/filter1']).toEqual([{ value: 'val1', text: 'Value 1' }])
430
+ })
431
+
432
+ it('should handle mixed filters with and without parents', () => {
433
+ const sharedFilters: SharedFilter[] = [
434
+ {
435
+ key: 'parent',
436
+ apiFilter: {
437
+ apiEndpoint: 'api.example.com/parent',
438
+ valueSelector: 'value',
439
+ textSelector: 'text'
440
+ }
441
+ },
442
+ {
443
+ key: 'child1',
444
+ parents: ['parent'],
445
+ apiFilter: {
446
+ apiEndpoint: 'api.example.com/child1',
447
+ valueSelector: 'value',
448
+ textSelector: 'text'
449
+ }
450
+ },
451
+ {
452
+ key: 'independent',
453
+ apiFilter: {
454
+ apiEndpoint: 'api.example.com/independent',
455
+ valueSelector: 'value',
456
+ textSelector: 'text'
457
+ }
458
+ },
459
+ {
460
+ key: 'child2',
461
+ parents: ['child1'],
462
+ apiFilter: {
463
+ apiEndpoint: 'api.example.com/child2',
464
+ valueSelector: 'value',
465
+ textSelector: 'text'
466
+ }
467
+ }
468
+ ]
469
+
470
+ const apiFilterDropdowns: APIFilterDropdowns = {
471
+ 'api.example.com/parent': [{ value: 'p1', text: 'Parent 1' }],
472
+ 'api.example.com/child1': [{ value: 'c1', text: 'Child 1' }],
473
+ 'api.example.com/independent': [{ value: 'i1', text: 'Independent 1' }],
474
+ 'api.example.com/child2': [{ value: 'c2', text: 'Child 2' }]
475
+ }
476
+
477
+ const result = clearChildFilterDropdowns(sharedFilters, apiFilterDropdowns)
478
+
479
+ // Parent and independent should remain unchanged
480
+ expect(result['api.example.com/parent']).toEqual([{ value: 'p1', text: 'Parent 1' }])
481
+ expect(result['api.example.com/independent']).toEqual([{ value: 'i1', text: 'Independent 1' }])
482
+
483
+ // Children should be cleared
484
+ expect(result['api.example.com/child1']).toEqual([])
485
+ expect(result['api.example.com/child2']).toEqual([])
486
+ })
487
+
488
+ it('should not mutate original apiFilterDropdowns', () => {
489
+ const sharedFilters: SharedFilter[] = [
490
+ {
491
+ key: 'child',
492
+ parents: ['parent'],
493
+ apiFilter: {
494
+ apiEndpoint: 'api.example.com/child',
495
+ valueSelector: 'value',
496
+ textSelector: 'text'
497
+ }
498
+ }
499
+ ]
500
+
501
+ const apiFilterDropdowns: APIFilterDropdowns = {
502
+ 'api.example.com/child': [{ value: 'child1', text: 'Child 1' }]
503
+ }
504
+
505
+ const originalDropdowns = _.cloneDeep(apiFilterDropdowns)
506
+ const result = clearChildFilterDropdowns(sharedFilters, apiFilterDropdowns)
507
+
508
+ // Original should remain unchanged
509
+ expect(apiFilterDropdowns).toEqual(originalDropdowns)
510
+ // Result should have cleared child
511
+ expect(result['api.example.com/child']).toEqual([])
512
+ })
513
+
514
+ it('should handle filters without apiFilter', () => {
515
+ const sharedFilters: SharedFilter[] = [
516
+ {
517
+ key: 'dataFilter',
518
+ parents: ['parent'],
519
+ values: ['value1', 'value2']
520
+ }
521
+ ]
522
+
523
+ const apiFilterDropdowns: APIFilterDropdowns = {
524
+ 'api.example.com/other': [{ value: 'val1', text: 'Value 1' }]
525
+ }
526
+
527
+ const result = clearChildFilterDropdowns(sharedFilters, apiFilterDropdowns)
528
+
529
+ // Should remain unchanged since dataFilter doesn't have apiFilter
530
+ expect(result).toEqual(apiFilterDropdowns)
531
+ })
532
+ })
@@ -2,55 +2,86 @@ import { SharedFilter } from '../../types/SharedFilter'
2
2
  import { updateChildFilters } from '../updateChildFilters'
3
3
 
4
4
  describe('updateChildFilters', () => {
5
- it('should filter data based on the provided filters', () => {
5
+ it('should filter data based on a single parent filter', () => {
6
6
  const filters = [
7
7
  {
8
8
  tier: 1,
9
9
  columnName: 'name',
10
10
  active: 'John',
11
11
  key: 'Parent Filter',
12
- values: ['John', 'Kelly', 'Norman', 'Jane']
12
+ values: ['John', 'Kelly', 'Norman', 'Jane'],
13
+ type: 'datafilter'
13
14
  },
14
15
  {
15
16
  tier: 2,
16
17
  columnName: 'lastName',
17
18
  active: '',
18
19
  key: 'Child Filter',
19
- parents: 'Parent Filter',
20
- values: ['Deer', 'Roberts']
20
+ parents: ['Parent Filter'],
21
+ values: ['Deer', 'Roberts'],
22
+ type: 'datafilter'
21
23
  }
22
24
  ] as SharedFilter[]
23
25
  const data = {
24
26
  vizKey: [
25
- [
26
- { name: 'John', lastName: 'Deer' },
27
- { name: 'John', lastName: 'Roberts' },
28
- { name: 'Kelly', lastName: 'Adams' },
29
- { name: 'Norman', lastName: 'Sally' },
30
- { name: 'Jane', lastName: 'Gorman' }
31
- ]
27
+ { name: 'John', lastName: 'Deer' },
28
+ { name: 'John', lastName: 'Roberts' },
29
+ { name: 'Kelly', lastName: 'Adams' },
30
+ { name: 'Norman', lastName: 'Sally' },
31
+ { name: 'Jane', lastName: 'Gorman' }
32
32
  ]
33
33
  }
34
34
 
35
- let exprectedResult = [
35
+ const result = updateChildFilters(filters, data)
36
+
37
+ expect(result[0]).toEqual(filters[0]) // Parent unchanged
38
+ expect(result[1].values).toEqual(['Deer', 'Roberts']) // Child values filtered
39
+ expect(result[1].active).toBe('Deer') // Active set to first value when empty
40
+ })
41
+
42
+ it('should filter data based on multiple parent filters', () => {
43
+ const filters = [
36
44
  {
37
45
  tier: 1,
38
- columnName: 'name',
39
- active: 'John',
40
- key: 'Parent Filter',
41
- values: ['John', 'Kelly', 'Norman', 'Jane']
46
+ columnName: 'state',
47
+ active: 'CA',
48
+ key: 'State Filter',
49
+ values: ['CA', 'NY', 'TX'],
50
+ type: 'datafilter'
51
+ },
52
+ {
53
+ tier: 1,
54
+ columnName: 'year',
55
+ active: '2023',
56
+ key: 'Year Filter',
57
+ values: ['2022', '2023', '2024'],
58
+ type: 'datafilter'
42
59
  },
43
60
  {
44
61
  tier: 2,
45
- columnName: 'lastName',
62
+ columnName: 'city',
46
63
  active: '',
47
- key: 'Child Filter',
48
- parents: 'Parent Filter',
49
- values: ['Deer', 'Roberts'] // updated values only
64
+ key: 'City Filter',
65
+ parents: ['State Filter', 'Year Filter'],
66
+ values: [],
67
+ type: 'datafilter'
50
68
  }
51
- ]
69
+ ] as SharedFilter[]
70
+ const data = {
71
+ vizKey: [
72
+ { state: 'CA', year: '2023', city: 'Los Angeles' },
73
+ { state: 'CA', year: '2023', city: 'San Francisco' },
74
+ { state: 'CA', year: '2022', city: 'Sacramento' },
75
+ { state: 'NY', year: '2023', city: 'New York City' },
76
+ { state: 'TX', year: '2023', city: 'Austin' }
77
+ ]
78
+ }
79
+
52
80
  const result = updateChildFilters(filters, data)
53
81
 
54
- expect(result).toEqual(exprectedResult)
82
+ // Should only include cities where state=CA AND year=2023
83
+ expect(result[2].values).toEqual(['Los Angeles', 'San Francisco'])
84
+ expect(result[2].values).not.toContain('Sacramento') // Different year
85
+ expect(result[2].values).not.toContain('New York City') // Different state
55
86
  })
56
87
  })