Skip to content

Commit a8d1985

Browse files
authored
feat(annotation-thread): introduce useAnnotationThread and handle useAnnotatorEvents (#3097)
* feat(annotation-thread): handle events in AnnotationThreadContent * feat(annotation-thread): handle events in AnnotationThreadContent
1 parent dc3642e commit a8d1985

File tree

9 files changed

+607
-288
lines changed

9 files changed

+607
-288
lines changed

src/elements/content-sidebar/activity-feed/annotation-thread/AnnotationThread.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type Props = {
2828
className: string,
2929
clientName: string,
3030
currentUser: User,
31-
eventEmitter?: EventEmitter,
31+
eventEmitter: EventEmitter,
3232
file: BoxItem,
3333
handleCancel: () => void,
3434
language?: string,
@@ -46,6 +46,7 @@ const AnnotationThread = ({
4646
className = '',
4747
clientName,
4848
currentUser,
49+
eventEmitter,
4950
file,
5051
handleCancel,
5152
language,
@@ -102,6 +103,7 @@ const AnnotationThread = ({
102103
api={api}
103104
annotationId={annotationId}
104105
currentUser={currentUser}
106+
eventEmitter={eventEmitter}
105107
file={file}
106108
getAvatarUrl={getAvatarUrl}
107109
getMentionWithQuery={getMentionWithQuery}

src/elements/content-sidebar/activity-feed/annotation-thread/AnnotationThreadContent.js

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
// @flow
22
import React from 'react';
3+
import type EventEmitter from 'events';
4+
import useAnnotationThread from './useAnnotationThread';
35
import ActivityError from '../common/activity-error';
46
import ActivityThread from '../activity-feed/ActivityThread';
57
import AnnotationActivity from '../annotations';
68
import API from '../../../../api/APIFactory';
79
import LoadingIndicator from '../../../../components/loading-indicator/LoadingIndicator';
8-
import useAnnotationAPI from './useAnnotationAPI';
910

1011
import type { BoxItem, SelectorItems, User } from '../../../../common/types/core';
1112
import type { ErrorContextProps } from '../../../../common/types/api';
12-
import type { BoxCommentPermission, FeedItemStatus } from '../../../../common/types/feed';
1313

1414
import './AnnotationThreadContent.scss';
1515

1616
type Props = {
1717
annotationId: string,
1818
api: API,
1919
currentUser: User,
20+
eventEmitter: EventEmitter,
2021
file: BoxItem,
2122
getAvatarUrl: string => Promise<?string>,
2223
getMentionWithQuery: (searchStr: string) => void,
@@ -27,6 +28,7 @@ const AnnotationThreadContent = ({
2728
annotationId,
2829
api,
2930
currentUser,
31+
eventEmitter,
3032
file,
3133
getAvatarUrl,
3234
getMentionWithQuery,
@@ -40,31 +42,18 @@ const AnnotationThreadContent = ({
4042
replies,
4143
isLoading,
4244
error,
43-
handleEdit,
44-
handleStatusChange,
45-
handleDelete,
46-
handleCreateReply,
47-
handleDeleteReply,
48-
handleEditReply,
49-
} = useAnnotationAPI({
45+
annotationActions: { handleAnnotationStatusChange, handleAnnotationDelete, handleAnnotationEdit },
46+
repliesActions: { handleReplyEdit, handleReplyCreate, handleReplyDelete },
47+
} = useAnnotationThread({
5048
api,
5149
annotationId,
5250
currentUser,
51+
eventEmitter,
5352
fileId,
5453
filePermissions: permissions,
5554
errorCallback: onError,
5655
});
5756

58-
const onReplyEditHandler = (
59-
id: string,
60-
text: string,
61-
status?: FeedItemStatus,
62-
hasMention?: boolean,
63-
replyPermissions: BoxCommentPermission,
64-
) => {
65-
handleEditReply(id, text, status, replyPermissions);
66-
};
67-
6857
return (
6958
<ActivityThread
7059
hasReplies
@@ -73,9 +62,9 @@ const AnnotationThreadContent = ({
7362
isAlwaysExpanded
7463
isRepliesLoading={isLoading}
7564
mentionSelectorContacts={mentionSelectorContacts}
76-
onReplyCreate={handleCreateReply}
77-
onReplyDelete={handleDeleteReply}
78-
onReplyEdit={onReplyEditHandler}
65+
onReplyCreate={handleReplyCreate}
66+
onReplyDelete={handleReplyDelete}
67+
onReplyEdit={handleReplyEdit}
7968
replies={replies}
8069
repliesTotalCount={replies.length}
8170
>
@@ -93,9 +82,9 @@ const AnnotationThreadContent = ({
9382
item={annotation}
9483
getMentionWithQuery={getMentionWithQuery}
9584
mentionSelectorContacts={mentionSelectorContacts}
96-
onEdit={handleEdit}
97-
onDelete={handleDelete}
98-
onStatusChange={handleStatusChange}
85+
onEdit={handleAnnotationEdit}
86+
onDelete={handleAnnotationDelete}
87+
onStatusChange={handleAnnotationStatusChange}
9988
/>
10089
)}
10190
</ActivityThread>

src/elements/content-sidebar/activity-feed/annotation-thread/__tests__/AnnotationThreadContent.test.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,22 @@ let mockedError;
1313
let mockedIsLoading = false;
1414

1515
jest.mock('react-intl', () => jest.requireActual('react-intl'));
16-
jest.mock('../useAnnotationAPI', () => {
16+
jest.mock('../useAnnotationThread', () => {
1717
return jest.fn(() => ({
1818
annotation: mockedAnnotation,
19+
annotationActions: {
20+
handleDelete: jest.fn(),
21+
handleEdit: jest.fn(),
22+
handleResolve: jest.fn(),
23+
},
1924
error: mockedError,
2025
isLoading: mockedIsLoading,
21-
handleDelete: jest.fn(),
22-
handleEdit: jest.fn(),
23-
handleResolve: jest.fn(),
2426
replies: [],
25-
handleCreateReply: jest.fn(),
26-
handleEditReply: jest.fn(),
27-
handleDeleteReply: jest.fn(),
27+
repliesActions: {
28+
handleCreateReply: jest.fn(),
29+
handleEditReply: jest.fn(),
30+
handleDeleteReply: jest.fn(),
31+
},
2832
}));
2933
});
3034

@@ -38,6 +42,7 @@ describe('elements/content-sidebar/activity-feed/annotation-thread/AnnotationThr
3842
getAnnotation: jest.fn(),
3943
}),
4044
},
45+
eventEmitter: {},
4146
file: {
4247
id: 'fileId',
4348
permissions: {

src/elements/content-sidebar/activity-feed/annotation-thread/__tests__/useAnnotationAPI.test.js

Lines changed: 33 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ describe('src/elements/content-sidebar/activity-feed/useAnnotattionAPI', () => {
1010
updateAnnotation = jest.fn(),
1111
deleteAnnotation = jest.fn(),
1212
createAnnotationReply = jest.fn,
13-
deleteComment = jest.fn(),
14-
updateComment = jest.fn(),
1513
}) => {
1614
const getAnnotationsAPI = () => ({
1715
createAnnotationReply,
@@ -20,14 +18,8 @@ describe('src/elements/content-sidebar/activity-feed/useAnnotattionAPI', () => {
2018
deleteAnnotation,
2119
});
2220

23-
const getThreadedCommentsAPI = () => ({
24-
deleteComment,
25-
updateComment,
26-
});
27-
2821
return {
2922
getAnnotationsAPI,
30-
getThreadedCommentsAPI,
3123
};
3224
};
3325

@@ -38,7 +30,6 @@ describe('src/elements/content-sidebar/activity-feed/useAnnotattionAPI', () => {
3830
renderHook(() =>
3931
useAnnotationAPI({
4032
api: getApi({}),
41-
currentUser: {},
4233
fileId: 'fileId',
4334
filePermissions,
4435
annotationId: annotation.id,
@@ -47,80 +38,80 @@ describe('src/elements/content-sidebar/activity-feed/useAnnotattionAPI', () => {
4738
}),
4839
);
4940

50-
test('Should return correct default values', () => {
51-
const { result } = getHook();
52-
53-
expect(result.current.annotation).toEqual(undefined);
54-
expect(result.current.isLoading).toEqual(true);
55-
expect(result.current.error).toEqual(undefined);
56-
expect(result.current.replies).toEqual([]);
57-
});
58-
59-
test('Should call fetch annotation on mount', () => {
60-
const { replies, ...normalizedAnnotation } = annotation;
61-
const mockGetAnnotation = jest.fn((fileId, annotationId, permissions, successCallback) => {
62-
successCallback(annotation);
63-
});
41+
test('should call api function on handleFetch with correct arguments', () => {
42+
const mockSuccessCallback = jest.fn();
43+
const mockGetAnnotation = jest.fn();
6444
const api = getApi({ getAnnotation: mockGetAnnotation });
6545

6646
const { result } = getHook({ api });
6747

48+
act(() => {
49+
result.current.handleFetch({ annotationId: annotation.id, successCallback: mockSuccessCallback });
50+
});
51+
6852
expect(mockGetAnnotation).toBeCalledWith(
6953
'fileId',
7054
annotation.id,
7155
{ can_annotate: true, can_view_annotations: true },
72-
expect.any(Function),
73-
expect.any(Function),
56+
mockSuccessCallback,
57+
errorCallback,
7458
true,
7559
);
76-
77-
expect(result.current.isLoading).toEqual(false);
78-
expect(result.current.error).toEqual(undefined);
79-
expect(result.current.annotation).toEqual(normalizedAnnotation);
80-
expect(result.current.replies).toEqual(replies);
8160
});
8261

83-
test('should call api function on handleEdit with correct arguments and set pending state', () => {
62+
test('should call api function on handleEdit with correct arguments', () => {
63+
const mockSuccessCallback = jest.fn();
8464
const mockUpdateAnnotation = jest.fn();
8565
const api = getApi({ updateAnnotation: mockUpdateAnnotation });
8666
const { result } = getHook({ api });
8767

8868
act(() => {
89-
result.current.handleEdit(annotation.id, 'new text', { can_edit: true });
69+
result.current.handleEdit({
70+
id: annotation.id,
71+
permissions: { can_edit: true },
72+
successCallback: mockSuccessCallback,
73+
text: 'new text',
74+
});
9075
});
9176

9277
expect(mockUpdateAnnotation).toBeCalledWith(
9378
'fileId',
9479
annotation.id,
9580
{ can_edit: true },
9681
{ message: 'new text' },
97-
expect.any(Function),
98-
expect.any(Function),
82+
mockSuccessCallback,
83+
errorCallback,
9984
);
100-
expect(result.current.annotation.isPending).toEqual(true);
10185
});
10286

10387
test('should call api function on handleStatusChange with correct arguments', () => {
88+
const mockSuccessCallback = jest.fn();
10489
const mockUpdateAnnotation = jest.fn();
10590
const api = getApi({ updateAnnotation: mockUpdateAnnotation });
10691
const { result } = getHook({ api });
10792

10893
act(() => {
109-
result.current.handleStatusChange(annotation.id, 'resolved', { can_resolve: true });
94+
result.current.handleStatusChange({
95+
id: annotation.id,
96+
permissions: { can_resolve: true },
97+
status: 'resolved',
98+
successCallback: mockSuccessCallback,
99+
});
110100
});
111101

112102
expect(mockUpdateAnnotation).toBeCalledWith(
113103
'fileId',
114104
annotation.id,
115105
{ can_resolve: true },
116106
{ status: 'resolved' },
117-
expect.any(Function),
118-
expect.any(Function),
107+
mockSuccessCallback,
108+
errorCallback,
119109
);
120-
expect(result.current.annotation.isPending).toEqual(true);
121110
});
122111

123112
test('should call api function on handleDelete with correct arguments', () => {
113+
const mockSuccessCallback = jest.fn();
114+
124115
const mockDeleteAnnotation = jest.fn();
125116
const api = getApi({ deleteAnnotation: mockDeleteAnnotation });
126117
const { result } = getHook({ api });
@@ -129,74 +120,16 @@ describe('src/elements/content-sidebar/activity-feed/useAnnotattionAPI', () => {
129120
result.current.handleDelete({
130121
id: annotation.id,
131122
permissions: { can_delete: true },
123+
successCallback: mockSuccessCallback,
132124
});
133125
});
134126

135127
expect(mockDeleteAnnotation).toBeCalledWith(
136128
'fileId',
137129
annotation.id,
138130
{ can_delete: true },
139-
expect.any(Function),
140-
expect.any(Function),
131+
mockSuccessCallback,
132+
errorCallback,
141133
);
142-
expect(result.current.annotation.isPending).toEqual(true);
143-
});
144-
145-
test('should call api function on handleDeleteReply and set correct values on successCallback', () => {
146-
const mockDeleteComment = jest.fn(({ successCallback }) => {
147-
successCallback();
148-
});
149-
const api = getApi({ deleteComment: mockDeleteComment });
150-
const { id, permissions } = annotation.replies[0];
151-
const { result } = getHook({ api });
152-
153-
act(() => {
154-
result.current.handleDeleteReply({ id, permissions });
155-
});
156-
expect(result.current.replies.length).toEqual(0);
157-
});
158-
159-
test('should call api function on handleEditReply and set correct values on successCallback', () => {
160-
const message = 'New message';
161-
const updatedReply = { ...annotation.replies[0], message };
162-
const mockUpdateComment = jest.fn(({ successCallback }) => {
163-
successCallback(updatedReply);
164-
});
165-
const mockGetAnnotation = jest.fn((fileId, annotationId, permissions, successCallback) => {
166-
successCallback(annotation);
167-
});
168-
const api = getApi({ updateComment: mockUpdateComment, getAnnotation: mockGetAnnotation });
169-
const { id, permissions } = annotation.replies[0];
170-
171-
const { result } = getHook({ api });
172-
173-
act(() => {
174-
result.current.handleEditReply(id, message, undefined, false, permissions);
175-
});
176-
177-
expect(result.current.replies[0].isPending).toEqual(false);
178-
expect(result.current.replies[0].message).toEqual(message);
179-
});
180-
181-
test('should call api function on handleCreateReply and set correct values on successCallback', () => {
182-
const message = 'New message';
183-
const newReply = { ...annotation.replies[0], message, id: 'reply_new' };
184-
const mockCreateAnnotationReply = jest.fn((fileId, annotationId, permissions, newMessage, successCallback) => {
185-
successCallback(newReply);
186-
});
187-
const mockGetAnnotation = jest.fn((fileId, annotationId, permissions, successCallback) => {
188-
successCallback(annotation);
189-
});
190-
const api = getApi({ createAnnotationReply: mockCreateAnnotationReply, getAnnotation: mockGetAnnotation });
191-
192-
const { result } = getHook({ api });
193-
194-
act(() => {
195-
result.current.handleCreateReply(message);
196-
});
197-
198-
const createdReply = result.current.replies[1];
199-
expect(createdReply.isPending).toEqual(false);
200-
expect(createdReply.message).toEqual(message);
201134
});
202135
});

0 commit comments

Comments
 (0)