Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions i18n/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ be.contentSharing.badRequestError = The request for this item was malformed.
be.contentSharing.collaboratorsLoadingError = Could not retrieve collaborators for this item.
# Message that appears when users cannot be retrieved in the ContentSharing Element.
be.contentSharing.getContactsError = Could not retrieve contacts.
# Display text for a Group contact type
be.contentSharing.groupContactLabel = Group
# Message that appears when the ContentSharing Element cannot be loaded.
be.contentSharing.loadingError = Could not load shared link for this item.
# Message that appears when the user cannot access the item for the ContentSharing Element.
Expand Down
29 changes: 16 additions & 13 deletions src/elements/content-sharing/SharingModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type {
import type {
ContentSharingItemAPIResponse,
ContentSharingSharedLinkType,
GetContactByEmailFnType,
GetContactsFnType,
GetContactsByEmailFnType,
SendInvitesFnType,
Expand Down Expand Up @@ -80,18 +81,16 @@ function SharingModal({
const [collaboratorsList, setCollaboratorsList] = React.useState<collaboratorsListType | null>(null);
const [onAddLink, setOnAddLink] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [onRemoveLink, setOnRemoveLink] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [
changeSharedLinkAccessLevel,
setChangeSharedLinkAccessLevel,
] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [
changeSharedLinkPermissionLevel,
setChangeSharedLinkPermissionLevel,
] = React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [changeSharedLinkAccessLevel, setChangeSharedLinkAccessLevel] =
React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [changeSharedLinkPermissionLevel, setChangeSharedLinkPermissionLevel] =
React.useState<null | SharedLinkUpdateLevelFnType>(null);
const [onSubmitSettings, setOnSubmitSettings] = React.useState<null | SharedLinkUpdateSettingsFnType>(null);
const [currentView, setCurrentView] = React.useState<string>(CONTENT_SHARING_VIEWS.UNIFIED_SHARE_MODAL);
const [getContacts, setGetContacts] = React.useState<null | GetContactsFnType>(null);
const [getContactsByEmail, setGetContactsByEmail] = React.useState<null | GetContactsByEmailFnType>(null);
const [getContactsByEmail, setGetContactsByEmail] = React.useState<
null | GetContactsByEmailFnType | GetContactByEmailFnType,
>(null);
const [sendInvites, setSendInvites] = React.useState<null | SendInvitesFnType>(null);
const [isLoading, setIsLoading] = React.useState<boolean>(true);

Expand Down Expand Up @@ -191,11 +190,15 @@ function SharingModal({

// Set the getContactsByEmail function. This call is not associated with a banner notification,
// which is why it exists at this level and not in SharingNotification
const getContactsByEmailFn: GetContactsByEmailFnType | null = useContactsByEmail(api, itemID, {
transformUsers: data => convertUserContactsByEmailResponse(data),
});
const getContactsByEmailFn: GetContactsByEmailFnType | GetContactByEmailFnType | null = useContactsByEmail(
api,
itemID,
{
transformUsers: data => convertUserContactsByEmailResponse(data),
},
);
if (getContactsByEmailFn && !getContactsByEmail) {
setGetContactsByEmail((): GetContactsByEmailFnType => getContactsByEmailFn);
setGetContactsByEmail((): GetContactsByEmailFnType | GetContactByEmailFnType => getContactsByEmailFn);
}

// Display a notification if there is an error in retrieving initial data
Expand Down
218 changes: 147 additions & 71 deletions src/elements/content-sharing/__tests__/useContactsByEmail.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// @flow

import React, { act } from 'react';
import { mount } from 'enzyme';
import API from '../../../api';
import { renderHook, act } from '@testing-library/react';
import useContactsByEmail from '../hooks/useContactsByEmail';
import {
MOCK_CONTACTS_API_RESPONSE,
Expand All @@ -12,56 +10,43 @@ import {

const handleSuccess = jest.fn();
const handleError = jest.fn();
const transformUsersSpy = jest.fn().mockReturnValue(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE);
const mockTransformUsers = jest.fn().mockReturnValue(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE);

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

function FakeComponent({ api, transformUsers }: { api: API, transformUsers: Function }) {
const [getContactsByEmail, setGetContactsByEmail] = React.useState(null);

const updatedGetContactsByEmailFn = useContactsByEmail(api, MOCK_ITEM_ID, {
handleSuccess,
handleError,
transformUsers,
});

if (updatedGetContactsByEmailFn && !getContactsByEmail) {
setGetContactsByEmail(() => updatedGetContactsByEmailFn);
}

return (
getContactsByEmail && (
<button onClick={getContactsByEmail} type="submit">
&#9835; Box UI Elements &#9835;
</button>
)
);
}

const MOCK_EMAIL = 'contentsharing@box.com';

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

describe('with a successful API call', () => {
beforeAll(() => {
beforeEach(() => {
getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => {
return getUsersInEnterpriseSuccess(MOCK_CONTACTS_API_RESPONSE);
});
mockAPI = createAPIMock({ getUsersInEnterprise });
});

test('should set the value of getContactsByEmail() and retrieve contacts on invocation', () => {
let fakeComponent;
act(() => {
fakeComponent = mount(<FakeComponent api={mockAPI} transformUsers={transformUsersSpy} />);
});
fakeComponent.update();
afterEach(() => {
jest.resetAllMocks();
});

const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] });
test('should set the value of getContactsByEmail() and retrieve contacts on invocation', async () => {
const { result } = renderHook(() =>
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
handleSuccess,
handleError,
transformUsers: mockTransformUsers,
}),
);

let contacts;
await act(async () => {
contacts = await result.current({ emails: [MOCK_EMAIL] });
});

expect(getUsersInEnterprise).toHaveBeenCalledWith(
MOCK_ITEM_ID,
Expand All @@ -70,18 +55,22 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
{ filter_term: MOCK_EMAIL },
);
expect(handleSuccess).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE);
expect(transformUsersSpy).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE);
return expect(contacts).resolves.toEqual(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE);
expect(mockTransformUsers).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE);
expect(contacts).toEqual(MOCK_CONTACTS_BY_EMAIL_CONVERTED_RESPONSE);
});

test('should return the entries from the API data if transformUsers() is not provided', () => {
let fakeComponent;
act(() => {
fakeComponent = mount(<FakeComponent api={mockAPI} />);
});
fakeComponent.update();
test('should return the entries from the API data if transformUsers() is not provided', async () => {
const { result } = renderHook(() =>
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
handleSuccess,
handleError,
}),
);

const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] });
let contacts;
await act(async () => {
contacts = await result.current({ emails: [MOCK_EMAIL] });
});

expect(getUsersInEnterprise).toHaveBeenCalledWith(
MOCK_ITEM_ID,
Expand All @@ -90,28 +79,33 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
{ filter_term: MOCK_EMAIL },
);
expect(handleSuccess).toHaveBeenCalledWith(MOCK_CONTACTS_API_RESPONSE);
expect(transformUsersSpy).not.toHaveBeenCalled();
expect(contacts).resolves.toEqual(MOCK_CONTACTS_API_RESPONSE.entries);
expect(mockTransformUsers).not.toHaveBeenCalled();
expect(contacts).toEqual(MOCK_CONTACTS_API_RESPONSE.entries);
});

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

let fakeComponent;
act(() => {
fakeComponent = mount(<FakeComponent api={mockAPI} transformUsers={transformUsersSpy} />);
});
fakeComponent.update();
const { result } = renderHook(() =>
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
handleSuccess,
handleError,
transformUsers: mockTransformUsers,
}),
);

