box-ui-elements
Version:
Box UI Elements
540 lines (446 loc) • 22.2 kB
JavaScript
import * as React from 'react';
import { shallow } from 'enzyme/build';
import { createBrowserHistory } from 'history';
import { Router } from 'react-router-dom';
import { render, waitFor, act, screen } from '../../../../test-utils/testing-library';
import messages from '../messages';
import openUrlInsideIframe from '../../../../utils/iframe';
import VersionsSidebarAPI from '../VersionsSidebarAPI';
import VersionsSidebar from '../VersionsSidebar';
import { VersionsSidebarContainerComponent } from '../VersionsSidebarContainer';
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
withRouter: Component => Component,
}));
jest.mock('../../../common/api-context', () => ({
withAPIContext: Component => Component,
}));
jest.mock('../../../../utils/iframe', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('../VersionsSidebarAPI');
jest.mock('../VersionsSidebar', () => jest.fn(() => <div data-testid="versions-sidebar" />));
const versions = [
{ id: '123', name: 'Version 1' },
{ id: '456', name: 'Version 2' },
{ id: '789', name: 'Version 3' },
];
describe('elements/content-sidebar/versions/VersionsSidebarContainer', () => {
const mockedAPIMethods = {
fetchData: jest.fn(),
deleteVersion: jest.fn(),
fetchVersion: jest.fn(),
fetchDownloadUrl: jest.fn(),
promoteVersion: jest.fn(),
restoreVersion: jest.fn(),
addPermissions: jest.fn(),
sortVersions: jest.fn(),
};
const versionsAPI = {
...mockedAPIMethods,
};
const api = {
getVersionsAPI: () => versionsAPI,
};
VersionsSidebarAPI.mockImplementation(() => mockedAPIMethods);
const getWrapper = (props = {}) =>
shallow(<VersionsSidebarContainerComponent api={api} fileId="12345" {...props} />);
const history = createBrowserHistory();
const renderComponent = (props = {}) => {
return render(
<Router history={history}>
<VersionsSidebarContainerComponent api={api} fileId="12345" match={{}} history={history} {...props} />
</Router>,
);
};
const renderComponentWithoutRouter = (props = {}) => {
return render(<VersionsSidebarContainerComponent api={api} fileId="12345" routerDisabled={true} {...props} />);
};
beforeEach(() => {
jest.clearAllMocks();
mockedAPIMethods.fetchData.mockResolvedValue([{}, {}]);
mockedAPIMethods.deleteVersion.mockResolvedValue();
mockedAPIMethods.fetchVersion.mockResolvedValue({});
mockedAPIMethods.fetchDownloadUrl.mockResolvedValue();
mockedAPIMethods.promoteVersion.mockResolvedValue();
mockedAPIMethods.restoreVersion.mockResolvedValue();
});
describe('componentDidMount', () => {
beforeEach(() => {
history.push = jest.fn();
});
test('should call onLoad after a successful fetchData() call', async () => {
const onLoad = jest.fn();
await act(async () => {
renderComponent({ onLoad });
});
await waitFor(() => {
expect(onLoad).toHaveBeenCalledTimes(1);
});
});
});
describe('handleActionDelete', () => {
test.each`
scenario | versionIdToDelete | selectedVersionId | shouldCallHistoryPush | expectedHistoryPath
${'non-selected version'} | ${'456'} | ${'123'} | ${false} | ${null}
${'currently selected version'} | ${'456'} | ${'456'} | ${true} | ${'/versions/123'}
`(
'should handle delete operation correctly when deleting $scenario',
async ({ versionIdToDelete, selectedVersionId, shouldCallHistoryPush, expectedHistoryPath }) => {
const handleDelete = jest.fn();
const newVersion = { id: versionIdToDelete, trash_at: '' };
const versionToDelete = {
id: versionIdToDelete,
name: 'Version 1',
created_at: '2023-01-01T00:00:00Z',
};
const mostRecentVersion = { id: '123', name: 'Current Version', created_at: '2023-01-02T00:00:00Z' };
let triggerDelete;
VersionsSidebar.mockImplementation(({ onDelete, versions: versionsList }) => {
triggerDelete = () => onDelete(versionIdToDelete);
return (
<div data-testid="versions-sidebar">
{versionsList && versionsList.length > 0 && (
<div data-testid="versions-loaded">Versions loaded</div>
)}
</div>
);
});
// Include both versions so verifyVersion can find the current version
const mockVersionsResponse = { entries: [mostRecentVersion, versionToDelete], total_count: 2 };
const mockFileResponse = { version_limit: null };
mockedAPIMethods.addPermissions.mockReturnValue(mockVersionsResponse);
mockedAPIMethods.fetchData.mockResolvedValue([mockFileResponse, mockVersionsResponse]);
mockedAPIMethods.fetchVersion.mockResolvedValue(newVersion);
const historyPushSpy = jest.fn();
renderComponent({
onVersionDelete: handleDelete,
versionId: selectedVersionId,
history: { ...history, push: historyPushSpy },
match: { path: '/versions/:versionId', params: { versionId: selectedVersionId } },
});
await waitFor(() => {
expect(screen.getByTestId('versions-sidebar')).toBeInTheDocument();
});
await waitFor(() => {
expect(screen.queryByTestId('versions-loaded')).toBeInTheDocument();
});
await act(async () => {
triggerDelete();
});
await waitFor(() => {
expect(mockedAPIMethods.deleteVersion).toHaveBeenCalledWith(versionToDelete);
expect(mockedAPIMethods.fetchVersion).toHaveBeenCalledWith(versionToDelete.id);
expect(handleDelete).toHaveBeenCalledWith(versionToDelete.id);
if (shouldCallHistoryPush) {
expect(historyPushSpy).toHaveBeenCalledWith(expect.stringContaining(expectedHistoryPath));
} else {
expect(historyPushSpy).not.toHaveBeenCalled();
}
});
},
);
});
describe('handleActionDelete - Router Disabled', () => {
test.each`
scenario | versionIdToDelete | selectedVersionId | shouldCallNavigationHandler | expectedNavigationCall
${'non-selected version'} | ${'456'} | ${'123'} | ${false} | ${null}
${'currently selected version'} | ${'456'} | ${'456'} | ${true} | ${{ versionId: '123' }}
`(
'should handle delete operation correctly when deleting $scenario',
async ({ versionIdToDelete, selectedVersionId, shouldCallNavigationHandler, expectedNavigationCall }) => {
const handleDelete = jest.fn();
const mockInternalSidebarNavigationHandler = jest.fn();
const newVersion = { id: versionIdToDelete, trash_at: '' };
const versionToDelete = {
id: versionIdToDelete,
name: 'Version 1',
created_at: '2023-01-01T00:00:00Z',
};
const mostRecentVersion = { id: '123', name: 'Current Version', created_at: '2023-01-02T00:00:00Z' };
const mockInternalSidebarNavigation = {
sidebar: 'activity',
activeFeedEntryType: 'comments',
activeFeedEntryId: '789',
};
let triggerDelete;
VersionsSidebar.mockImplementation(({ onDelete, versions: versionsList }) => {
triggerDelete = () => onDelete(versionIdToDelete);
return (
<div data-testid="versions-sidebar">
{versionsList && versionsList.length > 0 && (
<div data-testid="versions-loaded">Versions loaded</div>
)}
</div>
);
});
// Include both versions so verifyVersion can find the current version
const mockVersionsResponse = { entries: [mostRecentVersion, versionToDelete], total_count: 2 };
const mockFileResponse = { version_limit: null };
mockedAPIMethods.addPermissions.mockReturnValue(mockVersionsResponse);
mockedAPIMethods.fetchData.mockResolvedValue([mockFileResponse, mockVersionsResponse]);
mockedAPIMethods.fetchVersion.mockResolvedValue(newVersion);
renderComponentWithoutRouter({
onVersionDelete: handleDelete,
versionId: selectedVersionId,
internalSidebarNavigation: mockInternalSidebarNavigation,
internalSidebarNavigationHandler: mockInternalSidebarNavigationHandler,
});
await waitFor(() => {
expect(screen.getByTestId('versions-sidebar')).toBeInTheDocument();
});
await waitFor(() => {
expect(screen.queryByTestId('versions-loaded')).toBeInTheDocument();
});
await act(async () => {
triggerDelete();
});
await waitFor(() => {
expect(mockedAPIMethods.deleteVersion).toHaveBeenCalledWith(versionToDelete);
expect(mockedAPIMethods.fetchVersion).toHaveBeenCalledWith(versionToDelete.id);
expect(handleDelete).toHaveBeenCalledWith(versionToDelete.id);
if (shouldCallNavigationHandler) {
expect(mockInternalSidebarNavigationHandler).toHaveBeenCalledWith({
...mockInternalSidebarNavigation,
...expectedNavigationCall,
});
} else {
expect(mockInternalSidebarNavigationHandler).not.toHaveBeenCalled();
}
});
},
);
});
describe('handleActionDownload', () => {
test('should call api endpoint helpers', () => {
const downloadUrl = 'https://box.com/url';
const handleDownload = jest.fn();
const wrapper = getWrapper({ onVersionDownload: handleDownload, versionId: '123' });
const instance = wrapper.instance();
const version = { id: '456' };
instance.api.fetchDownloadUrl = jest.fn().mockResolvedValueOnce(downloadUrl);
instance.findVersion = jest.fn(() => version);
instance.handleActionDownload(version.id).then(() => {
expect(instance.api.fetchDownloadUrl).toHaveBeenCalledWith(version);
expect(openUrlInsideIframe).toHaveBeenCalledWith(downloadUrl);
expect(handleDownload).toHaveBeenCalledWith(version.id);
});
});
});
describe('handleActionPromote', () => {
test('should call api endpoint helpers', () => {
const handlePromote = jest.fn();
const wrapper = getWrapper({ onVersionPromote: handlePromote, versionId: '123' });
const instance = wrapper.instance();
const version = { id: '456' };
instance.api.fetchData = jest.fn().mockResolvedValueOnce();
instance.api.promoteVersion = jest.fn().mockResolvedValueOnce();
instance.findVersion = jest.fn(() => version);
instance.handleFetchSuccess = jest.fn();
instance.handlePromoteSuccess = jest.fn();
instance.handleActionPromote(version.id).then(() => {
expect(instance.api.promoteVersion).toHaveBeenCalledWith(version);
expect(instance.api.fetchData).toHaveBeenCalled();
expect(instance.handleFetchSuccess).toHaveBeenCalled();
expect(instance.handlePromoteSuccess).toHaveBeenCalled();
expect(handlePromote).toHaveBeenCalledWith(version.id);
});
});
});
describe('handleActionRestore', () => {
test('should call api endpoint helpers', () => {
const handleRestore = jest.fn();
const wrapper = getWrapper({ onVersionRestore: handleRestore, versionId: '123' });
const instance = wrapper.instance();
const version = { id: '456' };
const newVersion = { id: '456', restored_by: '' };
instance.api.restoreVersion = jest.fn().mockResolvedValueOnce();
instance.api.fetchVersion = jest.fn().mockResolvedValueOnce(newVersion);
instance.findVersion = jest.fn(() => version);
instance.handleRestoreSuccess = jest.fn();
instance.handleActionRestore(version.id).then(() => {
expect(instance.api.restoreVersion).toHaveBeenCalledWith(version);
expect(instance.api.fetchVersion).toHaveBeenCalled();
expect(instance.handleRestoreSuccess).toHaveBeenCalledWith(newVersion);
expect(handleRestore).toHaveBeenCalledWith(version.id);
});
});
});
describe('handleDeleteSuccess', () => {
test('should update version if the same id', () => {
const wrapper = getWrapper({
versionId: '123',
});
const instance = wrapper.instance();
const version = { id: '123' };
instance.updateVersionToCurrent = jest.fn();
instance.mergeResponse = jest.fn();
instance.handleDeleteSuccess(version);
expect(instance.updateVersionToCurrent).toHaveBeenCalled();
expect(instance.mergeResponse).toHaveBeenCalledWith(version);
});
});
describe('handleRestoreSuccess', () => {
test('should call mergeResponse', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const version = { id: '123' };
instance.mergeResponse = jest.fn();
instance.handleRestoreSuccess(version);
expect(instance.mergeResponse).toHaveBeenCalledWith(version);
});
});
describe('handleFetchError', () => {
test('should set state to default values with error message', () => {
const wrapper = getWrapper();
wrapper.instance().handleFetchError({ status: 500 });
expect(wrapper.state()).toMatchObject({
error: messages.versionFetchError,
isLoading: false,
isWatermarked: false,
versionCount: 0,
versionLimit: Infinity,
versions: [],
});
expect(wrapper).toMatchSnapshot();
});
test('should create StaticVersionSidebar if onUpgradeClick is present', () => {
const wrapper = getWrapper({
onUpgradeClick: () => {},
});
wrapper.instance().handleFetchError({ status: 403 });
expect(wrapper).toMatchSnapshot();
});
});
describe('handleFetchSuccess', () => {
let file;
let version;
let currentVersion;
let versionsWithCurrent;
beforeEach(() => {
file = { id: '12345', permissions: {}, version_limit: 10 };
version = { id: '123', permissions: {} };
currentVersion = { entries: [{ id: '321', permissions: {} }], total_count: 1 };
versionsWithCurrent = { entries: [version, ...currentVersion.entries], total_count: 2 };
versionsAPI.addPermissions.mockReturnValueOnce(versionsWithCurrent);
});
test('should set state with the updated versions', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
instance.sortVersions = jest.fn().mockReturnValue(versionsWithCurrent.entries);
instance.verifyVersion = jest.fn();
instance.handleFetchSuccess([file, versionsWithCurrent]);
expect(versionsAPI.addPermissions).toBeCalledWith(versionsWithCurrent, file);
expect(instance.verifyVersion).toBeCalled();
expect(instance.sortVersions).toBeCalledWith(versionsWithCurrent.entries);
expect(wrapper.state()).toMatchObject({
error: undefined,
isArchived: false,
isLoading: false,
isWatermarked: false,
versionCount: 2,
versionLimit: 10,
});
expect(wrapper.state('versions')).toBe(versionsWithCurrent.entries);
});
test('should set state with isWatermarked if file is watermarked', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const testFile = { ...file, watermark_info: { is_watermarked: true } };
instance.verifyVersion = jest.fn();
instance.handleFetchSuccess([testFile, versionsWithCurrent]);
expect(wrapper.state('isWatermarked')).toBe(true);
});
});
describe('findVersion', () => {
test('should return the version stored in state if available', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
wrapper.setState({ versions });
expect(instance.findVersion('456')).toEqual(versions[1]);
expect(instance.findVersion('abc')).toEqual(undefined);
});
});
describe('mergeVersions', () => {
test('should update state', () => {
const wrapper = getWrapper();
wrapper.setState({
versions,
});
const instance = wrapper.instance();
const newVersion = { id: versions[1].id, trashed_at: null };
const newVersions = instance.mergeVersions(newVersion);
expect(newVersions[0]).toEqual(versions[0]);
expect(newVersions[1]).toEqual(Object.assign(versions[1], newVersion));
});
});
describe('mergeResponse', () => {
test('should update state', () => {
const wrapper = getWrapper();
wrapper.setState({
error: 'error',
isLoading: true,
versions,
});
const instance = wrapper.instance();
const response = { id: '000', name: 'Version 0' };
const newVersions = [response];
instance.mergeVersions = jest.fn().mockReturnValue(newVersions);
instance.mergeResponse(response);
expect(wrapper.state('error')).toBe(undefined);
expect(wrapper.state('isLoading')).toBe(false);
expect(wrapper.state('versions')).toEqual(newVersions);
expect(instance.mergeVersions).toHaveBeenCalledWith(response);
});
});
describe('refresh', () => {
test('should refetch data when refresh is called', () => {
const instance = getWrapper().instance();
const fetchData = jest.fn();
instance.fetchData = fetchData;
instance.refresh();
expect(fetchData).toHaveBeenCalled();
});
});
describe('sortVersions', () => {
test('should sort versions by their created date', () => {
const instance = getWrapper().instance();
const unsortedVersions = [
{ id: '123', created_at: '2018-10-02T13:01:24-07:00' },
{ id: '456', created_at: '2018-10-02T13:01:41-07:00' },
{ id: '789', created_at: '2018-11-29T17:47:57-08:00' },
];
const sortedVersions = instance.sortVersions(unsortedVersions);
expect(unsortedVersions).not.toBe(sortedVersions); // Sort call should create a new array
expect(sortedVersions).toEqual([unsortedVersions[2], unsortedVersions[1], unsortedVersions[0]]);
});
});
describe('verifyVersion', () => {
const onVersionChange = jest.fn();
test('should emit an onVersionChange event if the passed version is available', () => {
const wrapper = getWrapper({ onVersionChange, versionId: '456' });
wrapper.setState({ versions });
wrapper.instance().verifyVersion();
expect(onVersionChange).toHaveBeenCalledWith(versions[1], {
currentVersionId: versions[0].id,
updateVersionToCurrent: wrapper.instance().updateVersionToCurrent,
});
});
test('should reset the selected version if the passed version is not available', () => {
const wrapper = getWrapper({ onVersionChange, versionId: '99999' });
const instance = wrapper.instance();
instance.updateVersionToCurrent = jest.fn();
wrapper.setState({ versions });
instance.verifyVersion();
expect(onVersionChange).not.toHaveBeenCalled();
expect(instance.updateVersionToCurrent).toHaveBeenCalled();
});
});
describe('render', () => {
test('should match its snapshot', () => {
const wrapper = getWrapper({ parentName: 'activity' });
expect(wrapper).toMatchSnapshot();
});
});
});