@openmrs/esm-patient-tests-app 11.3.1-pre.9435 → 11.3.1-pre.9437

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 (41) hide show
  1. package/.turbo/turbo-build.log +3 -3
  2. package/dist/1477.js +1 -1
  3. package/dist/1477.js.map +1 -1
  4. package/dist/1935.js +1 -1
  5. package/dist/1935.js.map +1 -1
  6. package/dist/3509.js +1 -1
  7. package/dist/3509.js.map +1 -1
  8. package/dist/4300.js +1 -1
  9. package/dist/6301.js +1 -1
  10. package/dist/6301.js.map +1 -1
  11. package/dist/main.js +1 -1
  12. package/dist/main.js.map +1 -1
  13. package/dist/openmrs-esm-patient-tests-app.js +1 -1
  14. package/dist/openmrs-esm-patient-tests-app.js.buildmanifest.json +15 -15
  15. package/dist/routes.json +1 -1
  16. package/package.json +2 -2
  17. package/src/index.ts +1 -1
  18. package/src/routes.json +1 -1
  19. package/src/test-orders/add-test-order/add-test-order.test.tsx +1 -1
  20. package/src/test-orders/add-test-order/add-test-order.workspace.tsx +1 -1
  21. package/src/test-orders/add-test-order/test-order-form.component.tsx +1 -1
  22. package/src/test-orders/add-test-order/test-type-search.component.tsx +1 -1
  23. package/src/test-orders/lab-order-basket-panel/lab-order-basket-panel.test.tsx +2 -2
  24. package/src/test-results/filter/filter-context.test.tsx +556 -0
  25. package/src/test-results/filter/filter-context.tsx +1 -1
  26. package/src/test-results/filter/filter-reducer.test.ts +540 -0
  27. package/src/test-results/filter/filter-reducer.ts +1 -1
  28. package/src/test-results/filter/filter-set.test.tsx +694 -0
  29. package/src/test-results/grouped-timeline/grouped-timeline.test.tsx +1 -1
  30. package/src/test-results/grouped-timeline/useObstreeData.test.ts +471 -0
  31. package/src/test-results/individual-results-table-tablet/usePanelData.tsx +40 -26
  32. package/src/test-results/loadPatientTestData/helpers.ts +29 -12
  33. package/src/test-results/loadPatientTestData/usePatientResultsData.ts +18 -7
  34. package/src/test-results/overview/external-overview.extension.tsx +1 -2
  35. package/src/test-results/print-modal/print-modal.extension.tsx +1 -1
  36. package/src/test-results/results-viewer/results-viewer.extension.tsx +7 -3
  37. package/src/test-results/tree-view/tree-view.component.tsx +6 -1
  38. package/src/test-results/tree-view/tree-view.test.tsx +117 -1
  39. package/src/test-results/trendline/trendline.component.tsx +88 -52
  40. package/src/test-results/ui-elements/reset-filters-empty-state/filter-empty-data-illustration.tsx +2 -2
  41. package/translations/en.json +1 -1
