Skip to content

Commit 05b50cd

Browse files
authored
feat(content-sharing): Create contact service getContactByEmail (#4342)
* feat(content-sharing): Create contact service getContactByEmail * fix: types and improve code * fix: previous pr comment * fix: nit * fix: flow
1 parent 8ae6ccc commit 05b50cd

File tree

11 files changed

+385
-131
lines changed

11 files changed

+385
-131
lines changed

i18n/en-US.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ be.contentSharing.badRequestError = The request for this item was malformed.
140140
be.contentSharing.collaboratorsLoadingError = Could not retrieve collaborators for this item.
141141
# Message that appears when users cannot be retrieved in the ContentSharing Element.
142142
be.contentSharing.getContactsError = Could not retrieve contacts.
143+
# Display text for a Group contact type
144+
be.contentSharing.groupContactLabel = Group
143145
# Message that appears when the ContentSharing Element cannot be loaded.
144146
be.contentSharing.loadingError = Could not load shared link for this item.
145147
# Message that appears when the user cannot access the item for the ContentSharing Element.

src/elements/content-sharing/SharingModal.js

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import type {
3939
import type {
4040
ContentSharingItemAPIResponse,
4141
ContentSharingSharedLinkType,
42+
GetContactByEmailFnType,
4243
GetContactsFnType,
4344
GetContactsByEmailFnType,
4445
SendInvitesFnType,
@@ -80,18 +81,16 @@ function SharingModal({
8081
const [collaboratorsList, setCollaboratorsList] = React.useState<collaboratorsListType | null>(null);
8182
const [onAddLink, setOnAddLink] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
8283
const [onRemoveLink, setOnRemoveLink] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
83-
const [
84-
changeSharedLinkAccessLevel,
85-
setChangeSharedLinkAccessLevel,
86-
] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
87-
const [
88-
changeSharedLinkPermissionLevel,
89-
setChangeSharedLinkPermissionLevel,
90-
] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
84+
const [changeSharedLinkAccessLevel, setChangeSharedLinkAccessLevel] =
85+
React.useState<null | SharedLinkUpdateLevelFnType>(null);
86+
const [changeSharedLinkPermissionLevel, setChangeSharedLinkPermissionLevel] =
87+
React.useState<null | SharedLinkUpdateLevelFnType>(null);
9188
const [onSubmitSettings, setOnSubmitSettings] = React.useState<null | SharedLinkUpdateSettingsFnType>(null);
9289
const [currentView, setCurrentView] = React.useState<string>(CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL);
9390
const [getContacts, setGetContacts] = React.useState<null | GetContactsFnType>(null);
94-
const [getContactsByEmail, setGetContactsByEmail] = React.useState<null | GetContactsByEmailFnType>(null);
91+
const [getContactsByEmail, setGetContactsByEmail] = React.useState<
92+
null | GetContactsByEmailFnType | GetContactByEmailFnType,
93+
>(null);
9594
const [sendInvites, setSendInvites] = React.useState<null | SendInvitesFnType>(null);
9695
const [isLoading, setIsLoading] = React.useState<boolean>(true);
9796

@@ -191,11 +190,15 @@ function SharingModal({
191190

192191
// Set the getContactsByEmail function. This call is not associated with a banner notification,
193192
// which is why it exists at this level and not in SharingNotification
194-
const getContactsByEmailFn: GetContactsByEmailFnType | null = useContactsByEmail(api, itemID, {
195-
transformUsers: data => convertUserContactsByEmailResponse(data),
196-
});
193+
const getContactsByEmailFn: GetContactsByEmailFnType | GetContactByEmailFnType | null = useContactsByEmail(
194+
api,
195+
itemID,
196+
{
197+
transformUsers: data => convertUserContactsByEmailResponse(data),
198+
},
199+
);
197200
if (getContactsByEmailFn && !getContactsByEmail) {
198-
setGetContactsByEmail((): GetContactsByEmailFnType => getContactsByEmailFn);
201+
setGetContactsByEmail((): GetContactsByEmailFnType | GetContactByEmailFnType => getContactsByEmailFn);
199202
}
200203

201204
// Display a notification if there is an error in retrieving initial data
Lines changed: 147 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// @flow
22

3-
import React, { act } from 'react';
4-
import { mount } from 'enzyme';
5-
import API from '../../../api';
3+
import { renderHook, act } from '@testing-library/react';
64
import useContactsByEmail from '../hooks/useContactsByEmail';
75
import {
86
MOCK_CONTACTS_API_RESPONSE,
@@ -12,56 +10,43 @@ import {
1210

1311
const handleSuccess = jest.fn();
1412
const handleError = jest.fn();
15-
const transformUsersSpy = jest.fn().mockReturnValue(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE);
13+
const mockTransformUsers = jest.fn().mockReturnValue(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE);
1614

1715
const createAPIMock = markerBasedUsersAPI => ({
1816
getMarkerBasedUsersAPI: jest.fn().mockReturnValue(markerBasedUsersAPI),
1917
});
2018

21-
function FakeComponent({ api, transformUsers }: { api: API, transformUsers: Function }) {
22-
const [getContactsByEmail, setGetContactsByEmail] = React.useState(null);
23-
24-
const updatedGetContactsByEmailFn = useContactsByEmail(api, MOCK_ITEM_ID, {
25-
handleSuccess,
26-
handleError,
27-
transformUsers,
28-
});
29-
30-
if (updatedGetContactsByEmailFn && !getContactsByEmail) {
31-
setGetContactsByEmail(() => updatedGetContactsByEmailFn);
32-
}
33-
34-
return (
35-
getContactsByEmail && (
36-
<button onClick={getContactsByEmail} type="submit">
37-
&#9835; Box UI Elements &#9835;
38-
</button>
39-
)
40-
);
41-
}
42-
4319
const MOCK_EMAIL = '[email protected]';
4420

4521
describe('elements/content-sharing/hooks/useContactsByEmail', () => {
4622
let getUsersInEnterprise;
4723
let mockAPI;
4824

4925
describe('with a successful API call', () => {
50-
beforeAll(() => {
26+
beforeEach(() => {
5127
getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => {
5228
return getUsersInEnterpriseSuccess(MOCK_CONTACTS_API_RESPONSE);
5329
});
5430
mockAPI = createAPIMock({ getUsersInEnterprise });
5531
});
5632

57-
test('should set the value of getContactsByEmail() and retrieve contacts on invocation', () => {
58-
let fakeComponent;
59-
act(() => {
60-
fakeComponent = mount(<FakeComponent api={mockAPI} transformUsers={transformUsersSpy} />);
61-
});
62-
fakeComponent.update();
33+
afterEach(() => {
34+
jest.resetAllMocks();
35+
});
6336

64-
const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] });
37+
test('should set the value of getContactsByEmail() and retrieve contacts on invocation', async () => {
38+
const { result } = renderHook(() =>
39+
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
40+
handleSuccess,
41+
handleError,
42+
transformUsers: mockTransformUsers,
43+
}),
44+
);
45+
46+
let contacts;
47+
await act(async () => {
48+
contacts = await result.current({ emails: [MOCK_EMAIL] });
49+
});
6550

6651
expect(getUsersInEnterprise).toHaveBeenCalledWith(
6752
MOCK_ITEM_ID,
@@ -70,18 +55,22 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
7055
{ filter_term: MOCK_EMAIL },
7156
);
7257
expect(handleSuccess).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE);
73-
expect(transformUsersSpy).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE);
74-
return expect(contacts).resolves.toEqual(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE);
58+
expect(mockTransformUsers).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE);
59+
expect(contacts).toEqual(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE);
7560
});
7661

77-
test('should return the entries from the API data if transformUsers() is not provided', () => {
78-
let fakeComponent;
79-
act(() => {
80-
fakeComponent = mount(<FakeComponent api={mockAPI} />);
81-
});
82-
fakeComponent.update();
62+
test('should return the entries from the API data if transformUsers() is not provided', async () => {
63+
const { result } = renderHook(() =>
64+
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
65+
handleSuccess,
66+
handleError,
67+
}),
68+
);
8369

84-
const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] });
70+
let contacts;
71+
await act(async () => {
72+
contacts = await result.current({ emails: [MOCK_EMAIL] });
73+
});
8574

8675
expect(getUsersInEnterprise).toHaveBeenCalledWith(
8776
MOCK_ITEM_ID,
@@ -90,28 +79,33 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
9079
{ filter_term: MOCK_EMAIL },
9180
);
9281
expect(handleSuccess).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE);
93-
expect(transformUsersSpy).not.toHaveBeenCalled();
94-
expect(contacts).resolves.toEqual(MOCK_CONTACTS_API_RESPONSE.entries);
82+
expect(mockTransformUsers).not.toHaveBeenCalled();
83+
expect(contacts).toEqual(MOCK_CONTACTS_API_RESPONSE.entries);
9584
});
9685

97-
test('should set the value of getContactsByEmail() to an empty object when no results are found', () => {
86+
test('should set the value of getContactsByEmail() to an empty object when no results are found', async () => {
9887
const EMPTY_USERS = { entries: [] };
9988
getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => {
10089
return getUsersInEnterpriseSuccess(EMPTY_USERS);
10190
});
10291
mockAPI = createAPIMock({ getUsersInEnterprise });
10392

104-
let fakeComponent;
105-
act(() => {
106-
fakeComponent = mount(<FakeComponent api={mockAPI} transformUsers={transformUsersSpy} />);
107-
});
108-
fakeComponent.update();
93+
const { result } = renderHook(() =>
94+
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
95+
handleSuccess,
96+
handleError,
97+
transformUsers: mockTransformUsers,
98+
}),
99+
);
109100

110-
const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] });
101+
let contacts;
102+
await act(async () => {
103+
contacts = await result.current({ emails: [MOCK_EMAIL] });
104+
});
111105

112106
expect(handleSuccess).toHaveBeenCalledWith(EMPTY_USERS);
113-
expect(transformUsersSpy).not.toHaveBeenCalled();
114-
return expect(contacts).resolves.toEqual({});
107+
expect(mockTransformUsers).not.toHaveBeenCalled();
108+
expect(contacts).toEqual({});
115109
});
116110

