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
2 changes: 0 additions & 2 deletions i18n/en-US.properties
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,6 @@ be.contentSidebar.boxSignRequestSignature = Request Signature
be.contentSidebar.boxSignSecurityBlockedTooltip = This action is unavailable due to a security policy.
# One of the dropdown options that opens a Box Sign sign myself experience
be.contentSidebar.boxSignSignMyself = Sign Myself
# label for button that opens a Box Sign signature fulfillment experience
be.contentSidebar.boxSignSignature = Sign
# Tooltip text for when Box Sign is blocked due to an item being watermarked
be.contentSidebar.boxSignWatermarkBlockedTooltip = This action is unavailable, because the file is watermarked.
# title for when editing an existing approval task
Expand Down
4 changes: 4 additions & 0 deletions src/elements/content-sidebar/ContentSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type { DetailsSidebarProps } from './DetailsSidebar';
import type { DocGenSidebarProps } from './DocGenSidebar/DocGenSidebar';
import type { MetadataSidebarProps } from './MetadataSidebar';
import type { VersionsSidebarProps } from './versions';
import type { SignSidebarProps } from './SidebarNavSign';
import type { WithLoggerProps } from '../../common/types/logging';
import type { ElementsXhrError, RequestOptions, ErrorContextProps } from '../../common/types/api';
import type { MetadataEditor } from '../../common/types/metadata';
Expand Down Expand Up @@ -87,6 +88,7 @@ type Props = {
sharedLink?: string,
sharedLinkPassword?: string,
theme?: Theme,
signSidebarProps: SignSidebarProps,
token: Token,
versionsSidebarProps: VersionsSidebarProps,
} & ErrorContextProps &
Expand Down Expand Up @@ -366,6 +368,7 @@ class ContentSidebar extends React.Component<Props, State> {
onPanelChange,
onVersionChange,
onVersionHistoryClick,
signSidebarProps,
theme,
versionsSidebarProps,
}: Props = this.props;
Expand Down Expand Up @@ -408,6 +411,7 @@ class ContentSidebar extends React.Component<Props, State> {
onPanelChange={onPanelChange}
onVersionChange={onVersionChange}
onVersionHistoryClick={onVersionHistoryClick}
signSidebarProps={signSidebarProps}
theme={theme}
versionsSidebarProps={versionsSidebarProps}
wrappedComponentRef={ref => {
Expand Down
4 changes: 4 additions & 0 deletions src/elements/content-sidebar/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { VersionsSidebarProps } from './versions';
import type { AdditionalSidebarTab } from './flowTypes';
import type { MetadataEditor } from '../../common/types/metadata';
import type { BoxItem, User } from '../../common/types/core';
import type { SignSidebarProps } from './SidebarNavSign';
import type { Errors } from '../common/flowTypes';
// $FlowFixMe TypeScript file
import type { Theme } from '../common/theming';
Expand Down Expand Up @@ -69,6 +70,7 @@ type Props = {
onPanelChange?: (name: string, isInitialState: boolean) => void,
onVersionChange?: Function,
onVersionHistoryClick?: Function,
signSidebarProps: SignSidebarProps,
theme?: Theme,
versionsSidebarProps: VersionsSidebarProps,
};
Expand Down Expand Up @@ -309,6 +311,7 @@ class Sidebar extends React.Component<Props, State> {
metadataSidebarProps,
onAnnotationSelect,
onVersionChange,
signSidebarProps,
theme,
versionsSidebarProps,
}: Props = this.props;
Expand Down Expand Up @@ -348,6 +351,7 @@ class Sidebar extends React.Component<Props, State> {
hasDocGen={docGenSidebarProps.isDocGenTemplate}
isOpen={isOpen}
onPanelChange={this.handlePanelChange}
signSidebarProps={signSidebarProps}
/>
)}
<SidebarPanels
Expand Down
7 changes: 5 additions & 2 deletions src/elements/content-sidebar/SidebarNav.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
import { useFeatureConfig } from '../common/feature-checking';
import type { NavigateOptions, AdditionalSidebarTab } from './flowTypes';
import './SidebarNav.scss';
import type { SignSidebarProps } from './SidebarNavSign';

type Props = {
additionalTabs?: Array<AdditionalSidebarTab>,
Expand All @@ -49,6 +50,7 @@ type Props = {
isOpen?: boolean,
onNavigate?: (SyntheticEvent<>, NavigateOptions) => void,
onPanelChange?: (name: string, isInitialState: boolean) => void,
signSidebarProps: SignSidebarProps,
};

const SidebarNav = ({
Expand All @@ -66,8 +68,9 @@ const SidebarNav = ({
isOpen,
onNavigate,
onPanelChange = noop,
signSidebarProps,
}: Props) => {
const { enabled: hasBoxSign } = useFeatureConfig('boxSign');
const { enabled: hasBoxSign } = signSidebarProps || {};
const { disabledTooltip: boxAIDisabledTooltip, showOnlyNavButton: showOnlyBoxAINavButton } =
useFeatureConfig('boxai.sidebar');

Expand Down Expand Up @@ -159,7 +162,7 @@ const SidebarNav = ({

{hasBoxSign && (
<div className="bcs-SidebarNav-secondary">
<SidebarNavSign />
<SidebarNavSign {...signSidebarProps} />
</div>
)}

Expand Down
65 changes: 29 additions & 36 deletions src/elements/content-sidebar/SidebarNavSign.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';

// @ts-ignore Module is written in Flow
import { useFeatureConfig } from '../common/feature-checking';
// @ts-ignore Module is written in Flow
import { SIDEBAR_NAV_TARGETS } from '../common/interactionTargets';

Expand All @@ -17,48 +15,43 @@ import { Menu, MenuItem } from '../../components/menu';
import messages from './messages';

import './SidebarNavSign.scss';
// @ts-ignore Module is written in Flow
import type { TargetingApi } from '../../features/targeting/types';

export interface SignSidebarProps {
blockedReason: string;
enabled: boolean;
onClick: () => void;
onClickSignMyself: () => void;
targetingApi?: TargetingApi;
}

export function SidebarNavSign() {
export function SidebarNavSign(signSidebarProps: SignSidebarProps) {
const {
blockedReason: boxSignBlockedReason,
onClick: onBoxClickRequestSignature,
onClickSignMyself: onBoxClickSignMyself,
status: boxSignStatus,
targetingApi: boxSignTargetingApi,
isSignRemoveInterstitialEnabled,
} = useFeatureConfig('boxSign');
} = signSidebarProps;

return (
<>
{isSignRemoveInterstitialEnabled ? (
<DropdownMenu isResponsive constrainToWindow isRightAligned>
<SidebarNavSignButton
blockedReason={boxSignBlockedReason}
status={boxSignStatus}
targetingApi={boxSignTargetingApi}
data-resin-target={SIDEBAR_NAV_TARGETS.SIGN}
/>
<Menu>
<MenuItem data-testid="sign-request-signature-button" onClick={onBoxClickRequestSignature}>
<SignMeOthers32 width={16} height={16} className="bcs-SidebarNavSign-icon" />
<FormattedMessage {...messages.boxSignRequestSignature} />
</MenuItem>
<MenuItem data-testid="sign-sign-myself-button" onClick={onBoxClickSignMyself}>
<SignMe32 width={16} height={16} className="bcs-SidebarNavSign-icon" />
<FormattedMessage {...messages.boxSignSignMyself} />
</MenuItem>
</Menu>
</DropdownMenu>
) : (
<SidebarNavSignButton
blockedReason={boxSignBlockedReason}
data-resin-target={SIDEBAR_NAV_TARGETS.SIGN}
onClick={onBoxClickRequestSignature}
status={boxSignStatus}
targetingApi={boxSignTargetingApi}
/>
)}
</>
<DropdownMenu isResponsive constrainToWindow isRightAligned>
<SidebarNavSignButton
blockedReason={boxSignBlockedReason}
targetingApi={boxSignTargetingApi}
data-resin-target={SIDEBAR_NAV_TARGETS.SIGN}
/>
<Menu>
<MenuItem onClick={onBoxClickRequestSignature}>
<SignMeOthers32 width={16} height={16} className="bcs-SidebarNavSign-icon" />
<FormattedMessage {...messages.boxSignRequestSignature} />
</MenuItem>
<MenuItem onClick={onBoxClickSignMyself}>
<SignMe32 width={16} height={16} className="bcs-SidebarNavSign-icon" />
<FormattedMessage {...messages.boxSignSignMyself} />
</MenuItem>
</Menu>
</DropdownMenu>
);
}

Expand Down
7 changes: 3 additions & 4 deletions src/elements/content-sidebar/SidebarNavSignButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import './SidebarNavSignButton.scss';
export type Props = PlainButtonProps & {
blockedReason?: string;
intl: IntlShape;
status?: string;
targetingApi?: {
canShow: boolean;
onClose: () => void;
Expand All @@ -25,11 +24,11 @@ export type Props = PlainButtonProps & {

export const PlaceholderTooltip = ({ children }: { children: React.ReactNode }) => children;

export function SidebarNavSignButton({ blockedReason, intl, status, targetingApi, ...rest }: Props) {
export function SidebarNavSignButton({ blockedReason, intl, targetingApi, ...rest }: Props) {
const isSignDisabled = !!blockedReason;
const isTargeted = targetingApi && targetingApi.canShow;
const isTargeted = targetingApi?.canShow;
const FtuxTooltip = !isSignDisabled && isTargeted ? TargetedClickThroughGuideTooltip : PlaceholderTooltip;
const label = intl.formatMessage(status === 'active' ? messages.boxSignSignature : messages.boxSignRequest);
const label = intl.formatMessage(messages.boxSignRequest);
const buttonClassName = classnames('bcs-SidebarNavSignButton', { 'bdl-is-disabled': isSignDisabled });

let tooltipMessage = label;
Expand Down
12 changes: 4 additions & 8 deletions src/elements/content-sidebar/__tests__/SidebarNav.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,19 +166,15 @@ describe('elements/content-sidebar/SidebarNav', () => {
});

test('should render the Box Sign entry point if its feature is enabled', () => {
const features = {
boxSign: {
const props = {
signSidebarProps: {
enabled: true,
onClick: () => {},
},
};
const wrapper = getWrapper({}, 'activity', features);
expect(wrapper.exists(SidebarNavSignButton)).toBe(true);
});
const wrapper = getWrapper(props);

test('should not render the Box Sign entry point if its feature is not enabled', () => {
const wrapper = getWrapper();
expect(wrapper.exists(SidebarNavSignButton)).toBe(false);
expect(wrapper.exists(SidebarNavSignButton)).toBe(true);
});
test('should render docgen tab', () => {
const props = {
Expand Down
101 changes: 39 additions & 62 deletions src/elements/content-sidebar/__tests__/SidebarNavSign.test.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,57 @@
import * as React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { userEvent } from '@testing-library/user-event';
import { render, screen } from '../../../test-utils/testing-library';
import SidebarNavSign from '../SidebarNavSign';
// @ts-ignore Module is written in Flow
import FeatureProvider from '../../common/feature-checking/FeatureProvider';

describe('elements/content-sidebar/SidebarNavSign', () => {
const onClickRequestSignature = jest.fn();
const onClickSignMyself = jest.fn();
const renderComponent = (props = {}) => {
const defaultProps = {
blockedReason: '',
enabled: true,
targetingApi: null,
onClick: jest.fn(),
onClickSignMyself: jest.fn(),
};

const renderComponent = (props = {}, features = {}) =>
render(
<FeatureProvider features={features}>
<SidebarNavSign {...props} />
</FeatureProvider>,
);
render(<SidebarNavSign {...defaultProps} {...props} />);
};

test.each([true, false])('should render sign button', isRemoveInterstitialEnabled => {
const features = {
boxSign: {
isSignRemoveInterstitialEnabled: isRemoveInterstitialEnabled,
},
};
test('should render sign button', () => {
renderComponent();

const wrapper = renderComponent({}, features);
expect(wrapper.getByTestId('sign-button')).toBeVisible();
expect(screen.getByRole('button', { name: 'Request Signature' })).toBeInTheDocument();
});

test('should call correct handler when sign button is clicked', () => {
const features = {
boxSign: {
isSignRemoveInterstitialEnabled: false,
onClick: onClickRequestSignature,
},
};
const { getByTestId } = renderComponent({}, features);
test('should open dropdown with 2 menu items when sign button is clicked', async () => {
renderComponent();

fireEvent.click(getByTestId('sign-button'));
await userEvent.click(screen.getByRole('button', { name: 'Request Signature' }));

expect(onClickRequestSignature).toBeCalled();
expect(screen.getByRole('menuitem', { name: 'Request Signature' })).toBeVisible();
expect(screen.getByRole('menuitem', { name: 'Sign Myself' })).toBeVisible();
});

test('should open dropdown with 2 menu items when sign button is clicked', () => {
const features = {
boxSign: {
isSignRemoveInterstitialEnabled: true,
},
};
const { getByTestId } = renderComponent({}, features);
fireEvent.click(getByTestId('sign-button'));
expect(getByTestId('sign-request-signature-button')).toBeVisible();
expect(getByTestId('sign-sign-myself-button')).toBeVisible();
});
test('should call correct handler when request signature option is clicked', async () => {
const onClickRequestSignatureMock = jest.fn();
renderComponent({ onClick: onClickRequestSignatureMock });

test('should call correct handler when request signature option is clicked', () => {
const features = {
boxSign: {
isSignRemoveInterstitialEnabled: true,
onClick: onClickRequestSignature,
},
};
const { getByTestId } = renderComponent({}, features);
fireEvent.click(getByTestId('sign-button'));
fireEvent.click(getByTestId('sign-request-signature-button'));
expect(onClickRequestSignature).toBeCalled();
expect(onClickRequestSignatureMock).not.toHaveBeenCalled();

await userEvent.click(screen.getByRole('button', { name: 'Request Signature' }));
await userEvent.click(screen.getByRole('menuitem', { name: 'Request Signature' }));

expect(onClickRequestSignatureMock).toHaveBeenCalledTimes(1);
});

test('should call correct handler when sign myself option is clicked', () => {
const features = {
boxSign: {
isSignRemoveInterstitialEnabled: true,
onClickSignMyself,
},
};
const { getByTestId } = renderComponent({}, features);
fireEvent.click(getByTestId('sign-button'));
fireEvent.click(getByTestId('sign-sign-myself-button'));
expect(onClickSignMyself).toBeCalled();
test('should call correct handler when sign myself option is clicked', async () => {
const onClickSignMyselfMock = jest.fn();
renderComponent({ onClickSignMyself: onClickSignMyselfMock });

expect(onClickSignMyselfMock).not.toHaveBeenCalled();

await userEvent.click(screen.getByRole('button', { name: 'Request Signature' }));
await userEvent.click(screen.getByRole('menuitem', { name: 'Sign Myself' }));

expect(onClickSignMyselfMock).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@ import Tooltip from '../../../components/tooltip';
describe('elements/content-sidebar/SidebarNavSignButton', () => {
const getWrapper = (props = {}) => shallow(<SidebarNavSignButton {...props} />).dive();

test.each`
status | label
${undefined} | ${'Request Signature'}
${'random'} | ${'Request Signature'}
${'active'} | ${'Sign'}
`('should render the correct label based on the current signature status', ({ label, status }) => {
const wrapper = getWrapper({ status });
test('should render the correct label', () => {
const wrapper = getWrapper();

expect(wrapper.find(Tooltip).prop('text')).toBe(label);
expect(wrapper.find(PlainButton).prop('aria-label')).toBe(label);
expect(wrapper.find(Tooltip).prop('text')).toBe('Request Signature');
expect(wrapper.find(PlainButton).prop('aria-label')).toBe('Request Signature');
expect(wrapper.exists(BoxSign28)).toBe(true);
});

Expand Down
5 changes: 0 additions & 5 deletions src/elements/content-sidebar/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ const messages = defineMessages({
defaultMessage: 'Sign Myself',
description: 'One of the dropdown options that opens a Box Sign sign myself experience',
},
boxSignSignature: {
id: 'be.contentSidebar.boxSignSignature',
defaultMessage: 'Sign',
description: 'label for button that opens a Box Sign signature fulfillment experience',
},
boxSignSecurityBlockedTooltip: {
defaultMessage: 'This action is unavailable due to a security policy.',
description: 'Tooltip text for when Box Sign is blocked due to a security policy',
Expand Down