@dhis2/analytics 28.1.3 → 29.0.0
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 +13 -24
- 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 +14 -25
- 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,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
|
+
});
|
package/build/cjs/components/Interpretations/InterpretationsProvider/groupInterpretationIdsByDate.js
ADDED
|
@@ -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;
|