Skip to content

Commit b1ca157

Browse files
feat(content-picker): convert Footer to TypeScript (#3939)
* feat(content-picker): convert Footer component to TypeScript * test(content-picker): convert Footer tests to TypeScript and React Testing Library * fix(content-picker): restore disabled attribute for accessibility * fix(content-picker): fix PrimaryButton props typing * refactor(content-picker): switch Footer to use useIntl hook * fix(content-picker): improve accessibility and test assertions * fix(button-group): revert aria attribute changes * fix(content-picker): remove group role test and attributes --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent 89c6c9a commit b1ca157

File tree

3 files changed

+111
-118
lines changed

3 files changed

+111
-118
lines changed
Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,44 @@
1-
/**
2-
* @flow
3-
* @file Footer list component
4-
* @author Box
5-
*/
6-
71
import * as React from 'react';
8-
import { injectIntl, FormattedMessage } from 'react-intl';
9-
import type { Node } from 'react';
10-
2+
import { FormattedMessage, useIntl } from 'react-intl';
113
import type { Collection, BoxItem } from '../../common/types/core';
12-
import Button from '../../components/button';
4+
import Button, { ButtonType } from '../../components/button';
135
import ButtonGroup from '../../components/button-group';
146
import IconCheck from '../../icons/general/IconCheck';
157
import IconClose from '../../icons/general/IconClose';
168
import messages from '../common/messages';
17-
189
import PrimaryButton from '../../components/primary-button';
1910
import Tooltip from '../common/Tooltip';
2011
import './Footer.scss';
2112

22-
type Props = {
23-
cancelButtonLabel?: string,
24-
children?: any,
25-
chooseButtonLabel?: string,
26-
currentCollection: Collection,
27-
hasHitSelectionLimit: boolean,
28-
intl: any,
29-
isSingleSelect: boolean,
30-
onCancel: Function,
31-
onChoose: Function,
32-
onSelectedClick: Function,
33-
renderCustomActionButtons?: ({
34-
onCancel: Function,
35-
onChoose: Function,
36-
selectedCount: number,
37-
selectedItems: BoxItem[],
38-
}) => Node,
39-
selectedCount: number,
40-
selectedItems: BoxItem[],
41-
showSelectedButton: boolean,
42-
};
13+
interface Props {
14+
cancelButtonLabel?: string;
15+
children?: React.ReactNode;
16+
chooseButtonLabel?: string;
17+
currentCollection: Collection;
18+
hasHitSelectionLimit: boolean;
19+
isSingleSelect: boolean;
20+
onCancel: () => void;
21+
onChoose: () => void;
22+
onSelectedClick: () => void;
23+
renderCustomActionButtons?: (options: {
24+
currentFolderId: string;
25+
currentFolderName: string;
26+
onCancel: () => void;
27+
onChoose: () => void;
28+
selectedCount: number;
29+
selectedItems: BoxItem[];
30+
}) => React.ReactNode;
31+
selectedCount: number;
32+
selectedItems: BoxItem[];
33+
showSelectedButton: boolean;
34+
}
4335

4436
const Footer = ({
4537
currentCollection,
4638
selectedCount,
4739
selectedItems,
4840
onSelectedClick,
4941
hasHitSelectionLimit,
50-
intl,
5142
isSingleSelect,
5243
onCancel,
5344
onChoose,
@@ -56,15 +47,17 @@ const Footer = ({
5647
children,
5748
renderCustomActionButtons,
5849
showSelectedButton,
59-
}: Props) => {
60-
const cancelMessage = intl.formatMessage(messages.cancel);
61-
const chooseMessage = intl.formatMessage(messages.choose);
50+
}: Props): React.ReactElement => {
51+
const { formatMessage } = useIntl();
52+
const cancelMessage = formatMessage(messages.cancel);
53+
const chooseMessage = formatMessage(messages.choose);
6254
const isChooseButtonDisabled = !selectedCount;
55+
6356
return (
6457
<footer className="bcp-footer">
6558
<div className="bcp-footer-left">
6659
{showSelectedButton && !isSingleSelect && (
67-
<Button className="bcp-selected" onClick={onSelectedClick} type="button">
60+
<Button className="bcp-selected" onClick={onSelectedClick} type={ButtonType.BUTTON}>
6861
<FormattedMessage
6962
className="bcp-selected-count"
7063
{...messages.selected}
@@ -80,7 +73,6 @@ const Footer = ({
8073
</div>
8174
<div className="bcp-footer-right">
8275
{children}
83-
8476
{renderCustomActionButtons ? (
8577
renderCustomActionButtons({
8678
currentFolderId: currentCollection.id,
@@ -93,17 +85,17 @@ const Footer = ({
9385
) : (
9486
<ButtonGroup className="bcp-footer-actions">
9587
<Tooltip text={cancelButtonLabel || cancelMessage}>
96-
<Button aria-label={cancelMessage} onClick={onCancel} type="button">
88+
<Button aria-label={cancelMessage} onClick={onCancel} type={ButtonType.BUTTON}>
9789
<IconClose height={16} width={16} />
9890
</Button>
9991
</Tooltip>
10092
<Tooltip isDisabled={isChooseButtonDisabled} text={chooseButtonLabel || chooseMessage}>
10193
<PrimaryButton
10294
aria-label={chooseMessage}
103-
disabled={isChooseButtonDisabled} // sets disabled attribute
104-
isDisabled={isChooseButtonDisabled} // used in Button component
95+
isDisabled={isChooseButtonDisabled}
10596
onClick={onChoose}
106-
type="button"
97+
type={ButtonType.BUTTON}
98+
{...{ disabled: isChooseButtonDisabled }} // sets disabled attribute for native HTML button
10799
>
108100
<IconCheck color="#fff" height={16} width={16} />
109101
</PrimaryButton>
@@ -115,4 +107,4 @@ const Footer = ({
115107
);
116108
};
117109

118-
export default injectIntl(Footer);
110+
export default Footer;

src/elements/content-picker/__tests__/Footer.test.js

Lines changed: 0 additions & 75 deletions
This file was deleted.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import * as React from 'react';
2+
import { render, screen } from '../../../test-utils/testing-library';
3+
import Footer from '../Footer';
4+
import type { Collection, BoxItem } from '../../../common/types/core';
5+
6+
describe('elements/content-picker/Footer', () => {
7+
const defaultProps = {
8+
children: <div data-testid="footer-child" />,
9+
currentCollection: { id: '123', name: 'Folder' } as Collection,
10+
hasHitSelectionLimit: false,
11+
isSingleSelect: false,
12+
onCancel: jest.fn(),
13+
onChoose: jest.fn(),
14+
onSelectedClick: jest.fn(),
15+
selectedCount: 0,
16+
selectedItems: [] as BoxItem[],
17+
showSelectedButton: true,
18+
};
19+
20+
const renderComponent = (props = {}) => {
21+
render(<Footer {...defaultProps} {...props} />);
22+
};
23+
24+
test('should render Footer', () => {
25+
renderComponent();
26+
27+
expect(screen.getByRole('contentinfo')).toBeInTheDocument();
28+
expect(screen.getByTestId('footer-child')).toBeInTheDocument();
29+
});
30+
31+
test('should render footer with disabled button', () => {
32+
renderComponent();
33+
34+
const chooseButton = screen.getByRole('button', { name: 'Choose' });
35+
expect(chooseButton).toHaveAttribute('aria-disabled', 'true');
36+
expect(chooseButton).toBeDisabled();
37+
});
38+
39+
test('should render Footer buttons with aria-label', () => {
40+
renderComponent();
41+
42+
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
43+
expect(screen.getByRole('button', { name: 'Choose' })).toBeInTheDocument();
44+
});
45+
46+
test('should render Footer with custom action button', () => {
47+
const renderCustomActionButtons = jest.fn().mockReturnValue(<div data-testid="custom-button" />);
48+
49+
renderComponent({
50+
renderCustomActionButtons,
51+
});
52+
53+
expect(screen.getByTestId('custom-button')).toBeInTheDocument();
54+
expect(renderCustomActionButtons).toHaveBeenCalledWith({
55+
currentFolderId: defaultProps.currentCollection.id,
56+
currentFolderName: defaultProps.currentCollection.name,
57+
onCancel: defaultProps.onCancel,
58+
onChoose: defaultProps.onChoose,
59+
selectedCount: defaultProps.selectedCount,
60+
selectedItems: defaultProps.selectedItems,
61+
});
62+
});
63+
64+
test.each`
65+
showSelectedButton | isSingleSelect | shown | should
66+
${false} | ${false} | ${false} | ${'should not show selected button'}
67+
${false} | ${true} | ${false} | ${'should not show selected button'}
68+
${true} | ${false} | ${true} | ${'should show selected button'}
69+
${true} | ${true} | ${false} | ${'should not show selected button'}
70+
`('$should', ({ isSingleSelect, shown, showSelectedButton }) => {
71+
renderComponent({ isSingleSelect, showSelectedButton });
72+
73+
const selectedButton = screen.queryByRole('button', { name: '0 Selected' });
74+
expect(!!selectedButton).toBe(shown);
75+
});
76+
});

0 commit comments

Comments
 (0)