@openmrs/esm-generic-patient-widgets-app 11.3.1-pre.9398 → 11.3.1-pre.9403
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 +16 -16
- package/dist/1936.js +1 -0
- package/dist/1936.js.map +1 -0
- package/dist/2606.js +2 -0
- package/dist/2606.js.map +1 -0
- package/dist/4300.js +1 -1
- package/dist/5670.js +1 -1
- package/dist/7545.js +2 -0
- package/dist/7545.js.map +1 -0
- package/dist/8803.js +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-generic-patient-widgets-app.js +1 -1
- package/dist/openmrs-esm-generic-patient-widgets-app.js.buildmanifest.json +91 -91
- package/dist/openmrs-esm-generic-patient-widgets-app.js.map +1 -1
- package/dist/routes.json +1 -1
- package/package.json +2 -2
- package/src/config-schema-obs-horizontal.ts +12 -0
- package/src/obs-graph/obs-graph.component.tsx +12 -12
- package/src/obs-switchable/obs-switchable.component.tsx +5 -9
- package/src/obs-switchable/obs-switchable.test.tsx +22 -12
- package/src/obs-table/obs-table.component.tsx +2 -1
- package/src/obs-table-horizontal/obs-table-horizontal.component.tsx +466 -56
- package/src/obs-table-horizontal/obs-table-horizontal.resource.ts +67 -0
- package/src/obs-table-horizontal/obs-table-horizontal.scss +47 -0
- package/src/obs-table-horizontal/obs-table-horizontal.test.tsx +912 -0
- package/src/resources/useConcepts.ts +14 -4
- package/src/resources/useEncounterTypes.ts +34 -0
- package/src/resources/useObs.ts +29 -47
- package/translations/en.json +6 -1
- package/dist/251.js +0 -2
- package/dist/251.js.map +0 -1
- package/dist/8743.js +0 -2
- package/dist/8743.js.map +0 -1
- package/dist/9351.js +0 -1
- package/dist/9351.js.map +0 -1
- /package/dist/{251.js.LICENSE.txt → 2606.js.LICENSE.txt} +0 -0
- /package/dist/{8743.js.LICENSE.txt → 7545.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, waitFor, within } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import {
|
|
5
|
+
getDefaultsFromConfigSchema,
|
|
6
|
+
useConfig,
|
|
7
|
+
showSnackbar,
|
|
8
|
+
useLayoutType,
|
|
9
|
+
isDesktop,
|
|
10
|
+
useSession,
|
|
11
|
+
userHasAccess,
|
|
12
|
+
} from '@openmrs/esm-framework';
|
|
13
|
+
import ObsTableHorizontal from './obs-table-horizontal.component';
|
|
14
|
+
import { useObs, type ObsResult } from '../resources/useObs';
|
|
15
|
+
import { configSchemaHorizontal } from '../config-schema-obs-horizontal';
|
|
16
|
+
import { updateObservation, createObservationInEncounter, createEncounter } from './obs-table-horizontal.resource';
|
|
17
|
+
import { useEncounterTypes } from '../resources/useEncounterTypes';
|
|
18
|
+
|
|
19
|
+
jest.mock('../resources/useObs', () => ({
|
|
20
|
+
useObs: jest.fn(),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
jest.mock('../resources/useEncounterTypes', () => ({
|
|
24
|
+
useEncounterTypes: jest.fn(),
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
jest.mock('./obs-table-horizontal.resource', () => ({
|
|
28
|
+
updateObservation: jest.fn(),
|
|
29
|
+
createObservationInEncounter: jest.fn(),
|
|
30
|
+
createEncounter: jest.fn(),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
jest.mock('@openmrs/esm-framework', () => {
|
|
34
|
+
const originalModule = jest.requireActual('@openmrs/esm-framework');
|
|
35
|
+
return {
|
|
36
|
+
...originalModule,
|
|
37
|
+
showSnackbar: jest.fn(),
|
|
38
|
+
useLayoutType: jest.fn(),
|
|
39
|
+
isDesktop: jest.fn(),
|
|
40
|
+
useSession: jest.fn(),
|
|
41
|
+
userHasAccess: jest.fn(() => true),
|
|
42
|
+
formatDate: jest.fn((date, options) => {
|
|
43
|
+
if (options?.time) return 'Jan 1, 2021, 12:00 AM';
|
|
44
|
+
return 'Jan 1, 2021';
|
|
45
|
+
}),
|
|
46
|
+
formatTime: jest.fn(() => '12:00 AM'),
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const mockUseObs = jest.mocked(useObs);
|
|
51
|
+
const mockUseConfig = jest.mocked(useConfig);
|
|
52
|
+
const mockShowSnackbar = jest.mocked(showSnackbar);
|
|
53
|
+
const mockUpdateObservation = jest.mocked(updateObservation);
|
|
54
|
+
const mockCreateObservationInEncounter = jest.mocked(createObservationInEncounter);
|
|
55
|
+
const mockCreateEncounter = jest.mocked(createEncounter);
|
|
56
|
+
const mockUseLayoutType = jest.mocked(useLayoutType);
|
|
57
|
+
const mockIsDesktop = jest.mocked(isDesktop);
|
|
58
|
+
const mockUseSession = jest.mocked(useSession);
|
|
59
|
+
const mockUseEncounterTypes = jest.mocked(useEncounterTypes);
|
|
60
|
+
|
|
61
|
+
const mockObsData = [
|
|
62
|
+
{
|
|
63
|
+
id: 'obs-1',
|
|
64
|
+
code: { text: 'Height', coding: [{ code: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }] },
|
|
65
|
+
conceptUuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
66
|
+
dataType: 'Numeric',
|
|
67
|
+
effectiveDateTime: '2021-02-01T00:00:00Z',
|
|
68
|
+
valueQuantity: { value: 182 },
|
|
69
|
+
encounter: { reference: 'Encounter/234', name: 'Outpatient' },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'obs-2',
|
|
73
|
+
code: { text: 'Weight', coding: [{ code: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }] },
|
|
74
|
+
conceptUuid: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
75
|
+
dataType: 'Numeric',
|
|
76
|
+
effectiveDateTime: '2021-02-01T00:00:00Z',
|
|
77
|
+
valueQuantity: { value: 72 },
|
|
78
|
+
encounter: { reference: 'Encounter/234', name: 'Outpatient' },
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'obs-3',
|
|
82
|
+
code: { text: 'Height', coding: [{ code: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }] },
|
|
83
|
+
conceptUuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
84
|
+
dataType: 'Numeric',
|
|
85
|
+
effectiveDateTime: '2021-01-01T00:00:00Z',
|
|
86
|
+
valueQuantity: { value: 180 },
|
|
87
|
+
encounter: { reference: 'Encounter/123', name: 'Inpatient' },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'obs-4',
|
|
91
|
+
code: { text: 'Weight', coding: [{ code: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }] },
|
|
92
|
+
conceptUuid: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
93
|
+
dataType: 'Numeric',
|
|
94
|
+
effectiveDateTime: '2021-01-01T00:00:00Z',
|
|
95
|
+
valueQuantity: { value: 70 },
|
|
96
|
+
encounter: { reference: 'Encounter/123', name: 'Inpatient' },
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'obs-5',
|
|
100
|
+
code: { text: 'Chief Complaint', coding: [{ code: '164162AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }] },
|
|
101
|
+
conceptUuid: '164162AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
102
|
+
dataType: 'Text',
|
|
103
|
+
effectiveDateTime: '2021-01-01T00:00:00Z',
|
|
104
|
+
valueString: 'Headache',
|
|
105
|
+
encounter: { reference: 'Encounter/123', name: 'Inpatient' },
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'obs-6',
|
|
109
|
+
code: { text: 'Diagnosis', coding: [{ code: '1284AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' }] },
|
|
110
|
+
conceptUuid: '1284AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
111
|
+
dataType: 'Coded',
|
|
112
|
+
effectiveDateTime: '2021-01-01T00:00:00Z',
|
|
113
|
+
valueCodeableConcept: {
|
|
114
|
+
coding: [{ code: 'answer-uuid-1', display: 'Malaria' }],
|
|
115
|
+
},
|
|
116
|
+
encounter: { reference: 'Encounter/123', name: 'Inpatient' },
|
|
117
|
+
},
|
|
118
|
+
] as Array<ObsResult>;
|
|
119
|
+
|
|
120
|
+
const mockConceptData = [
|
|
121
|
+
{ uuid: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Height', dataType: 'Numeric' },
|
|
122
|
+
{ uuid: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Weight', dataType: 'Numeric' },
|
|
123
|
+
{ uuid: '164162AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', display: 'Chief Complaint', dataType: 'Text' },
|
|
124
|
+
{
|
|
125
|
+
uuid: '1284AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
126
|
+
display: 'Diagnosis',
|
|
127
|
+
dataType: 'Coded',
|
|
128
|
+
answers: [
|
|
129
|
+
{ uuid: 'answer-uuid-1', display: 'Malaria' },
|
|
130
|
+
{ uuid: 'answer-uuid-2', display: 'Typhoid' },
|
|
131
|
+
{ uuid: 'answer-uuid-3', display: 'Pneumonia' },
|
|
132
|
+
],
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
const mockEncounters = [
|
|
137
|
+
{ reference: 'Encounter/123', display: 'Inpatient', encounterTypeUuid: 'encounter-type-uuid-1' },
|
|
138
|
+
{ reference: 'Encounter/234', display: 'Outpatient', encounterTypeUuid: 'encounter-type-uuid-2' },
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
describe('ObsTableHorizontal', () => {
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
jest.clearAllMocks();
|
|
144
|
+
mockUseLayoutType.mockReturnValue('small-desktop');
|
|
145
|
+
mockIsDesktop.mockReturnValue(true);
|
|
146
|
+
mockUseEncounterTypes.mockReturnValue({
|
|
147
|
+
encounterTypes: [
|
|
148
|
+
{ uuid: 'encounter-type-uuid-1', editPrivilege: { uuid: 'privilege-1', display: 'Edit Privilege 1' } },
|
|
149
|
+
{ uuid: 'encounter-type-uuid-2', editPrivilege: { uuid: 'privilege-2', display: 'Edit Privilege 2' } },
|
|
150
|
+
],
|
|
151
|
+
error: null,
|
|
152
|
+
isLoading: false,
|
|
153
|
+
isValidating: false,
|
|
154
|
+
mutate: jest.fn(),
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should render observations in a horizontal table', () => {
|
|
159
|
+
mockUseObs.mockReturnValue({
|
|
160
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
161
|
+
error: null,
|
|
162
|
+
isLoading: false,
|
|
163
|
+
isValidating: false,
|
|
164
|
+
mutate: jest.fn(),
|
|
165
|
+
});
|
|
166
|
+
mockUseConfig.mockReturnValue({
|
|
167
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
168
|
+
title: 'Vitals',
|
|
169
|
+
editable: false,
|
|
170
|
+
data: [
|
|
171
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
172
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
173
|
+
],
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
177
|
+
|
|
178
|
+
expect(screen.getByText('Height')).toBeInTheDocument();
|
|
179
|
+
expect(screen.getByText('Weight')).toBeInTheDocument();
|
|
180
|
+
expect(screen.getByText('182')).toBeInTheDocument();
|
|
181
|
+
expect(screen.getByText('72')).toBeInTheDocument();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('ObsTableHorizontal editable mode', () => {
|
|
186
|
+
beforeEach(() => {
|
|
187
|
+
jest.clearAllMocks();
|
|
188
|
+
mockUseLayoutType.mockReturnValue('small-desktop');
|
|
189
|
+
mockIsDesktop.mockReturnValue(true);
|
|
190
|
+
mockUseSession.mockReturnValue({
|
|
191
|
+
sessionLocation: { uuid: 'location-uuid-123' },
|
|
192
|
+
user: { uuid: 'user-uuid-123' },
|
|
193
|
+
} as any);
|
|
194
|
+
mockUseEncounterTypes.mockReturnValue({
|
|
195
|
+
encounterTypes: [
|
|
196
|
+
{ uuid: 'encounter-type-uuid-1', editPrivilege: { uuid: 'privilege-1', display: 'Edit Privilege 1' } },
|
|
197
|
+
{ uuid: 'encounter-type-uuid-2', editPrivilege: { uuid: 'privilege-2', display: 'Edit Privilege 2' } },
|
|
198
|
+
],
|
|
199
|
+
error: null,
|
|
200
|
+
isLoading: false,
|
|
201
|
+
isValidating: false,
|
|
202
|
+
mutate: jest.fn(),
|
|
203
|
+
});
|
|
204
|
+
mockUpdateObservation.mockResolvedValue({ data: {} } as any);
|
|
205
|
+
mockCreateObservationInEncounter.mockResolvedValue({ data: {} } as any);
|
|
206
|
+
mockCreateEncounter.mockResolvedValue({ data: { uuid: 'new-encounter-uuid' } } as any);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should show edit button on hover when editable is true', async () => {
|
|
210
|
+
const user = userEvent.setup();
|
|
211
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
212
|
+
|
|
213
|
+
mockUseObs.mockReturnValue({
|
|
214
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
215
|
+
error: null,
|
|
216
|
+
isLoading: false,
|
|
217
|
+
isValidating: false,
|
|
218
|
+
mutate: mockMutate,
|
|
219
|
+
});
|
|
220
|
+
mockUseConfig.mockReturnValue({
|
|
221
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
222
|
+
title: 'Vitals',
|
|
223
|
+
editable: true,
|
|
224
|
+
data: [
|
|
225
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
226
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
227
|
+
],
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
231
|
+
|
|
232
|
+
const heightCell = screen.getByRole('cell', { name: /182/ });
|
|
233
|
+
expect(heightCell).toBeInTheDocument();
|
|
234
|
+
|
|
235
|
+
// Hover over the cell to trigger the edit button visibility
|
|
236
|
+
await user.hover(heightCell);
|
|
237
|
+
// The edit button should be in the document (even if opacity is 0, it's still in DOM)
|
|
238
|
+
const editButtons = screen.queryAllByLabelText('Edit');
|
|
239
|
+
expect(editButtons.length).toBeGreaterThan(0);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('should not show edit button when editable is false', () => {
|
|
243
|
+
mockUseObs.mockReturnValue({
|
|
244
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
245
|
+
error: null,
|
|
246
|
+
isLoading: false,
|
|
247
|
+
isValidating: false,
|
|
248
|
+
mutate: jest.fn(),
|
|
249
|
+
});
|
|
250
|
+
mockUseConfig.mockReturnValue({
|
|
251
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
252
|
+
title: 'Vitals',
|
|
253
|
+
editable: false,
|
|
254
|
+
data: [
|
|
255
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
256
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
257
|
+
],
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
261
|
+
|
|
262
|
+
const editButtons = screen.queryAllByLabelText('Edit');
|
|
263
|
+
expect(editButtons).toHaveLength(0);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("should show 'tap to edit' message when editable is true and on tablet", () => {
|
|
267
|
+
mockUseLayoutType.mockReturnValue('tablet');
|
|
268
|
+
mockIsDesktop.mockReturnValue(false);
|
|
269
|
+
mockUseObs.mockReturnValue({
|
|
270
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
271
|
+
error: null,
|
|
272
|
+
isLoading: false,
|
|
273
|
+
isValidating: false,
|
|
274
|
+
mutate: jest.fn(),
|
|
275
|
+
});
|
|
276
|
+
mockUseConfig.mockReturnValue({
|
|
277
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
278
|
+
title: 'Vitals',
|
|
279
|
+
editable: true,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
283
|
+
expect(screen.getByText(/tap an observation to edit/i)).toBeInTheDocument();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should open input field when edit button is clicked', async () => {
|
|
287
|
+
const user = userEvent.setup();
|
|
288
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
289
|
+
|
|
290
|
+
mockUseObs.mockReturnValue({
|
|
291
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
292
|
+
error: null,
|
|
293
|
+
isLoading: false,
|
|
294
|
+
isValidating: false,
|
|
295
|
+
mutate: mockMutate,
|
|
296
|
+
});
|
|
297
|
+
mockUseConfig.mockReturnValue({
|
|
298
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
299
|
+
title: 'Vitals',
|
|
300
|
+
editable: true,
|
|
301
|
+
data: [
|
|
302
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
303
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
304
|
+
],
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
308
|
+
|
|
309
|
+
const heightCell = screen.getByRole('cell', { name: /182/ });
|
|
310
|
+
await user.hover(heightCell);
|
|
311
|
+
const editButton = within(heightCell).getByRole('button', { name: 'Edit' });
|
|
312
|
+
await user.click(editButton);
|
|
313
|
+
|
|
314
|
+
await waitFor(() => {
|
|
315
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
316
|
+
});
|
|
317
|
+
const input = screen.getByRole('spinbutton');
|
|
318
|
+
expect(input).toHaveValue(182);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('should update existing observation when value is changed and saved', async () => {
|
|
322
|
+
const user = userEvent.setup();
|
|
323
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
324
|
+
|
|
325
|
+
mockUseObs.mockReturnValue({
|
|
326
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
327
|
+
error: null,
|
|
328
|
+
isLoading: false,
|
|
329
|
+
isValidating: false,
|
|
330
|
+
mutate: mockMutate,
|
|
331
|
+
});
|
|
332
|
+
mockUseConfig.mockReturnValue({
|
|
333
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
334
|
+
title: 'Vitals',
|
|
335
|
+
editable: true,
|
|
336
|
+
data: [
|
|
337
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
338
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
339
|
+
],
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
343
|
+
|
|
344
|
+
const heightCell = screen.getByRole('cell', { name: /182/ });
|
|
345
|
+
await user.hover(heightCell);
|
|
346
|
+
const editButton = within(heightCell).getByRole('button', { name: 'Edit' });
|
|
347
|
+
await user.click(editButton);
|
|
348
|
+
|
|
349
|
+
await waitFor(() => {
|
|
350
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
const input = screen.getByRole('spinbutton');
|
|
354
|
+
await user.clear(input);
|
|
355
|
+
await user.type(input, '185');
|
|
356
|
+
|
|
357
|
+
const saveButton = screen.getByRole('button', { name: 'Save' });
|
|
358
|
+
await user.click(saveButton);
|
|
359
|
+
|
|
360
|
+
await waitFor(() => {
|
|
361
|
+
expect(mockUpdateObservation).toHaveBeenCalledWith('obs-1', 185);
|
|
362
|
+
});
|
|
363
|
+
expect(mockMutate).toHaveBeenCalled();
|
|
364
|
+
expect(mockShowSnackbar).toHaveBeenCalledWith(
|
|
365
|
+
expect.objectContaining({
|
|
366
|
+
kind: 'success',
|
|
367
|
+
title: 'Observation saved successfully',
|
|
368
|
+
}),
|
|
369
|
+
);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('should create new observation when editing empty cell', async () => {
|
|
373
|
+
const user = userEvent.setup();
|
|
374
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
375
|
+
|
|
376
|
+
// Create obs data without weight observation
|
|
377
|
+
const obsDataWithoutWeight = mockObsData.filter(
|
|
378
|
+
(obs) => obs.conceptUuid !== '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
mockUseObs.mockReturnValue({
|
|
382
|
+
data: { observations: obsDataWithoutWeight, concepts: mockConceptData, encounters: mockEncounters },
|
|
383
|
+
error: null,
|
|
384
|
+
isLoading: false,
|
|
385
|
+
isValidating: false,
|
|
386
|
+
mutate: mockMutate,
|
|
387
|
+
});
|
|
388
|
+
mockUseConfig.mockReturnValue({
|
|
389
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
390
|
+
title: 'Vitals',
|
|
391
|
+
editable: true,
|
|
392
|
+
data: [
|
|
393
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
394
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
395
|
+
],
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
399
|
+
|
|
400
|
+
// Find an empty cell in the Weight row (should show '--' or '-- Edit')
|
|
401
|
+
const weightRow = screen.getByRole('row', { name: /Weight/i });
|
|
402
|
+
const allCells = within(weightRow).getAllByRole('cell');
|
|
403
|
+
// Skip the first cell (label) and find a cell that contains '--' (empty value cell)
|
|
404
|
+
const weightEmptyCell = allCells.slice(1).find((cell) => {
|
|
405
|
+
const cellText = cell.textContent || '';
|
|
406
|
+
return cellText.includes('--');
|
|
407
|
+
});
|
|
408
|
+
expect(weightEmptyCell).toBeInTheDocument();
|
|
409
|
+
|
|
410
|
+
await user.hover(weightEmptyCell);
|
|
411
|
+
const editButton = within(weightEmptyCell).getByRole('button', { name: 'Edit' });
|
|
412
|
+
await user.click(editButton);
|
|
413
|
+
|
|
414
|
+
await waitFor(() => {
|
|
415
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
const input = screen.getByRole('spinbutton');
|
|
419
|
+
await user.type(input, '75');
|
|
420
|
+
|
|
421
|
+
const saveButton = screen.getByRole('button', { name: 'Save' });
|
|
422
|
+
await user.click(saveButton);
|
|
423
|
+
|
|
424
|
+
await waitFor(() => {
|
|
425
|
+
expect(mockCreateObservationInEncounter).toHaveBeenCalledWith(
|
|
426
|
+
'234',
|
|
427
|
+
'patient-123',
|
|
428
|
+
'5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
|
|
429
|
+
75,
|
|
430
|
+
);
|
|
431
|
+
});
|
|
432
|
+
expect(mockMutate).toHaveBeenCalled();
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should cancel editing when cancel button is clicked', async () => {
|
|
436
|
+
const user = userEvent.setup();
|
|
437
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
438
|
+
|
|
439
|
+
mockUseObs.mockReturnValue({
|
|
440
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
441
|
+
error: null,
|
|
442
|
+
isLoading: false,
|
|
443
|
+
isValidating: false,
|
|
444
|
+
mutate: mockMutate,
|
|
445
|
+
});
|
|
446
|
+
mockUseConfig.mockReturnValue({
|
|
447
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
448
|
+
title: 'Vitals',
|
|
449
|
+
editable: true,
|
|
450
|
+
data: [
|
|
451
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
452
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
453
|
+
],
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
457
|
+
|
|
458
|
+
const heightCell = screen.getByRole('cell', { name: /182/ });
|
|
459
|
+
await user.hover(heightCell);
|
|
460
|
+
const editButton = within(heightCell).getByRole('button', { name: 'Edit' });
|
|
461
|
+
await user.click(editButton);
|
|
462
|
+
|
|
463
|
+
await waitFor(() => {
|
|
464
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
const input = screen.getByRole('spinbutton');
|
|
468
|
+
await user.clear(input);
|
|
469
|
+
await user.type(input, '185');
|
|
470
|
+
|
|
471
|
+
// Click cancel button
|
|
472
|
+
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
|
473
|
+
await user.click(cancelButton);
|
|
474
|
+
|
|
475
|
+
// Verify that the input is no longer visible and value was not saved
|
|
476
|
+
await waitFor(() => {
|
|
477
|
+
expect(screen.queryByRole('spinbutton')).not.toBeInTheDocument();
|
|
478
|
+
});
|
|
479
|
+
expect(mockUpdateObservation).not.toHaveBeenCalled();
|
|
480
|
+
expect(mockMutate).not.toHaveBeenCalled();
|
|
481
|
+
// Verify the original value is still displayed
|
|
482
|
+
expect(screen.getByRole('cell', { name: /182/ })).toBeInTheDocument();
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('should cancel editing when Escape key is pressed', async () => {
|
|
486
|
+
const user = userEvent.setup();
|
|
487
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
488
|
+
|
|
489
|
+
mockUseObs.mockReturnValue({
|
|
490
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
491
|
+
error: null,
|
|
492
|
+
isLoading: false,
|
|
493
|
+
isValidating: false,
|
|
494
|
+
mutate: mockMutate,
|
|
495
|
+
});
|
|
496
|
+
mockUseConfig.mockReturnValue({
|
|
497
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
498
|
+
title: 'Vitals',
|
|
499
|
+
editable: true,
|
|
500
|
+
data: [
|
|
501
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
502
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
503
|
+
],
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
507
|
+
|
|
508
|
+
const heightCell = screen.getByRole('cell', { name: /182/ });
|
|
509
|
+
await user.hover(heightCell);
|
|
510
|
+
const editButton = within(heightCell).getByRole('button', { name: 'Edit' });
|
|
511
|
+
await user.click(editButton);
|
|
512
|
+
|
|
513
|
+
await waitFor(() => {
|
|
514
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const input = screen.getByRole('spinbutton');
|
|
518
|
+
await user.type(input, '{Escape}');
|
|
519
|
+
|
|
520
|
+
await waitFor(() => {
|
|
521
|
+
expect(screen.queryByRole('spinbutton')).not.toBeInTheDocument();
|
|
522
|
+
});
|
|
523
|
+
expect(mockUpdateObservation).not.toHaveBeenCalled();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('should save when Enter key is pressed', async () => {
|
|
527
|
+
const user = userEvent.setup();
|
|
528
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
529
|
+
|
|
530
|
+
mockUseObs.mockReturnValue({
|
|
531
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
532
|
+
error: null,
|
|
533
|
+
isLoading: false,
|
|
534
|
+
isValidating: false,
|
|
535
|
+
mutate: mockMutate,
|
|
536
|
+
});
|
|
537
|
+
mockUseConfig.mockReturnValue({
|
|
538
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
539
|
+
title: 'Vitals',
|
|
540
|
+
editable: true,
|
|
541
|
+
data: [
|
|
542
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
543
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
544
|
+
],
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
548
|
+
|
|
549
|
+
const heightCell = screen.getByRole('cell', { name: /182/ });
|
|
550
|
+
await user.hover(heightCell);
|
|
551
|
+
const editButton = within(heightCell).getByRole('button', { name: 'Edit' });
|
|
552
|
+
await user.click(editButton);
|
|
553
|
+
|
|
554
|
+
await waitFor(() => {
|
|
555
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const input = screen.getByRole('spinbutton');
|
|
559
|
+
await user.clear(input);
|
|
560
|
+
await user.type(input, '185');
|
|
561
|
+
await user.type(input, '{Enter}');
|
|
562
|
+
|
|
563
|
+
await waitFor(() => {
|
|
564
|
+
expect(mockUpdateObservation).toHaveBeenCalledWith('obs-1', 185);
|
|
565
|
+
});
|
|
566
|
+
expect(mockMutate).toHaveBeenCalled();
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it('should handle text observations', async () => {
|
|
570
|
+
const user = userEvent.setup();
|
|
571
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
572
|
+
|
|
573
|
+
mockUseObs.mockReturnValue({
|
|
574
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
575
|
+
error: null,
|
|
576
|
+
isLoading: false,
|
|
577
|
+
isValidating: false,
|
|
578
|
+
mutate: mockMutate,
|
|
579
|
+
});
|
|
580
|
+
mockUseConfig.mockReturnValue({
|
|
581
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
582
|
+
title: 'Vitals',
|
|
583
|
+
editable: true,
|
|
584
|
+
data: [{ concept: '164162AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Chief Complaint' }],
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
588
|
+
|
|
589
|
+
const complaintCell = screen.getByRole('cell', { name: /Headache/ });
|
|
590
|
+
await user.hover(complaintCell);
|
|
591
|
+
const editButton = within(complaintCell).getByRole('button', { name: 'Edit' });
|
|
592
|
+
await user.click(editButton);
|
|
593
|
+
|
|
594
|
+
await waitFor(() => {
|
|
595
|
+
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
596
|
+
});
|
|
597
|
+
const textInput = screen.getByRole('textbox');
|
|
598
|
+
expect(textInput).toHaveValue('Headache');
|
|
599
|
+
|
|
600
|
+
await user.clear(textInput);
|
|
601
|
+
await user.type(textInput, 'Fever');
|
|
602
|
+
|
|
603
|
+
const saveButton = screen.getByRole('button', { name: 'Save' });
|
|
604
|
+
await user.click(saveButton);
|
|
605
|
+
|
|
606
|
+
await waitFor(() => {
|
|
607
|
+
expect(mockUpdateObservation).toHaveBeenCalledWith('obs-5', 'Fever');
|
|
608
|
+
});
|
|
609
|
+
expect(mockMutate).toHaveBeenCalled();
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it('should handle coded observations', async () => {
|
|
613
|
+
const user = userEvent.setup();
|
|
614
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
615
|
+
|
|
616
|
+
mockUseObs.mockReturnValue({
|
|
617
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
618
|
+
error: null,
|
|
619
|
+
isLoading: false,
|
|
620
|
+
isValidating: false,
|
|
621
|
+
mutate: mockMutate,
|
|
622
|
+
});
|
|
623
|
+
mockUseConfig.mockReturnValue({
|
|
624
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
625
|
+
title: 'Diagnosis',
|
|
626
|
+
editable: true,
|
|
627
|
+
data: [{ concept: '1284AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Diagnosis' }],
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
631
|
+
|
|
632
|
+
const diagnosisCell = screen.getByRole('cell', { name: /Malaria/ });
|
|
633
|
+
expect(diagnosisCell).toBeInTheDocument();
|
|
634
|
+
|
|
635
|
+
await user.hover(diagnosisCell);
|
|
636
|
+
const editButton = within(diagnosisCell).getByRole('button', { name: 'Edit' });
|
|
637
|
+
await user.click(editButton);
|
|
638
|
+
|
|
639
|
+
await waitFor(() => {
|
|
640
|
+
expect(within(diagnosisCell).getByRole('combobox')).toBeInTheDocument();
|
|
641
|
+
});
|
|
642
|
+
const select = within(diagnosisCell).getByRole('combobox');
|
|
643
|
+
expect(select).toHaveValue('answer-uuid-1');
|
|
644
|
+
|
|
645
|
+
// Change to a different answer
|
|
646
|
+
await user.selectOptions(select, 'answer-uuid-2');
|
|
647
|
+
|
|
648
|
+
const saveButton = screen.getByRole('button', { name: 'Save' });
|
|
649
|
+
await user.click(saveButton);
|
|
650
|
+
|
|
651
|
+
await waitFor(() => {
|
|
652
|
+
expect(mockUpdateObservation).toHaveBeenCalledWith('obs-6', 'answer-uuid-2');
|
|
653
|
+
});
|
|
654
|
+
expect(mockMutate).toHaveBeenCalled();
|
|
655
|
+
expect(mockShowSnackbar).toHaveBeenCalledWith(
|
|
656
|
+
expect.objectContaining({
|
|
657
|
+
kind: 'success',
|
|
658
|
+
title: 'Observation saved successfully',
|
|
659
|
+
}),
|
|
660
|
+
);
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
it('should not save when value is unchanged', async () => {
|
|
664
|
+
const user = userEvent.setup();
|
|
665
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
666
|
+
|
|
667
|
+
mockUseObs.mockReturnValue({
|
|
668
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
669
|
+
error: null,
|
|
670
|
+
isLoading: false,
|
|
671
|
+
isValidating: false,
|
|
672
|
+
mutate: mockMutate,
|
|
673
|
+
});
|
|
674
|
+
mockUseConfig.mockReturnValue({
|
|
675
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
676
|
+
title: 'Vitals',
|
|
677
|
+
editable: true,
|
|
678
|
+
data: [
|
|
679
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
680
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
681
|
+
],
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
685
|
+
|
|
686
|
+
const heightCell = screen.getByRole('cell', { name: /182/ });
|
|
687
|
+
await user.hover(heightCell);
|
|
688
|
+
const editButton = within(heightCell).getByRole('button', { name: 'Edit' });
|
|
689
|
+
await user.click(editButton);
|
|
690
|
+
|
|
691
|
+
await waitFor(() => {
|
|
692
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// Click save without changing value - should not save
|
|
696
|
+
const saveButton = screen.getByRole('button', { name: 'Save' });
|
|
697
|
+
await user.click(saveButton);
|
|
698
|
+
|
|
699
|
+
await waitFor(() => {
|
|
700
|
+
expect(mockUpdateObservation).not.toHaveBeenCalled();
|
|
701
|
+
});
|
|
702
|
+
expect(mockMutate).not.toHaveBeenCalled();
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it('should not create observation when editing empty cell and nothing is entered', async () => {
|
|
706
|
+
const user = userEvent.setup();
|
|
707
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
708
|
+
|
|
709
|
+
// Create obs data where one encounter is missing a Weight observation
|
|
710
|
+
// This will create an empty cell in an existing encounter
|
|
711
|
+
const obsDataWithoutWeightInOneEncounter = mockObsData.filter(
|
|
712
|
+
(obs) =>
|
|
713
|
+
!(obs.conceptUuid === '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' && obs.encounter.reference === 'Encounter/234'),
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
mockUseObs.mockReturnValue({
|
|
717
|
+
data: { observations: obsDataWithoutWeightInOneEncounter, concepts: mockConceptData, encounters: mockEncounters },
|
|
718
|
+
error: null,
|
|
719
|
+
isLoading: false,
|
|
720
|
+
isValidating: false,
|
|
721
|
+
mutate: mockMutate,
|
|
722
|
+
});
|
|
723
|
+
mockUseConfig.mockReturnValue({
|
|
724
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
725
|
+
title: 'Vitals',
|
|
726
|
+
editable: true,
|
|
727
|
+
data: [
|
|
728
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
729
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
730
|
+
],
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
734
|
+
|
|
735
|
+
// Find an empty cell in the Weight row (should show '--')
|
|
736
|
+
const weightRow = screen.getByRole('row', { name: /Weight/i });
|
|
737
|
+
const allCells = within(weightRow).getAllByRole('cell');
|
|
738
|
+
const emptyCell = allCells.slice(1).find((cell) => {
|
|
739
|
+
const cellText = cell.textContent || '';
|
|
740
|
+
return cellText.includes('--');
|
|
741
|
+
});
|
|
742
|
+
expect(emptyCell).toBeInTheDocument();
|
|
743
|
+
|
|
744
|
+
// Hover and click the edit button
|
|
745
|
+
await user.hover(emptyCell);
|
|
746
|
+
const editButton = within(emptyCell).getByRole('button', { name: 'Edit' });
|
|
747
|
+
await user.click(editButton);
|
|
748
|
+
|
|
749
|
+
// Wait for the input to appear
|
|
750
|
+
await waitFor(() => {
|
|
751
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// Click cancel without entering any value
|
|
755
|
+
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
|
756
|
+
await user.click(cancelButton);
|
|
757
|
+
|
|
758
|
+
// Wait a bit to ensure no API calls are made
|
|
759
|
+
await waitFor(() => {
|
|
760
|
+
expect(mockCreateObservationInEncounter).not.toHaveBeenCalled();
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
expect(mockCreateEncounter).not.toHaveBeenCalled();
|
|
764
|
+
expect(mockMutate).not.toHaveBeenCalled();
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it('should create and save encounter', async () => {
|
|
768
|
+
const user = userEvent.setup();
|
|
769
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
770
|
+
const newEncounterUuid = 'new-encounter-uuid-123';
|
|
771
|
+
|
|
772
|
+
mockUseObs.mockReturnValue({
|
|
773
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
774
|
+
error: null,
|
|
775
|
+
isLoading: false,
|
|
776
|
+
isValidating: false,
|
|
777
|
+
mutate: mockMutate,
|
|
778
|
+
});
|
|
779
|
+
mockUseConfig.mockReturnValue({
|
|
780
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
781
|
+
title: 'Vitals',
|
|
782
|
+
editable: true,
|
|
783
|
+
encounterTypeToCreateUuid: 'dd528487-82a5-4082-9c72-ed246bd49591',
|
|
784
|
+
data: [
|
|
785
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
786
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
787
|
+
],
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
mockCreateEncounter.mockResolvedValue({
|
|
791
|
+
data: { uuid: newEncounterUuid },
|
|
792
|
+
} as any);
|
|
793
|
+
|
|
794
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
795
|
+
|
|
796
|
+
// Click the "+" button to add a new encounter
|
|
797
|
+
const addButton = screen.getByRole('button', { name: /add encounter/i });
|
|
798
|
+
await user.click(addButton);
|
|
799
|
+
|
|
800
|
+
// Wait for the new column to appear
|
|
801
|
+
await waitFor(() => {
|
|
802
|
+
const headers = screen.getAllByRole('columnheader');
|
|
803
|
+
expect(headers.length).toBeGreaterThan(2); // Should have label column + existing columns + new column
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
expect(mockCreateEncounter).not.toHaveBeenCalled();
|
|
807
|
+
|
|
808
|
+
// Find an empty cell in the Weight row in the new temporary encounter
|
|
809
|
+
const weightRow = screen.getByRole('row', { name: /Weight/i });
|
|
810
|
+
const allCells = within(weightRow).getAllByRole('cell');
|
|
811
|
+
// Find a cell that contains '--' (empty value cell) - should be the last one (new temporary encounter)
|
|
812
|
+
const emptyCell = allCells.slice(1).find((cell) => {
|
|
813
|
+
const cellText = cell.textContent || '';
|
|
814
|
+
return cellText.includes('--');
|
|
815
|
+
});
|
|
816
|
+
expect(emptyCell).toBeInTheDocument();
|
|
817
|
+
|
|
818
|
+
// Hover and click the edit button
|
|
819
|
+
await user.hover(emptyCell);
|
|
820
|
+
const editButton = within(emptyCell).getByRole('button', { name: 'Edit' });
|
|
821
|
+
await user.click(editButton);
|
|
822
|
+
|
|
823
|
+
// Wait for the input to appear
|
|
824
|
+
await waitFor(() => {
|
|
825
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// Enter a value
|
|
829
|
+
const input = screen.getByRole('spinbutton');
|
|
830
|
+
await user.type(input, '75');
|
|
831
|
+
|
|
832
|
+
// Click save button
|
|
833
|
+
const saveButton = screen.getByRole('button', { name: 'Save' });
|
|
834
|
+
await user.click(saveButton);
|
|
835
|
+
|
|
836
|
+
// Verify that createEncounter was called with correct parameters
|
|
837
|
+
await waitFor(() => {
|
|
838
|
+
expect(mockCreateEncounter).toHaveBeenCalledWith(
|
|
839
|
+
'patient-123',
|
|
840
|
+
'dd528487-82a5-4082-9c72-ed246bd49591',
|
|
841
|
+
'location-uuid-123',
|
|
842
|
+
[{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', value: 75 }],
|
|
843
|
+
);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
// Verify that mutate was called (via onEncounterCreated)
|
|
847
|
+
await waitFor(() => {
|
|
848
|
+
expect(mockMutate).toHaveBeenCalled();
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// Verify success snackbar is shown
|
|
852
|
+
await waitFor(() => {
|
|
853
|
+
expect(mockShowSnackbar).toHaveBeenCalledWith(
|
|
854
|
+
expect.objectContaining({
|
|
855
|
+
kind: 'success',
|
|
856
|
+
title: 'Observation saved successfully',
|
|
857
|
+
}),
|
|
858
|
+
);
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
it('should show error snackbar when update fails', async () => {
|
|
863
|
+
const user = userEvent.setup();
|
|
864
|
+
const mockMutate = jest.fn().mockResolvedValue(undefined);
|
|
865
|
+
const error = new Error('Update failed');
|
|
866
|
+
mockUpdateObservation.mockRejectedValue(error);
|
|
867
|
+
|
|
868
|
+
mockUseObs.mockReturnValue({
|
|
869
|
+
data: { observations: mockObsData, concepts: mockConceptData, encounters: mockEncounters },
|
|
870
|
+
error: null,
|
|
871
|
+
isLoading: false,
|
|
872
|
+
isValidating: false,
|
|
873
|
+
mutate: mockMutate,
|
|
874
|
+
});
|
|
875
|
+
mockUseConfig.mockReturnValue({
|
|
876
|
+
...(getDefaultsFromConfigSchema(configSchemaHorizontal) as Object),
|
|
877
|
+
title: 'Vitals',
|
|
878
|
+
editable: true,
|
|
879
|
+
data: [
|
|
880
|
+
{ concept: '5090AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Height' },
|
|
881
|
+
{ concept: '5089AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', label: 'Weight' },
|
|
882
|
+
],
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
render(<ObsTableHorizontal patientUuid="patient-123" />);
|
|
886
|
+
|
|
887
|
+
const heightCell = screen.getByRole('cell', { name: /182/ });
|
|
888
|
+
await user.hover(heightCell);
|
|
889
|
+
const editButton = within(heightCell).getByRole('button', { name: 'Edit' });
|
|
890
|
+
await user.click(editButton);
|
|
891
|
+
|
|
892
|
+
await waitFor(() => {
|
|
893
|
+
expect(screen.getByRole('spinbutton')).toBeInTheDocument();
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
const input = screen.getByRole('spinbutton');
|
|
897
|
+
await user.clear(input);
|
|
898
|
+
await user.type(input, '185');
|
|
899
|
+
|
|
900
|
+
const saveButton = screen.getByRole('button', { name: 'Save' });
|
|
901
|
+
await user.click(saveButton);
|
|
902
|
+
|
|
903
|
+
await waitFor(() => {
|
|
904
|
+
expect(mockShowSnackbar).toHaveBeenCalledWith(
|
|
905
|
+
expect.objectContaining({
|
|
906
|
+
kind: 'error',
|
|
907
|
+
title: 'Error saving observation',
|
|
908
|
+
}),
|
|
909
|
+
);
|
|
910
|
+
});
|
|
911
|
+
});
|
|
912
|
+
});
|