Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@
"@box/types": "^2.1.8",
"@box/unified-share-modal": "^2.15.16",
"@box/user-selector": "^1.76.19",
"@box/uploads-manager": "^1.14.0",
"@box/uploads-manager": "^1.15.0",
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
"@chromatic-com/storybook": "^4.0.1",
"@commitlint/cli": "^19.8.0",
Expand Down Expand Up @@ -314,7 +314,7 @@
"@box/types": "^2.1.8",
"@box/unified-share-modal": "^2.15.16",
"@box/user-selector": "^1.76.19",
"@box/uploads-manager": "^1.14.0",
"@box/uploads-manager": "^1.15.0",
"@hapi/address": "^2.1.4",
"@tanstack/react-virtual": "^3.13.12",
"axios": "^0.31.1",
Expand Down
45 changes: 37 additions & 8 deletions src/elements/content-uploader/ContentUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AxiosRequestConfig, AxiosResponse } from 'axios';
import DroppableContent from './DroppableContent';
import Footer from './Footer';
import UploadsManager from './UploadsManager';
import { getModernizedItemId, mapToModernizedUploadItems } from './utils/mapToModernizedUploadItem';
import API from '../../api';
import Browser from '../../utils/Browser';
import Internationalize from '../common/Internationalize';
Expand Down Expand Up @@ -1219,6 +1220,28 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
}
};

/**
* Find legacy UploadItem by the id used by the modernized uploads manager.
*/
findItemByModernizedId = (id: string): UploadItem | undefined => {
const { rootFolderId } = this.props;
return this.state.items.find(item => getModernizedItemId(item, rootFolderId) === id);
};

