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