@dhis2/analytics 28.1.3 → 29.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/__demo__/InterpretationsUnit.stories.js +9 -6
- package/build/cjs/__fixtures__/interpretationsMockData.js +204 -0
- package/build/cjs/components/Interpretations/DashboardItemInterpretations/DashboardInterpretationThread.js +56 -0
- package/build/cjs/components/Interpretations/DashboardItemInterpretations/DashboardItemInterpretations.js +54 -0
- package/build/cjs/components/Interpretations/DashboardItemInterpretations/index.js +12 -0
- package/build/cjs/components/Interpretations/InterpretationModal/Comment.js +12 -17
- package/build/cjs/components/Interpretations/InterpretationModal/CommentAddForm.js +20 -34
- package/build/cjs/components/Interpretations/InterpretationModal/CommentDeleteButton.js +11 -36
- package/build/cjs/components/Interpretations/InterpretationModal/CommentUpdateForm.js +11 -27
- package/build/cjs/components/Interpretations/InterpretationModal/InterpretationModal.js +11 -68
- package/build/cjs/components/Interpretations/InterpretationModal/InterpretationThread.js +11 -24
- package/build/cjs/components/Interpretations/InterpretationsProvider/InterpretationsManager.js +275 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/InterpretationsProvider.js +28 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/__tests__/groupInterpretationIdsByDate.spec.js +37 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/__tests__/hooks.spec.js +565 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/groupInterpretationIdsByDate.js +16 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/hooks.js +278 -0
- package/build/cjs/components/Interpretations/InterpretationsProvider/index.js +12 -0
- package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationForm.js +25 -30
- package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationList.js +8 -38
- package/build/cjs/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +22 -73
- package/build/cjs/components/Interpretations/common/Interpretation/Interpretation.js +20 -34
- package/build/cjs/components/Interpretations/common/Interpretation/InterpretationDeleteButton.js +10 -12
- package/build/cjs/components/Interpretations/common/Interpretation/InterpretationUpdateForm.js +14 -25
- package/build/cjs/components/Interpretations/common/Message/MessageEditorContainer.js +3 -3
- package/build/cjs/index.js +72 -63
- package/build/cjs/locales/en/translations.json +10 -1
- package/build/es/__demo__/InterpretationsUnit.stories.js +9 -6
- package/build/es/__fixtures__/interpretationsMockData.js +198 -0
- package/build/es/components/Interpretations/DashboardItemInterpretations/DashboardInterpretationThread.js +48 -0
- package/build/es/components/Interpretations/DashboardItemInterpretations/DashboardItemInterpretations.js +45 -0
- package/build/es/components/Interpretations/DashboardItemInterpretations/index.js +1 -0
- package/build/es/components/Interpretations/InterpretationModal/Comment.js +14 -19
- package/build/es/components/Interpretations/InterpretationModal/CommentAddForm.js +21 -35
- package/build/es/components/Interpretations/InterpretationModal/CommentDeleteButton.js +11 -35
- package/build/es/components/Interpretations/InterpretationModal/CommentUpdateForm.js +12 -28
- package/build/es/components/Interpretations/InterpretationModal/InterpretationModal.js +12 -69
- package/build/es/components/Interpretations/InterpretationModal/InterpretationThread.js +11 -24
- package/build/es/components/Interpretations/InterpretationsProvider/InterpretationsManager.js +268 -0
- package/build/es/components/Interpretations/InterpretationsProvider/InterpretationsProvider.js +19 -0
- package/build/es/components/Interpretations/InterpretationsProvider/__tests__/groupInterpretationIdsByDate.spec.js +35 -0
- package/build/es/components/Interpretations/InterpretationsProvider/__tests__/hooks.spec.js +561 -0
- package/build/es/components/Interpretations/InterpretationsProvider/groupInterpretationIdsByDate.js +9 -0
- package/build/es/components/Interpretations/InterpretationsProvider/hooks.js +258 -0
- package/build/es/components/Interpretations/InterpretationsProvider/index.js +1 -0
- package/build/es/components/Interpretations/InterpretationsUnit/InterpretationForm.js +26 -31
- package/build/es/components/Interpretations/InterpretationsUnit/InterpretationList.js +8 -38
- package/build/es/components/Interpretations/InterpretationsUnit/InterpretationsUnit.js +23 -75
- package/build/es/components/Interpretations/common/Interpretation/Interpretation.js +21 -35
- package/build/es/components/Interpretations/common/Interpretation/InterpretationDeleteButton.js +11 -13
- package/build/es/components/Interpretations/common/Interpretation/InterpretationUpdateForm.js +15 -26
- package/build/es/components/Interpretations/common/Message/MessageEditorContainer.js +3 -3
- package/build/es/index.js +3 -1
- package/build/es/locales/en/translations.json +10 -1
- package/package.json +1 -1
- package/build/cjs/components/Interpretations/common/Interpretation/useLike.js +0 -56
- package/build/es/components/Interpretations/common/Interpretation/useLike.js +0 -50
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import * as mockData from '../../../../__fixtures__/interpretationsMockData.js';
|
|
5
|
+
import { useInterpretationsList, useActiveInterpretation, useInterpretation, useLike, useInterpretationsCurrentUser, useCreateInterpretation, useDeleteInterpretation, useUpdateInterpretationText, useAddCommentToActiveInterpretation, useDeleteCommentFromActiveInterpretation, useUpdateCommentForActiveInterpretation, useInterpretationAccess, useCommentAccess, useInterpretationsManager } from '../hooks.js';
|
|
6
|
+
import { InterpretationsProvider } from '../InterpretationsProvider.js';
|
|
7
|
+
const mockQuery = jest.fn();
|
|
8
|
+
const mockMutate = jest.fn();
|
|
9
|
+
|
|
10
|
+
// Mock the useDataEngine hook to return our mock
|
|
11
|
+
jest.mock('@dhis2/app-runtime', () => ({
|
|
12
|
+
useDataEngine: () => ({
|
|
13
|
+
...jest.requireActual('@dhis2/app-runtime'),
|
|
14
|
+
query: mockQuery,
|
|
15
|
+
mutate: mockMutate
|
|
16
|
+
})
|
|
17
|
+
}));
|
|
18
|
+
const createMockImplementation = (data, shouldError = false) => () => new Promise((resolve, reject) => {
|
|
19
|
+
if (shouldError) {
|
|
20
|
+
const msg = typeof data === 'string' ? data : 'OOPSIE';
|
|
21
|
+
reject(new Error(msg));
|
|
22
|
+
} else {
|
|
23
|
+
resolve(data);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Test wrapper component that properly pre-populates manager
|
|
28
|
+
const createWrapper = (initialInterpretations = []) => {
|
|
29
|
+
const InterpretationsPopulator = ({
|
|
30
|
+
children
|
|
31
|
+
}) => {
|
|
32
|
+
const manager = useInterpretationsManager();
|
|
33
|
+
if (initialInterpretations.length > 0 && manager.interpretations.size === 0) {
|
|
34
|
+
manager.currentType = mockData.visualization.type;
|
|
35
|
+
manager.currentVisualizationId = mockData.visualization.id;
|
|
36
|
+
manager.resetInterpretations(initialInterpretations);
|
|
37
|
+
manager.activeInterpretationId = initialInterpretations[0].id;
|
|
38
|
+
}
|
|
39
|
+
return children;
|
|
40
|
+
};
|
|
41
|
+
const TestWrapper = ({
|
|
42
|
+
children
|
|
43
|
+
}) => {
|
|
44
|
+
return /*#__PURE__*/React.createElement(InterpretationsProvider, {
|
|
45
|
+
currentUser: mockData.currentUser
|
|
46
|
+
}, /*#__PURE__*/React.createElement(InterpretationsPopulator, null, children));
|
|
47
|
+
};
|
|
48
|
+
const propTypes = {
|
|
49
|
+
children: PropTypes.node
|
|
50
|
+
};
|
|
51
|
+
TestWrapper.propTypes = propTypes;
|
|
52
|
+
InterpretationsPopulator.propTypes = propTypes;
|
|
53
|
+
return TestWrapper;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Custom hook to test interpretation-focused hooks (with pre-populated manager)
|
|
57
|
+
const useAllHooks = (visualizationType, visualizationId, interpretationId) => {
|
|
58
|
+
// Not used directly but useful here to test things like unsubscriptions etc
|
|
59
|
+
const manager = useInterpretationsManager();
|
|
60
|
+
// Core data hooks - no manual population needed since manager is pre-populated
|
|
61
|
+
const currentUser = useInterpretationsCurrentUser();
|
|
62
|
+
const interpretationsList = useInterpretationsList(visualizationType, visualizationId);
|
|
63
|
+
const activeInterpretation = useActiveInterpretation(interpretationId);
|
|
64
|
+
const interpretation = useInterpretation(interpretationId);
|
|
65
|
+
|
|
66
|
+
// Like hook (includes mutation)
|
|
67
|
+
const like = useLike(interpretationId);
|
|
68
|
+
|
|
69
|
+
// Access hooks
|
|
70
|
+
const interpretationAccess = useInterpretationAccess(interpretation);
|
|
71
|
+
const commentAccess = useCommentAccess(mockData.interpretationDetails.comments[0], true); // Mock comment access
|
|
72
|
+
|
|
73
|
+
// Mutation hooks
|
|
74
|
+
const [createInterpretation, createState] = useCreateInterpretation({
|
|
75
|
+
type: visualizationType,
|
|
76
|
+
id: visualizationId,
|
|
77
|
+
text: 'New interpretation'
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/* Note that we are NOT deleting the active interpretation but another one!
|
|
81
|
+
* Some of the hooks used in the modal in in this test hook require the
|
|
82
|
+
* presence of an active interpretation and will crash without it. This is
|
|
83
|
+
* not a problem when the components are actually used int an app, because
|
|
84
|
+
* the modal closes when the active interpretation is removed. */
|
|
85
|
+
const [deleteInterpretation, deleteState] = useDeleteInterpretation({
|
|
86
|
+
id: mockData.interpretations[1].id
|
|
87
|
+
});
|
|
88
|
+
const [updateInterpretationText, updateState] = useUpdateInterpretationText({
|
|
89
|
+
id: interpretationId,
|
|
90
|
+
text: 'Updated text'
|
|
91
|
+
});
|
|
92
|
+
const [addComment, addCommentState] = useAddCommentToActiveInterpretation({
|
|
93
|
+
text: 'New comment'
|
|
94
|
+
});
|
|
95
|
+
const [deleteComment, deleteCommentState] = useDeleteCommentFromActiveInterpretation({
|
|
96
|
+
id: 'commentid1'
|
|
97
|
+
});
|
|
98
|
+
const [updateComment, updateCommentState] = useUpdateCommentForActiveInterpretation({
|
|
99
|
+
id: 'commentid1',
|
|
100
|
+
text: 'Updated comment'
|
|
101
|
+
});
|
|
102
|
+
return {
|
|
103
|
+
manager,
|
|
104
|
+
// Data hooks
|
|
105
|
+
currentUser,
|
|
106
|
+
interpretationsList,
|
|
107
|
+
activeInterpretation,
|
|
108
|
+
interpretation,
|
|
109
|
+
// Access hooks
|
|
110
|
+
interpretationAccess,
|
|
111
|
+
commentAccess,
|
|
112
|
+
// Like is also a mutation but does not implement the useInterpretationsManagerMutation internally
|
|
113
|
+
like,
|
|
114
|
+
// Mutation hooks
|
|
115
|
+
mutations: {
|
|
116
|
+
createInterpretation,
|
|
117
|
+
createState,
|
|
118
|
+
deleteInterpretation,
|
|
119
|
+
deleteState,
|
|
120
|
+
updateInterpretationText,
|
|
121
|
+
updateState,
|
|
122
|
+
addComment,
|
|
123
|
+
addCommentState,
|
|
124
|
+
deleteComment,
|
|
125
|
+
deleteCommentState,
|
|
126
|
+
updateComment,
|
|
127
|
+
updateCommentState
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/* Extracted to as function because it is reused by all other tests
|
|
133
|
+
* apart from the list test */
|
|
134
|
+
const shouldLoadActiveInterpretation = async () => {
|
|
135
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
136
|
+
interpretations: {
|
|
137
|
+
interpretations: mockData.interpretations
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
141
|
+
interpretation: mockData.interpretationDetails
|
|
142
|
+
}));
|
|
143
|
+
const renderHookResult = renderHook(() => useAllHooks(mockData.visualization.type, mockData.visualization.id, mockData.interpretationDetails.id), {
|
|
144
|
+
wrapper: createWrapper(mockData.interpretations)
|
|
145
|
+
});
|
|
146
|
+
const {
|
|
147
|
+
result
|
|
148
|
+
} = renderHookResult;
|
|
149
|
+
expect(result.current.activeInterpretation.loading).toBe(true);
|
|
150
|
+
expect(result.current.activeInterpretation.data).toBeUndefined();
|
|
151
|
+
expect(result.current.activeInterpretation.error).toBeUndefined();
|
|
152
|
+
await waitFor(() => {
|
|
153
|
+
expect(result.current.activeInterpretation.loading).toBe(false);
|
|
154
|
+
expect(result.current.activeInterpretation.error).toBeUndefined();
|
|
155
|
+
expect(result.current.activeInterpretation.data).toEqual(mockData.interpretationDetails);
|
|
156
|
+
});
|
|
157
|
+
return renderHookResult;
|
|
158
|
+
};
|
|
159
|
+
describe('Interpretations hooks integration tests', () => {
|
|
160
|
+
beforeEach(() => {
|
|
161
|
+
mockQuery.mockClear();
|
|
162
|
+
mockMutate.mockClear();
|
|
163
|
+
});
|
|
164
|
+
describe('Listing interpretations', () => {
|
|
165
|
+
test('should show loading state then load interpretations list successfully', async () => {
|
|
166
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
167
|
+
interpretations: {
|
|
168
|
+
interpretations: mockData.interpretations
|
|
169
|
+
}
|
|
170
|
+
}));
|
|
171
|
+
const {
|
|
172
|
+
result
|
|
173
|
+
} = renderHook(() => useInterpretationsList(mockData.visualization.type, mockData.visualization.id), {
|
|
174
|
+
wrapper: createWrapper()
|
|
175
|
+
});
|
|
176
|
+
expect(result.current.loading).toBe(true);
|
|
177
|
+
expect(result.current.data).toBeUndefined();
|
|
178
|
+
expect(result.current.error).toBeUndefined();
|
|
179
|
+
await waitFor(() => {
|
|
180
|
+
expect(result.current.loading).toBe(false);
|
|
181
|
+
expect(result.current.data).toEqual({
|
|
182
|
+
'2025-09-01': ['interpretation4'],
|
|
183
|
+
'2025-09-02': ['interpretation3'],
|
|
184
|
+
'2025-09-03': ['interpretation2'],
|
|
185
|
+
'2025-09-04': ['interpretation1']
|
|
186
|
+
});
|
|
187
|
+
expect(result.current.error).toBeUndefined();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
test('should handle interpretations list loading error', async () => {
|
|
191
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
192
|
+
const errorMsg = 'Failed to load interpretations';
|
|
193
|
+
try {
|
|
194
|
+
mockQuery.mockImplementationOnce(createMockImplementation(errorMsg, true));
|
|
195
|
+
const {
|
|
196
|
+
result
|
|
197
|
+
} = renderHook(() => useInterpretationsList(mockData.visualization.type, mockData.visualization.id), {
|
|
198
|
+
wrapper: createWrapper()
|
|
199
|
+
});
|
|
200
|
+
expect(result.current.loading).toBe(true);
|
|
201
|
+
expect(result.current.data).toBeUndefined();
|
|
202
|
+
expect(result.current.error).toBeUndefined();
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
expect(result.current.loading).toBe(false);
|
|
205
|
+
expect(result.current.error).toEqual(new Error(errorMsg));
|
|
206
|
+
expect(result.current.data).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
} finally {
|
|
209
|
+
consoleErrorSpy.mockRestore();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
test('should reload when type/id changes', async () => {
|
|
213
|
+
// First load with initial params
|
|
214
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
215
|
+
interpretations: {
|
|
216
|
+
interpretations: mockData.interpretations
|
|
217
|
+
}
|
|
218
|
+
}));
|
|
219
|
+
const {
|
|
220
|
+
result,
|
|
221
|
+
rerender
|
|
222
|
+
} = renderHook(({
|
|
223
|
+
type,
|
|
224
|
+
id
|
|
225
|
+
}) => useInterpretationsList(type, id), {
|
|
226
|
+
wrapper: createWrapper(),
|
|
227
|
+
initialProps: {
|
|
228
|
+
type: mockData.visualization.type,
|
|
229
|
+
id: mockData.visualization.id
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
expect(result.current.loading).toBe(false);
|
|
234
|
+
expect(result.current.data).toEqual({
|
|
235
|
+
'2025-09-01': ['interpretation4'],
|
|
236
|
+
'2025-09-02': ['interpretation3'],
|
|
237
|
+
'2025-09-03': ['interpretation2'],
|
|
238
|
+
'2025-09-04': ['interpretation1']
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Mock second call with empty result
|
|
243
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
244
|
+
interpretations: {
|
|
245
|
+
interpretations: []
|
|
246
|
+
}
|
|
247
|
+
}));
|
|
248
|
+
|
|
249
|
+
// Change type/id to trigger reload
|
|
250
|
+
rerender({
|
|
251
|
+
type: 'TABLE',
|
|
252
|
+
id: 'newViz456'
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Should show loading again
|
|
256
|
+
expect(result.current.loading).toBe(true);
|
|
257
|
+
await waitFor(() => {
|
|
258
|
+
expect(result.current.loading).toBe(false);
|
|
259
|
+
expect(result.current.data).toEqual({});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Verify that mockQuery was called twice
|
|
263
|
+
expect(mockQuery).toHaveBeenCalledTimes(2);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
describe('Loading active interpretation', () => {
|
|
267
|
+
test('should load active interpretation', async () => {
|
|
268
|
+
await shouldLoadActiveInterpretation();
|
|
269
|
+
});
|
|
270
|
+
test('should handle active interpretation loading error', async () => {
|
|
271
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
272
|
+
const errorMsg = 'Failed to load interpretation details';
|
|
273
|
+
try {
|
|
274
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
275
|
+
interpretations: {
|
|
276
|
+
interpretations: mockData.interpretations
|
|
277
|
+
}
|
|
278
|
+
}));
|
|
279
|
+
mockQuery.mockImplementationOnce(createMockImplementation(errorMsg, true));
|
|
280
|
+
const {
|
|
281
|
+
result
|
|
282
|
+
} = renderHook(() => useAllHooks(mockData.visualization.type, mockData.visualization.id, mockData.interpretationDetails.id), {
|
|
283
|
+
wrapper: createWrapper(mockData.interpretations)
|
|
284
|
+
});
|
|
285
|
+
expect(result.current.activeInterpretation.loading).toBe(true);
|
|
286
|
+
expect(result.current.activeInterpretation.data).toBeUndefined();
|
|
287
|
+
expect(result.current.activeInterpretation.error).toBeUndefined();
|
|
288
|
+
await waitFor(() => {
|
|
289
|
+
expect(result.current.activeInterpretation.loading).toBe(false);
|
|
290
|
+
expect(result.current.activeInterpretation.error).toEqual(new Error(errorMsg));
|
|
291
|
+
expect(result.current.activeInterpretation.data).toBeUndefined();
|
|
292
|
+
});
|
|
293
|
+
} finally {
|
|
294
|
+
consoleErrorSpy.mockRestore();
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
test('should reload when interpretation ID changes', async () => {
|
|
298
|
+
// Mock first interpretation load
|
|
299
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
300
|
+
interpretations: {
|
|
301
|
+
interpretations: mockData.interpretations
|
|
302
|
+
}
|
|
303
|
+
}));
|
|
304
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
305
|
+
interpretation: mockData.interpretationDetails
|
|
306
|
+
}));
|
|
307
|
+
const {
|
|
308
|
+
result,
|
|
309
|
+
rerender
|
|
310
|
+
} = renderHook(({
|
|
311
|
+
interpretationId
|
|
312
|
+
}) => useAllHooks(mockData.visualization.type, mockData.visualization.id, interpretationId), {
|
|
313
|
+
wrapper: createWrapper(mockData.interpretations),
|
|
314
|
+
initialProps: {
|
|
315
|
+
interpretationId: mockData.interpretationDetails.id
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
await waitFor(() => {
|
|
319
|
+
expect(result.current.activeInterpretation.loading).toBe(false);
|
|
320
|
+
expect(result.current.activeInterpretation.data).toEqual(mockData.interpretationDetails);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Mock second interpretation load
|
|
324
|
+
const secondInterpretation = mockData.interpretations[1];
|
|
325
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
326
|
+
interpretation: secondInterpretation
|
|
327
|
+
}));
|
|
328
|
+
|
|
329
|
+
// Change interpretation ID to trigger reload
|
|
330
|
+
rerender({
|
|
331
|
+
interpretationId: secondInterpretation.id
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Should show loading again
|
|
335
|
+
expect(result.current.activeInterpretation.loading).toBe(true);
|
|
336
|
+
await waitFor(() => {
|
|
337
|
+
expect(result.current.activeInterpretation.loading).toBe(false);
|
|
338
|
+
expect(result.current.activeInterpretation.data).toEqual(secondInterpretation);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
// Verify that mockQuery was called for both interpretations
|
|
342
|
+
expect(mockQuery).toHaveBeenCalledTimes(3); // 1 for list + 2 for interpretations
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
describe('Interpretation subscription', () => {
|
|
346
|
+
// Update behaviour is tested in the mutation hooks
|
|
347
|
+
it('observers subscribe when mounting and unsubscribes when unmounting', async () => {
|
|
348
|
+
const {
|
|
349
|
+
result,
|
|
350
|
+
unmount
|
|
351
|
+
} = await shouldLoadActiveInterpretation();
|
|
352
|
+
const manager = result.current.manager;
|
|
353
|
+
const initialObserversCount = manager.interpretationObservers.get(mockData.interpretationDetails.id).size;
|
|
354
|
+
|
|
355
|
+
// useInterpretation (1), useActiveInterpretation(2)
|
|
356
|
+
expect(initialObserversCount).toBe(2);
|
|
357
|
+
expect(typeof manager.interpretationsListCallback).toBe('function');
|
|
358
|
+
unmount();
|
|
359
|
+
const newObserversCount = manager.interpretationObservers.get(mockData.interpretationDetails.id).size;
|
|
360
|
+
expect(newObserversCount).toBe(0);
|
|
361
|
+
expect(manager.interpretationsListCallback).toBe(null);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
describe('Access and current user hooks', () => {
|
|
365
|
+
test('useInterpretationAccess returns a valid access result', async () => {
|
|
366
|
+
const {
|
|
367
|
+
result
|
|
368
|
+
} = await shouldLoadActiveInterpretation();
|
|
369
|
+
expect(result.current.interpretationAccess).toEqual({
|
|
370
|
+
comment: true,
|
|
371
|
+
delete: true,
|
|
372
|
+
edit: true,
|
|
373
|
+
share: true
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
test('useCommentAccess returns a valid access result', async () => {
|
|
377
|
+
const {
|
|
378
|
+
result
|
|
379
|
+
} = await shouldLoadActiveInterpretation();
|
|
380
|
+
expect(result.current.commentAccess).toEqual({
|
|
381
|
+
delete: true,
|
|
382
|
+
edit: true
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
test('useInterpretationsCurrentUser returns the current user', async () => {
|
|
386
|
+
const {
|
|
387
|
+
result
|
|
388
|
+
} = await shouldLoadActiveInterpretation();
|
|
389
|
+
expect(result.current.currentUser).toEqual(mockData.currentUser);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
describe('Generic mutation behaviour', () => {
|
|
393
|
+
/* The following hooks all simply call `useInterpretationsManagerMutation`
|
|
394
|
+
* with a different `methodname`:
|
|
395
|
+
* - useCreateInterpretation
|
|
396
|
+
* - useUpdateInterpretationText
|
|
397
|
+
* - useDeleteInterpretation
|
|
398
|
+
* - useAddCommentToActiveInterpretation
|
|
399
|
+
* - useUpdateCommentForActiveInterpretation
|
|
400
|
+
* - useDeleteCommentFromActiveInterpretation
|
|
401
|
+
* So we only have to test one to test the shared behaviour */
|
|
402
|
+
it('can complete successfully after loading', async () => {
|
|
403
|
+
const {
|
|
404
|
+
result
|
|
405
|
+
} = await shouldLoadActiveInterpretation();
|
|
406
|
+
mockMutate.mockImplementationOnce(createMockImplementation({}));
|
|
407
|
+
expect(result.current.mutations.deleteState.loading).toBe(false);
|
|
408
|
+
expect(result.current.mutations.deleteState.error).toBe(undefined);
|
|
409
|
+
expect(result.current.mutations.deleteState.data).toBe(undefined);
|
|
410
|
+
act(() => {
|
|
411
|
+
result.current.mutations.deleteInterpretation();
|
|
412
|
+
});
|
|
413
|
+
await waitFor(() => {
|
|
414
|
+
expect(result.current.mutations.deleteState.loading).toBe(true);
|
|
415
|
+
expect(result.current.mutations.deleteState.error).toBe(undefined);
|
|
416
|
+
expect(result.current.mutations.deleteState.data).toBe(undefined);
|
|
417
|
+
});
|
|
418
|
+
await waitFor(() => {
|
|
419
|
+
expect(result.current.mutations.deleteState.loading).toBe(false);
|
|
420
|
+
expect(result.current.mutations.deleteState.error).toBe(undefined);
|
|
421
|
+
expect(result.current.mutations.deleteState.data).toBe(null);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
it('can return an error after loading', async () => {
|
|
425
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
426
|
+
const errorMsg = 'Failed to delete interpretation';
|
|
427
|
+
try {
|
|
428
|
+
const {
|
|
429
|
+
result
|
|
430
|
+
} = await shouldLoadActiveInterpretation();
|
|
431
|
+
mockMutate.mockImplementationOnce(createMockImplementation(errorMsg, true));
|
|
432
|
+
expect(result.current.mutations.deleteState.loading).toBe(false);
|
|
433
|
+
expect(result.current.mutations.deleteState.error).toBe(undefined);
|
|
434
|
+
expect(result.current.mutations.deleteState.data).toBe(undefined);
|
|
435
|
+
act(() => {
|
|
436
|
+
result.current.mutations.deleteInterpretation();
|
|
437
|
+
});
|
|
438
|
+
await waitFor(() => {
|
|
439
|
+
expect(result.current.mutations.deleteState.loading).toBe(true);
|
|
440
|
+
expect(result.current.mutations.deleteState.error).toBe(undefined);
|
|
441
|
+
expect(result.current.mutations.deleteState.data).toBe(undefined);
|
|
442
|
+
});
|
|
443
|
+
await waitFor(() => {
|
|
444
|
+
expect(result.current.mutations.deleteState.loading).toBe(false);
|
|
445
|
+
expect(result.current.mutations.deleteState.error).toEqual(new Error(errorMsg));
|
|
446
|
+
expect(result.current.mutations.deleteState.data).toBe(undefined);
|
|
447
|
+
});
|
|
448
|
+
} finally {
|
|
449
|
+
consoleErrorSpy.mockRestore();
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
describe('Like behaviour', () => {
|
|
454
|
+
test('Interpretation like can be toggled', async () => {
|
|
455
|
+
const {
|
|
456
|
+
result
|
|
457
|
+
} = await shouldLoadActiveInterpretation();
|
|
458
|
+
expect(result.current.like.isLikedByCurrentUser).toBe(true);
|
|
459
|
+
expect(result.current.like.toggleLikeInProgress).toBe(false);
|
|
460
|
+
act(() => {
|
|
461
|
+
result.current.like.toggleLike();
|
|
462
|
+
});
|
|
463
|
+
await waitFor(() => {
|
|
464
|
+
expect(result.current.like.toggleLikeInProgress).toBe(true);
|
|
465
|
+
expect(result.current.like.isLikedByCurrentUser).toBe(true);
|
|
466
|
+
});
|
|
467
|
+
await waitFor(() => {
|
|
468
|
+
expect(result.current.like.toggleLikeInProgress).toBe(false);
|
|
469
|
+
expect(result.current.like.isLikedByCurrentUser).toBe(false);
|
|
470
|
+
// This is also reflected in the interpretation observers
|
|
471
|
+
expect(result.current.interpretation.likes).toBe(0);
|
|
472
|
+
expect(result.current.interpretation.likedBy).toEqual([]);
|
|
473
|
+
expect(result.current.activeInterpretation.data.likes).toBe(0);
|
|
474
|
+
expect(result.current.activeInterpretation.data.likedBy).toEqual([]);
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
describe('State synchronization', () => {
|
|
479
|
+
const computeListLength = idsByDate => Object.values(idsByDate).reduce((total, idList) => total + idList.length, 0);
|
|
480
|
+
test('Creating an interpretation updates the list', async () => {
|
|
481
|
+
const {
|
|
482
|
+
result
|
|
483
|
+
} = await shouldLoadActiveInterpretation();
|
|
484
|
+
mockMutate.mockImplementationOnce(createMockImplementation({}));
|
|
485
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
486
|
+
interpretations: {
|
|
487
|
+
interpretations: mockData.interpretations.concat(mockData.newInterpretation)
|
|
488
|
+
}
|
|
489
|
+
}));
|
|
490
|
+
const initialListLength = computeListLength(result.current.interpretationsList.data);
|
|
491
|
+
act(() => {
|
|
492
|
+
result.current.mutations.createInterpretation();
|
|
493
|
+
});
|
|
494
|
+
await waitFor(() => {
|
|
495
|
+
expect(computeListLength(result.current.interpretationsList.data)).toEqual(initialListLength + 1);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
test('Deleting an interpretation updates the list', async () => {
|
|
499
|
+
const {
|
|
500
|
+
result
|
|
501
|
+
} = await shouldLoadActiveInterpretation();
|
|
502
|
+
mockMutate.mockImplementationOnce(createMockImplementation({}));
|
|
503
|
+
const initialListLength = computeListLength(result.current.interpretationsList.data);
|
|
504
|
+
act(() => {
|
|
505
|
+
result.current.mutations.deleteInterpretation();
|
|
506
|
+
});
|
|
507
|
+
await waitFor(() => {
|
|
508
|
+
expect(computeListLength(result.current.interpretationsList.data)).toBe(initialListLength - 1);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
test('Updating an interpretation text updates the (active) interpretation', async () => {
|
|
512
|
+
const {
|
|
513
|
+
result
|
|
514
|
+
} = await shouldLoadActiveInterpretation();
|
|
515
|
+
mockMutate.mockImplementationOnce(createMockImplementation({}));
|
|
516
|
+
act(() => {
|
|
517
|
+
result.current.mutations.updateInterpretationText();
|
|
518
|
+
});
|
|
519
|
+
await waitFor(() => {
|
|
520
|
+
expect(result.current.interpretation.text).toBe('Updated text');
|
|
521
|
+
expect(result.current.activeInterpretation.data.text).toBe('Updated text');
|
|
522
|
+
});
|
|
523
|
+
});
|
|
524
|
+
/* Note that adding/removing likes also updates the interpretation state, but this
|
|
525
|
+
* behaviour is asserted in the "Like behaviour" tests above */
|
|
526
|
+
test('Adding a comment to an interpretation updates the (active) interpretation', async () => {
|
|
527
|
+
const {
|
|
528
|
+
result
|
|
529
|
+
} = await shouldLoadActiveInterpretation();
|
|
530
|
+
mockMutate.mockImplementationOnce(createMockImplementation({}));
|
|
531
|
+
mockQuery.mockImplementationOnce(createMockImplementation({
|
|
532
|
+
interpretation: {
|
|
533
|
+
...mockData.interpretationDetails,
|
|
534
|
+
comments: [...mockData.interpretationDetails.comments, mockData.newComment]
|
|
535
|
+
}
|
|
536
|
+
}));
|
|
537
|
+
const initialCommentsLength = result.current.interpretation.comments.length;
|
|
538
|
+
act(() => {
|
|
539
|
+
result.current.mutations.addComment();
|
|
540
|
+
});
|
|
541
|
+
await waitFor(() => {
|
|
542
|
+
expect(result.current.interpretation.comments.length).toBe(initialCommentsLength + 1);
|
|
543
|
+
expect(result.current.activeInterpretation.data.comments.length).toBe(initialCommentsLength + 1);
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
test('Removing a comment from an interpretation updates the (active) interpretation', async () => {
|
|
547
|
+
const {
|
|
548
|
+
result
|
|
549
|
+
} = await shouldLoadActiveInterpretation();
|
|
550
|
+
mockMutate.mockImplementationOnce(createMockImplementation({}));
|
|
551
|
+
const initialCommentsLength = result.current.interpretation.comments.length;
|
|
552
|
+
act(() => {
|
|
553
|
+
result.current.mutations.deleteComment();
|
|
554
|
+
});
|
|
555
|
+
await waitFor(() => {
|
|
556
|
+
expect(result.current.interpretation.comments.length).toBe(initialCommentsLength - 1);
|
|
557
|
+
expect(result.current.activeInterpretation.data.comments.length).toBe(initialCommentsLength - 1);
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
});
|
package/build/es/components/Interpretations/InterpretationsProvider/groupInterpretationIdsByDate.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const groupInterpretationIdsByDate = interpretations => interpretations.sort((a, b) => new Date(b.created) - new Date(a.created)).reduce((dateGroups, interpretation) => {
|
|
2
|
+
const dateKey = interpretation.created.split('T')[0];
|
|
3
|
+
if (dateKey in dateGroups) {
|
|
4
|
+
dateGroups[dateKey].push(interpretation.id);
|
|
5
|
+
} else {
|
|
6
|
+
dateGroups[dateKey] = [interpretation.id];
|
|
7
|
+
}
|
|
8
|
+
return dateGroups;
|
|
9
|
+
}, {});
|