diff --git a/src/elements/content-sidebar/versions/VersionsItem.js b/src/elements/content-sidebar/versions/VersionsItem.js index 7c4f1d5e05..4569a0e133 100644 --- a/src/elements/content-sidebar/versions/VersionsItem.js +++ b/src/elements/content-sidebar/versions/VersionsItem.js @@ -130,7 +130,7 @@ const VersionsItem = ({ onClick={handleAction(onPreview)} >
- +
diff --git a/src/elements/content-sidebar/versions/VersionsItem.scss b/src/elements/content-sidebar/versions/VersionsItem.scss index 01d8e78741..236b698902 100644 --- a/src/elements/content-sidebar/versions/VersionsItem.scss +++ b/src/elements/content-sidebar/versions/VersionsItem.scss @@ -28,6 +28,12 @@ box-shadow: $bdl-btn-primary-box-shadow; } } + + .bcs-VersionsItemActions-toggle--modernized { + position: absolute; + top: var(--space-2); + right: var(--space-2); + } } .bcs-VersionsItem-badge { diff --git a/src/elements/content-sidebar/versions/VersionsItemActions.js b/src/elements/content-sidebar/versions/VersionsItemActions.js index 15b8e915bb..0e2c726f6c 100644 --- a/src/elements/content-sidebar/versions/VersionsItemActions.js +++ b/src/elements/content-sidebar/versions/VersionsItemActions.js @@ -5,7 +5,10 @@ */ import * as React from 'react'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { IconButton } from '@box/blueprint-web'; +import { Ellipsis } from '@box/blueprint-web-assets/icons/Fill'; +import { useFeatureConfig } from '../../common/feature-checking/hooks'; import DropdownMenu from '../../../components/dropdown-menu'; import IconClockPast from '../../../icons/general/IconClockPast'; import IconDownload from '../../../icons/general/IconDownload'; @@ -59,6 +62,9 @@ const VersionsItemActions = ({ showPromote = false, showRestore = false, }: Props) => { + const { enabled: isPreviewModernizationEnabled } = useFeatureConfig('previewModernization'); + const { formatMessage } = useIntl(); + if (!showDelete && !showDownload && !showPreview && !showPromote && !showRestore) { return null; } @@ -71,19 +77,32 @@ const VersionsItemActions = ({ isRightAligned onMenuClose={handleMenuClose} > - - - - {(text: string) => {text}} - - + {isPreviewModernizationEnabled ? ( + + ) : ( + + + + {(text: string) => {text}} + + + )} { +const VersionsItemBadge = ({ intl, isCurrent, versionNumber }: Props) => { const intlValues = { versionNumber }; return ( -
+
); diff --git a/src/elements/content-sidebar/versions/VersionsList.js b/src/elements/content-sidebar/versions/VersionsList.js index 23ef1a14d9..e2e5a20384 100644 --- a/src/elements/content-sidebar/versions/VersionsList.js +++ b/src/elements/content-sidebar/versions/VersionsList.js @@ -6,7 +6,9 @@ import * as React from 'react'; import { Route } from 'react-router-dom'; +import classNames from 'classnames'; import VersionsItem from './VersionsItem'; +import { useFeatureConfig } from '../../common/feature-checking/hooks'; import type { BoxItemVersion } from '../../../common/types/core'; import type { InternalSidebarNavigation } from '../../common/types/SidebarNavigation'; import './VersionsList.scss'; @@ -22,6 +24,8 @@ type Props = { }; const VersionsList = ({ currentId, internalSidebarNavigation, routerDisabled = false, versions, ...rest }: Props) => { + const { enabled: isPreviewModernizationEnabled } = useFeatureConfig('previewModernization'); + const renderVersionItemWithoutRouter = (version: BoxItemVersion) => ( +
    {versions.map(version => (
  • {routerDisabled ? renderVersionItemWithoutRouter(version) : renderVersionItemWithRouter(version)} diff --git a/src/elements/content-sidebar/versions/__tests__/VersionsItemActions.test.js b/src/elements/content-sidebar/versions/__tests__/VersionsItemActions.test.js index 7c2b0849ba..4d49e267ca 100644 --- a/src/elements/content-sidebar/versions/__tests__/VersionsItemActions.test.js +++ b/src/elements/content-sidebar/versions/__tests__/VersionsItemActions.test.js @@ -1,44 +1,134 @@ import * as React from 'react'; -import { shallow } from 'enzyme/build'; -import IconClockPast from '../../../../icons/general/IconClockPast'; -import IconDownload from '../../../../icons/general/IconDownload'; -import IconEllipsis from '../../../../icons/general/IconEllipsis'; -import IconOpenWith from '../../../../icons/general/IconOpenWith'; -import IconTrash from '../../../../icons/general/IconTrash'; -import IconUpload from '../../../../icons/general/IconUpload'; -import Tooltip from '../../../../components/tooltip/Tooltip'; +import { userEvent } from '@testing-library/user-event'; +import { screen, render } from '../../../../test-utils/testing-library'; import VersionsItemActions from '../VersionsItemActions'; describe('elements/content-sidebar/versions/VersionsItemActions', () => { - const getWrapper = (props = {}) => shallow(); + const defaultProps = { + fileId: '12345', + }; + + const renderComponent = (props = {}, features = {}) => + render(, { + wrapperProps: { features }, + }); describe('render', () => { - test.each([true, false])('should return the correct menu items based on options', option => { - const wrapper = getWrapper({ - showDelete: option, - showDownload: option, - showPreview: option, - showPromote: option, - showRestore: option, + test('should return null when no actions are shown', () => { + const { container } = renderComponent({ + showDelete: false, + showDownload: false, + showPreview: false, + showPromote: false, + showRestore: false, + }); + + expect(container).toBeEmptyDOMElement(); + }); + + test('should render actions toggle button when at least one action is shown', () => { + renderComponent({ showDownload: true }); + + expect(screen.getByRole('button', { name: 'Toggle Actions Menu' })).toBeInTheDocument(); + }); + + test.each([ + { actionProp: 'showPreview', label: 'Preview' }, + { actionProp: 'showDownload', label: 'Download' }, + { actionProp: 'showPromote', label: 'Make Current' }, + { actionProp: 'showRestore', label: 'Restore' }, + { actionProp: 'showDelete', label: 'Delete' }, + ])('should render $label action when $actionProp is true', async ({ actionProp, label }) => { + renderComponent({ [actionProp]: true }); + + const toggleButton = screen.getByRole('button', { name: 'Toggle Actions Menu' }); + await userEvent.click(toggleButton); + + expect(screen.getByRole('menuitem', { name: new RegExp(label) })).toBeInTheDocument(); + }); + + test('should render all actions when all show props are true', async () => { + renderComponent({ + showDelete: true, + showDownload: true, + showPreview: true, + showPromote: true, + showRestore: true, }); - expect(wrapper.find(IconEllipsis).exists()).toBe(option); // Versions show actions if any permission is true - expect(wrapper.find(IconClockPast).exists()).toBe(option); - expect(wrapper.find(IconDownload).exists()).toBe(option); - expect(wrapper.find(IconOpenWith).exists()).toBe(option); - expect(wrapper.find(IconTrash).exists()).toBe(option); - expect(wrapper.find(IconUpload).exists()).toBe(option); - expect(wrapper).toMatchSnapshot(); + const toggleButton = screen.getByRole('button', { name: 'Toggle Actions Menu' }); + await userEvent.click(toggleButton); + + expect(screen.getByRole('menuitem', { name: /Preview/ })).toBeInTheDocument(); + expect(screen.getByRole('menuitem', { name: /Download/ })).toBeInTheDocument(); + expect(screen.getByRole('menuitem', { name: /Make Current/ })).toBeInTheDocument(); + expect(screen.getByRole('menuitem', { name: /Restore/ })).toBeInTheDocument(); + expect(screen.getByRole('menuitem', { name: /Delete/ })).toBeInTheDocument(); }); - test.each([true, false])('should enable/disable actions and tooltips if isRetained is %s', option => { - const wrapper = getWrapper({ - isRetained: option, + test('should disable delete action when isRetained is true', async () => { + renderComponent({ + isRetained: true, showDelete: true, }); - expect(wrapper.find(Tooltip).prop('isDisabled')).toBe(!option); - expect(wrapper).toMatchSnapshot(); + const toggleButton = screen.getByRole('button', { name: 'Toggle Actions Menu' }); + await userEvent.click(toggleButton); + + const deleteButton = screen.getByRole('menuitem', { name: /Delete/ }); + expect(deleteButton).toHaveAttribute('aria-disabled', 'true'); + }); + + test('should not disable delete action when isRetained is false', async () => { + renderComponent({ + isRetained: false, + showDelete: true, + }); + + const toggleButton = screen.getByRole('button', { name: 'Toggle Actions Menu' }); + await userEvent.click(toggleButton); + + const deleteButton = screen.getByRole('menuitem', { name: /Delete/ }); + expect(deleteButton).not.toHaveAttribute('aria-disabled', 'true'); + }); + + describe('with previewModernization feature enabled', () => { + const previewModernizationFeatures = { + previewModernization: { enabled: true }, + }; + + test('should render modernized toggle button', () => { + renderComponent({ showDownload: true }, previewModernizationFeatures); + + const toggleButton = screen.getByRole('button', { name: 'Toggle Actions Menu' }); + expect(toggleButton).toHaveClass('bcs-VersionsItemActions-toggle--modernized'); + }); + + test('should render actions in dropdown menu', async () => { + renderComponent( + { + showDownload: true, + showPreview: true, + }, + previewModernizationFeatures, + ); + + const toggleButton = screen.getByRole('button', { name: 'Toggle Actions Menu' }); + await userEvent.click(toggleButton); + + expect(screen.getByRole('menuitem', { name: /Download/ })).toBeInTheDocument(); + expect(screen.getByRole('menuitem', { name: /Preview/ })).toBeInTheDocument(); + }); + }); + + describe('with previewModernization feature disabled', () => { + test('should render legacy toggle button', () => { + renderComponent({ showDownload: true }); + + const toggleButton = screen.getByRole('button', { name: 'Toggle Actions Menu' }); + expect(toggleButton).toHaveClass('bcs-VersionsItemActions-toggle'); + expect(toggleButton).not.toHaveClass('bcs-VersionsItemActions-toggle--modernized'); + }); }); }); }); diff --git a/src/elements/content-sidebar/versions/__tests__/VersionsItemBadge.test.js b/src/elements/content-sidebar/versions/__tests__/VersionsItemBadge.test.js index a3e1068fe8..20644c52ed 100644 --- a/src/elements/content-sidebar/versions/__tests__/VersionsItemBadge.test.js +++ b/src/elements/content-sidebar/versions/__tests__/VersionsItemBadge.test.js @@ -1,14 +1,41 @@ import * as React from 'react'; -import { render } from 'enzyme/build'; +import { screen, render } from '../../../../test-utils/testing-library'; import VersionsItemBadge from '../VersionsItemBadge'; describe('elements/content-sidebar/versions/VersionsItemBadge', () => { - const getWrapper = (props = {}) => render(); + const renderComponent = (props = {}) => render(); describe('render', () => { - test('should match its snapshot', () => { - const wrapper = getWrapper({ versionNumber: '1' }); - expect(wrapper).toMatchSnapshot(); + test('should render version number badge', () => { + renderComponent({ versionNumber: '1' }); + + expect(screen.getByText('V1')).toBeInTheDocument(); + }); + + test('should have correct aria-label', () => { + renderComponent({ versionNumber: '5' }); + + expect(screen.getByLabelText('Version number 5')).toBeInTheDocument(); }); + + test.each` + isCurrent | shouldHaveCurrentClass + ${true} | ${true} + ${false} | ${false} + ${undefined} | ${false} + `( + 'should apply current class correctly when isCurrent is $isCurrent', + ({ isCurrent, shouldHaveCurrentClass }) => { + renderComponent({ versionNumber: '1', isCurrent }); + + const badge = screen.getByText('V1'); + expect(badge).toHaveClass('bcs-VersionsItemBadge'); + if (shouldHaveCurrentClass) { + expect(badge).toHaveClass('bcs-VersionsItemBadge--current'); + } else { + expect(badge).not.toHaveClass('bcs-VersionsItemBadge--current'); + } + }, + ); }); }); diff --git a/src/elements/content-sidebar/versions/__tests__/__snapshots__/VersionsItemActions.test.js.snap b/src/elements/content-sidebar/versions/__tests__/__snapshots__/VersionsItemActions.test.js.snap deleted file mode 100644 index 2ab763ccad..0000000000 --- a/src/elements/content-sidebar/versions/__tests__/__snapshots__/VersionsItemActions.test.js.snap +++ /dev/null @@ -1,238 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`elements/content-sidebar/versions/VersionsItemActions render should enable/disable actions and tooltips if isRetained is false 1`] = ` - - - - - - - - - - } - theme="default" - > - - - - - - - -`; - -exports[`elements/content-sidebar/versions/VersionsItemActions render should enable/disable actions and tooltips if isRetained is true 1`] = ` - - - - - - - - - - } - theme="default" - > - - - - - - - -`; - -exports[`elements/content-sidebar/versions/VersionsItemActions render should return the correct menu items based on options 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - } - theme="default" - > - - - - - - - -`; - -exports[`elements/content-sidebar/versions/VersionsItemActions render should return the correct menu items based on options 2`] = `""`; diff --git a/src/elements/content-sidebar/versions/__tests__/__snapshots__/VersionsItemBadge.test.js.snap b/src/elements/content-sidebar/versions/__tests__/__snapshots__/VersionsItemBadge.test.js.snap deleted file mode 100644 index 6754fa4ed7..0000000000 --- a/src/elements/content-sidebar/versions/__tests__/__snapshots__/VersionsItemBadge.test.js.snap +++ /dev/null @@ -1,10 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`elements/content-sidebar/versions/VersionsItemBadge render should match its snapshot 1`] = ` -
    -
    -
    -`;