box-ui-elements 23.4.0-beta.36 → 23.4.0-beta.38
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/dist/explorer.css +1 -1
- package/dist/explorer.js +1 -1
- package/dist/preview.css +1 -1
- package/dist/preview.js +1 -1
- package/dist/sidebar.css +1 -1
- package/dist/sidebar.js +1 -1
- package/es/common/types/metadata.js.flow +5 -4
- package/es/common/types/metadata.js.map +1 -1
- package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js +43 -22
- package/es/elements/content-explorer/stories/tests/MetadataView-visual.stories.js.map +1 -1
- package/es/elements/content-preview/PreviewNavigation.js +0 -2
- package/es/elements/content-preview/PreviewNavigation.js.flow +0 -2
- package/es/elements/content-preview/PreviewNavigation.js.map +1 -1
- package/es/elements/content-sidebar/versions/VersionsSidebarContainer.js +29 -7
- package/es/elements/content-sidebar/versions/VersionsSidebarContainer.js.flow +44 -5
- package/es/elements/content-sidebar/versions/VersionsSidebarContainer.js.map +1 -1
- package/es/elements/content-sidebar/withSidebarAnnotations.js +141 -35
- package/es/elements/content-sidebar/withSidebarAnnotations.js.flow +199 -37
- package/es/elements/content-sidebar/withSidebarAnnotations.js.map +1 -1
- package/es/features/metadata-instance-editor/CascadePolicy.js +6 -3
- package/es/features/metadata-instance-editor/CascadePolicy.js.flow +8 -2
- package/es/features/metadata-instance-editor/CascadePolicy.js.map +1 -1
- package/es/features/metadata-instance-editor/Instance.js +1 -0
- package/es/features/metadata-instance-editor/Instance.js.flow +1 -0
- package/es/features/metadata-instance-editor/Instance.js.map +1 -1
- package/es/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.d.ts +1 -1
- package/i18n/bn-IN.js +1 -1
- package/i18n/bn-IN.properties +4 -0
- package/i18n/da-DK.js +1 -1
- package/i18n/da-DK.properties +4 -0
- package/i18n/de-DE.js +1 -1
- package/i18n/de-DE.properties +4 -0
- package/i18n/en-AU.js +1 -1
- package/i18n/en-AU.properties +4 -0
- package/i18n/en-CA.js +1 -1
- package/i18n/en-CA.properties +4 -0
- package/i18n/en-GB.js +1 -1
- package/i18n/en-GB.properties +4 -0
- package/i18n/es-419.js +1 -1
- package/i18n/es-419.properties +4 -0
- package/i18n/es-ES.js +1 -1
- package/i18n/es-ES.properties +4 -0
- package/i18n/fi-FI.js +1 -1
- package/i18n/fi-FI.properties +4 -0
- package/i18n/fr-CA.js +1 -1
- package/i18n/fr-CA.properties +4 -0
- package/i18n/fr-FR.js +1 -1
- package/i18n/fr-FR.properties +4 -0
- package/i18n/hi-IN.js +1 -1
- package/i18n/hi-IN.properties +4 -0
- package/i18n/it-IT.js +1 -1
- package/i18n/it-IT.properties +4 -0
- package/i18n/ja-JP.js +1 -1
- package/i18n/ja-JP.properties +4 -0
- package/i18n/ko-KR.js +1 -1
- package/i18n/ko-KR.properties +4 -0
- package/i18n/nb-NO.js +1 -1
- package/i18n/nb-NO.properties +4 -0
- package/i18n/nl-NL.js +1 -1
- package/i18n/nl-NL.properties +4 -0
- package/i18n/pl-PL.js +1 -1
- package/i18n/pl-PL.properties +4 -0
- package/i18n/pt-BR.js +1 -1
- package/i18n/pt-BR.properties +4 -0
- package/i18n/ru-RU.js +1 -1
- package/i18n/ru-RU.properties +4 -0
- package/i18n/sv-SE.js +1 -1
- package/i18n/sv-SE.properties +4 -0
- package/i18n/tr-TR.js +1 -1
- package/i18n/tr-TR.properties +4 -0
- package/i18n/zh-CN.js +1 -1
- package/i18n/zh-CN.properties +4 -0
- package/i18n/zh-TW.js +1 -1
- package/i18n/zh-TW.properties +4 -0
- package/package.json +3 -3
- package/src/common/types/metadata.js +5 -4
- package/src/elements/content-explorer/stories/tests/MetadataView-visual.stories.tsx +47 -31
- package/src/elements/content-preview/PreviewNavigation.js +0 -2
- package/src/elements/content-preview/__tests__/PreviewNavigation.test.js +12 -12
- package/src/elements/content-sidebar/__tests__/withSidebarAnnotations.rtl.test.js +1152 -0
- package/src/elements/content-sidebar/versions/VersionsSidebarContainer.js +44 -5
- package/src/elements/content-sidebar/versions/__tests__/VersionsSidebarContainer.test.js +200 -43
- package/src/elements/content-sidebar/versions/__tests__/__snapshots__/VersionsSidebarContainer.test.js.snap +2 -2
- package/src/elements/content-sidebar/withSidebarAnnotations.js +199 -37
- package/src/features/metadata-instance-editor/CascadePolicy.js +8 -2
- package/src/features/metadata-instance-editor/Instance.js +1 -0
- package/src/features/metadata-instance-editor/__tests__/CascadePolicy.test.js +45 -0
- package/src/elements/content-sidebar/__tests__/withSidebarAnnotations.test.js +0 -626
|
@@ -0,0 +1,1152 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
import { render } from '../../../test-utils/testing-library';
|
|
4
|
+
import withSidebarAnnotations from '../withSidebarAnnotations';
|
|
5
|
+
import { Action } from '../../common/annotator-context/types';
|
|
6
|
+
import { FEED_ITEM_TYPE_VERSION } from '../../../constants';
|
|
7
|
+
|
|
8
|
+
describe('elements/content-sidebar/withSidebarAnnotations', () => {
|
|
9
|
+
const TestComponent = React.forwardRef((props, ref) => <div data-testid="test-component" ref={ref} />);
|
|
10
|
+
const WrappedComponent = withSidebarAnnotations(TestComponent);
|
|
11
|
+
|
|
12
|
+
const currentUser = {
|
|
13
|
+
id: 'foo',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const file = {
|
|
17
|
+
id: 'id',
|
|
18
|
+
file_version: {
|
|
19
|
+
id: '123',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const feedAPI = {
|
|
24
|
+
addAnnotation: jest.fn(),
|
|
25
|
+
addPendingReply: jest.fn(),
|
|
26
|
+
feedItems: jest.fn(),
|
|
27
|
+
getCachedItems: jest.fn(),
|
|
28
|
+
deleteAnnotation: jest.fn(),
|
|
29
|
+
deleteFeedItem: jest.fn(),
|
|
30
|
+
deleteReplyItem: jest.fn(),
|
|
31
|
+
modifyFeedItemRepliesCountBy: jest.fn(),
|
|
32
|
+
updateFeedItem: jest.fn(),
|
|
33
|
+
updateReplyItem: jest.fn(),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const api = {
|
|
37
|
+
getFeedAPI: () => feedAPI,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const getAnnotationsPath = jest.fn();
|
|
41
|
+
const getAnnotationsMatchPath = jest.fn();
|
|
42
|
+
|
|
43
|
+
const annotatorContextProps = {
|
|
44
|
+
getAnnotationsMatchPath,
|
|
45
|
+
getAnnotationsPath,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const history = {
|
|
49
|
+
push: jest.fn(),
|
|
50
|
+
replace: jest.fn(),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const onVersionChange = jest.fn();
|
|
54
|
+
|
|
55
|
+
const internalSidebarNavigationHandler = jest.fn();
|
|
56
|
+
|
|
57
|
+
const defaultProps = {
|
|
58
|
+
api,
|
|
59
|
+
...annotatorContextProps,
|
|
60
|
+
file,
|
|
61
|
+
history,
|
|
62
|
+
onVersionChange,
|
|
63
|
+
location: { pathname: '/activity' },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const createComponentElement = (props = {}) => {
|
|
67
|
+
const componentProps = {
|
|
68
|
+
...defaultProps,
|
|
69
|
+
...props,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<MemoryRouter initialEntries={['/activity']}>
|
|
74
|
+
<WrappedComponent {...componentProps} />
|
|
75
|
+
</MemoryRouter>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const createRouterDisabledComponentElement = (props = {}) => {
|
|
80
|
+
const routerDisabledProps = {
|
|
81
|
+
routerDisabled: true,
|
|
82
|
+
internalSidebarNavigationHandler,
|
|
83
|
+
internalSidebarNavigation: { sidebar: 'activity' },
|
|
84
|
+
...defaultProps,
|
|
85
|
+
...props,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return <WrappedComponent {...routerDisabledProps} />;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const renderWithSidebarAnnotations = (props = {}) => {
|
|
92
|
+
return render(createComponentElement(props));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const renderWithSidebarAnnotationsRouterDisabled = (props = {}) => {
|
|
96
|
+
return render(createRouterDisabledComponentElement(props));
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
beforeEach(() => {
|
|
100
|
+
jest.resetAllMocks();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('constructor', () => {
|
|
104
|
+
test('should call redirectDeeplinkedAnnotation (use side effect to test it)', () => {
|
|
105
|
+
renderWithSidebarAnnotations();
|
|
106
|
+
|
|
107
|
+
expect(getAnnotationsMatchPath).toHaveBeenCalledTimes(1);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test.each`
|
|
111
|
+
fileVersionId | annotationId | expectedCallCount
|
|
112
|
+
${undefined} | ${'987'} | ${0}
|
|
113
|
+
${'123'} | ${'987'} | ${0}
|
|
114
|
+
${'124'} | ${'987'} | ${1}
|
|
115
|
+
${'124'} | ${undefined} | ${1}
|
|
116
|
+
`(
|
|
117
|
+
'should call history.replace appropriately if router location annotationId=$annotationId and fileVersionId=$fileVersionId',
|
|
118
|
+
({ annotationId, fileVersionId, expectedCallCount }) => {
|
|
119
|
+
// Setup mocks before rendering (constructor will run during render)
|
|
120
|
+
getAnnotationsMatchPath.mockReturnValue({ params: { annotationId, fileVersionId } });
|
|
121
|
+
|
|
122
|
+
renderWithSidebarAnnotations();
|
|
123
|
+
|
|
124
|
+
expect(history.replace).toHaveBeenCalledTimes(expectedCallCount);
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
test.each`
|
|
129
|
+
fileVersionId | annotationId | expectedPath
|
|
130
|
+
${'124'} | ${'987'} | ${'/activity/annotations/123/987'}
|
|
131
|
+
${'124'} | ${undefined} | ${'/activity/annotations/123'}
|
|
132
|
+
`('should call history.replace with $expectedPath', ({ fileVersionId, annotationId, expectedPath }) => {
|
|
133
|
+
getAnnotationsMatchPath.mockReturnValue({ params: { annotationId, fileVersionId } });
|
|
134
|
+
getAnnotationsPath.mockReturnValue(expectedPath);
|
|
135
|
+
|
|
136
|
+
renderWithSidebarAnnotations();
|
|
137
|
+
|
|
138
|
+
expect(history.replace).toHaveBeenCalledWith(expectedPath);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('constructor - Router Disabled', () => {
|
|
142
|
+
test('should call redirectDeeplinkedAnnotation when router disabled', () => {
|
|
143
|
+
// In router-disabled mode, getAnnotationsMatchPath is NOT called
|
|
144
|
+
// Only getInternalNavigationMatch is used internally
|
|
145
|
+
renderWithSidebarAnnotationsRouterDisabled();
|
|
146
|
+
|
|
147
|
+
expect(getAnnotationsMatchPath).toHaveBeenCalledTimes(0);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test.each`
|
|
151
|
+
fileVersionId | annotationId | expectedCallCount
|
|
152
|
+
${undefined} | ${'987'} | ${0}
|
|
153
|
+
${'123'} | ${'987'} | ${0}
|
|
154
|
+
${'124'} | ${'987'} | ${1}
|
|
155
|
+
${'124'} | ${undefined} | ${1}
|
|
156
|
+
`(
|
|
157
|
+
'should call internalSidebarNavigationHandler appropriately if internal navigation annotationId=$annotationId and fileVersionId=$fileVersionId',
|
|
158
|
+
({ annotationId, fileVersionId, expectedCallCount }) => {
|
|
159
|
+
// Only provide fileVersionId and annotationId if they should trigger navigation
|
|
160
|
+
const internalNavigation = fileVersionId
|
|
161
|
+
? {
|
|
162
|
+
sidebar: 'activity',
|
|
163
|
+
activeFeedEntryType: 'annotations',
|
|
164
|
+
activeFeedEntryId: annotationId,
|
|
165
|
+
fileVersionId,
|
|
166
|
+
}
|
|
167
|
+
: { sidebar: 'activity' };
|
|
168
|
+
|
|
169
|
+
renderWithSidebarAnnotationsRouterDisabled({
|
|
170
|
+
internalSidebarNavigation: internalNavigation,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledTimes(expectedCallCount);
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
test.each`
|
|
178
|
+
fileVersionId | annotationId | expectedNavigation
|
|
179
|
+
${'124'} | ${'987'} | ${{ sidebar: 'activity', activeFeedEntryType: 'annotations', activeFeedEntryId: '987', fileVersionId: '123' }}
|
|
180
|
+
${'124'} | ${undefined} | ${{ sidebar: 'activity', activeFeedEntryType: 'annotations', activeFeedEntryId: undefined, fileVersionId: '123' }}
|
|
181
|
+
`(
|
|
182
|
+
'should call internalSidebarNavigationHandler with correct navigation for fileVersionId=$fileVersionId and annotationId=$annotationId',
|
|
183
|
+
({ fileVersionId, annotationId, expectedNavigation }) => {
|
|
184
|
+
const internalNavigation = {
|
|
185
|
+
sidebar: 'activity',
|
|
186
|
+
activeFeedEntryType: 'annotations',
|
|
187
|
+
activeFeedEntryId: annotationId,
|
|
188
|
+
fileVersionId,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
renderWithSidebarAnnotationsRouterDisabled({
|
|
192
|
+
internalSidebarNavigation: internalNavigation,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// The second parameter is `true` indicating this is a redirect/correction
|
|
196
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledWith(expectedNavigation, true);
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe('componentDidUpdate', () => {
|
|
203
|
+
test.each`
|
|
204
|
+
fileId | expectedCount
|
|
205
|
+
${'123'} | ${0}
|
|
206
|
+
${'456'} | ${1}
|
|
207
|
+
`('should call onVersionChange appropriately if file id changes to $fileId', ({ fileId, expectedCount }) => {
|
|
208
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
209
|
+
fileId: '123',
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
jest.clearAllMocks();
|
|
213
|
+
|
|
214
|
+
// Trigger componentDidUpdate by changing the fileId
|
|
215
|
+
rerender(
|
|
216
|
+
createComponentElement({
|
|
217
|
+
fileId,
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(onVersionChange).toHaveBeenCalledTimes(expectedCount);
|
|
222
|
+
|
|
223
|
+
if (expectedCount > 0) {
|
|
224
|
+
expect(onVersionChange).toHaveBeenCalledWith(null);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('refreshActivitySidebar', () => {
|
|
230
|
+
test.each`
|
|
231
|
+
pathname | isOpen | hasRefCurrent | expectedCount
|
|
232
|
+
${'/'} | ${false} | ${false} | ${0}
|
|
233
|
+
${'/details'} | ${true} | ${false} | ${0}
|
|
234
|
+
${'/activity'} | ${false} | ${false} | ${0}
|
|
235
|
+
${'/activity'} | ${true} | ${false} | ${0}
|
|
236
|
+
${'/activity'} | ${false} | ${true} | ${0}
|
|
237
|
+
${'/activity'} | ${true} | ${true} | ${1}
|
|
238
|
+
${'/activity/versions/12345'} | ${true} | ${true} | ${1}
|
|
239
|
+
${'/activity/versions/12345/67890'} | ${true} | ${true} | ${1}
|
|
240
|
+
${'/details'} | ${true} | ${true} | ${0}
|
|
241
|
+
${'/'} | ${true} | ${true} | ${0}
|
|
242
|
+
`(
|
|
243
|
+
'should refresh the sidebarPanels ref accordingly if pathname=$pathname, isOpen=$isOpen, hasRefCurrent=$hasRefCurrent',
|
|
244
|
+
({ pathname, isOpen, hasRefCurrent, expectedCount }) => {
|
|
245
|
+
const mockRefresh = jest.fn();
|
|
246
|
+
const annotationUpdate = {
|
|
247
|
+
id: '123',
|
|
248
|
+
description: {
|
|
249
|
+
message: 'text',
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
const annotatorStateMock = {
|
|
253
|
+
action: Action.UPDATE_END,
|
|
254
|
+
annotation: annotationUpdate,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Create a custom test component that exposes the ref
|
|
258
|
+
const TestComponentWithRef = React.forwardRef((props, ref) => {
|
|
259
|
+
React.useImperativeHandle(ref, () => (hasRefCurrent ? { refresh: mockRefresh } : null));
|
|
260
|
+
return <div data-testid="test-component" />;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const WrappedComponentWithRef = withSidebarAnnotations(TestComponentWithRef);
|
|
264
|
+
|
|
265
|
+
const { rerender } = render(
|
|
266
|
+
<MemoryRouter initialEntries={[pathname]}>
|
|
267
|
+
<WrappedComponentWithRef
|
|
268
|
+
{...defaultProps}
|
|
269
|
+
annotatorState={{ annotation: {}, action: Action.CREATE_START }}
|
|
270
|
+
currentUser={currentUser}
|
|
271
|
+
isOpen={isOpen}
|
|
272
|
+
location={{ pathname }}
|
|
273
|
+
/>
|
|
274
|
+
</MemoryRouter>,
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
jest.clearAllMocks();
|
|
278
|
+
|
|
279
|
+
// Trigger updateAnnotation by changing the annotatorState
|
|
280
|
+
rerender(
|
|
281
|
+
<MemoryRouter initialEntries={[pathname]}>
|
|
282
|
+
<WrappedComponentWithRef
|
|
283
|
+
{...defaultProps}
|
|
284
|
+
annotatorState={annotatorStateMock}
|
|
285
|
+
currentUser={currentUser}
|
|
286
|
+
isOpen={isOpen}
|
|
287
|
+
location={{ pathname }}
|
|
288
|
+
/>
|
|
289
|
+
</MemoryRouter>,
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
expect(mockRefresh).toHaveBeenCalledTimes(expectedCount);
|
|
293
|
+
if (expectedCount > 0) {
|
|
294
|
+
expect(mockRefresh).toHaveBeenCalledWith(false);
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
describe('refreshActivitySidebar - Router Disabled', () => {
|
|
300
|
+
test.each`
|
|
301
|
+
navigation | isOpen | hasRefCurrent | expectedCount
|
|
302
|
+
${{ sidebar: 'details' }} | ${false} | ${false} | ${0}
|
|
303
|
+
${{ sidebar: 'details' }} | ${true} | ${false} | ${0}
|
|
304
|
+
${{ sidebar: 'activity' }} | ${false} | ${false} | ${0}
|
|
305
|
+
${{ sidebar: 'activity' }} | ${true} | ${false} | ${0}
|
|
306
|
+
${{ sidebar: 'activity' }} | ${false} | ${true} | ${0}
|
|
307
|
+
${{ sidebar: 'activity' }} | ${true} | ${true} | ${1}
|
|
308
|
+
${{ sidebar: 'activity', versionId: '12345' }} | ${true} | ${true} | ${1}
|
|
309
|
+
${{ sidebar: 'activity', versionId: '12345', annotationId: '67890' }} | ${true} | ${true} | ${1}
|
|
310
|
+
${{ sidebar: 'details' }} | ${true} | ${true} | ${0}
|
|
311
|
+
${{ sidebar: 'metadata' }} | ${true} | ${true} | ${0}
|
|
312
|
+
`(
|
|
313
|
+
'should refresh the sidebarPanels ref accordingly if navigation=$navigation, isOpen=$isOpen, hasRefCurrent=$hasRefCurrent',
|
|
314
|
+
({ navigation, isOpen, hasRefCurrent, expectedCount }) => {
|
|
315
|
+
const mockRefresh = jest.fn();
|
|
316
|
+
const annotationUpdate = {
|
|
317
|
+
id: '123',
|
|
318
|
+
description: {
|
|
319
|
+
message: 'text',
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
const annotatorStateMock = {
|
|
323
|
+
action: Action.UPDATE_END,
|
|
324
|
+
annotation: annotationUpdate,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Create a custom test component that exposes the ref
|
|
328
|
+
const TestComponentWithRef = React.forwardRef((props, ref) => {
|
|
329
|
+
React.useImperativeHandle(ref, () => (hasRefCurrent ? { refresh: mockRefresh } : null));
|
|
330
|
+
return <div data-testid="test-component" />;
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const WrappedComponentWithRef = withSidebarAnnotations(TestComponentWithRef);
|
|
334
|
+
|
|
335
|
+
const { rerender } = render(
|
|
336
|
+
<WrappedComponentWithRef
|
|
337
|
+
{...defaultProps}
|
|
338
|
+
routerDisabled
|
|
339
|
+
internalSidebarNavigationHandler={internalSidebarNavigationHandler}
|
|
340
|
+
internalSidebarNavigation={navigation}
|
|
341
|
+
annotatorState={{ annotation: {}, action: Action.CREATE_START }}
|
|
342
|
+
currentUser={currentUser}
|
|
343
|
+
isOpen={isOpen}
|
|
344
|
+
/>,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
jest.clearAllMocks();
|
|
348
|
+
|
|
349
|
+
// Trigger updateAnnotation by changing the annotatorState
|
|
350
|
+
rerender(
|
|
351
|
+
<WrappedComponentWithRef
|
|
352
|
+
{...defaultProps}
|
|
353
|
+
routerDisabled
|
|
354
|
+
internalSidebarNavigationHandler={internalSidebarNavigationHandler}
|
|
355
|
+
internalSidebarNavigation={navigation}
|
|
356
|
+
annotatorState={annotatorStateMock}
|
|
357
|
+
currentUser={currentUser}
|
|
358
|
+
isOpen={isOpen}
|
|
359
|
+
/>,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
expect(mockRefresh).toHaveBeenCalledTimes(expectedCount);
|
|
363
|
+
if (expectedCount > 0) {
|
|
364
|
+
expect(mockRefresh).toHaveBeenCalledWith(false);
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
describe('addAnnotation', () => {
|
|
372
|
+
test('should throw if no user', () => {
|
|
373
|
+
const annotatorStateMock = {
|
|
374
|
+
annotation: {},
|
|
375
|
+
action: Action.CREATE_END,
|
|
376
|
+
meta: { requestId: '123' },
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
expect(() => {
|
|
380
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
381
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
rerender(
|
|
385
|
+
createComponentElement({
|
|
386
|
+
annotatorState: annotatorStateMock,
|
|
387
|
+
}),
|
|
388
|
+
);
|
|
389
|
+
}).toThrow('Bad box user!');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test('should do nothing if meta or requestId is not present', () => {
|
|
393
|
+
const annotatorStateMock = {
|
|
394
|
+
annotation: {},
|
|
395
|
+
action: Action.CREATE_END,
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
399
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
400
|
+
currentUser,
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
jest.clearAllMocks();
|
|
404
|
+
|
|
405
|
+
rerender(
|
|
406
|
+
createComponentElement({
|
|
407
|
+
annotatorState: annotatorStateMock,
|
|
408
|
+
currentUser,
|
|
409
|
+
}),
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
expect(feedAPI.addAnnotation).not.toHaveBeenCalled();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test.each`
|
|
416
|
+
hasItems | expectedAddCount
|
|
417
|
+
${undefined} | ${0}
|
|
418
|
+
${[]} | ${1}
|
|
419
|
+
`(
|
|
420
|
+
'should add the annotation to the feed cache accordingly if the cache items is $hasItems',
|
|
421
|
+
({ hasItems, expectedAddCount }) => {
|
|
422
|
+
const annotatorStateMock = {
|
|
423
|
+
annotation: {},
|
|
424
|
+
action: Action.CREATE_END,
|
|
425
|
+
meta: { requestId: '123' },
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
feedAPI.getCachedItems.mockReturnValue({ items: hasItems });
|
|
429
|
+
|
|
430
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
431
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
432
|
+
currentUser,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
jest.clearAllMocks();
|
|
436
|
+
feedAPI.getCachedItems.mockReturnValue({ items: hasItems });
|
|
437
|
+
|
|
438
|
+
rerender(
|
|
439
|
+
createComponentElement({
|
|
440
|
+
annotatorState: annotatorStateMock,
|
|
441
|
+
currentUser,
|
|
442
|
+
}),
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
expect(feedAPI.addAnnotation).toHaveBeenCalledTimes(expectedAddCount);
|
|
446
|
+
|
|
447
|
+
if (expectedAddCount > 0) {
|
|
448
|
+
expect(feedAPI.addAnnotation).toHaveBeenCalledWith(
|
|
449
|
+
file,
|
|
450
|
+
currentUser,
|
|
451
|
+
{},
|
|
452
|
+
'123',
|
|
453
|
+
false, // isPending = false for 'create_end' action
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
test.each`
|
|
460
|
+
annotation | expectedCount
|
|
461
|
+
${{}} | ${1}
|
|
462
|
+
${undefined} | ${0}
|
|
463
|
+
${null} | ${0}
|
|
464
|
+
`(
|
|
465
|
+
'should call addAnnotation $expectedCount times if annotation changed to $annotation',
|
|
466
|
+
({ annotation, expectedCount }) => {
|
|
467
|
+
const annotatorStateMock = {
|
|
468
|
+
annotation,
|
|
469
|
+
action: Action.CREATE_END,
|
|
470
|
+
meta: { requestId: '123' },
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
feedAPI.getCachedItems.mockReturnValue({ items: [] });
|
|
474
|
+
|
|
475
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
476
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
477
|
+
currentUser,
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
jest.clearAllMocks();
|
|
481
|
+
feedAPI.getCachedItems.mockReturnValue({ items: [] });
|
|
482
|
+
|
|
483
|
+
// Trigger componentDidUpdate by changing the annotatorState
|
|
484
|
+
rerender(
|
|
485
|
+
createComponentElement({
|
|
486
|
+
annotatorState: annotatorStateMock,
|
|
487
|
+
currentUser,
|
|
488
|
+
}),
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
// Verify the side effect of addAnnotation being called
|
|
492
|
+
expect(feedAPI.addAnnotation).toHaveBeenCalledTimes(expectedCount);
|
|
493
|
+
|
|
494
|
+
if (expectedCount > 0) {
|
|
495
|
+
expect(feedAPI.addAnnotation).toHaveBeenCalledWith(
|
|
496
|
+
file,
|
|
497
|
+
currentUser,
|
|
498
|
+
annotation,
|
|
499
|
+
'123',
|
|
500
|
+
false, // isPending = false for 'create_end' action
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
describe('addAnnotationReply', () => {
|
|
508
|
+
test.each`
|
|
509
|
+
action
|
|
510
|
+
${Action.REPLY_CREATE_START}
|
|
511
|
+
${Action.REPLY_CREATE_END}
|
|
512
|
+
`('should call addAnnotationReply if given action = $action', ({ action }) => {
|
|
513
|
+
const annotation = { id: '123' };
|
|
514
|
+
const annotationReply = { id: '456', tagged_message: 'abc' };
|
|
515
|
+
const requestId = 'comment_456';
|
|
516
|
+
const annotatorStateMock = {
|
|
517
|
+
action,
|
|
518
|
+
annotation,
|
|
519
|
+
annotationReply,
|
|
520
|
+
meta: { requestId },
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
feedAPI.getCachedItems.mockReturnValue({
|
|
524
|
+
items: [
|
|
525
|
+
{
|
|
526
|
+
id: '123',
|
|
527
|
+
replies: [annotationReply],
|
|
528
|
+
total_reply_count: 2,
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
534
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
535
|
+
currentUser,
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
jest.clearAllMocks();
|
|
539
|
+
|
|
540
|
+
rerender(
|
|
541
|
+
createComponentElement({
|
|
542
|
+
annotatorState: annotatorStateMock,
|
|
543
|
+
currentUser,
|
|
544
|
+
}),
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
if (action === Action.REPLY_CREATE_START) {
|
|
548
|
+
expect(feedAPI.addPendingReply).toHaveBeenCalledTimes(1);
|
|
549
|
+
expect(feedAPI.addPendingReply).toHaveBeenCalledWith(annotation.id, currentUser, {
|
|
550
|
+
...annotationReply,
|
|
551
|
+
id: requestId,
|
|
552
|
+
});
|
|
553
|
+
expect(feedAPI.modifyFeedItemRepliesCountBy).not.toHaveBeenCalled();
|
|
554
|
+
expect(feedAPI.updateReplyItem).not.toHaveBeenCalled();
|
|
555
|
+
} else {
|
|
556
|
+
expect(feedAPI.modifyFeedItemRepliesCountBy).toHaveBeenCalledTimes(1);
|
|
557
|
+
expect(feedAPI.modifyFeedItemRepliesCountBy).toHaveBeenCalledWith(annotation.id, 1);
|
|
558
|
+
expect(feedAPI.updateReplyItem).toHaveBeenCalledTimes(1);
|
|
559
|
+
expect(feedAPI.updateReplyItem).toHaveBeenCalledWith(
|
|
560
|
+
{ ...annotationReply, isPending: false },
|
|
561
|
+
annotation.id,
|
|
562
|
+
requestId,
|
|
563
|
+
);
|
|
564
|
+
expect(feedAPI.addPendingReply).not.toHaveBeenCalled();
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
describe('deleteAnnotation', () => {
|
|
570
|
+
test.each`
|
|
571
|
+
action
|
|
572
|
+
${Action.DELETE_START}
|
|
573
|
+
${Action.DELETE_END}
|
|
574
|
+
`('should call deleteAnnotation if given action = $action', ({ action }) => {
|
|
575
|
+
const annotation = { id: '123' };
|
|
576
|
+
const annotatorStateMock = {
|
|
577
|
+
annotation,
|
|
578
|
+
action,
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
582
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
jest.clearAllMocks();
|
|
586
|
+
|
|
587
|
+
rerender(
|
|
588
|
+
createComponentElement({
|
|
589
|
+
annotatorState: annotatorStateMock,
|
|
590
|
+
}),
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
if (action === Action.DELETE_START) {
|
|
594
|
+
expect(feedAPI.updateFeedItem).toHaveBeenCalledTimes(1);
|
|
595
|
+
expect(feedAPI.updateFeedItem).toHaveBeenCalledWith({ isPending: true }, annotation.id);
|
|
596
|
+
expect(feedAPI.deleteFeedItem).not.toHaveBeenCalled();
|
|
597
|
+
} else {
|
|
598
|
+
expect(feedAPI.deleteFeedItem).toHaveBeenCalledTimes(1);
|
|
599
|
+
expect(feedAPI.deleteFeedItem).toHaveBeenCalledWith(annotation.id);
|
|
600
|
+
expect(feedAPI.updateFeedItem).not.toHaveBeenCalled();
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
describe('deleteAnnotationReply', () => {
|
|
606
|
+
test.each`
|
|
607
|
+
action
|
|
608
|
+
${Action.REPLY_DELETE_START}
|
|
609
|
+
${Action.REPLY_DELETE_END}
|
|
610
|
+
`('should call deleteAnnotationReply if given action = $action', ({ action }) => {
|
|
611
|
+
const annotation = { id: '123' };
|
|
612
|
+
const annotationReply = { id: '456' };
|
|
613
|
+
const annotatorStateMock = {
|
|
614
|
+
action,
|
|
615
|
+
annotation,
|
|
616
|
+
annotationReply,
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
feedAPI.getCachedItems.mockReturnValue({
|
|
620
|
+
items: [
|
|
621
|
+
{
|
|
622
|
+
id: '123',
|
|
623
|
+
replies: [{ id: '456', tagged_message: 'abc' }],
|
|
624
|
+
total_reply_count: 2,
|
|
625
|
+
},
|
|
626
|
+
],
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
630
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
jest.clearAllMocks();
|
|
634
|
+
|
|
635
|
+
rerender(
|
|
636
|
+
createComponentElement({
|
|
637
|
+
annotatorState: annotatorStateMock,
|
|
638
|
+
}),
|
|
639
|
+
);
|
|
640
|
+
|
|
641
|
+
if (action === Action.REPLY_DELETE_START) {
|
|
642
|
+
expect(feedAPI.updateReplyItem).toHaveBeenCalledTimes(1);
|
|
643
|
+
expect(feedAPI.updateReplyItem).toHaveBeenCalledWith(
|
|
644
|
+
{ isPending: true },
|
|
645
|
+
annotation.id,
|
|
646
|
+
annotationReply.id,
|
|
647
|
+
);
|
|
648
|
+
expect(feedAPI.deleteReplyItem).not.toHaveBeenCalled();
|
|
649
|
+
expect(feedAPI.modifyFeedItemRepliesCountBy).not.toHaveBeenCalled();
|
|
650
|
+
} else {
|
|
651
|
+
expect(feedAPI.deleteReplyItem).toHaveBeenCalledTimes(1);
|
|
652
|
+
expect(feedAPI.deleteReplyItem).toHaveBeenCalledWith(annotationReply.id, annotation.id);
|
|
653
|
+
expect(feedAPI.modifyFeedItemRepliesCountBy).toHaveBeenCalledTimes(1);
|
|
654
|
+
expect(feedAPI.modifyFeedItemRepliesCountBy).toHaveBeenCalledWith(annotation.id, -1);
|
|
655
|
+
expect(feedAPI.updateReplyItem).not.toHaveBeenCalled();
|
|
656
|
+
}
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
test('should update appropriate annotation if reply is currently not in the feed given action = reply_delete_end', () => {
|
|
660
|
+
const annotation = { id: '123' };
|
|
661
|
+
const annotationReply = { id: '456' };
|
|
662
|
+
const annotatorStateMock = {
|
|
663
|
+
action: Action.REPLY_DELETE_END,
|
|
664
|
+
annotation,
|
|
665
|
+
annotationReply,
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
// Mock setup: reply with id '456' is NOT in the cached replies (only '999' is present)
|
|
669
|
+
feedAPI.getCachedItems.mockReturnValue({
|
|
670
|
+
items: [
|
|
671
|
+
{
|
|
672
|
+
id: '123',
|
|
673
|
+
replies: [{ id: '999', tagged_message: 'abc' }],
|
|
674
|
+
total_reply_count: 2,
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
680
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
jest.clearAllMocks();
|
|
684
|
+
|
|
685
|
+
rerender(
|
|
686
|
+
createComponentElement({
|
|
687
|
+
annotatorState: annotatorStateMock,
|
|
688
|
+
}),
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
// Since reply is not in cached items, only modifyFeedItemRepliesCountBy should be called
|
|
692
|
+
expect(feedAPI.modifyFeedItemRepliesCountBy).toHaveBeenCalledTimes(1);
|
|
693
|
+
expect(feedAPI.modifyFeedItemRepliesCountBy).toHaveBeenCalledWith(annotation.id, -1);
|
|
694
|
+
expect(feedAPI.deleteReplyItem).not.toHaveBeenCalled();
|
|
695
|
+
expect(feedAPI.updateReplyItem).not.toHaveBeenCalled();
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
describe('updateAnnotation', () => {
|
|
700
|
+
test.each`
|
|
701
|
+
action | expectedIsPending
|
|
702
|
+
${Action.UPDATE_START} | ${true}
|
|
703
|
+
${Action.UPDATE_END} | ${false}
|
|
704
|
+
`('should call updateAnnotation if given action = $action', ({ action, expectedIsPending }) => {
|
|
705
|
+
const annotation = { id: '123', status: 'resolved' };
|
|
706
|
+
const annotatorStateMock = {
|
|
707
|
+
annotation,
|
|
708
|
+
action,
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
712
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
jest.clearAllMocks();
|
|
716
|
+
|
|
717
|
+
rerender(
|
|
718
|
+
createComponentElement({
|
|
719
|
+
annotatorState: annotatorStateMock,
|
|
720
|
+
}),
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
const expectedAnnotationData = { ...annotation, isPending: expectedIsPending };
|
|
724
|
+
|
|
725
|
+
expect(feedAPI.updateFeedItem).toHaveBeenCalledTimes(1);
|
|
726
|
+
expect(feedAPI.updateFeedItem).toHaveBeenCalledWith(expectedAnnotationData, annotation.id);
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
describe('updateAnnotationReply', () => {
|
|
731
|
+
test.each`
|
|
732
|
+
action | expectedIsPending
|
|
733
|
+
${Action.REPLY_UPDATE_START} | ${true}
|
|
734
|
+
${Action.REPLY_UPDATE_END} | ${false}
|
|
735
|
+
`('should call updateAnnotationReply if given action = $action', ({ action, expectedIsPending }) => {
|
|
736
|
+
const annotation = { id: '123' };
|
|
737
|
+
const annotationReply = { id: '456', tagged_message: 'abc' };
|
|
738
|
+
const annotatorStateMock = {
|
|
739
|
+
action,
|
|
740
|
+
annotation,
|
|
741
|
+
annotationReply,
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
745
|
+
annotatorState: { annotation: {}, action: Action.CREATE_START },
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
jest.clearAllMocks();
|
|
749
|
+
|
|
750
|
+
rerender(
|
|
751
|
+
createComponentElement({
|
|
752
|
+
annotatorState: annotatorStateMock,
|
|
753
|
+
}),
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
const expectedReplyData = { ...annotationReply, isPending: expectedIsPending };
|
|
757
|
+
|
|
758
|
+
expect(feedAPI.updateReplyItem).toHaveBeenCalledTimes(1);
|
|
759
|
+
expect(feedAPI.updateReplyItem).toHaveBeenCalledWith(expectedReplyData, annotation.id, annotationReply.id);
|
|
760
|
+
});
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
describe('updateActiveAnnotation', () => {
|
|
764
|
+
test.each`
|
|
765
|
+
condition | prevActiveAnnotationId | activeAnnotationId | isAnnotationsPath | expectedHistoryPushCalls
|
|
766
|
+
${'annotation ids are the same'} | ${'123'} | ${'123'} | ${true} | ${0}
|
|
767
|
+
${'annotation ids are different'} | ${'123'} | ${'234'} | ${true} | ${1}
|
|
768
|
+
${'annotation deselected on annotations path'} | ${'123'} | ${null} | ${true} | ${1}
|
|
769
|
+
${'annotation deselected not on annotations path'} | ${'123'} | ${null} | ${false} | ${0}
|
|
770
|
+
${'annotation selected not on annotations path'} | ${null} | ${'123'} | ${false} | ${1}
|
|
771
|
+
${'annotation selected on annotations path'} | ${null} | ${'123'} | ${true} | ${1}
|
|
772
|
+
`(
|
|
773
|
+
'should call updateActiveAnnotation appropriately if $condition',
|
|
774
|
+
({ prevActiveAnnotationId, activeAnnotationId, isAnnotationsPath, expectedHistoryPushCalls }) => {
|
|
775
|
+
const location = { pathname: '/activity', state: { foo: 'bar' } };
|
|
776
|
+
|
|
777
|
+
getAnnotationsPath.mockReturnValue('/activity/annotations/456/123');
|
|
778
|
+
getAnnotationsMatchPath.mockReturnValue(
|
|
779
|
+
isAnnotationsPath ? { params: { fileVersionId: '456' } } : null,
|
|
780
|
+
);
|
|
781
|
+
|
|
782
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
783
|
+
annotatorState: { activeAnnotationId: prevActiveAnnotationId },
|
|
784
|
+
location,
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
jest.clearAllMocks();
|
|
788
|
+
|
|
789
|
+
rerender(
|
|
790
|
+
createComponentElement({
|
|
791
|
+
annotatorState: { activeAnnotationId, action: 'set_active' },
|
|
792
|
+
location,
|
|
793
|
+
}),
|
|
794
|
+
);
|
|
795
|
+
|
|
796
|
+
expect(history.push).toHaveBeenCalledTimes(expectedHistoryPushCalls);
|
|
797
|
+
},
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
test.each`
|
|
801
|
+
activeAnnotationId | fileVersionId | location | expectedPath | expectedState
|
|
802
|
+
${'234'} | ${'456'} | ${{ pathname: '/' }} | ${'/activity/annotations/456/234'} | ${{ open: true }}
|
|
803
|
+
${'234'} | ${undefined} | ${{ pathname: '/' }} | ${'/activity/annotations/123/234'} | ${{ open: true }}
|
|
804
|
+
${null} | ${'456'} | ${{ pathname: '/' }} | ${'/activity/annotations/456'} | ${undefined}
|
|
805
|
+
${null} | ${'456'} | ${{ pathname: '/', state: { foo: 'bar' } }} | ${'/activity/annotations/456'} | ${{ foo: 'bar' }}
|
|
806
|
+
${'234'} | ${'456'} | ${{ pathname: '/', state: { foo: 'bar' } }} | ${'/activity/annotations/456/234'} | ${{ open: true }}
|
|
807
|
+
`(
|
|
808
|
+
'should set location path based on match param fileVersionId=$fileVersionId and activeAnnotationId=$activeAnnotationId',
|
|
809
|
+
({ activeAnnotationId, fileVersionId, location, expectedPath, expectedState }) => {
|
|
810
|
+
getAnnotationsMatchPath.mockReturnValue({ params: { fileVersionId } });
|
|
811
|
+
getAnnotationsPath.mockReturnValue(expectedPath);
|
|
812
|
+
|
|
813
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
814
|
+
annotatorState: { activeAnnotationId: 'initial' },
|
|
815
|
+
location,
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
jest.clearAllMocks();
|
|
819
|
+
|
|
820
|
+
rerender(
|
|
821
|
+
createComponentElement({
|
|
822
|
+
annotatorState: { activeAnnotationId, action: 'set_active' },
|
|
823
|
+
location,
|
|
824
|
+
}),
|
|
825
|
+
);
|
|
826
|
+
|
|
827
|
+
expect(history.push).toHaveBeenCalledTimes(1);
|
|
828
|
+
expect(history.push).toHaveBeenCalledWith({ pathname: expectedPath, state: expectedState });
|
|
829
|
+
},
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
test('should use the provided fileVersionId in the annotatorState if provided', () => {
|
|
833
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
834
|
+
annotatorState: { activeAnnotationId: 'initial' },
|
|
835
|
+
location: { pathname: '/' },
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
jest.clearAllMocks();
|
|
839
|
+
|
|
840
|
+
rerender(
|
|
841
|
+
createComponentElement({
|
|
842
|
+
annotatorState: {
|
|
843
|
+
activeAnnotationFileVersionId: '456',
|
|
844
|
+
activeAnnotationId: '123',
|
|
845
|
+
action: 'set_active',
|
|
846
|
+
},
|
|
847
|
+
location: { pathname: '/' },
|
|
848
|
+
}),
|
|
849
|
+
);
|
|
850
|
+
|
|
851
|
+
expect(getAnnotationsPath).toHaveBeenCalledWith('456', '123');
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
test('should fall back to the fileVersionId in the file if none other is provided', () => {
|
|
855
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
856
|
+
annotatorState: { activeAnnotationId: null },
|
|
857
|
+
location: { pathname: '/' },
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
jest.clearAllMocks();
|
|
861
|
+
|
|
862
|
+
rerender(
|
|
863
|
+
createComponentElement({
|
|
864
|
+
annotatorState: {
|
|
865
|
+
// No activeAnnotationFileVersionId provided, so it should fall back to file version
|
|
866
|
+
activeAnnotationId: 'some-annotation-id',
|
|
867
|
+
action: 'set_active',
|
|
868
|
+
},
|
|
869
|
+
location: { pathname: '/' },
|
|
870
|
+
}),
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
expect(getAnnotationsPath).toHaveBeenCalledWith('123', 'some-annotation-id');
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
|
|
877
|
+
describe('updateActiveAnnotation - Router Disabled', () => {
|
|
878
|
+
test.each`
|
|
879
|
+
condition | prevActiveAnnotationId | activeAnnotationId | isAnnotationsPath | expectedNavigationHandlerCalls
|
|
880
|
+
${'annotation ids are the same'} | ${'123'} | ${'123'} | ${true} | ${0}
|
|
881
|
+
${'annotation ids are different'} | ${'123'} | ${'234'} | ${true} | ${1}
|
|
882
|
+
${'annotation deselected on annotations path'} | ${'123'} | ${null} | ${true} | ${1}
|
|
883
|
+
${'annotation deselected not on annotations path'} | ${'123'} | ${null} | ${false} | ${0}
|
|
884
|
+
${'annotation selected not on annotations path'} | ${null} | ${'123'} | ${false} | ${1}
|
|
885
|
+
${'annotation selected on annotations path'} | ${null} | ${'123'} | ${true} | ${1}
|
|
886
|
+
`(
|
|
887
|
+
'should call internalSidebarNavigationHandler appropriately if $condition',
|
|
888
|
+
({ prevActiveAnnotationId, activeAnnotationId, isAnnotationsPath, expectedNavigationHandlerCalls }) => {
|
|
889
|
+
// Build prevNavigation dynamically based on isAnnotationsPath
|
|
890
|
+
let prevNavigation;
|
|
891
|
+
if (isAnnotationsPath) {
|
|
892
|
+
prevNavigation = {
|
|
893
|
+
sidebar: 'activity',
|
|
894
|
+
activeFeedEntryType: 'annotations',
|
|
895
|
+
fileVersionId: '456',
|
|
896
|
+
// For getInternalNavigationMatch to work, we need activeFeedEntryId to be truthy
|
|
897
|
+
// Use the prevActiveAnnotationId if available, otherwise use a placeholder for "on annotations path"
|
|
898
|
+
activeFeedEntryId: prevActiveAnnotationId || 'placeholder',
|
|
899
|
+
};
|
|
900
|
+
} else {
|
|
901
|
+
prevNavigation = { sidebar: 'activity' };
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const { rerender } = renderWithSidebarAnnotationsRouterDisabled({
|
|
905
|
+
internalSidebarNavigation: prevNavigation,
|
|
906
|
+
annotatorState: { activeAnnotationId: prevActiveAnnotationId },
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
jest.clearAllMocks();
|
|
910
|
+
|
|
911
|
+
rerender(
|
|
912
|
+
createRouterDisabledComponentElement({
|
|
913
|
+
internalSidebarNavigation: prevNavigation,
|
|
914
|
+
annotatorState: { activeAnnotationId, action: 'set_active' },
|
|
915
|
+
}),
|
|
916
|
+
);
|
|
917
|
+
|
|
918
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledTimes(expectedNavigationHandlerCalls);
|
|
919
|
+
},
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
test('should use the provided fileVersionId from internal navigation if provided', () => {
|
|
923
|
+
const internalNavigation = {};
|
|
924
|
+
|
|
925
|
+
const { rerender } = renderWithSidebarAnnotationsRouterDisabled({
|
|
926
|
+
internalSidebarNavigation: internalNavigation,
|
|
927
|
+
annotatorState: { activeAnnotationId: 'initial' },
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
jest.clearAllMocks();
|
|
931
|
+
|
|
932
|
+
rerender(
|
|
933
|
+
createRouterDisabledComponentElement({
|
|
934
|
+
internalSidebarNavigation: internalNavigation,
|
|
935
|
+
annotatorState: {
|
|
936
|
+
activeAnnotationFileVersionId: '456',
|
|
937
|
+
activeAnnotationId: '123',
|
|
938
|
+
action: 'set_active',
|
|
939
|
+
},
|
|
940
|
+
}),
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledTimes(1);
|
|
944
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledWith({
|
|
945
|
+
sidebar: 'activity',
|
|
946
|
+
activeFeedEntryType: 'annotations',
|
|
947
|
+
activeFeedEntryId: '123',
|
|
948
|
+
fileVersionId: '456',
|
|
949
|
+
open: true,
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
test('should fall back to file version if no fileVersionId in internal navigation', () => {
|
|
954
|
+
const internalNavigation = {
|
|
955
|
+
sidebar: 'activity',
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
const { rerender } = renderWithSidebarAnnotationsRouterDisabled({
|
|
959
|
+
internalSidebarNavigation: internalNavigation,
|
|
960
|
+
annotatorState: { activeAnnotationId: null },
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
jest.clearAllMocks();
|
|
964
|
+
|
|
965
|
+
rerender(
|
|
966
|
+
createRouterDisabledComponentElement({
|
|
967
|
+
internalSidebarNavigation: internalNavigation,
|
|
968
|
+
annotatorState: {
|
|
969
|
+
activeAnnotationId: 'some-annotation-id',
|
|
970
|
+
action: 'set_active',
|
|
971
|
+
},
|
|
972
|
+
}),
|
|
973
|
+
);
|
|
974
|
+
|
|
975
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledTimes(1);
|
|
976
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledWith({
|
|
977
|
+
sidebar: 'activity',
|
|
978
|
+
activeFeedEntryType: 'annotations',
|
|
979
|
+
activeFeedEntryId: 'some-annotation-id',
|
|
980
|
+
fileVersionId: '123',
|
|
981
|
+
open: true,
|
|
982
|
+
});
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
test.each`
|
|
986
|
+
activeAnnotationId | fileVersionId | internalNavigation | expectedNavigation
|
|
987
|
+
${'234'} | ${'456'} | ${{ sidebar: 'activity' }} | ${{ sidebar: 'activity', activeFeedEntryType: 'annotations', activeFeedEntryId: '234', fileVersionId: '456', open: true }}
|
|
988
|
+
${'234'} | ${undefined} | ${{ sidebar: 'activity' }} | ${{ sidebar: 'activity', activeFeedEntryType: 'annotations', activeFeedEntryId: '234', fileVersionId: '123', open: true }}
|
|
989
|
+
${null} | ${'456'} | ${{ sidebar: 'activity' }} | ${{ sidebar: 'activity', activeFeedEntryType: 'annotations', activeFeedEntryId: undefined, fileVersionId: '456' }}
|
|
990
|
+
${null} | ${'456'} | ${{ sidebar: 'activity', someOtherProp: 'value' }} | ${{ sidebar: 'activity', activeFeedEntryType: 'annotations', activeFeedEntryId: undefined, fileVersionId: '456' }}
|
|
991
|
+
${'234'} | ${'456'} | ${{ sidebar: 'activity', someOtherProp: 'value' }} | ${{ sidebar: 'activity', activeFeedEntryType: 'annotations', activeFeedEntryId: '234', fileVersionId: '456', open: true }}
|
|
992
|
+
`(
|
|
993
|
+
'should set internal navigation based on fileVersionId=$fileVersionId and activeAnnotationId=$activeAnnotationId',
|
|
994
|
+
({ activeAnnotationId, fileVersionId, internalNavigation, expectedNavigation }) => {
|
|
995
|
+
const { rerender } = renderWithSidebarAnnotationsRouterDisabled({
|
|
996
|
+
internalSidebarNavigation: internalNavigation,
|
|
997
|
+
annotatorState: { activeAnnotationId: 'initial' },
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
jest.clearAllMocks();
|
|
1001
|
+
|
|
1002
|
+
// Set up the navigation to simulate having the fileVersionId in navigation
|
|
1003
|
+
const navigationWithFileVersion = fileVersionId
|
|
1004
|
+
? {
|
|
1005
|
+
...internalNavigation,
|
|
1006
|
+
activeFeedEntryType: 'annotations',
|
|
1007
|
+
activeFeedEntryId: 'placeholder', // Need this for getInternalNavigationMatch to work
|
|
1008
|
+
fileVersionId,
|
|
1009
|
+
}
|
|
1010
|
+
: internalNavigation;
|
|
1011
|
+
|
|
1012
|
+
rerender(
|
|
1013
|
+
createRouterDisabledComponentElement({
|
|
1014
|
+
internalSidebarNavigation: navigationWithFileVersion,
|
|
1015
|
+
annotatorState: { activeAnnotationId, action: 'set_active' },
|
|
1016
|
+
}),
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledTimes(1);
|
|
1020
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledWith(expectedNavigation);
|
|
1021
|
+
},
|
|
1022
|
+
);
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
describe('updateActiveVersion', () => {
|
|
1026
|
+
test.each`
|
|
1027
|
+
prevFileVersionId | fileVersionId | expectedOnVersionChangeCalls
|
|
1028
|
+
${'122'} | ${'122'} | ${0}
|
|
1029
|
+
${'122'} | ${undefined} | ${0}
|
|
1030
|
+
${'122'} | ${'123'} | ${1}
|
|
1031
|
+
`(
|
|
1032
|
+
'should call updateActiveVersion if fileVersionId changes from $prevFileVersionId to $fileVersionId',
|
|
1033
|
+
({ prevFileVersionId, fileVersionId, expectedOnVersionChangeCalls }) => {
|
|
1034
|
+
const version = { type: FEED_ITEM_TYPE_VERSION, id: fileVersionId };
|
|
1035
|
+
const versions = [{ type: FEED_ITEM_TYPE_VERSION, id: prevFileVersionId }];
|
|
1036
|
+
if (fileVersionId) {
|
|
1037
|
+
versions.push(version);
|
|
1038
|
+
}
|
|
1039
|
+
feedAPI.getCachedItems.mockReturnValue({ items: versions });
|
|
1040
|
+
|
|
1041
|
+
getAnnotationsPath.mockReturnValue('/activity/annotations/123');
|
|
1042
|
+
getAnnotationsMatchPath
|
|
1043
|
+
.mockReturnValueOnce({ params: { fileVersionId: prevFileVersionId } }) // constructor call
|
|
1044
|
+
.mockReturnValueOnce({ params: { fileVersionId: prevFileVersionId } }) // first componentDidUpdate call
|
|
1045
|
+
.mockReturnValueOnce({ params: { fileVersionId } }); // second componentDidUpdate call
|
|
1046
|
+
|
|
1047
|
+
const { rerender } = renderWithSidebarAnnotations({
|
|
1048
|
+
location: { pathname: '/foo' },
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
// When updateActiveVersion is called, it should get the NEW fileVersionId from the match
|
|
1052
|
+
getAnnotationsMatchPath.mockReturnValue({ params: { fileVersionId } });
|
|
1053
|
+
|
|
1054
|
+
jest.clearAllMocks();
|
|
1055
|
+
|
|
1056
|
+
rerender(
|
|
1057
|
+
createComponentElement({
|
|
1058
|
+
location: { pathname: '/bar' },
|
|
1059
|
+
}),
|
|
1060
|
+
);
|
|
1061
|
+
|
|
1062
|
+
expect(onVersionChange).toHaveBeenCalledTimes(expectedOnVersionChangeCalls);
|
|
1063
|
+
|
|
1064
|
+
if (expectedOnVersionChangeCalls > 0) {
|
|
1065
|
+
expect(onVersionChange).toHaveBeenCalledWith(
|
|
1066
|
+
version,
|
|
1067
|
+
expect.objectContaining({
|
|
1068
|
+
currentVersionId: '123',
|
|
1069
|
+
updateVersionToCurrent: expect.any(Function),
|
|
1070
|
+
}),
|
|
1071
|
+
);
|
|
1072
|
+
|
|
1073
|
+
const callback = onVersionChange.mock.calls[0][1].updateVersionToCurrent;
|
|
1074
|
+
callback();
|
|
1075
|
+
expect(getAnnotationsPath).toHaveBeenCalledWith('123');
|
|
1076
|
+
expect(history.push).toHaveBeenCalledWith('/activity/annotations/123');
|
|
1077
|
+
}
|
|
1078
|
+
},
|
|
1079
|
+
);
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
describe('updateActiveVersion - Router Disabled', () => {
|
|
1083
|
+
test.each`
|
|
1084
|
+
prevFileVersionId | fileVersionId | expectedOnVersionChangeCalls
|
|
1085
|
+
${'122'} | ${'122'} | ${0}
|
|
1086
|
+
${'122'} | ${undefined} | ${0}
|
|
1087
|
+
${'122'} | ${'123'} | ${1}
|
|
1088
|
+
`(
|
|
1089
|
+
'should call updateActiveVersion if fileVersionId changes from $prevFileVersionId to $fileVersionId',
|
|
1090
|
+
({ prevFileVersionId, fileVersionId, expectedOnVersionChangeCalls }) => {
|
|
1091
|
+
const version = { type: FEED_ITEM_TYPE_VERSION, id: fileVersionId };
|
|
1092
|
+
|
|
1093
|
+
const versions = [{ type: FEED_ITEM_TYPE_VERSION, id: prevFileVersionId }];
|
|
1094
|
+
if (fileVersionId) {
|
|
1095
|
+
versions.push({ type: FEED_ITEM_TYPE_VERSION, id: fileVersionId });
|
|
1096
|
+
}
|
|
1097
|
+
feedAPI.getCachedItems.mockReturnValue({ items: versions });
|
|
1098
|
+
|
|
1099
|
+
// Build internal navigation for router-disabled mode
|
|
1100
|
+
const prevInternalNavigation = {
|
|
1101
|
+
sidebar: 'activity',
|
|
1102
|
+
activeFeedEntryType: 'annotations',
|
|
1103
|
+
activeFeedEntryId: 'annotation123',
|
|
1104
|
+
fileVersionId: prevFileVersionId,
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
const newInternalNavigation = fileVersionId
|
|
1108
|
+
? {
|
|
1109
|
+
sidebar: 'activity',
|
|
1110
|
+
activeFeedEntryType: 'annotations',
|
|
1111
|
+
activeFeedEntryId: 'annotation123',
|
|
1112
|
+
fileVersionId,
|
|
1113
|
+
}
|
|
1114
|
+
: { sidebar: 'activity' };
|
|
1115
|
+
|
|
1116
|
+
const { rerender } = renderWithSidebarAnnotationsRouterDisabled({
|
|
1117
|
+
internalSidebarNavigation: prevInternalNavigation,
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
// Clear mocks after initial render to only measure componentDidUpdate effects
|
|
1121
|
+
jest.clearAllMocks();
|
|
1122
|
+
|
|
1123
|
+
rerender(
|
|
1124
|
+
createRouterDisabledComponentElement({
|
|
1125
|
+
internalSidebarNavigation: newInternalNavigation,
|
|
1126
|
+
}),
|
|
1127
|
+
);
|
|
1128
|
+
|
|
1129
|
+
expect(onVersionChange).toHaveBeenCalledTimes(expectedOnVersionChangeCalls);
|
|
1130
|
+
|
|
1131
|
+
if (expectedOnVersionChangeCalls > 0) {
|
|
1132
|
+
expect(onVersionChange).toHaveBeenCalledWith(
|
|
1133
|
+
version,
|
|
1134
|
+
expect.objectContaining({
|
|
1135
|
+
currentVersionId: '123',
|
|
1136
|
+
updateVersionToCurrent: expect.any(Function),
|
|
1137
|
+
}),
|
|
1138
|
+
);
|
|
1139
|
+
|
|
1140
|
+
const callback = onVersionChange.mock.calls[0][1].updateVersionToCurrent;
|
|
1141
|
+
callback();
|
|
1142
|
+
expect(internalSidebarNavigationHandler).toHaveBeenCalledWith({
|
|
1143
|
+
sidebar: 'activity',
|
|
1144
|
+
activeFeedEntryType: 'annotations',
|
|
1145
|
+
activeFeedEntryId: undefined,
|
|
1146
|
+
fileVersionId: '123',
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
);
|
|
1151
|
+
});
|
|
1152
|
+
});
|