1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

TextFieldBase house keeping 🧹 (#16667)

* TextFieldBase house keeping updates

* Fixing story

* Updating custom input story

* Updating ButtonIcon props and lint issues

* Updating snapshots
This commit is contained in:
George Marshall 2022-12-06 11:51:48 -08:00 committed by GitHub
parent 1fa213835f
commit 971f153e65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 197 additions and 154 deletions

View File

@ -179,7 +179,7 @@ export const DefaultStory = (args) => {
backgroundColor={COLORS.BACKGROUND_ALTERNATIVE} backgroundColor={COLORS.BACKGROUND_ALTERNATIVE}
rightAccessory={ rightAccessory={
<ButtonIcon <ButtonIcon
icon={ICON_NAMES.COPY_FILLED} iconName={ICON_NAMES.COPY_FILLED}
size={SIZES.SM} size={SIZES.SM}
color={COLORS.ICON_ALTERNATIVE} color={COLORS.ICON_ALTERNATIVE}
ariaLabel="Copy to clipboard" ariaLabel="Copy to clipboard"

View File

@ -35,8 +35,8 @@ Defaults to `md`
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library';
import { SIZES } from '../../../helpers/constants/design-system'; import { SIZES } from '../../../helpers/constants/design-system';
import { TextFieldBase } from '../../component-library';
<TextFieldBase size={SIZES.SM} /> <TextFieldBase size={SIZES.SM} />
<TextFieldBase size={SIZES.MD} /> <TextFieldBase size={SIZES.MD} />
@ -60,7 +60,7 @@ Defaults to `text`.
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
<TextFieldBase type="text" /> // (Default) <TextFieldBase type="text" /> // (Default)
<TextFieldBase type="number" /> <TextFieldBase type="number" />
@ -76,7 +76,7 @@ Use the `truncate` prop to truncate the text of the the `TextFieldBase`. Default
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
<TextFieldBase truncate />; // truncate is set to `true` by default <TextFieldBase truncate />; // truncate is set to `true` by default
<TextFieldBase truncate={false} />; <TextFieldBase truncate={false} />;
@ -92,9 +92,7 @@ Use the `leftAccessory` and `rightAccessory` props to add components such as ico
```jsx ```jsx
import { COLORS, SIZES, DISPLAY } from '../../../helpers/constants/design-system'; import { COLORS, SIZES, DISPLAY } from '../../../helpers/constants/design-system';
import { Icon, ICON_NAMES } from '../../ui/components/component-library'; import { ButtonIcon, Icon, ICON_NAMES, TextFieldBase } from '../../component-library';
import { TextFieldBase } from '../../ui/components/component-library';
<TextFieldBase <TextFieldBase
placeholder="Search" placeholder="Search"
@ -109,18 +107,11 @@ import { TextFieldBase } from '../../ui/components/component-library';
<TextFieldBase <TextFieldBase
placeholder="Public address (0x), or ENS" placeholder="Public address (0x), or ENS"
rightAccessory={ rightAccessory={
// TODO: replace with ButtonIcon <ButtonIcon
<Box iconName={ICON_NAMES.SCAN_BARCODE_FILLED}
as="button" ariaLabel="Scan QR code"
display={DISPLAY.FLEX} iconProps={{ color: COLORS.PRIMARY_DEFAULT }}
style={{ padding: 0 }}
backgroundColor={COLORS.TRANSPARENT}
>
<Icon
color={COLORS.PRIMARY_DEFAULT}
name={ICON_NAMES.SCAN_BARCODE_FILLED}
/> />
</Box>
} }
/> />
@ -156,7 +147,7 @@ Use the `inputRef` prop to access the ref of the `<input />` html element of `Te
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { Button, TextFieldBase } from '../../component-library';
const inputRef = useRef(null); const inputRef = useRef(null);
const [value, setValue] = useState(''); const [value, setValue] = useState('');
@ -172,20 +163,9 @@ const handleOnChange = (e) => {
value={value} value={value}
onChange={handleOnChange} onChange={handleOnChange}
/> />
// TODO: replace with Button component <Button marginLeft={1} onClick={handleOnClick}>
<Box
as="button"
backgroundColor={COLORS.BACKGROUND_ALTERNATIVE}
color={COLORS.TEXT_DEFAULT}
borderColor={COLORS.BORDER_DEFAULT}
borderRadius={SIZES.XL}
marginLeft={1}
paddingLeft={2}
paddingRight={2}
onClick={handleOnClick}
>
Edit Edit
</Box> </Button>
``` ```
### Input Component ### Input Component
@ -227,7 +207,7 @@ To function fully the custom component should accept the following props:
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase, Icon, ICON_NAMES } from '../../ui/component-library'; import { TextFieldBase, Icon, ICON_NAMES } from '../../component-library';
// should map the props to the custom input component // should map the props to the custom input component
const CustomInputComponent = () => <div>{/* Custom input component */}</div>; const CustomInputComponent = () => <div>{/* Custom input component */}</div>;
@ -252,7 +232,7 @@ Use the `autoComplete` prop to set the autocomplete html attribute. It allows th
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
<TextFieldBase type="password" autoComplete />; <TextFieldBase type="password" autoComplete />;
``` ```
@ -266,7 +246,7 @@ Use the `autoFocus` prop to focus the `TextFieldBase` during the first mount
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
<TextFieldBase autoFocus />; <TextFieldBase autoFocus />;
``` ```
@ -280,7 +260,7 @@ Use the `defaultValue` prop to set the default value of the `TextFieldBase`
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
<TextFieldBase defaultValue="default value" />; <TextFieldBase defaultValue="default value" />;
``` ```
@ -294,7 +274,7 @@ Use the `disabled` prop to set the disabled state of the `TextFieldBase`
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
<TextFieldBase disabled />; <TextFieldBase disabled />;
``` ```
@ -308,7 +288,7 @@ Use the `error` prop to set the error state of the `TextFieldBase`
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
<TextFieldBase error />; <TextFieldBase error />;
``` ```
@ -322,7 +302,7 @@ Use the `maxLength` prop to set the maximum allowed input characters for the `Te
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
<TextFieldBase maxLength={10} />; <TextFieldBase maxLength={10} />;
``` ```
@ -336,7 +316,7 @@ Use the `readOnly` prop to set the `TextFieldBase` to read only. When `readOnly`
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
<TextFieldBase readOnly />; <TextFieldBase readOnly />;
``` ```
@ -350,7 +330,7 @@ Use the `required` prop to set the `TextFieldBase` to required. Currently there
</Canvas> </Canvas>
```jsx ```jsx
import { TextFieldBase } from '../../ui/components/component-library'; import { TextFieldBase } from '../../component-library';
// Currently no visual difference // Currently no visual difference
<TextFieldBase required />; <TextFieldBase required />;

View File

@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TextFieldBase should render correctly 1`] = `
<div>
<div
class="box mm-text-field-base mm-text-field-base--size-md mm-text-field-base--truncate box--display-inline-flex box--flex-direction-row box--align-items-center box--background-color-background-default box--rounded-sm box--border-width-1 box--border-style-solid"
>
<input
autocomplete="off"
class="box mm-text mm-text-field-base__input mm-text--body-md mm-text--color-text-default box--padding-right-4 box--padding-left-4 box--flex-direction-row box--background-color-transparent"
focused="false"
type="text"
value=""
/>
</div>
</div>
`;

View File

@ -67,7 +67,7 @@ export const TextFieldBase = ({
setFocused(true); setFocused(true);
} }
if (onClick) { if (onClick && !disabled) {
onClick(event); onClick(event);
} }
}; };
@ -97,7 +97,7 @@ export const TextFieldBase = ({
'mm-text-field-base', 'mm-text-field-base',
`mm-text-field-base--size-${size}`, `mm-text-field-base--size-${size}`,
{ {
'mm-text-field-base--focused': focused && !disabled && !readOnly, 'mm-text-field-base--focused': focused && !disabled,
'mm-text-field-base--error': error, 'mm-text-field-base--error': error,
'mm-text-field-base--disabled': disabled, 'mm-text-field-base--disabled': disabled,
'mm-text-field-base--truncate': truncate, 'mm-text-field-base--truncate': truncate,

View File

@ -1,4 +1,6 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import { useArgs } from '@storybook/client-api';
import PropTypes from 'prop-types';
import { import {
SIZES, SIZES,
@ -10,16 +12,22 @@ import {
} from '../../../helpers/constants/design-system'; } from '../../../helpers/constants/design-system';
import Box from '../../ui/box/box'; import Box from '../../ui/box/box';
import { Icon, ICON_NAMES } from '../icon'; import {
import { AvatarToken } from '../avatar-token'; AvatarAccount,
import { AvatarAccount } from '../avatar-account'; AvatarToken,
import { Text } from '../text'; Button,
ButtonIcon,
ICON_NAMES,
Icon,
Text,
} from '..';
import { import {
TEXT_FIELD_BASE_SIZES, TEXT_FIELD_BASE_SIZES,
TEXT_FIELD_BASE_TYPES, TEXT_FIELD_BASE_TYPES,
} from './text-field-base.constants'; } from './text-field-base.constants';
import { TextFieldBase } from './text-field-base'; import { TextFieldBase } from './text-field-base';
import README from './README.mdx'; import README from './README.mdx';
const marginSizeControlOptions = [ const marginSizeControlOptions = [
@ -144,7 +152,13 @@ export default {
}, },
}; };
const Template = (args) => <TextFieldBase {...args} />; const Template = (args) => {
const [{ value }, updateArgs] = useArgs();
const handleOnChange = (e) => {
updateArgs({ value: e.target.value });
};
return <TextFieldBase {...args} value={value} onChange={handleOnChange} />;
};
export const DefaultStory = Template.bind({}); export const DefaultStory = Template.bind({});
DefaultStory.storyName = 'Default'; DefaultStory.storyName = 'Default';
@ -228,7 +242,6 @@ export const LeftAccessoryRightAccessory = (args) => {
value={value.search} value={value.search}
name="search" name="search"
onChange={handleOnChange} onChange={handleOnChange}
showClear
leftAccessory={ leftAccessory={
<Icon <Icon
color={COLORS.ICON_ALTERNATIVE} color={COLORS.ICON_ALTERNATIVE}
@ -243,17 +256,11 @@ export const LeftAccessoryRightAccessory = (args) => {
name="address" name="address"
onChange={handleOnChange} onChange={handleOnChange}
rightAccessory={ rightAccessory={
<Box <ButtonIcon
as="button" iconName={ICON_NAMES.SCAN_BARCODE_FILLED}
display={DISPLAY.FLEX} ariaLabel="Scan QR code"
style={{ padding: 0 }} iconProps={{ color: COLORS.PRIMARY_DEFAULT }}
backgroundColor={COLORS.TRANSPARENT}
>
<Icon
color={COLORS.PRIMARY_DEFAULT}
name={ICON_NAMES.SCAN_BARCODE_FILLED}
/> />
</Box>
} }
/> />
<TextFieldBase <TextFieldBase
@ -274,11 +281,11 @@ export const LeftAccessoryRightAccessory = (args) => {
alignItems={ALIGN_ITEMS.CENTER} alignItems={ALIGN_ITEMS.CENTER}
> >
<AvatarToken <AvatarToken
tokenName="ast" tokenName="eth"
tokenImageUrl="./AST.png" tokenImageUrl="./images/eth_logo.svg"
size={SIZES.SM} size={SIZES.SM}
/> />
<Text>AST</Text> <Text>ETH</Text>
<Icon <Icon
name={ICON_NAMES.ARROW_DOWN} name={ICON_NAMES.ARROW_DOWN}
color={COLORS.ICON_DEFAULT} color={COLORS.ICON_DEFAULT}
@ -287,8 +294,12 @@ export const LeftAccessoryRightAccessory = (args) => {
</Box> </Box>
} }
rightAccessory={ rightAccessory={
<Text variant={TEXT.BODY_SM} color={COLORS.TEXT_ALTERNATIVE}> <Text
= ${handleTokenPrice(value.amount, 0.11)} variant={TEXT.BODY_SM}
color={COLORS.TEXT_ALTERNATIVE}
style={{ whiteSpace: 'nowrap' }}
>
= ${handleTokenPrice(value.amount, 1173.58)}
</Text> </Text>
} }
/> />
@ -327,31 +338,23 @@ export const InputRef = (args) => {
setValue(e.target.value); setValue(e.target.value);
}; };
return ( return (
<> <Box display={DISPLAY.FLEX}>
<TextFieldBase <TextFieldBase
{...args} {...args}
inputRef={inputRef} inputRef={inputRef}
value={value} value={value}
onChange={handleOnChange} onChange={handleOnChange}
/> />
<Box <Button marginLeft={1} onClick={handleOnClick}>
as="button"
backgroundColor={COLORS.BACKGROUND_ALTERNATIVE}
color={COLORS.TEXT_DEFAULT}
borderColor={COLORS.BORDER_DEFAULT}
borderRadius={SIZES.XL}
marginLeft={1}
paddingLeft={2}
paddingRight={2}
onClick={handleOnClick}
>
Edit Edit
</Button>
</Box> </Box>
</>
); );
}; };
const CustomInputComponent = ({ const CustomInputComponent = React.forwardRef(
(
{
as, as,
autoComplete, autoComplete,
autoFocus, autoFocus,
@ -359,6 +362,8 @@ const CustomInputComponent = ({
disabled, disabled,
focused, focused,
id, id,
inputProps,
inputRef,
maxLength, maxLength,
name, name,
onBlur, onBlur,
@ -369,7 +374,6 @@ const CustomInputComponent = ({
paddingRight, paddingRight,
placeholder, placeholder,
readOnly, readOnly,
ref,
required, required,
value, value,
variant, variant,
@ -377,20 +381,23 @@ const CustomInputComponent = ({
className, className,
'aria-invalid': ariaInvalid, 'aria-invalid': ariaInvalid,
...props ...props
}) => { },
return ( ref,
) => (
<Box <Box
display={DISPLAY.FLEX} display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN} flexDirection={FLEX_DIRECTION.COLUMN}
ref={ref}
{...{ padding, paddingLeft, paddingRight, ...props }} {...{ padding, paddingLeft, paddingRight, ...props }}
> >
<Box display={DISPLAY.INLINE_FLEX}> <Box display={DISPLAY.INLINE_FLEX}>
<Text <Text
style={{ padding: 0 }} style={{ padding: 0 }}
aria-invalid={ariaInvalid} aria-invalid={ariaInvalid}
ref={inputRef}
{...{ {...{
as,
className, className,
as,
autoComplete, autoComplete,
autoFocus, autoFocus,
defaultValue, defaultValue,
@ -404,11 +411,11 @@ const CustomInputComponent = ({
onFocus, onFocus,
placeholder, placeholder,
readOnly, readOnly,
ref,
required, required,
value, value,
variant, variant,
type, type,
...inputProps,
}} }}
/> />
<Text variant={TEXT.BODY_XS} color={COLORS.TEXT_ALTERNATIVE}> <Text variant={TEXT.BODY_XS} color={COLORS.TEXT_ALTERNATIVE}>
@ -417,10 +424,43 @@ const CustomInputComponent = ({
</Box> </Box>
<Text variant={TEXT.BODY_XS}>No conversion rate available</Text> <Text variant={TEXT.BODY_XS}>No conversion rate available</Text>
</Box> </Box>
); ),
);
CustomInputComponent.propTypes = {
/**
* The custom input component should accepts all props that the
* InputComponent accepts in ./text-field-base.js
*/
autoFocus: PropTypes.bool,
className: PropTypes.string,
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
disabled: PropTypes.bool,
id: PropTypes.string,
inputProps: PropTypes.object,
inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
maxLength: PropTypes.number,
name: PropTypes.string,
onBlur: PropTypes.func,
onChange: PropTypes.func,
onFocus: PropTypes.func,
placeholder: PropTypes.string,
readOnly: PropTypes.bool,
required: PropTypes.bool,
type: PropTypes.oneOf(Object.values(TEXT_FIELD_BASE_TYPES)),
/**
* Because we manipulate the type in TextFieldBase so the html element
* receives the correct attribute we need to change the autoComplete
* propType to a string
*/
autoComplete: PropTypes.string,
/**
* The custom input component should also accept all the props from Box
*/
...Box.propTypes,
}; };
CustomInputComponent.propTypes = { ...TextFieldBase.propTypes }; CustomInputComponent.displayName = 'CustomInputComponent';
export const InputComponent = (args) => ( export const InputComponent = (args) => (
<TextFieldBase <TextFieldBase
@ -435,6 +475,8 @@ export const InputComponent = (args) => (
/> />
); );
InputComponent.args = { autoComplete: true };
export const AutoComplete = Template.bind({}); export const AutoComplete = Template.bind({});
AutoComplete.args = { AutoComplete.args = {
autoComplete: true, autoComplete: true,

View File

@ -1,7 +1,8 @@
/* eslint-disable jest/require-top-level-describe */ /* eslint-disable jest/require-top-level-describe */
import React from 'react'; import React from 'react';
import { fireEvent, render } from '@testing-library/react'; import { fireEvent, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event'; import { renderWithUserEvent } from '../../../../test/lib/render-helpers';
import { SIZES } from '../../../helpers/constants/design-system'; import { SIZES } from '../../../helpers/constants/design-system';
import Box from '../../ui/box'; import Box from '../../ui/box';
@ -10,8 +11,9 @@ import { TextFieldBase } from './text-field-base';
describe('TextFieldBase', () => { describe('TextFieldBase', () => {
it('should render correctly', () => { it('should render correctly', () => {
const { getByRole } = render(<TextFieldBase />); const { getByRole, container } = render(<TextFieldBase />);
expect(getByRole('textbox')).toBeDefined(); expect(getByRole('textbox')).toBeDefined();
expect(container).toMatchSnapshot();
}); });
it('should render and be able to input text', () => { it('should render and be able to input text', () => {
const { getByTestId } = render( const { getByTestId } = render(
@ -25,54 +27,54 @@ describe('TextFieldBase', () => {
fireEvent.change(textFieldBase, { target: { value: '' } }); // reset value fireEvent.change(textFieldBase, { target: { value: '' } }); // reset value
expect(textFieldBase.value).toBe(''); // value is empty string after reset expect(textFieldBase.value).toBe(''); // value is empty string after reset
}); });
it('should render with focused state when clicked', () => { it('should render with focused state when clicked', async () => {
const { getByTestId } = render( const { getByTestId, user } = renderWithUserEvent(
<TextFieldBase <TextFieldBase
data-testid="text-field-base" data-testid="text-field-base"
inputProps={{ 'data-testid': 'input' }} inputProps={{ 'data-testid': 'input' }}
/>, />,
); );
const textFieldBase = getByTestId('text-field-base'); const textFieldBase = getByTestId('input');
fireEvent.click(textFieldBase); await user.click(textFieldBase);
expect(getByTestId('input')).toHaveFocus(); expect(getByTestId('input')).toHaveFocus();
expect(getByTestId('text-field-base')).toHaveClass( expect(getByTestId('text-field-base')).toHaveClass(
'mm-text-field-base--focused ', 'mm-text-field-base--focused ',
); );
}); });
it('should render and fire onFocus and onBlur events', () => { it('should render and fire onFocus and onBlur events', async () => {
const onFocus = jest.fn(); const onFocus = jest.fn();
const onBlur = jest.fn(); const onBlur = jest.fn();
const { getByTestId } = render( const { getByTestId, user } = renderWithUserEvent(
<TextFieldBase <TextFieldBase
inputProps={{ 'data-testid': 'text-field-base' }} inputProps={{ 'data-testid': 'text-field-base' }}
onFocus={onFocus} onFocus={onFocus}
onBlur={onBlur} onBlur={onBlur}
/>, />,
); );
const textFieldBase = getByTestId('text-field-base');
fireEvent.focus(textFieldBase); const textFieldBase = getByTestId('text-field-base');
await user.click(textFieldBase);
expect(onFocus).toHaveBeenCalledTimes(1); expect(onFocus).toHaveBeenCalledTimes(1);
fireEvent.blur(textFieldBase); fireEvent.blur(textFieldBase);
expect(onBlur).toHaveBeenCalledTimes(1); expect(onBlur).toHaveBeenCalledTimes(1);
}); });
it('should render and fire onChange event', () => { it('should render and fire onChange event', async () => {
const onChange = jest.fn(); const onChange = jest.fn();
const { getByTestId } = render( const { getByTestId, user } = renderWithUserEvent(
<TextFieldBase <TextFieldBase
inputProps={{ 'data-testid': 'text-field-base' }} inputProps={{ 'data-testid': 'text-field-base' }}
onChange={onChange} onChange={onChange}
/>, />,
); );
const textFieldBase = getByTestId('text-field-base'); const textFieldBase = getByTestId('text-field-base');
await user.type(textFieldBase, '123');
fireEvent.change(textFieldBase, { target: { value: 'text value' } }); expect(textFieldBase).toHaveValue('123');
expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledTimes(3);
}); });
it('should render and fire onClick event', () => { it('should render and fire onClick event', async () => {
const onClick = jest.fn(); const onClick = jest.fn();
const { getByTestId } = render( const { getByTestId, user } = renderWithUserEvent(
<TextFieldBase <TextFieldBase
inputProps={{ 'data-testid': 'text-field-base' }} inputProps={{ 'data-testid': 'text-field-base' }}
onClick={onClick} onClick={onClick}
@ -80,7 +82,7 @@ describe('TextFieldBase', () => {
); );
const textFieldBase = getByTestId('text-field-base'); const textFieldBase = getByTestId('text-field-base');
fireEvent.click(textFieldBase); await user.click(textFieldBase);
expect(onClick).toHaveBeenCalledTimes(1); expect(onClick).toHaveBeenCalledTimes(1);
}); });
it('should render with different size classes', () => { it('should render with different size classes', () => {
@ -175,14 +177,21 @@ describe('TextFieldBase', () => {
); );
expect(getByRole('textbox').value).toBe('default value'); expect(getByRole('textbox').value).toBe('default value');
}); });
it('should render in disabled state and not focus or be clickable', () => { it('should render in disabled state and not focus or be clickable', async () => {
const mockOnClick = jest.fn(); const mockOnClick = jest.fn();
const mockOnFocus = jest.fn(); const mockOnFocus = jest.fn();
const { getByRole } = render( const { getByRole, getByTestId, user } = renderWithUserEvent(
<TextFieldBase disabled onFocus={mockOnFocus} onClick={mockOnClick} />, <TextFieldBase
disabled
onFocus={mockOnFocus}
onClick={mockOnClick}
data-testid="text-field-base"
/>,
); );
getByRole('textbox').focus(); const textFieldBase = getByTestId('text-field-base');
await user.click(textFieldBase);
expect(getByRole('textbox')).toBeDisabled(); expect(getByRole('textbox')).toBeDisabled();
expect(mockOnClick).toHaveBeenCalledTimes(0); expect(mockOnClick).toHaveBeenCalledTimes(0);
expect(mockOnFocus).toHaveBeenCalledTimes(0); expect(mockOnFocus).toHaveBeenCalledTimes(0);
@ -196,29 +205,24 @@ describe('TextFieldBase', () => {
); );
}); });
it('should render with maxLength and not allow more than the set characters', async () => { it('should render with maxLength and not allow more than the set characters', async () => {
const { getByRole } = render(<TextFieldBase maxLength={5} />); const { getByRole, user } = renderWithUserEvent(
<TextFieldBase maxLength={5} />,
);
const textFieldBase = getByRole('textbox'); const textFieldBase = getByRole('textbox');
await userEvent.type(textFieldBase, '1234567890'); await user.type(textFieldBase, '1234567890');
expect(getByRole('textbox')).toBeDefined(); expect(getByRole('textbox')).toBeDefined();
expect(textFieldBase.maxLength).toBe(5); expect(textFieldBase.maxLength).toBe(5);
expect(textFieldBase.value).toBe('12345'); expect(textFieldBase.value).toBe('12345');
expect(textFieldBase.value).toHaveLength(5); expect(textFieldBase.value).toHaveLength(5);
}); });
it('should render with readOnly attr when readOnly is true', () => { it('should render with readOnly attr when readOnly is true', async () => {
const { getByTestId } = render( const { getByTestId, getByRole, user } = renderWithUserEvent(
<TextFieldBase <TextFieldBase readOnly data-testid="read-only" />,
readOnly
data-testid="read-only"
inputProps={{ 'data-testid': 'text-field-base-readonly' }}
/>,
);
expect(getByTestId('read-only')).not.toHaveClass(
'mm-text-field-base--focused ',
);
expect(getByTestId('text-field-base-readonly')).toHaveAttribute(
'readonly',
'',
); );
const textFieldBase = getByTestId('read-only');
await user.type(textFieldBase, '1234567890');
expect(getByRole('textbox').value).toBe('');
expect(getByRole('textbox')).toHaveAttribute('readonly', '');
}); });
it('should render with required attr when required is true', () => { it('should render with required attr when required is true', () => {
const { getByTestId } = render( const { getByTestId } = render(
@ -232,12 +236,12 @@ describe('TextFieldBase', () => {
'', '',
); );
}); });
it('should render with a custom input and still work', () => { it('should render with a custom input and still work', async () => {
const CustomInputComponent = React.forwardRef((props, ref) => ( const CustomInputComponent = React.forwardRef((props, ref) => (
<Box ref={ref} as="input" {...props} /> <Box ref={ref} as="input" {...props} />
)); ));
CustomInputComponent.displayName = 'CustomInputComponent'; // fixes eslint error CustomInputComponent.displayName = 'CustomInputComponent'; // fixes eslint error
const { getByTestId } = render( const { getByTestId, user } = renderWithUserEvent(
<TextFieldBase <TextFieldBase
InputComponent={CustomInputComponent} InputComponent={CustomInputComponent}
inputProps={{ 'data-testid': 'text-field-base', className: 'test' }} inputProps={{ 'data-testid': 'text-field-base', className: 'test' }}
@ -246,7 +250,7 @@ describe('TextFieldBase', () => {
const textFieldBase = getByTestId('text-field-base'); const textFieldBase = getByTestId('text-field-base');
expect(textFieldBase.value).toBe(''); // initial value is empty string expect(textFieldBase.value).toBe(''); // initial value is empty string
fireEvent.change(textFieldBase, { target: { value: 'text value' } }); await user.type(textFieldBase, 'text value');
expect(textFieldBase.value).toBe('text value'); expect(textFieldBase.value).toBe('text value');
fireEvent.change(textFieldBase, { target: { value: '' } }); // reset value fireEvent.change(textFieldBase, { target: { value: '' } }); // reset value
expect(textFieldBase.value).toBe(''); // value is empty string after reset expect(textFieldBase.value).toBe(''); // value is empty string after reset

View File

@ -30,7 +30,7 @@ export const TextField = ({
<ButtonIcon <ButtonIcon
className="mm-text-field__button-clear" className="mm-text-field__button-clear"
ariaLabel="Clear" // TODO: i18n ariaLabel="Clear" // TODO: i18n
icon={ICON_NAMES.CLOSE_OUTLINE} iconName={ICON_NAMES.CLOSE_OUTLINE}
size={SIZES.SM} size={SIZES.SM}
onClick={clearButtonOnClick} onClick={clearButtonOnClick}
{...clearButtonProps} {...clearButtonProps}