handleModernizedItemAction = (id: string) => {
const item = this.findItemByModernizedId(id);
if (item) {
Comment thread
jpan-box marked this conversation as resolved.
this.onClick(item);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
};

handleModernizedItemRemove = (id: string) => {
const item = this.findItemByModernizedId(id);
if (item) {
this.removeFileFromUploadQueue(item);
}
};

/**
* Empties the items queue
*
Expand Down Expand Up @@ -1282,6 +1305,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
messages,
onClose,
onUpgradeCTAClick,
rootFolderId,
theme,
useUploadsManager,
}: ContentUploaderProps = this.props;
Expand All @@ -1303,7 +1327,14 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
return (
<div ref={measureRef} className={styleClassName} id={this.id}>
<ThemingStyles selector={`#${this.id}`} theme={theme} />
<UploadsManagerBP items={[]} />
<UploadsManagerBP
items={mapToModernizedUploadItems(items, rootFolderId)}
Comment thread
jpan-box marked this conversation as resolved.
isExpanded={isUploadsManagerExpanded}
onToggle={this.toggleUploadsManager}
onItemCancel={this.handleModernizedItemAction}
onItemRetry={this.handleModernizedItemAction}
Comment thread
dealwith marked this conversation as resolved.
onItemRemove={this.handleModernizedItemRemove}
/>
</div>
);
}
Expand All @@ -1326,9 +1357,9 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
view={view}
/>
</div>
)
);
}

return (
<div ref={measureRef} className={styleClassName} id={this.id}>
<ThemingStyles selector={`#${this.id}`} theme={theme} />
Expand All @@ -1353,14 +1384,12 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
isDone={isDone}
/>
</div>
)
}
);
};

return (
<Internationalize language={language} messages={messages}>
<TooltipProvider>
{renderUploader()}
</TooltipProvider>
<TooltipProvider>{renderUploader()}</TooltipProvider>
</Internationalize>
);
}
Expand Down
119 changes: 116 additions & 3 deletions src/elements/content-uploader/__tests__/ContentUploader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -775,18 +775,131 @@ describe('elements/content-uploader/ContentUploader', () => {
expect(wrapper.find(UploadsManagerBP)).toHaveLength(0);
});

test('should render modernized UploadsManager when enableModernizedUploads is true', () => {
test('should render modernized UploadsManagerBP when enableModernizedUploads is true', () => {
const wrapper = getWrapper({ enableModernizedUploads: true });
expect(wrapper.find(UploadsManagerBP)).toHaveLength(1);
expect(wrapper.find(UploadsManager)).toHaveLength(0);
expect(wrapper.find(DroppableContent)).toHaveLength(0);
});

test('should render modernized UploadsManager when enableModernizedUploads is true and useUploadsManager is true', () => {
test('should render modernized UploadsManagerBP even when useUploadsManager is true', () => {
const wrapper = getWrapper({ enableModernizedUploads: true, useUploadsManager: true });
expect(wrapper.find(UploadsManagerBP)).toHaveLength(1);
expect(wrapper.find(UploadsManager)).toHaveLength(0);
expect(wrapper.find(DroppableContent)).toHaveLength(0);
});

test('should map state.items to modernized item shape', () => {
const wrapper = getWrapper({ enableModernizedUploads: true });
wrapper.setState({
items: [
{
name: 'foo.pdf',
extension: 'pdf',
progress: 42,
status: STATUS_IN_PROGRESS,
file: { name: 'foo.pdf' },
},
],
});
const items = wrapper.find(UploadsManagerBP).prop('items');
expect(items).toHaveLength(1);
expect(items[0]).toMatchObject({
name: 'foo.pdf',
extension: 'pdf',
progress: 42,
status: 'uploading',
});
});

test('should pass isExpanded from state', () => {
const wrapper = getWrapper({ enableModernizedUploads: true });
wrapper.setState({ isUploadsManagerExpanded: true });
expect(wrapper.find(UploadsManagerBP).prop('isExpanded')).toBe(true);
});

test('should call onClick when onItemCancel is invoked', () => {
const wrapper = getWrapper({ enableModernizedUploads: true });
const item = {
name: 'foo.pdf',
extension: 'pdf',
progress: 0,
status: STATUS_PENDING,
file: { name: 'foo.pdf' },
};
wrapper.setState({ items: [item] });
const instance = wrapper.instance();
const onClickSpy = jest.spyOn(instance, 'onClick').mockImplementation(() => {});

wrapper.find(UploadsManagerBP).prop('onItemCancel')('foo.pdf');

expect(onClickSpy).toHaveBeenCalledWith(item);
});

test('should call removeFileFromUploadQueue when onItemRemove is invoked', () => {
const wrapper = getWrapper({ enableModernizedUploads: true });
const item = {
name: 'foo.pdf',
extension: 'pdf',
progress: 0,
status: STATUS_COMPLETE,
file: { name: 'foo.pdf' },
};
wrapper.setState({ items: [item] });
const instance = wrapper.instance();
const removeSpy = jest.spyOn(instance, 'removeFileFromUploadQueue').mockImplementation(() => {});

wrapper.find(UploadsManagerBP).prop('onItemRemove')('foo.pdf');

expect(removeSpy).toHaveBeenCalledWith(item);
});

test('should no-op when modernized id does not match any item', () => {
const wrapper = getWrapper({ enableModernizedUploads: true });
wrapper.setState({ items: [] });
const instance = wrapper.instance();
const onClickSpy = jest.spyOn(instance, 'onClick').mockImplementation(() => {});

wrapper.find(UploadsManagerBP).prop('onItemCancel')('missing-id');

expect(onClickSpy).not.toHaveBeenCalled();
});

test('should not crash when state contains a folder item without a file', () => {
const wrapper = getWrapper({ enableModernizedUploads: true, rootFolderId: '0' });
const folderItem = {
name: 'my-folder',
extension: '',
progress: 0,
status: STATUS_PENDING,
isFolder: true,
api: {},
};
wrapper.setState({ items: [folderItem] });

expect(() => wrapper.find(UploadsManagerBP).prop('items')).not.toThrow();
const items = wrapper.find(UploadsManagerBP).prop('items');
expect(items).toHaveLength(1);
expect(items[0]).toMatchObject({ name: 'my-folder', isFolder: true });
});

test('should resolve folder item handler via modernized id', () => {
const wrapper = getWrapper({ enableModernizedUploads: true, rootFolderId: '0' });
const folderItem = {
name: 'my-folder',
extension: '',
progress: 0,
status: STATUS_PENDING,
isFolder: true,
api: {},
};
wrapper.setState({ items: [folderItem] });
const instance = wrapper.instance();
const onClickSpy = jest.spyOn(instance, 'onClick').mockImplementation(() => {});

const folderId = wrapper.find(UploadsManagerBP).prop('items')[0].id;
wrapper.find(UploadsManagerBP).prop('onItemCancel')(folderId);

expect(onClickSpy).toHaveBeenCalledWith(folderItem);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {
STATUS_PENDING,
STATUS_IN_PROGRESS,
STATUS_STAGED,
STATUS_COMPLETE,
STATUS_ERROR,
} from '../../../../constants';
import { mapToModernizedUploadItem, mapToModernizedUploadItems } from '../mapToModernizedUploadItem';

const buildLegacyItem = (overrides = {}) => ({
name: 'foo.pdf',
extension: 'pdf',
progress: 50,
status: STATUS_IN_PROGRESS,
size: 100,
file: { name: 'foo.pdf' } as File,
api: {} as never,
Comment thread
jpan-box marked this conversation as resolved.
...overrides,
});

describe('mapToModernizedUploadItem()', () => {
test('maps core fields', () => {
const result = mapToModernizedUploadItem(buildLegacyItem(), '0');
expect(result).toEqual({
id: 'foo.pdf',
name: 'foo.pdf',
extension: 'pdf',
progress: 50,
status: 'uploading',
isFolder: undefined,
errorMessage: undefined,
});
});

test.each([
[STATUS_PENDING, 'pending'],
[STATUS_IN_PROGRESS, 'uploading'],
[STATUS_STAGED, 'staged'],
[STATUS_COMPLETE, 'complete'],
[STATUS_ERROR, 'error'],
])('maps legacy status %s to modernized %s', (legacy, modernized) => {
const result = mapToModernizedUploadItem(buildLegacyItem({ status: legacy }), '0');
expect(result.status).toBe(modernized);
});

test('extracts errorMessage from item.error', () => {
const result = mapToModernizedUploadItem(
buildLegacyItem({ status: STATUS_ERROR, error: { message: 'Boom' } }),
'0',
);
expect(result.errorMessage).toBe('Boom');
});

test('forwards isFolder', () => {
const result = mapToModernizedUploadItem(buildLegacyItem({ isFolder: true }), '0');
expect(result.isFolder).toBe(true);
});

test('defaults missing extension and progress', () => {
const result = mapToModernizedUploadItem(buildLegacyItem({ extension: undefined, progress: undefined }), '0');
expect(result.extension).toBe('');
expect(result.progress).toBe(0);
});
});

describe('mapToModernizedUploadItems()', () => {
test('maps a list', () => {
const result = mapToModernizedUploadItems(
[
buildLegacyItem({ name: 'a.pdf', file: { name: 'a.pdf' } as File }),
buildLegacyItem({ name: 'b.pdf', file: { name: 'b.pdf' } as File }),
],
'0',
);
expect(result).toHaveLength(2);
expect(result[0].id).toBe('a.pdf');
expect(result[1].id).toBe('b.pdf');
});

test('does not crash when item has no file (folder item)', () => {
const folderItem = {
name: 'my-folder',
extension: '',
progress: 0,
status: STATUS_PENDING,
size: 1,
isFolder: true,
api: {} as never,
} as never;

expect(() => mapToModernizedUploadItems([folderItem], '0')).not.toThrow();
});

test('produces stable id for folder item without options', () => {
const folderItem = {
name: 'my-folder',
extension: '',
progress: 0,
status: STATUS_PENDING,
size: 1,
isFolder: true,
api: {} as never,
} as never;

const result = mapToModernizedUploadItems([folderItem], '0');
expect(result[0].id).toBe('my-folder_0');
});

test('produces distinct ids for folder items with different folderId options', () => {
const folderA = {
name: 'shared',
extension: '',
progress: 0,
status: STATUS_PENDING,
size: 1,
isFolder: true,
options: { folderId: '111' },
api: {} as never,
} as never;
const folderB = {
name: 'shared',
extension: '',
progress: 0,
status: STATUS_PENDING,
size: 1,
isFolder: true,
options: { folderId: '222' },
api: {} as never,
} as never;

const result = mapToModernizedUploadItems([folderA, folderB], '0');
expect(result[0].id).not.toBe(result[1].id);
});
});
Loading
Loading