@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.
- package/.turbo/turbo-build.log +3 -3
- package/dist/1477.js +1 -1
- package/dist/1477.js.map +1 -1
- package/dist/1935.js +1 -1
- package/dist/1935.js.map +1 -1
- package/dist/3509.js +1 -1
- package/dist/3509.js.map +1 -1
- package/dist/4300.js +1 -1
- package/dist/6301.js +1 -1
- package/dist/6301.js.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-tests-app.js +1 -1
- package/dist/openmrs-esm-patient-tests-app.js.buildmanifest.json +15 -15
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/index.ts +1 -1
- package/src/routes.json +1 -1
- package/src/test-orders/add-test-order/add-test-order.test.tsx +1 -1
- package/src/test-orders/add-test-order/add-test-order.workspace.tsx +1 -1
- package/src/test-orders/add-test-order/test-order-form.component.tsx +1 -1
- package/src/test-orders/add-test-order/test-type-search.component.tsx +1 -1
- package/src/test-orders/lab-order-basket-panel/lab-order-basket-panel.test.tsx +2 -2
- package/src/test-results/filter/filter-context.test.tsx +556 -0
- package/src/test-results/filter/filter-context.tsx +1 -1
- package/src/test-results/filter/filter-reducer.test.ts +540 -0
- package/src/test-results/filter/filter-reducer.ts +1 -1
- package/src/test-results/filter/filter-set.test.tsx +694 -0
- package/src/test-results/grouped-timeline/grouped-timeline.test.tsx +1 -1
- package/src/test-results/grouped-timeline/useObstreeData.test.ts +471 -0
- package/src/test-results/individual-results-table-tablet/usePanelData.tsx +40 -26
- package/src/test-results/loadPatientTestData/helpers.ts +29 -12
- package/src/test-results/loadPatientTestData/usePatientResultsData.ts +18 -7
- package/src/test-results/overview/external-overview.extension.tsx +1 -2
- package/src/test-results/print-modal/print-modal.extension.tsx +1 -1
- package/src/test-results/results-viewer/results-viewer.extension.tsx +7 -3
- package/src/test-results/tree-view/tree-view.component.tsx +6 -1
- package/src/test-results/tree-view/tree-view.test.tsx +117 -1
- package/src/test-results/trendline/trendline.component.tsx +88 -52
- package/src/test-results/ui-elements/reset-filters-empty-state/filter-empty-data-illustration.tsx +2 -2
- 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__';
|