Skip to content

Commit 3bbe448

Browse files
committed
feat: Convert Content Sharing V2 API Response
1 parent 7c633b8 commit 3bbe448

17 files changed

+831
-13
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@
140140
"@box/metadata-view": "^0.48.14",
141141
"@box/react-virtualized": "^9.22.3-rc-box.10",
142142
"@box/types": "^0.2.1",
143-
"@box/unified-share-modal": "^0.48.8",
143+
"@box/unified-share-modal": "^0.50.0",
144144
"@box/user-selector": "^1.23.25",
145145
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
146146
"@chromatic-com/storybook": "^4.0.1",

src/elements/content-sharing/ContentSharing.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ function ContentSharing({
117117
if (isFeatureEnabled(features, 'contentSharingV2')) {
118118
return (
119119
<ContentSharingV2
120+
api={api}
120121
itemID={itemID}
121122
itemType={itemType}
122123
hasProviders={hasProviders}

src/elements/content-sharing/ContentSharingV2.tsx

Lines changed: 115 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,146 @@
11
import * as React from 'react';
2+
import isEmpty from 'lodash/isEmpty';
23

34
import { UnifiedShareModal } from '@box/unified-share-modal';
45

6+
import API from '../../api';
7+
import { FIELD_ENTERPRISE, FIELD_HOSTNAME, TYPE_FILE, TYPE_FOLDER } from '../../constants';
58
import Internationalize from '../common/Internationalize';
69
import Providers from '../common/Providers';
10+
import { CONTENT_SHARING_ITEM_FIELDS } from './constants';
11+
import { convertItemResponse } from './utils';
712

813
import type { ItemType, StringMap } from '../../common/types/core';
14+
import type { CollaborationRole, Item, SharedLink, User } from './types';
915

1016
export interface ContentSharingV2Props {
17+
/** api - API instance */
18+
api: API;
1119
/** children - Children for the element to open the Unified Share Modal */
1220
children?: React.ReactElement;
1321
/** itemID - Box file or folder ID */
1422
itemID: string;
1523
/** itemType - "file" or "folder" */
1624
itemType: ItemType;
1725
/** hasProviders - Whether the element has providers for USM already */
18-
hasProviders: boolean;
26+
hasProviders?: boolean;
1927
/** language - Language used for the element */
2028
language?: string;
2129
/** messages - Localized strings used by the element */
2230
messages?: StringMap;
2331
}
2432

25-
function ContentSharingV2({ children, itemID, itemType, hasProviders, language, messages }: ContentSharingV2Props) {
26-
// Retrieve item from API later
27-
const mockItem = {
28-
id: itemID,
29-
name: 'Box Development Guide.pdf',
30-
type: itemType,
33+
function ContentSharingV2({
34+
api,
35+
children,
36+
itemID,
37+
itemType,
38+
hasProviders,
39+
language,
40+
messages,
41+
}: ContentSharingV2Props) {
42+
const [item, setItem] = React.useState<Item | null>(null);
43+
const [sharedLink, setSharedLink] = React.useState<SharedLink | null>(null);
44+
const [currentUser, setCurrentUser] = React.useState<User | null>(null);
45+
const [collaborationRoles, setCollaborationRoles] = React.useState<CollaborationRole[] | null>(null);
46+
47+
// Handle successful GET requests to /files or /folders
48+
const handleGetItemSuccess = React.useCallback(itemData => {
49+
const {
50+
collaborationRoles: collaborationRolesFromAPI,
51+
item: itemFromAPI,
52+
sharedLink: sharedLinkFromAPI,
53+
} = convertItemResponse(itemData);
54+
setItem(itemFromAPI);
55+
setSharedLink(sharedLinkFromAPI);
56+
setCollaborationRoles(collaborationRolesFromAPI);
57+
}, []);
58+
59+
// Reset state if the API has changed
60+
React.useEffect(() => {
61+
setItem(null);
62+
setSharedLink(null);
63+
setCurrentUser(null);
64+
setCollaborationRoles(null);
65+
}, [api]);
66+
67+
// Get initial data for the item
68+
React.useEffect(() => {
69+
const getItem = () => {
70+
if (itemType === TYPE_FILE) {
71+
api.getFileAPI().getFile(
72+
itemID,
73+
handleGetItemSuccess,
74+
{},
75+
{
76+
fields: CONTENT_SHARING_ITEM_FIELDS,
77+
},
78+
);
79+
} else if (itemType === TYPE_FOLDER) {
80+
api.getFolderAPI().getFolderFields(
81+
itemID,
82+
handleGetItemSuccess,
83+
{},
84+
{
85+
fields: CONTENT_SHARING_ITEM_FIELDS,
86+
},
87+
);
88+
}
89+
};
90+
91+
if (api && !isEmpty(api) && !item && !sharedLink) {
92+
getItem();
93+
}
94+
}, [api, item, itemID, itemType, sharedLink, handleGetItemSuccess]);
95+
96+
// Get initial data for the user
97+
React.useEffect(() => {
98+
const getUserSuccess = userData => {
99+
const { enterprise, id } = userData;
100+
setCurrentUser({
101+
id,
102+
enterprise: {
103+
name: enterprise ? enterprise.name : '',
104+
},
105+
});
106+
};
107+
108+
const getUserData = () => {
109+
api.getUsersAPI(false).getUser(
110+
itemID,
111+
getUserSuccess,
112+
{},
113+
{
114+
params: {
115+
fields: [FIELD_ENTERPRISE, FIELD_HOSTNAME].toString(),
116+
},
117+
},
118+
);
119+
};
120+
121+
if (api && !isEmpty(api) && item && sharedLink && !currentUser) {
122+
getUserData();
123+
}
124+
}, [api, currentUser, item, itemID, itemType, sharedLink]);
125+
126+
const config = {
127+
sharedLinkEmail: false,
31128
};
32129

33130
return (
34131
<Internationalize language={language} messages={messages}>
35132
<Providers hasProviders={hasProviders}>
36-
<UnifiedShareModal item={mockItem}>{children}</UnifiedShareModal>
133+
{item && (
134+
<UnifiedShareModal
135+
config={config}
136+
collaborationRoles={collaborationRoles}
137+
currentUser={currentUser}
138+
item={item}
139+
sharedLink={sharedLink}
140+
>
141+
{children}
142+
</UnifiedShareModal>
143+
)}
37144
</Providers>
38145
</Internationalize>
39146
);
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import React from 'react';
2+
import { render, RenderResult, screen, waitFor } from '@testing-library/react';
3+
4+
import {
5+
DEFAULT_ITEM_API_RESPONSE,
6+
DEFAULT_USER_API_RESPONSE,
7+
MOCK_ITEM,
8+
MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK,
9+
MOCK_ITEM_API_RESPONSE_WITH_CLASSIFICATION,
10+
} from '../utils/__mocks__/ContentSharingV2Mocks';
11+
import { CONTENT_SHARING_ITEM_FIELDS } from '../constants';
12+
13+
import ContentSharingV2 from '../ContentSharingV2';
14+
15+
const createAPIMock = (fileAPI, folderAPI, usersAPI) => ({
16+
getFileAPI: jest.fn().mockReturnValue(fileAPI),
17+
getFolderAPI: jest.fn().mockReturnValue(folderAPI),
18+
getUsersAPI: jest.fn().mockReturnValue(usersAPI),
19+
});
20+
21+
const createSuccessMock = responseFromAPI => (id, successFn) => {
22+
return Promise.resolve(responseFromAPI).then(response => {
23+
successFn(response);
24+
});
25+
};
26+
27+
const getDefaultUserMock = jest.fn().mockImplementation(createSuccessMock(DEFAULT_USER_API_RESPONSE));
28+
const getDefaultFileMock = jest.fn().mockImplementation(createSuccessMock(DEFAULT_ITEM_API_RESPONSE));
29+
const getFileMockWithSharedLink = jest
30+
.fn()
31+
.mockImplementation(createSuccessMock(MOCK_ITEM_API_RESPONSE_WITH_SHARED_LINK));
32+
const getFileMockWithClassification = jest
33+
.fn()
34+
.mockImplementation(createSuccessMock(MOCK_ITEM_API_RESPONSE_WITH_CLASSIFICATION));
35+
const getDefaultFolderMock = jest.fn().mockImplementation(createSuccessMock(DEFAULT_ITEM_API_RESPONSE));
36+
const defaultAPIMock = createAPIMock(
37+
{ getFile: getDefaultFileMock },
38+
{ getFolderFields: getDefaultFolderMock },
39+
{ getUser: getDefaultUserMock },
40+
);
41+
42+
const getWrapper = (props): RenderResult =>
43+
render(
44+
<ContentSharingV2
45+
api={defaultAPIMock}
46+
itemID={MOCK_ITEM.id}
47+
itemType={MOCK_ITEM.type}
48+
hasProviders={true}
49+
{...props}
50+
></ContentSharingV2>,
51+
);
52+
53+
describe('elements/content-sharing/ContentSharingV2', () => {
54+
afterEach(() => {
55+
jest.clearAllMocks();
56+
});
57+
58+
test('should see the correct elements for files', async () => {
59+
getWrapper({});
60+
await waitFor(() => {
61+
expect(getDefaultFileMock).toHaveBeenCalledWith(
62+
MOCK_ITEM.id,
63+
expect.any(Function),
64+
{},
65+
{
66+
fields: CONTENT_SHARING_ITEM_FIELDS,
67+
},
68+
);
69+
});
70+
71+
expect(screen.getByRole('heading', { name: 'Share ‘Box Development Guide.pdf’' })).toBeVisible();
72+
expect(screen.getByRole('combobox', { name: 'Invite People' })).toBeVisible();
73+
expect(screen.getByRole('switch', { name: 'Shared link' })).toBeVisible();
74+
});
75+
76+
test('should see the correct elements for folders', async () => {
77+
getWrapper({ itemType: 'folder' });
78+
await waitFor(() => {
79+
expect(getDefaultFolderMock).toHaveBeenCalledWith(
80+
MOCK_ITEM.id,
81+
expect.any(Function),
82+
{},
83+
{
84+
fields: CONTENT_SHARING_ITEM_FIELDS,
85+
},
86+
);
87+
});
88+
89+
expect(screen.getByRole('heading', { name: 'Share ‘Box Development Guide.pdf’' })).toBeVisible();
90+
expect(screen.getByRole('combobox', { name: 'Invite People' })).toBeVisible();
91+
expect(screen.getByRole('switch', { name: 'Shared link' })).toBeVisible();
92+
});
93+
94+
test('should see the shared link elements if shared link is present', async () => {
95+
getWrapper({
96+
api: createAPIMock({ getFile: getFileMockWithSharedLink }, null, { getUser: getDefaultUserMock }),
97+
});
98+
await waitFor(() => {
99+
expect(getFileMockWithSharedLink).toHaveBeenCalledWith(
100+
MOCK_ITEM.id,
101+
expect.any(Function),
102+
{},
103+
{
104+
fields: CONTENT_SHARING_ITEM_FIELDS,
105+
},
106+
);
107+
});
108+
109+
expect(await screen.findByLabelText('Shared link URL')).toBeVisible();
110+
expect(await screen.findByRole('button', { name: 'People with the link' })).toBeVisible();
111+
expect(await screen.findByRole('button', { name: 'Can view and download' })).toBeVisible();
112+
expect(screen.getByRole('button', { name: 'Link Settings' })).toBeVisible();
113+
expect(screen.getByRole('button', { name: 'Copy' })).toBeVisible();
114+
});
115+
116+
test('should see the classification elements if classification is present', async () => {
117+
getWrapper({
118+
api: createAPIMock({ getFile: getFileMockWithClassification }, null, { getUser: getDefaultUserMock }),
119+
});
120+
await waitFor(() => {
121+
expect(getFileMockWithClassification).toHaveBeenCalledWith(
122+
MOCK_ITEM.id,
123+
expect.any(Function),
124+
{},
125+
{
126+
fields: CONTENT_SHARING_ITEM_FIELDS,
127+
},
128+
);
129+
});
130+
131+
// TODO: Figure out why classification is not being rendered in the DOM
132+
expect(await screen.findByText('BLUE')).toBeVisible();
133+
});
134+
});

src/elements/content-sharing/stories/ContentSharingV2.stories.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
import * as React from 'react';
2+
23
import { TYPE_FILE, TYPE_FOLDER } from '../../../constants';
4+
import { mockAPIWithSharedLink, mockAPIWithoutSharedLink } from '../utils/__mocks__/ContentSharingV2Mocks';
35
import ContentSharingV2 from '../ContentSharingV2';
46

57
export const basic = {};
68

9+
export const withSharedLink = {
10+
args: {
11+
api: mockAPIWithSharedLink,
12+
},
13+
};
14+
715
export default {
816
title: 'Elements/ContentSharingV2',
917
component: ContentSharingV2,
1018
args: {
19+
api: mockAPIWithoutSharedLink,
1120
children: <button>Open Unified Share Modal</button>,
1221
itemType: TYPE_FILE,
1322
itemID: global.FILE_ID,

src/elements/content-sharing/stories/tests/ContentSharingV2-visual.stories.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
1+
import * as React from 'react';
12
import { TYPE_FILE } from '../../../../constants';
3+
import { mockAPIWithSharedLink, mockAPIWithoutSharedLink } from '../../utils/__mocks__/ContentSharingV2Mocks';
24
import ContentSharingV2 from '../../ContentSharingV2';
35

46
export const withModernization = {
57
args: {
8+
api: mockAPIWithoutSharedLink,
69
enableModernizedComponents: true,
710
},
811
};
912

13+
export const withSharedLink = {
14+
args: {
15+
api: mockAPIWithSharedLink,
16+
},
17+
};
18+
1019
export default {
1120
title: 'Elements/ContentSharingV2/tests/visual-regression-tests',
1221
component: ContentSharingV2,
1322
args: {
23+
children: <button>Open Unified Share Modal</button>,
1424
itemType: TYPE_FILE,
1525
itemID: global.FILE_ID,
1626
},

0 commit comments

Comments
 (0)