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
16 changes: 10 additions & 6 deletions src/elements/content-sharing/ContentSharingV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import API from '../../api';
import Internationalize from '../common/Internationalize';
import Providers from '../common/Providers';
import { fetchAvatars, fetchCollaborators, fetchCurrentUser, fetchItem } from './apis';
import { useSharingService } from './hooks/useSharingService';
import { convertCollabsResponse, convertItemResponse } from './utils';

import type { Collaborations, ItemType, StringMap } from '../../common/types/core';
Expand Down Expand Up @@ -48,18 +49,20 @@ function ContentSharingV2({
const [collaboratorsData, setCollaboratorsData] = React.useState<Collaborations | null>(null);
const [owner, setOwner] = React.useState({ id: '', email: '', name: '' });

const { sharingService } = useSharingService(api, item, itemID, itemType, setItem, setSharedLink);

// Handle successful GET requests to /files or /folders
const handleGetItemSuccess = React.useCallback(itemData => {
const {
collaborationRoles: collaborationRolesFromAPI,
item: itemFromAPI,
collaborationRoles: collaborationRolesFromApi,
item: itemFromApi,
ownedBy,
sharedLink: sharedLinkFromAPI,
sharedLink: sharedLinkFromApi,
} = convertItemResponse(itemData);

setItem(itemFromAPI);
setSharedLink(sharedLinkFromAPI);
setCollaborationRoles(collaborationRolesFromAPI);
setItem(itemFromApi);
setSharedLink(sharedLinkFromApi);
setCollaborationRoles(collaborationRolesFromApi);
setOwner({ id: ownedBy.id, email: ownedBy.login, name: ownedBy.name });
}, []);

Expand Down Expand Up @@ -151,6 +154,7 @@ function ContentSharingV2({
currentUser={currentUser}
item={item}
sharedLink={sharedLink}
sharingService={sharingService}
>
{children}
</UnifiedShareModal>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { render, RenderResult, screen, waitFor } from '@testing-library/react';
import { render, type RenderResult, screen, waitFor } from '@testing-library/react';

import { useSharingService } from '../hooks/useSharingService';
import {
DEFAULT_ITEM_API_RESPONSE,
DEFAULT_USER_API_RESPONSE,
Expand Down Expand Up @@ -46,7 +47,11 @@ const defaultAPIMock = createAPIMock(
{ getCollaborations: getCollaborationsMock },
);

const getWrapper = (props): RenderResult =>
jest.mock('../hooks/useSharingService', () => ({
useSharingService: jest.fn().mockReturnValue({ sharingService: null }),
}));

const renderComponent = (props = {}): RenderResult =>
render(
<ContentSharingV2
api={defaultAPIMock}
Expand All @@ -63,7 +68,7 @@ describe('elements/content-sharing/ContentSharingV2', () => {
});

test('should see the correct elements for files', async () => {
getWrapper({});
renderComponent();
await waitFor(() => {
expect(getDefaultFileMock).toHaveBeenCalledWith(MOCK_ITEM.id, expect.any(Function), expect.any(Function), {
fields: CONTENT_SHARING_ITEM_FIELDS,
Expand All @@ -75,7 +80,7 @@ describe('elements/content-sharing/ContentSharingV2', () => {
});

test('should see the correct elements for folders', async () => {
getWrapper({ itemType: 'folder' });
renderComponent({ itemType: 'folder' });
await waitFor(() => {
expect(getDefaultFolderMock).toHaveBeenCalledWith(
MOCK_ITEM.id,
Expand All @@ -96,7 +101,7 @@ describe('elements/content-sharing/ContentSharingV2', () => {
...defaultAPIMock,
getFileAPI: jest.fn().mockReturnValue({ getFile: getFileMockWithSharedLink }),
};
getWrapper({ api: apiWithSharedLink });
renderComponent({ api: apiWithSharedLink });
await waitFor(() => {
expect(getFileMockWithSharedLink).toHaveBeenCalledWith(
MOCK_ITEM.id,
Expand All @@ -120,7 +125,8 @@ describe('elements/content-sharing/ContentSharingV2', () => {
...defaultAPIMock,
getFileAPI: jest.fn().mockReturnValue({ getFile: getFileMockWithClassification }),
};
getWrapper({ api: apiWithClassification });

renderComponent({ api: apiWithClassification });
await waitFor(() => {
expect(getFileMockWithClassification).toHaveBeenCalledWith(
MOCK_ITEM.id,
Expand All @@ -135,7 +141,7 @@ describe('elements/content-sharing/ContentSharingV2', () => {
});

test('should process collaborators with avatars correctly', async () => {
getWrapper({});
renderComponent();

await waitFor(() => {
expect(getCollaborationsMock).toHaveBeenCalledWith(
Expand All @@ -148,4 +154,19 @@ describe('elements/content-sharing/ContentSharingV2', () => {
expect(getAvatarUrlMock).toHaveBeenCalledWith('458', MOCK_ITEM.id);
});
});

test('should render UnifiedShareModal when sharingService is available', async () => {
const mockSharingService = {
changeSharedLinkPermission: jest.fn(),
};

(useSharingService as jest.Mock).mockReturnValue({
sharingService: mockSharingService,
});

renderComponent();
await waitFor(() => {
expect(screen.getByRole('heading', { name: /Box Development Guide.pdf/i })).toBeVisible();
});
});
});
67 changes: 67 additions & 0 deletions src/elements/content-sharing/__tests__/sharingService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { PERMISSION_CAN_DOWNLOAD, PERMISSION_CAN_PREVIEW } from '../../../constants';
import { CONTENT_SHARING_SHARED_LINK_UPDATE_PARAMS } from '../constants';
import { convertSharedLinkPermissions, createSharingService } from '../sharingService';

describe('elements/content-sharing/sharingService', () => {
describe('convertSharedLinkPermissions', () => {
test.each([
[PERMISSION_CAN_DOWNLOAD, { [PERMISSION_CAN_DOWNLOAD]: true, [PERMISSION_CAN_PREVIEW]: false }],
[PERMISSION_CAN_PREVIEW, { [PERMISSION_CAN_DOWNLOAD]: false, [PERMISSION_CAN_PREVIEW]: true }],
])('should return correct permissions for download permission level', (permissionLevel, expected) => {
const result = convertSharedLinkPermissions(permissionLevel);
expect(result).toEqual(expected);
});

test('should handle empty string permission level', () => {
const result = convertSharedLinkPermissions('');
expect(result).toEqual({});
});
});

describe('createSharingService', () => {
const mockItemApiInstance = {
updateSharedLink: jest.fn(),
};
const mockItemData = { id: '123' };
const mockOnSuccess = jest.fn();

afterEach(() => {
jest.clearAllMocks();
});

test('should return an object with changeSharedLinkPermission method', () => {
const service = createSharingService({
itemApiInstance: mockItemApiInstance,
itemData: mockItemData,
onSuccess: mockOnSuccess,
});

expect(service).toHaveProperty('changeSharedLinkPermission');
expect(typeof service.changeSharedLinkPermission).toBe('function');
});

test('should call updateSharedLink with correct parameters when changeSharedLinkPermission is called', async () => {
const service = createSharingService({
itemApiInstance: mockItemApiInstance,
itemData: mockItemData,
onSuccess: mockOnSuccess,
});

const permissionLevel = PERMISSION_CAN_DOWNLOAD;
const expectedPermissions = {
[PERMISSION_CAN_DOWNLOAD]: true,
[PERMISSION_CAN_PREVIEW]: false,
};

await service.changeSharedLinkPermission(permissionLevel);

expect(mockItemApiInstance.updateSharedLink).toHaveBeenCalledWith(
mockItemData,
{ permissions: expectedPermissions },
mockOnSuccess,
{},
CONTENT_SHARING_SHARED_LINK_UPDATE_PARAMS,
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@ function FakeComponent({
const [changeSharedLinkAccessLevel, setChangeSharedLinkAccessLevel] = React.useState<null | SharedLinkUpdateFnType>(
null,
);
const [
changeSharedLinkPermissionLevel,
setChangeSharedLinkPermissionLevel,
] = React.useState<null | SharedLinkUpdateFnType>(null);
const [changeSharedLinkPermissionLevel, setChangeSharedLinkPermissionLevel] =
React.useState<null | SharedLinkUpdateFnType>(null);
const [onSubmitSettings, setOnSubmitSettings] = React.useState<null | Function>(null);
const [generatedFunctions, setGeneratedFunctions] = React.useState<boolean>(false);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { renderHook } from '@testing-library/react';

import { TYPE_FILE, TYPE_FOLDER } from '../../../../constants';
import { createSharingService } from '../../sharingService';
import { convertItemResponse } from '../../utils/convertItemResponse';
import { useSharingService } from '../useSharingService';

jest.mock('../../utils/convertItemResponse');
jest.mock('../../sharingService');

const mockApi = {
getFileAPI: jest.fn(),
getFolderAPI: jest.fn(),
};
const mockItemApiInstance = {
updateSharedLink: jest.fn(),
};
const mockSharingService = {
changeSharedLinkPermission: jest.fn(),
};

const mockItemId = '123';
const mockItem = {
id: mockItemId,
permissions: {
can_download: true,
can_preview: false,
},
};

const mockSetItem = jest.fn();
const mockSetSharedLink = jest.fn();

describe('elements/content-sharing/hooks/useSharingService', () => {
beforeEach(() => {
(createSharingService as jest.Mock).mockReturnValue(mockSharingService);
(convertItemResponse as jest.Mock).mockReturnValue({
item: mockItem,
sharedLink: {},
});
});

afterEach(() => {
jest.clearAllMocks();
});

test('should return null itemApiInstance and sharingService when item is null', () => {
const { result } = renderHook(() =>
useSharingService(mockApi, null, mockItemId, TYPE_FILE, mockSetItem, mockSetSharedLink),
);

expect(result.current.sharingService).toBeNull();
expect(mockApi.getFileAPI).not.toHaveBeenCalled();
expect(mockApi.getFolderAPI).not.toHaveBeenCalled();
expect(createSharingService).not.toHaveBeenCalled();
});

test('should return null itemApiInstance and sharingService when itemType is neither TYPE_FILE nor TYPE_FOLDER', () => {
const { result } = renderHook(() =>
useSharingService(mockApi, mockItem, mockItemId, 'hubs', mockSetItem, mockSetSharedLink),
);

expect(result.current.sharingService).toBeNull();
expect(mockApi.getFileAPI).not.toHaveBeenCalled();
expect(mockApi.getFolderAPI).not.toHaveBeenCalled();
expect(createSharingService).not.toHaveBeenCalled();
});

describe('when itemType is TYPE_FILE', () => {
beforeEach(() => {
mockApi.getFileAPI.mockReturnValue(mockItemApiInstance);
});

test('should create file API instance and sharing service', () => {
const { result } = renderHook(() =>
useSharingService(mockApi, mockItem, mockItemId, TYPE_FILE, mockSetItem, mockSetSharedLink),
);

expect(mockApi.getFileAPI).toHaveBeenCalled();
expect(mockApi.getFolderAPI).not.toHaveBeenCalled();
expect(result.current.sharingService).toBe(mockSharingService);
expect(createSharingService).toHaveBeenCalledWith({
itemApiInstance: mockItemApiInstance,
itemData: {
id: mockItemId,
permissions: mockItem.permissions,
},
onSuccess: expect.any(Function),
});
});

test('should handle success callback correctly', () => {
const mockConvertedData = {
item: {
id: mockItemId,
permissions: { can_download: false },
},
sharedLink: {},
};

(convertItemResponse as jest.Mock).mockReturnValue(mockConvertedData);
renderHook(() =>
useSharingService(mockApi, mockItem, mockItemId, TYPE_FILE, mockSetItem, mockSetSharedLink),
);

// Get the onSuccess callback that was passed to mock createSharingService
const onSuccessCallback = (createSharingService as jest.Mock).mock.calls[0][0].onSuccess;
onSuccessCallback(mockConvertedData);

expect(mockSetItem).toHaveBeenCalledTimes(1);
expect(mockSetSharedLink).toHaveBeenCalledTimes(1);
});
});

describe('when itemType is TYPE_FOLDER', () => {
beforeEach(() => {
mockApi.getFolderAPI.mockReturnValue(mockItemApiInstance);
});

test('should create folder API instance and sharing service', () => {
const { result } = renderHook(() =>
useSharingService(mockApi, mockItem, mockItemId, TYPE_FOLDER, mockSetItem, mockSetSharedLink),
);

expect(mockApi.getFolderAPI).toHaveBeenCalled();
expect(mockApi.getFileAPI).not.toHaveBeenCalled();
expect(result.current.sharingService).toBe(mockSharingService);
expect(createSharingService).toHaveBeenCalledWith({
itemApiInstance: mockItemApiInstance,
itemData: {
id: mockItemId,
permissions: mockItem.permissions,
},
onSuccess: expect.any(Function),
});
});
});
});
44 changes: 44 additions & 0 deletions src/elements/content-sharing/hooks/useSharingService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as React from 'react';

import { TYPE_FILE, TYPE_FOLDER } from '../../../constants';
import { convertItemResponse } from '../utils/convertItemResponse';
import { createSharingService } from '../sharingService';

export const useSharingService = (api, item, itemId, itemType, setItem, setSharedLink) => {
const itemApiInstance = React.useMemo(() => {
if (!item) {
return null;
}

if (itemType === TYPE_FILE) {
return api.getFileAPI();
}

if (itemType === TYPE_FOLDER) {
return api.getFolderAPI();
}

return null;
}, [api, item, itemType]);

const sharingService = React.useMemo(() => {
if (!itemApiInstance) {
return null;
}

const itemData = {
id: itemId,
permissions: item.permissions,
};

const handleSuccess = updatedItemData => {
const { item: updatedItem, sharedLink: updatedSharedLink } = convertItemResponse(updatedItemData);
setItem(prevItem => ({ ...prevItem, ...updatedItem }));
setSharedLink(prevSharedLink => ({ ...prevSharedLink, ...updatedSharedLink }));
};

return createSharingService({ itemApiInstance, itemData, onSuccess: handleSuccess });
}, [itemApiInstance, item, itemId, setItem, setSharedLink]);

return { sharingService };
};
Loading