117111
test.each`
@@ -120,23 +114,95 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
120114
${{ content: 'sharing' }} | ${'an object, but does not have an emails key'}
121115
${{ emails: 'contentsharing' }} | ${'an object with the emails key, but filterTerm.emails is not an array'}
122116
${{ emails: [] }} | ${'an object with the emails key, but filterTerm.emails is an empty array'}
123-
`('should return an empty object when filterTerm is $description', ({ filterTerm }) => {
124-
let fakeComponent;
125-
act(() => {
126-
fakeComponent = mount(<FakeComponent api={mockAPI} />);
127-
});
128-
fakeComponent.update();
117+
`('should return an empty object when filterTerm is $description', async ({ filterTerm }) => {
118+
const { result } = renderHook(() =>
119+
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
120+
handleSuccess,
121+
handleError,
122+
}),
123+
);
129124

130-
const contacts = fakeComponent.find('button').invoke('onClick')(filterTerm);
125+
let contacts;
126+
await act(async () => {
127+
contacts = await result.current(filterTerm);
128+
});
131129

132130
expect(getUsersInEnterprise).not.toHaveBeenCalled();
133131
expect(handleError).not.toHaveBeenCalled();
134-
return expect(contacts).resolves.toEqual({});
132+
expect(contacts).toEqual({});
133+
});
134+
135+
test('should set the value of getContactsByEmail() and retrieve contacts when isContentSharingV2Enabled is true and email is provided', async () => {
136+
const mockUser1 = MOCK_CONTACTS_API_RESPONSE.entries[0];
137+
const { id, login: email, name, type } = mockUser1;
138+
const expectedTransformedResult = {
139+
id,
140+
email,
141+
name,
142+
type,
143+
value: email,
144+
};
145+
const MOCK_CONTACT_BY_EMAIL_API_RESPONSE = { entries: [mockUser1] };
146+
const mockTransformUsersV2 = jest.fn().mockReturnValue(expectedTransformedResult);
147+
getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => {
148+
return getUsersInEnterpriseSuccess(MOCK_CONTACT_BY_EMAIL_API_RESPONSE);
149+
});
150+
mockAPI = createAPIMock({ getUsersInEnterprise });
151+
152+
const { result } = renderHook(() =>
153+
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
154+
isContentSharingV2Enabled: true,
155+
handleSuccess,
156+
handleError,
157+
transformUsers: mockTransformUsersV2,
158+
}),
159+
);
160+
161+
let contacts;
162+
await act(async () => {
163+
contacts = await result.current('[email protected]');
164+
});
165+
166+
expect(getUsersInEnterprise).toHaveBeenCalledWith(
167+
MOCK_ITEM_ID,
168+
expect.anything(Function),
169+
expect.anything(Function),
170+
{ filter_term: '[email protected]' },
171+
);
172+
expect(handleSuccess).toHaveBeenCalledWith(MOCK_CONTACT_BY_EMAIL_API_RESPONSE);
173+
expect(mockTransformUsersV2).toHaveBeenCalledWith(MOCK_CONTACT_BY_EMAIL_API_RESPONSE);
174+
expect(contacts).toEqual(expectedTransformedResult);
175+
});
176+
177+
test('should set the value of getContactsByEmail() to an empty object when isContentSharingV2Enabled is true and email is not provided', async () => {
178+
const EMPTY_USERS = { entries: [] };
179+
getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => {
180+
return getUsersInEnterpriseSuccess(EMPTY_USERS);
181+
});
182+
mockAPI = createAPIMock({ getUsersInEnterprise });
183+
184+
const { result } = renderHook(() =>
185+
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
186+
isContentSharingV2Enabled: true,
187+
handleSuccess,
188+
handleError,
189+
transformUsers: mockTransformUsers,
190+
}),
191+
);
192+
193+
let contacts;
194+
await act(async () => {
195+
contacts = await result.current({ MOCK_EMAIL });
196+
});
197+
198+
expect(handleSuccess).toHaveBeenCalledWith(EMPTY_USERS);
199+
expect(mockTransformUsers).not.toHaveBeenCalled();
200+
expect(contacts).toEqual({});
135201
});
136202
});
137203

138204
describe('with a failed API call', () => {
139-
beforeAll(() => {
205+
beforeEach(() => {
140206
getUsersInEnterprise = jest
141207
.fn()
142208
.mockImplementation((itemID, getUsersInEnterpriseSuccess, getUsersInEnterpriseError) => {
@@ -145,14 +211,25 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
145211
mockAPI = createAPIMock({ getUsersInEnterprise });
146212
});
147213

148-
test('should set the value of getContactsByEmail() and call handleError() when invoked', () => {
149-
let fakeComponent;
150-
act(() => {
151-
fakeComponent = mount(<FakeComponent api={mockAPI} transformUsers={transformUsersSpy} />);
152-
});
153-
fakeComponent.update();
214+
afterEach(() => {
215+
jest.resetAllMocks();
216+
});
217+
218+
test('should set the value of getContactsByEmail() and call handleError() when invoked', async () => {
219+
const { result } = renderHook(() =>
220+
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
221+
handleSuccess,
222+
handleError,
223+
transformUsers: mockTransformUsers,
224+
}),
225+
);
226+
227+
result.current({ emails: [MOCK_EMAIL] });
154228

155-
const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] });
229+
// Wait a short time to ensure handleError is called
230+
await act(async () => {
231+
await new Promise(resolve => setTimeout(resolve, 100));
232+
});
156233

157234
expect(getUsersInEnterprise).toHaveBeenCalledWith(
158235
MOCK_ITEM_ID,
@@ -161,7 +238,6 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
161238
{ filter_term: MOCK_EMAIL },
162239
);
163240
expect(handleError).toHaveBeenCalled();
164-
expect(contacts).resolves.toBeFalsy();
165241
});
166242
});
167243
});

0 commit comments

Comments
 (0)