const contacts = fakeComponent.find('button').invoke('onClick')({ emails: [MOCK_EMAIL] });
let contacts;
await act(async () => {
contacts = await result.current({ emails: [MOCK_EMAIL] });
});

expect(handleSuccess).toHaveBeenCalledWith(EMPTY_USERS);
expect(transformUsersSpy).not.toHaveBeenCalled();
return expect(contacts).resolves.toEqual({});
expect(mockTransformUsers).not.toHaveBeenCalled();
expect(contacts).toEqual({});
});

test.each`
Expand All @@ -120,23 +114,95 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
${{ content: 'sharing' }} | ${'an object, but does not have an emails key'}
${{ emails: 'contentsharing' }} | ${'an object with the emails key, but filterTerm.emails is not an array'}
${{ emails: [] }} | ${'an object with the emails key, but filterTerm.emails is an empty array'}
`('should return an empty object when filterTerm is $description', ({ filterTerm }) => {
let fakeComponent;
act(() => {
fakeComponent = mount(<FakeComponent api={mockAPI} />);
});
fakeComponent.update();
`('should return an empty object when filterTerm is $description', async ({ filterTerm }) => {
const { result } = renderHook(() =>
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
handleSuccess,
handleError,
}),
);

const contacts = fakeComponent.find('button').invoke('onClick')(filterTerm);
let contacts;
await act(async () => {
contacts = await result.current(filterTerm);
});

expect(getUsersInEnterprise).not.toHaveBeenCalled();
expect(handleError).not.toHaveBeenCalled();
return expect(contacts).resolves.toEqual({});
expect(contacts).toEqual({});
});

test('should set the value of getContactsByEmail() and retrieve contacts when isContentSharingV2Enabled is true and email is provided', async () => {
const mockUser1 = MOCK_CONTACTS_API_RESPONSE.entries[0];
const { id, login: email, name, type } = mockUser1;
const expectedTransformedResult = {
id,
email,
name,
type,
value: email,
};
const MOCK_CONTACT_BY_EMAIL_API_RESPONSE = { entries: [mockUser1] };
const mockTransformUsersV2 = jest.fn().mockReturnValue(expectedTransformedResult);
getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => {
return getUsersInEnterpriseSuccess(MOCK_CONTACT_BY_EMAIL_API_RESPONSE);
});
mockAPI = createAPIMock({ getUsersInEnterprise });

const { result } = renderHook(() =>
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
isContentSharingV2Enabled: true,
handleSuccess,
handleError,
transformUsers: mockTransformUsersV2,
}),
);

let contacts;
await act(async () => {
contacts = await result.current('contentopenwith@box.com');
});

expect(getUsersInEnterprise).toHaveBeenCalledWith(
MOCK_ITEM_ID,
expect.anything(Function),
expect.anything(Function),
{ filter_term: 'contentopenwith@box.com' },
);
expect(handleSuccess).toHaveBeenCalledWith(MOCK_CONTACT_BY_EMAIL_API_RESPONSE);
expect(mockTransformUsersV2).toHaveBeenCalledWith(MOCK_CONTACT_BY_EMAIL_API_RESPONSE);
expect(contacts).toEqual(expectedTransformedResult);
});

test('should set the value of getContactsByEmail() to an empty object when isContentSharingV2Enabled is true and email is not provided', async () => {
const EMPTY_USERS = { entries: [] };
getUsersInEnterprise = jest.fn().mockImplementation((itemID, getUsersInEnterpriseSuccess) => {
return getUsersInEnterpriseSuccess(EMPTY_USERS);
});
mockAPI = createAPIMock({ getUsersInEnterprise });

const { result } = renderHook(() =>
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
isContentSharingV2Enabled: true,
handleSuccess,
handleError,
transformUsers: mockTransformUsers,
}),
);

let contacts;
await act(async () => {
contacts = await result.current({ MOCK_EMAIL });
});

expect(handleSuccess).toHaveBeenCalledWith(EMPTY_USERS);
expect(mockTransformUsers).not.toHaveBeenCalled();
expect(contacts).toEqual({});
});
});

describe('with a failed API call', () => {
beforeAll(() => {
beforeEach(() => {
getUsersInEnterprise = jest
.fn()
.mockImplementation((itemID, getUsersInEnterpriseSuccess, getUsersInEnterpriseError) => {
Expand All @@ -145,14 +211,25 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
mockAPI = createAPIMock({ getUsersInEnterprise });
});

test('should set the value of getContactsByEmail() and call handleError() when invoked', () => {
let fakeComponent;
act(() => {
fakeComponent = mount(<FakeComponent api={mockAPI} transformUsers={transformUsersSpy} />);
});
fakeComponent.update();
afterEach(() => {
jest.resetAllMocks();
});

test('should set the value of getContactsByEmail() and call handleError() when invoked', async () => {
const { result } = renderHook(() =>
useContactsByEmail(mockAPI, MOCK_ITEM_ID, {
handleSuccess,
handleError,
transformUsers: mockTransformUsers,
}),
);

result.current({ emails: [MOCK_EMAIL] });

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

expect(getUsersInEnterprise).toHaveBeenCalledWith(
MOCK_ITEM_ID,
Expand All @@ -161,7 +238,6 @@ describe('elements/content-sharing/hooks/useContactsByEmail', () => {
{ filter_term: MOCK_EMAIL },
);
expect(handleError).toHaveBeenCalled();
expect(contacts).resolves.toBeFalsy();
});
});
});
Loading