@@ -0,0 +1,694 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { getDefaultsFromConfigSchema, useConfig, useLayoutType } from '@openmrs/esm-framework';
5
+ import { type ConfigObject, configSchema } from '../../config-schema';
6
+ import { FilterProvider, type Roots } from './filter-context';
7
+ import { type TreeNode } from './filter-types';
8
+ import FilterSet from './filter-set.component';
9
+
10
+ const mockUseConfig = jest.mocked(useConfig<ConfigObject>);
11
+ const mockUseLayoutType = jest.mocked(useLayoutType);
12
+
13
+ // Create mock data with actual observations for testing
14
+ // This structure has nested parent-child relationships where CBC is a category with sub-panels
15
+ const mockRootsWithData: Array<TreeNode> = [
16
+ {
17
+ display: 'Hematology',
18
+ flatName: 'Hematology',
19
+ conceptUuid: '9a6f10d6-7fc5-4fb7-9428-24ef7b8d01f7',
20
+ hasData: true,
21
+ subSets: [
22
+ {
23
+ display: 'Complete Blood Count',
24
+ flatName: 'Hematology: Complete Blood Count',
25
+ conceptUuid: 'cbc-concept-uuid',
26
+ hasData: true,
27
+ subSets: [
28
+ {
29
+ display: 'Hemoglobin',
30
+ flatName: 'Hematology: Complete Blood Count: Hemoglobin',
31
+ conceptUuid: '21AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
32
+ datatype: 'Numeric',
33
+ units: 'g/dL',
34
+ hasData: true,
35
+ obs: [
36
+ {
37
+ obsDatetime: '2024-01-01',
38
+ value: '12.5',
39
+ interpretation: 'NORMAL',
40
+ },
41
+ ],
42
+ },
43
+ {
44
+ display: 'Hematocrit',
45
+ flatName: 'Hematology: Complete Blood Count: Hematocrit',
46
+ conceptUuid: '1015AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
47
+ datatype: 'Numeric',
48
+ units: '%',
49
+ hasData: true,
50
+ obs: [
51
+ {
52
+ obsDatetime: '2024-01-01',
53
+ value: '38.0',
54
+ interpretation: 'NORMAL',
55
+ },
56
+ ],
57
+ },
58
+ {
59
+ display: 'White Blood Cell Count',
60
+ flatName: 'Hematology: Complete Blood Count: WBC',
61
+ conceptUuid: '678AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
62
+ datatype: 'Numeric',
63
+ units: '10^9/L',
64
+ hasData: true,
65
+ obs: [
66
+ {
67
+ obsDatetime: '2024-01-01',
68
+ value: '7.5',
69
+ interpretation: 'NORMAL',
70
+ },
71
+ ],
72
+ },
73
+ ],
74
+ },
75
+ ],
76
+ },
77
+ {
78
+ display: 'Chemistry',
79
+ flatName: 'Chemistry',
80
+ conceptUuid: '856AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
81
+ hasData: true,
82
+ subSets: [
83
+ {
84
+ display: 'Lipid Panel',
85
+ flatName: 'Chemistry: Lipid Panel',
86
+ conceptUuid: 'lipid-concept-uuid',
87
+ hasData: true,
88
+ subSets: [
89
+ {
90
+ display: 'Total Cholesterol',
91
+ flatName: 'Chemistry: Lipid Panel: Cholesterol',
92
+ conceptUuid: '1006AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
93
+ datatype: 'Numeric',
94
+ units: 'mg/dL',
95
+ hasData: true,
96
+ obs: [
97
+ {
98
+ obsDatetime: '2024-01-01',
99
+ value: '180',
100
+ interpretation: 'NORMAL',
101
+ },
102
+ ],
103
+ },
104
+ ],
105
+ },
106
+ ],
107
+ },
108
+ ];
109
+
110
+ // Simpler mock for tests that don't need nested structure
111
+ const mockSimpleRootsWithData: Array<TreeNode> = [
112
+ {
113
+ display: 'Complete Blood Count',
114
+ flatName: 'CBC',
115
+ conceptUuid: '9a6f10d6-7fc5-4fb7-9428-24ef7b8d01f7',
116
+ hasData: true,
117
+ subSets: [
118
+ {
119
+ display: 'Hemoglobin',
120
+ flatName: 'CBC: Hemoglobin',
121
+ conceptUuid: '21AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
122
+ datatype: 'Numeric',
123
+ units: 'g/dL',
124
+ hasData: true,
125
+ obs: [
126
+ {
127
+ obsDatetime: '2024-01-01',
128
+ value: '12.5',
129
+ interpretation: 'NORMAL',
130
+ },
131
+ ],
132
+ },
133
+ {
134
+ display: 'Hematocrit',
135
+ flatName: 'CBC: Hematocrit',
136
+ conceptUuid: '1015AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
137
+ datatype: 'Numeric',
138
+ units: '%',
139
+ hasData: true,
140
+ obs: [
141
+ {
142
+ obsDatetime: '2024-01-01',
143
+ value: '38.0',
144
+ interpretation: 'NORMAL',
145
+ },
146
+ ],
147
+ },
148
+ {
149
+ display: 'White Blood Cell Count',
150
+ flatName: 'CBC: WBC',
151
+ conceptUuid: '678AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
152
+ datatype: 'Numeric',
153
+ units: '10^9/L',
154
+ hasData: true,
155
+ obs: [
156
+ {
157
+ obsDatetime: '2024-01-01',
158
+ value: '7.5',
159
+ interpretation: 'NORMAL',
160
+ },
161
+ ],
162
+ },
163
+ ],
164
+ },
165
+ {
166
+ display: 'Lipid Panel',
167
+ flatName: 'Lipid',
168
+ conceptUuid: '856AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
169
+ hasData: true,
170
+ subSets: [
171
+ {
172
+ display: 'Total Cholesterol',
173
+ flatName: 'Lipid: Cholesterol',
174
+ conceptUuid: '1006AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
175
+ datatype: 'Numeric',
176
+ units: 'mg/dL',
177
+ hasData: true,
178
+ obs: [
179
+ {
180
+ obsDatetime: '2024-01-01',
181
+ value: '180',
182
+ interpretation: 'NORMAL',
183
+ },
184
+ ],
185
+ },
186
+ {
187
+ display: 'HDL Cholesterol',
188
+ flatName: 'Lipid: HDL',
189
+ conceptUuid: '1007AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
190
+ datatype: 'Numeric',
191
+ units: 'mg/dL',
192
+ hasData: true,
193
+ obs: [
194
+ {
195
+ obsDatetime: '2024-01-01',
196
+ value: '55',
197
+ interpretation: 'NORMAL',
198
+ },
199
+ ],
200
+ },
201
+ ],
202
+ },
203
+ ];
204
+
205
+ describe('FilterSet', () => {
206
+ beforeEach(() => {
207
+ mockUseLayoutType.mockReturnValue('small-desktop');
208
+ mockUseConfig.mockReturnValue({
209
+ ...getDefaultsFromConfigSchema(configSchema),
210
+ resultsViewerConcepts: [
211
+ {
212
+ conceptUuid: '9a6f10d6-7fc5-4fb7-9428-24ef7b8d01f7', // CBC
213
+ defaultOpen: true,
214
+ },
215
+ {
216
+ conceptUuid: '856AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', // Lipid Panel
217
+ defaultOpen: false,
218
+ },
219
+ ],
220
+ orders: {
221
+ labOrderTypeUuid: '52a447d3-a64a-11e3-9aeb-50e549534c5e',
222
+ labOrderableConcepts: [],
223
+ },
224
+ additionalTestOrderTypes: [],
225
+ labTestsWithOrderReasons: [],
226
+ });
227
+ });
228
+
229
+ describe('User checkbox interactions', () => {
230
+ it('should render all configured test panels with their tests', () => {
231
+ render(
232
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
233
+ <FilterSet />
234
+ </FilterProvider>,
235
+ );
236
+
237
+ expect(screen.getByText('Complete Blood Count')).toBeInTheDocument();
238
+ expect(screen.getByText('Lipid Panel')).toBeInTheDocument();
239
+ expect(screen.getByRole('checkbox', { name: /hemoglobin/i })).toBeInTheDocument();
240
+ expect(screen.getByRole('checkbox', { name: /hematocrit/i })).toBeInTheDocument();
241
+ expect(screen.getByRole('checkbox', { name: /white blood cell count/i })).toBeInTheDocument();
242
+ });
243
+
244
+ it('should toggle individual test checkbox when clicked', async () => {
245
+ const user = userEvent.setup();
246
+
247
+ render(
248
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
249
+ <FilterSet />
250
+ </FilterProvider>,
251
+ );
252
+
253
+ const hemoglobinCheckbox = screen.getByRole('checkbox', { name: /hemoglobin/i });
254
+ expect(hemoglobinCheckbox).not.toBeChecked();
255
+
256
+ await user.click(hemoglobinCheckbox);
257
+
258
+ expect(hemoglobinCheckbox).toBeChecked();
259
+ });
260
+
261
+ it('should toggle multiple test checkboxes independently', async () => {
262
+ const user = userEvent.setup();
263
+
264
+ render(
265
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
266
+ <FilterSet />
267
+ </FilterProvider>,
268
+ );
269
+
270
+ const hemoglobinCheckbox = screen.getByRole('checkbox', { name: /hemoglobin/i });
271
+ const hematocritCheckbox = screen.getByRole('checkbox', { name: /hematocrit/i });
272
+
273
+ await user.click(hemoglobinCheckbox);
274
+ expect(hemoglobinCheckbox).toBeChecked();
275
+ expect(hematocritCheckbox).not.toBeChecked();
276
+
277
+ await user.click(hematocritCheckbox);
278
+ expect(hemoglobinCheckbox).toBeChecked();
279
+ expect(hematocritCheckbox).toBeChecked();
280
+ });
281
+
282
+ it('should not affect checkboxes in different panels', async () => {
283
+ const user = userEvent.setup();
284
+
285
+ render(
286
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
287
+ <FilterSet />
288
+ </FilterProvider>,
289
+ );
290
+
291
+ const hemoglobinCheckbox = screen.getByRole('checkbox', { name: /hemoglobin/i });
292
+ const cholesterolCheckbox = screen.getByRole('checkbox', { name: /total cholesterol/i });
293
+
294
+ await user.click(hemoglobinCheckbox);
295
+
296
+ expect(hemoglobinCheckbox).toBeChecked();
297
+ expect(cholesterolCheckbox).not.toBeChecked();
298
+ });
299
+ });
300
+
301
+ describe('Parent checkbox behavior', () => {
302
+ beforeEach(() => {
303
+ // Update config to include nested panel conceptUuids
304
+ mockUseConfig.mockReturnValue({
305
+ ...getDefaultsFromConfigSchema(configSchema),
306
+ resultsViewerConcepts: [
307
+ {
308
+ conceptUuid: '9a6f10d6-7fc5-4fb7-9428-24ef7b8d01f7', // Hematology
309
+ defaultOpen: true,
310
+ },
311
+ {
312
+ conceptUuid: 'cbc-concept-uuid', // Complete Blood Count (nested)
313
+ defaultOpen: true,
314
+ },
315
+ {
316
+ conceptUuid: '856AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', // Chemistry
317
+ defaultOpen: false,
318
+ },
319
+ {
320
+ conceptUuid: 'lipid-concept-uuid', // Lipid Panel (nested)
321
+ defaultOpen: false,
322
+ },
323
+ ],
324
+ orders: {
325
+ labOrderTypeUuid: '52a447d3-a64a-11e3-9aeb-50e549534c5e',
326
+ labOrderableConcepts: [],
327
+ },
328
+ additionalTestOrderTypes: [],
329
+ labTestsWithOrderReasons: [],
330
+ });
331
+ });
332
+
333
+ it('should show parent checkbox in indeterminate state when some children are checked', async () => {
334
+ const user = userEvent.setup();
335
+
336
+ render(
337
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
338
+ <FilterSet />
339
+ </FilterProvider>,
340
+ );
341
+
342
+ // Expand the accordion to reveal children
343
+ const expandButton = screen.getAllByRole('button', { name: /expand all/i })[0];
344
+ await user.click(expandButton);
345
+
346
+ const hemoglobinCheckbox = screen.getByRole('checkbox', { name: /hemoglobin/i });
347
+ await user.click(hemoglobinCheckbox);
348
+
349
+ // Find the parent checkbox - it will have the label "Complete Blood Count"
350
+ const parentCheckbox = screen.getByRole('checkbox', { name: /complete blood count/i });
351
+
352
+ // When some but not all children are checked, parent should be indeterminate
353
+ expect(parentCheckbox).toHaveProperty('indeterminate', true);
354
+ });
355
+
356
+ it('should check parent checkbox when all children are checked', async () => {
357
+ const user = userEvent.setup();
358
+
359
+ render(
360
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
361
+ <FilterSet />
362
+ </FilterProvider>,
363
+ );
364
+
365
+ // Expand the accordion
366
+ const expandButton = screen.getAllByRole('button', { name: /expand all/i })[0];
367
+ await user.click(expandButton);
368
+
369
+ // Check all children
370
+ await user.click(screen.getByRole('checkbox', { name: /hemoglobin/i }));
371
+ await user.click(screen.getByRole('checkbox', { name: /hematocrit/i }));
372
+ await user.click(screen.getByRole('checkbox', { name: /white blood cell count/i }));
373
+
374
+ const parentCheckbox = screen.getByRole('checkbox', { name: /complete blood count/i });
375
+
376
+ expect(parentCheckbox).toBeChecked();
377
+ expect(parentCheckbox).toHaveProperty('indeterminate', false);
378
+ });
379
+
380
+ it('should toggle all children when parent checkbox is clicked', async () => {
381
+ const user = userEvent.setup();
382
+
383
+ render(
384
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
385
+ <FilterSet />
386
+ </FilterProvider>,
387
+ );
388
+
389
+ // Expand the accordion
390
+ const expandButton = screen.getAllByRole('button', { name: /expand all/i })[0];
391
+ await user.click(expandButton);
392
+
393
+ const parentCheckbox = screen.getByRole('checkbox', { name: /complete blood count/i });
394
+
395
+ // All children should start unchecked
396
+ expect(screen.getByRole('checkbox', { name: /hemoglobin/i })).not.toBeChecked();
397
+ expect(screen.getByRole('checkbox', { name: /hematocrit/i })).not.toBeChecked();
398
+ expect(screen.getByRole('checkbox', { name: /white blood cell count/i })).not.toBeChecked();
399
+
400
+ // Click parent to check all
401
+ await user.click(parentCheckbox);
402
+
403
+ expect(screen.getByRole('checkbox', { name: /hemoglobin/i })).toBeChecked();
404
+ expect(screen.getByRole('checkbox', { name: /hematocrit/i })).toBeChecked();
405
+ expect(screen.getByRole('checkbox', { name: /white blood cell count/i })).toBeChecked();
406
+
407
+ // Click parent again to uncheck all
408
+ await user.click(parentCheckbox);
409
+
410
+ expect(screen.getByRole('checkbox', { name: /hemoglobin/i })).not.toBeChecked();
411
+ expect(screen.getByRole('checkbox', { name: /hematocrit/i })).not.toBeChecked();
412
+ expect(screen.getByRole('checkbox', { name: /white blood cell count/i })).not.toBeChecked();
413
+ });
414
+ });
415
+
416
+ describe('Accordion expand/collapse', () => {
417
+ it('should respect defaultOpen configuration', () => {
418
+ render(
419
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
420
+ <FilterSet />
421
+ </FilterProvider>,
422
+ );
423
+
424
+ // CBC should be open by default (defaultOpen: true)
425
+ expect(screen.getByRole('checkbox', { name: /^hemoglobin$/i })).toBeVisible();
426
+
427
+ // Lipid Panel should be closed by default (defaultOpen: false)
428
+ // The parent checkbox is visible, but children might not be
429
+ expect(screen.getByText('Lipid Panel')).toBeInTheDocument();
430
+ });
431
+
432
+ it('should expand all tests when Expand all button is clicked', async () => {
433
+ const user = userEvent.setup();
434
+
435
+ render(
436
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
437
+ <FilterSet />
438
+ </FilterProvider>,
439
+ );
440
+
441
+ const expandAllButtons = screen.getAllByRole('button', { name: /expand all/i });
442
+ await user.click(expandAllButtons[0]);
443
+
444
+ // All accordions should now be expanded
445
+ expect(screen.getByRole('checkbox', { name: /^hemoglobin$/i })).toBeVisible();
446
+ expect(screen.getByRole('checkbox', { name: /hematocrit/i })).toBeVisible();
447
+ });
448
+
449
+ it('should collapse all tests when Collapse all button is clicked after expanding', async () => {
450
+ const user = userEvent.setup();
451
+
452
+ render(
453
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
454
+ <FilterSet />
455
+ </FilterProvider>,
456
+ );
457
+
458
+ const expandAllButtons = screen.getAllByRole('button', { name: /expand all/i });
459
+ await user.click(expandAllButtons[0]);
460
+
461
+ const collapseAllButtons = screen.getAllByRole('button', { name: /collapse all/i });
462
+ await user.click(collapseAllButtons[0]);
463
+
464
+ // Button text should change back to "Expand all"
465
+ expect(screen.getAllByRole('button', { name: /expand all/i }).length).toBeGreaterThan(0);
466
+ });
467
+ });
468
+
469
+ describe('Configuration-based filtering', () => {
470
+ it('should render all panels with subSets regardless of configuration', () => {
471
+ // Note: This test documents current behavior where configuration filtering
472
+ // only applies to panels with empty subSets (line 66 in filter-set.component.tsx)
473
+ // Panels with non-empty subSets are rendered even if not in config
474
+ const mockRootsWithConfiguredAndUnconfigured: Array<TreeNode> = [
475
+ {
476
+ display: 'Complete Blood Count',
477
+ flatName: 'CBC',
478
+ conceptUuid: '9a6f10d6-7fc5-4fb7-9428-24ef7b8d01f7', // Configured
479
+ hasData: true,
480
+ subSets: [
481
+ {
482
+ display: 'Hemoglobin',
483
+ flatName: 'CBC: Hemoglobin',
484
+ conceptUuid: '21AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
485
+ hasData: true,
486
+ obs: [{ obsDatetime: '2024-01-01', value: '12', interpretation: 'NORMAL' }],
487
+ },
488
+ ],
489
+ },
490
+ {
491
+ display: 'Unconfigured Panel',
492
+ flatName: 'Unconfigured',
493
+ conceptUuid: 'unconfigured-uuid', // Not in config
494
+ hasData: true,
495
+ subSets: [
496
+ {
497
+ display: 'Unconfigured Test',
498
+ flatName: 'Unconfigured: Test',
499
+ conceptUuid: 'unconfigured-test-uuid', // Not in config
500
+ hasData: true,
501
+ obs: [{ obsDatetime: '2024-01-01', value: '100', interpretation: 'NORMAL' }],
502
+ },
503
+ ],
504
+ },
505
+ ];
506
+
507
+ // Override config to only include CBC
508
+ mockUseConfig.mockReturnValue({
509
+ ...getDefaultsFromConfigSchema(configSchema),
510
+ resultsViewerConcepts: [
511
+ {
512
+ conceptUuid: '9a6f10d6-7fc5-4fb7-9428-24ef7b8d01f7', // Only CBC configured
513
+ defaultOpen: true,
514
+ },
515
+ ],
516
+ orders: {
517
+ labOrderTypeUuid: '52a447d3-a64a-11e3-9aeb-50e549534c5e',
518
+ labOrderableConcepts: [],
519
+ },
520
+ additionalTestOrderTypes: [],
521
+ labTestsWithOrderReasons: [],
522
+ });
523
+
524
+ render(
525
+ <FilterProvider roots={mockRootsWithConfiguredAndUnconfigured as Roots} isLoading={false}>
526
+ <FilterSet />
527
+ </FilterProvider>,
528
+ );
529
+
530
+ // CBC should be visible
531
+ expect(screen.getByText('Complete Blood Count')).toBeInTheDocument();
532
+ expect(screen.getByRole('checkbox', { name: /hemoglobin/i })).toBeInTheDocument();
533
+
534
+ // Current behavior: Unconfigured panels with subSets ARE rendered
535
+ // because config filtering only applies when subSets.length === 0
536
+ expect(screen.getByText('Unconfigured Panel')).toBeInTheDocument();
537
+ expect(screen.getByRole('checkbox', { name: /unconfigured test/i })).toBeInTheDocument();
538
+ });
539
+
540
+ it('should filter out nodes without subSets at root level', () => {
541
+ const mockRootsWithLeafAtRoot: Array<TreeNode> = [
542
+ {
543
+ display: 'Valid Parent',
544
+ flatName: 'ValidParent',
545
+ conceptUuid: '9a6f10d6-7fc5-4fb7-9428-24ef7b8d01f7',
546
+ hasData: true,
547
+ subSets: [
548
+ {
549
+ display: 'Child Test',
550
+ flatName: 'ValidParent: Child',
551
+ hasData: true,
552
+ obs: [{ obsDatetime: '2024-01-01', value: '12', interpretation: 'NORMAL' }],
553
+ },
554
+ ],
555
+ },
556
+ {
557
+ display: 'Invalid Leaf at Root',
558
+ flatName: 'InvalidLeaf',
559
+ conceptUuid: 'leaf-uuid',
560
+ hasData: true,
561
+ obs: [{ obsDatetime: '2024-01-01', value: '100', interpretation: 'NORMAL' }],
562
+ },
563
+ ];
564
+
565
+ mockUseConfig.mockReturnValue({
566
+ ...getDefaultsFromConfigSchema(configSchema),
567
+ resultsViewerConcepts: [
568
+ { conceptUuid: '9a6f10d6-7fc5-4fb7-9428-24ef7b8d01f7', defaultOpen: true },
569
+ { conceptUuid: 'leaf-uuid', defaultOpen: true },
570
+ ],
571
+ orders: {
572
+ labOrderTypeUuid: '52a447d3-a64a-11e3-9aeb-50e549534c5e',
573
+ labOrderableConcepts: [],
574
+ },
575
+ additionalTestOrderTypes: [],
576
+ labTestsWithOrderReasons: [],
577
+ });
578
+
579
+ render(
580
+ <FilterProvider roots={mockRootsWithLeafAtRoot as Roots} isLoading={false}>
581
+ <FilterSet />
582
+ </FilterProvider>,
583
+ );
584
+
585
+ expect(screen.getByText('Valid Parent')).toBeInTheDocument();
586
+ expect(screen.queryByText('Invalid Leaf at Root')).not.toBeInTheDocument();
587
+ });
588
+ });
589
+
590
+ describe('Empty state', () => {
591
+ it('should show empty state when no results match filters', () => {
592
+ render(
593
+ <FilterProvider roots={[] as Roots} isLoading={false}>
594
+ <FilterSet />
595
+ </FilterProvider>,
596
+ );
597
+
598
+ expect(screen.getByText(/no results to display/i)).toBeInTheDocument();
599
+ expect(screen.getByText(/clear filters/i)).toBeInTheDocument();
600
+ });
601
+
602
+ it('should clear search and show all results when Clear filters is clicked', async () => {
603
+ const user = userEvent.setup();
604
+
605
+ render(
606
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
607
+ <FilterSet />
608
+ </FilterProvider>,
609
+ );
610
+
611
+ // Initially, results should be visible
612
+ expect(screen.getByText('Complete Blood Count')).toBeInTheDocument();
613
+
614
+ // Simulate a scenario where search would cause empty results
615
+ // Since we don't have direct access to search input in this simple version,
616
+ // we can test that the clear filter button is wired correctly when empty state appears
617
+ render(
618
+ <FilterProvider roots={[] as Roots} isLoading={false}>
619
+ <FilterSet />
620
+ </FilterProvider>,
621
+ );
622
+
623
+ const clearFiltersButton = screen.getByRole('button', { name: /clear filters/i });
624
+ expect(clearFiltersButton).toBeInTheDocument();
625
+ });
626
+ });
627
+
628
+ describe('Disabled states', () => {
629
+ it('should disable checkboxes for tests without data', () => {
630
+ const mockRootsWithNoData: Array<TreeNode> = [
631
+ {
632
+ display: 'Test Panel',
633
+ flatName: 'TestPanel',
634
+ conceptUuid: '9a6f10d6-7fc5-4fb7-9428-24ef7b8d01f7',
635
+ hasData: false,
636
+ subSets: [
637
+ {
638
+ display: 'Test Without Data',
639
+ flatName: 'TestPanel: NoData',
640
+ hasData: false,
641
+ obs: [],
642
+ },
643
+ {
644
+ display: 'Test With Data',
645
+ flatName: 'TestPanel: WithData',
646
+ hasData: true,
647
+ obs: [{ obsDatetime: '2024-01-01', value: '12', interpretation: 'NORMAL' }],
648
+ },
649
+ ],
650
+ },
651
+ ];
652
+
653
+ render(
654
+ <FilterProvider roots={mockRootsWithNoData as Roots} isLoading={false}>
655
+ <FilterSet />
656
+ </FilterProvider>,
657
+ );
658
+
659
+ const noDataCheckbox = screen.getByRole('checkbox', { name: /test without data/i });
660
+ const withDataCheckbox = screen.getByRole('checkbox', { name: /test with data/i });
661
+
662
+ expect(noDataCheckbox).toBeDisabled();
663
+ expect(withDataCheckbox).toBeEnabled();
664
+ });
665
+ });
666
+
667
+ describe('Responsive layout', () => {
668
+ it('should render tablet layout when layoutType is tablet', () => {
669
+ mockUseLayoutType.mockReturnValue('tablet');
670
+
671
+ render(
672
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
673
+ <FilterSet />
674
+ </FilterProvider>,
675
+ );
676
+
677
+ expect(screen.getByText('Complete Blood Count')).toBeInTheDocument();
678
+ // Component should render successfully in tablet mode
679
+ });
680
+
681
+ it('should render desktop layout when layoutType is small-desktop', () => {
682
+ mockUseLayoutType.mockReturnValue('small-desktop');
683
+
684
+ render(
685
+ <FilterProvider roots={mockRootsWithData as Roots} isLoading={false}>
686
+ <FilterSet />
687
+ </FilterProvider>,
688
+ );
689
+
690
+ expect(screen.getByText('Complete Blood Count')).toBeInTheDocument();
691
+ // Component should render successfully in desktop mode
692
+ });
693
+ });
694
+ });
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
3
2
  import userEvent from '@testing-library/user-event';
3
+ import { render, screen } from '@testing-library/react';
4
4
  import { getByTextWithMarkup } from 'tools';
5
5
  import { showModal } from '@openmrs/esm-framework';
6
6
  import { mockGroupedResults } from '__mocks__';