Adding `FormTextField` component (#16497)
* Adding FormTextField component * Adding to index.js * Adding id, label and helptext stories * Removing unneeded htmlFor and fixing accessibility on helpText story * Fixing issues with review suggestions * Fixing lint issue * Adding snapshot testfeature/default_network_editable
parent
e9508b4f7f
commit
5ee7da6afe
@ -0,0 +1,336 @@ |
||||
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs'; |
||||
|
||||
import { TextField, TextFieldBase } from '../'; |
||||
import { FormTextField } from './form-text-field'; |
||||
|
||||
# FormTextField |
||||
|
||||
The `FormTextField` is an input component to create forms. It bundles the [TextField](/docs/ui-components-component-library-text-field-text-field-stories-js--default-story), [Label](/docs/ui-components-component-library-label-label-stories-js--default-story) and [HelpText](/docs/ui-components-component-library-help-text-help-text-stories-js--default-story) components together. |
||||
|
||||
<Canvas> |
||||
<Story id="ui-components-component-library-form-text-field-form-text-field-stories-js--default-story" /> |
||||
</Canvas> |
||||
|
||||
## Props |
||||
|
||||
The `FormTextField` accepts all props below as well as all [Box](/docs/ui-components-ui-box-box-stories-js--default-story#props) component props |
||||
|
||||
<ArgsTable of={FormTextField} /> |
||||
|
||||
`FormTextField` accepts all [TextField](/docs/ui-components-component-library-text-field-text-field-stories-js--default-story#props) |
||||
component props |
||||
|
||||
<ArgsTable of={TextField} /> |
||||
|
||||
`FormTextField` accepts all [TextFieldBase](/docs/ui-components-component-library-text-field-base-text-field-base-stories-js--default-story#props) |
||||
component props |
||||
|
||||
<ArgsTable of={TextFieldBase} /> |
||||
|
||||
### Id |
||||
|
||||
Use the `id` prop to set the `id` of the `FormTextField` component. This is required for accessibility when the `label` prop is set. It is also used internally to link the `label` and `input` elements using `htmlFor`, so clicking on the `label` will focus the `input`. |
||||
|
||||
<Canvas> |
||||
<Story id="ui-components-component-library-form-text-field-form-text-field-stories-js--id" /> |
||||
</Canvas> |
||||
|
||||
```jsx |
||||
import { FormTextField } from '../../component-library'; |
||||
|
||||
<FormTextField |
||||
id="accessible-input-id" |
||||
label="If label prop exists id prop is required for accessibility" |
||||
/>; |
||||
``` |
||||
|
||||
### Label |
||||
|
||||
Use the `label` prop to add a label to the `FormTextField` component. Uses the [Label](/docs/ui-components-component-library-label-label-stories-js--default-story) component. Use the `labelProps` prop to pass props to the `Label` component. To use a custom label component see the [Custom Label or HelpText](#custom-label-or-helptext) story example. |
||||
|
||||
<Canvas> |
||||
<Story id="ui-components-component-library-form-text-field-form-text-field-stories-js--label-story" /> |
||||
</Canvas> |
||||
|
||||
```jsx |
||||
import { FormTextField } from '../../component-library'; |
||||
|
||||
<FormTextField id="input-with-label" label="Label content appears here" />; |
||||
``` |
||||
|
||||
### HelpText |
||||
|
||||
Use the `helpText` prop to add help text to the `FormTextField` component. Uses the [HelpText](/docs/ui-components-component-library-helpText-helpText-stories-js--default-story) component. Use the `helpTextProps` prop to pass props to the `HelpText` component. To use a custom help text component see the [Custom Label or HelpText](#custom-helpText-or-helptext) story example. When `error` is true the `helpText` will be rendered as an error message. |
||||
|
||||
<Canvas> |
||||
<Story id="ui-components-component-library-form-text-field-form-text-field-stories-js--help-text-story" /> |
||||
</Canvas> |
||||
|
||||
```jsx |
||||
import { FormTextField } from '../../component-library'; |
||||
|
||||
<FormTextField helpText="HelpText content appears here" />; |
||||
<FormTextField |
||||
error |
||||
helpText="When error is true the help text will be rendered as an error message" |
||||
/>; |
||||
``` |
||||
|
||||
### Form Example |
||||
|
||||
An example of a form using the `FormTextField` component. |
||||
|
||||
<Canvas> |
||||
<Story id="ui-components-component-library-form-text-field-form-text-field-stories-js--form-example" /> |
||||
</Canvas> |
||||
|
||||
```jsx |
||||
import React, { useState, useEffect } from 'react'; |
||||
import { |
||||
DISPLAY, |
||||
COLORS, |
||||
ALIGN_ITEMS, |
||||
TEXT, |
||||
} from '../../../helpers/constants/design-system'; |
||||
|
||||
import Box from '../../ui/box/box'; |
||||
|
||||
import { |
||||
ButtonPrimary, |
||||
ButtonSecondary, |
||||
FormTextField, |
||||
ICON_NAMES, |
||||
Text, |
||||
} from '../../component-library'; |
||||
|
||||
const FORM_STATE = { |
||||
DEFAULT: 'default', |
||||
SUCCESS: 'success', |
||||
ERROR: 'error', |
||||
}; |
||||
|
||||
const VALIDATED_VALUES = { |
||||
NETWORK_NAME: 'network name', |
||||
NEW_RPC_URL: 'new rpc url', |
||||
CHAIN_ID: 'chain id', |
||||
}; |
||||
|
||||
const ERROR_MESSAGES = { |
||||
NETWORK_NAME: `Please enter "${VALIDATED_VALUES.NETWORK_NAME}"`, |
||||
NEW_RPC_URL: `Please enter "${VALIDATED_VALUES.NEW_RPC_URL}"`, |
||||
CHAIN_ID: `Please enter "${VALIDATED_VALUES.CHAIN_ID}"`, |
||||
}; |
||||
|
||||
const [submitted, setSubmitted] = useState(FORM_STATE.DEFAULT); |
||||
|
||||
const [values, setValues] = useState({ |
||||
networkName: '', |
||||
newRpcUrl: '', |
||||
chainId: '', |
||||
}); |
||||
|
||||
const [errors, setErrors] = useState({ |
||||
networkName: '', |
||||
newRpcUrl: '', |
||||
chainId: '', |
||||
}); |
||||
|
||||
useEffect(() => { |
||||
setErrors({ |
||||
networkName: |
||||
values.networkName && |
||||
values.networkName.toLowerCase() !== VALIDATED_VALUES.NETWORK_NAME |
||||
? ERROR_MESSAGES.NETWORK_NAME |
||||
: '', |
||||
newRpcUrl: |
||||
values.newRpcUrl && |
||||
values.newRpcUrl.toLowerCase() !== VALIDATED_VALUES.NEW_RPC_URL |
||||
? ERROR_MESSAGES.NEW_RPC_URL |
||||
: '', |
||||
chainId: |
||||
values.chainId && |
||||
values.chainId.toLowerCase() !== VALIDATED_VALUES.CHAIN_ID |
||||
? ERROR_MESSAGES.CHAIN_ID |
||||
: '', |
||||
}); |
||||
}, [values]); |
||||
|
||||
const handleClearForm = () => { |
||||
setValues({ networkName: '', newRpcUrl: '', chainId: '' }); |
||||
setErrors({ networkName: '', newRpcUrl: '', chainId: '' }); |
||||
setSubmitted(FORM_STATE.DEFAULT); |
||||
}; |
||||
|
||||
const handleOnChange = (e) => { |
||||
if (submitted === FORM_STATE.ERROR) { |
||||
setErrors({ networkName: '', newRpcUrl: '', chainId: '' }); |
||||
setSubmitted(FORM_STATE.DEFAULT); |
||||
} |
||||
setValues({ |
||||
...values, |
||||
[e.target.name]: e.target.value, |
||||
}); |
||||
}; |
||||
|
||||
const handleOnSubmit = (e) => { |
||||
e.preventDefault(); |
||||
if (errors.networkName || errors.newRpcUrl || errors.chainId) { |
||||
setSubmitted(FORM_STATE.ERROR); |
||||
} else { |
||||
setSubmitted(FORM_STATE.SUCCESS); |
||||
} |
||||
}; |
||||
|
||||
return ( |
||||
<> |
||||
<Box |
||||
as="form" |
||||
onSubmit={handleOnSubmit} |
||||
marginBottom={4} |
||||
style={{ width: '100%', maxWidth: '420px' }} |
||||
> |
||||
<FormTextField |
||||
marginBottom={4} |
||||
label="Network name" |
||||
placeholder="Enter 'network name'" |
||||
required |
||||
name="networkName" |
||||
id="networkName" |
||||
onChange={handleOnChange} |
||||
value={values.networkName} |
||||
error={Boolean(submitted === FORM_STATE.ERROR && errors.networkName)} |
||||
helpText={submitted === FORM_STATE.ERROR ? errors.networkName : null} |
||||
/> |
||||
<FormTextField |
||||
marginBottom={4} |
||||
label="New RPC URL" |
||||
placeholder="Enter 'new RPC URL'" |
||||
required |
||||
name="newRpcUrl" |
||||
id="newRpcUrl" |
||||
onChange={handleOnChange} |
||||
value={values.newRpcUrl} |
||||
error={Boolean(submitted === FORM_STATE.ERROR && errors.newRpcUrl)} |
||||
helpText={submitted === FORM_STATE.ERROR ? errors.newRpcUrl : null} |
||||
/> |
||||
<FormTextField |
||||
label="Chain ID" |
||||
marginBottom={4} |
||||
placeholder="Enter 'chain ID'" |
||||
required |
||||
name="chainId" |
||||
id="chainId" |
||||
onChange={handleOnChange} |
||||
value={values.chainId} |
||||
error={Boolean(submitted === FORM_STATE.ERROR && errors.chainId)} |
||||
helpText={submitted === FORM_STATE.ERROR ? errors.chainId : null} |
||||
/> |
||||
<Box display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER} gap={1}> |
||||
<ButtonPrimary type="submit">Submit</ButtonPrimary> |
||||
</Box> |
||||
</Box> |
||||
<ButtonSecondary |
||||
icon={ICON_NAMES.CLOSE_OUTLINE} |
||||
onClick={handleClearForm} |
||||
danger |
||||
> |
||||
Clear form |
||||
</ButtonSecondary> |
||||
{submitted === FORM_STATE.SUCCESS && ( |
||||
<Text variant={TEXT.BODY_LG} color={COLORS.SUCCESS_DEFAULT} marginTop={4}> |
||||
Form successfully submitted! |
||||
</Text> |
||||
)} |
||||
</> |
||||
); |
||||
``` |
||||
|
||||
### Custom Label or HelpText |
||||
|
||||
There will be times when you will want to use a custom `Label` or `HelpText`. This can be done by simply not providing `label` or `helpText` props to the `FormTextField` component. You can then use the `Label` and `HelpText` components to create your own custom label or help text. |
||||
|
||||
<Canvas> |
||||
<Story id="ui-components-component-library-form-text-field-form-text-field-stories-js--custom-label-or-help-text" /> |
||||
</Canvas> |
||||
|
||||
```jsx |
||||
import { |
||||
SIZES, |
||||
DISPLAY, |
||||
COLORS, |
||||
ALIGN_ITEMS, |
||||
JUSTIFY_CONTENT, |
||||
} from '../../../helpers/constants/design-system'; |
||||
|
||||
import Box from '../../ui/box/box'; |
||||
|
||||
import { |
||||
ButtonLink, |
||||
FormTextField, |
||||
HelpText, |
||||
ICON_NAMES, |
||||
Icon, |
||||
Label, |
||||
TEXT_FIELD_TYPES, |
||||
Text, |
||||
} from '../../component-library'; |
||||
|
||||
<Text marginBottom={4}> |
||||
Examples of how one might customize the Label or HelpText within the |
||||
FormTextField component |
||||
</Text> |
||||
<Box |
||||
display={DISPLAY.FLEX} |
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN} |
||||
alignItems={ALIGN_ITEMS.FLEX_END} |
||||
> |
||||
<Box display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}> |
||||
{/** |
||||
* If you need a custom label |
||||
* or require adding some form of customization |
||||
* import the Label component separately |
||||
*/} |
||||
<Label htmlFor="custom-spending-cap" required> |
||||
Custom spending cap |
||||
</Label> |
||||
<Icon |
||||
name={ICON_NAMES.INFO_FILLED} |
||||
size={SIZES.SM} |
||||
marginLeft={1} |
||||
color={COLORS.ICON_ALTERNATIVE} |
||||
/> |
||||
</Box> |
||||
<ButtonLink size={SIZES.AUTO}>Use default</ButtonLink> |
||||
</Box> |
||||
<FormTextField |
||||
id="custom-spending-cap" |
||||
placeholder="Enter a number" |
||||
rightAccessory={<ButtonLink size={SIZES.AUTO}>Max</ButtonLink>} |
||||
marginBottom={4} |
||||
type={TEXT_FIELD_TYPES.NUMBER} |
||||
/> |
||||
<FormTextField |
||||
label="Swap from" |
||||
placeholder="0" |
||||
type={TEXT_FIELD_TYPES.NUMBER} |
||||
/> |
||||
<Box |
||||
display={DISPLAY.FLEX} |
||||
alignItems={ALIGN_ITEMS.FLEX_START} |
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN} |
||||
> |
||||
{/** |
||||
* If you need a custom help text |
||||
* or require adding some form of customization |
||||
* import the HelpText component separately and handle the error |
||||
* logic yourself |
||||
*/} |
||||
<HelpText htmlFor="chainId" required paddingRight={2} marginTop={1}> |
||||
Only enter a number that you're comfortable with the contract accessing |
||||
now or in the future. You can always increase the token limit later. |
||||
</HelpText> |
||||
<ButtonLink size={SIZES.AUTO} marginLeft="auto" marginTop={1}> |
||||
Max |
||||
</ButtonLink> |
||||
</Box> |
||||
``` |
@ -0,0 +1,21 @@ |
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP |
||||
|
||||
exports[`FormTextField should render correctly 1`] = ` |
||||
<div> |
||||
<div |
||||
class="box mm-form-text-field box--display-flex box--flex-direction-column" |
||||
> |
||||
<div |
||||
class="box mm-text-field-base mm-text-field-base--size-md mm-text-field-base--truncate mm-text-field mm-form-text-field__text-field 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 text mm-text-field-base__input text--body-md 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> |
||||
</div> |
||||
`; |
@ -0,0 +1,167 @@ |
||||
import React from 'react'; |
||||
import PropTypes from 'prop-types'; |
||||
import classnames from 'classnames'; |
||||
|
||||
import { |
||||
DISPLAY, |
||||
FLEX_DIRECTION, |
||||
SIZES, |
||||
} from '../../../helpers/constants/design-system'; |
||||
|
||||
import Box from '../../ui/box/box'; |
||||
|
||||
import { TextField } from '../text-field'; |
||||
import { HelpText } from '../help-text'; |
||||
import { Label } from '../label'; |
||||
|
||||
export const FormTextField = ({ |
||||
autoComplete, |
||||
autoFocus, |
||||
className, |
||||
defaultValue, |
||||
disabled, |
||||
error, |
||||
helpText, |
||||
helpTextProps, |
||||
id, |
||||
inputProps, |
||||
inputRef, |
||||
label, |
||||
labelProps, |
||||
leftAccessory, |
||||
maxLength, |
||||
name, |
||||
onBlur, |
||||
onChange, |
||||
onFocus, |
||||
placeholder, |
||||
readOnly, |
||||
required, |
||||
rightAccessory, |
||||
size = SIZES.MD, |
||||
textFieldProps, |
||||
truncate, |
||||
showClearButton, |
||||
clearButtonOnClick, |
||||
clearButtonProps, |
||||
type = 'text', |
||||
value, |
||||
...props |
||||
}) => ( |
||||
<Box |
||||
className={classnames( |
||||
'mm-form-text-field', |
||||
{ 'mm-form-text-field--disabled': disabled }, |
||||
className, |
||||
)} |
||||
display={DISPLAY.FLEX} |
||||
flexDirection={FLEX_DIRECTION.COLUMN} |
||||
{...props} |
||||
> |
||||
{label && ( |
||||
<Label |
||||
htmlFor={id} |
||||
required={required} |
||||
disabled={disabled} |
||||
{...labelProps} |
||||
> |
||||
{label} |
||||
</Label> |
||||
)} |
||||
<TextField |
||||
className={classnames( |
||||
'mm-form-text-field__text-field', |
||||
textFieldProps?.className, |
||||
)} |
||||
id={id} |
||||
{...{ |
||||
autoComplete, |
||||
autoFocus, |
||||
defaultValue, |
||||
disabled, |
||||
error, |
||||
id, |
||||
inputProps, |
||||
inputRef, |
||||
leftAccessory, |
||||
maxLength, |
||||
name, |
||||
onBlur, |
||||
onChange, |
||||
onFocus, |
||||
placeholder, |
||||
readOnly, |
||||
required, |
||||
rightAccessory, |
||||
showClearButton, |
||||
clearButtonOnClick, |
||||
clearButtonProps, |
||||
size, |
||||
truncate, |
||||
type, |
||||
value, |
||||
...textFieldProps, |
||||
}} |
||||
/> |
||||
{helpText && ( |
||||
<HelpText |
||||
className={classnames( |
||||
'mm-form-text-field__help-text', |
||||
helpTextProps?.className, |
||||
)} |
||||
error={error} |
||||
marginTop={1} |
||||
{...helpTextProps} |
||||
> |
||||
{helpText} |
||||
</HelpText> |
||||
)} |
||||
</Box> |
||||
); |
||||
|
||||
FormTextField.propTypes = { |
||||
/** |
||||
* An additional className to apply to the form-text-field |
||||
*/ |
||||
className: PropTypes.string, |
||||
/** |
||||
* The id of the FormTextField |
||||
* Required if label prop exists to ensure accessibility |
||||
* |
||||
* @param {object} props - The props passed to the component. |
||||
* @param {string} propName - The prop name in this case 'id'. |
||||
* @param {string} componentName - The name of the component. |
||||
*/ |
||||
id: (props, propName, componentName) => { |
||||
if (props.label && !props[propName]) { |
||||
return new Error( |
||||
`If a label prop exists you must provide an ${propName} prop for the label's htmlFor attribute for accessibility. Warning coming from ${componentName} ui/components/component-library/form-text-field/form-text-field.js`, |
||||
); |
||||
} |
||||
return null; |
||||
}, |
||||
/** |
||||
* The content of the Label component |
||||
*/ |
||||
label: PropTypes.string, |
||||
/** |
||||
* Props that are applied to the Label component |
||||
*/ |
||||
labelProps: PropTypes.object, |
||||
/** |
||||
* The content of the HelpText component |
||||
*/ |
||||
helpText: PropTypes.string, |
||||
/** |
||||
* Props that are applied to the HelpText component |
||||
*/ |
||||
helpTextProps: PropTypes.object, |
||||
/** |
||||
* Props that are applied to the TextField component |
||||
*/ |
||||
textFieldProps: PropTypes.object, |
||||
/** |
||||
* FormTextField accepts all the props from TextField and Box |
||||
*/ |
||||
...TextField.propTypes, |
||||
}; |
@ -0,0 +1,9 @@ |
||||
.mm-form-text-field { |
||||
--help-text-opacity-disabled: 0.5; |
||||
|
||||
&--disabled { |
||||
.mm-form-text-field__help-text { |
||||
opacity: var(--help-text-opacity-disabled); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,481 @@ |
||||
import React, { useState, useEffect } from 'react'; |
||||
import { useArgs } from '@storybook/client-api'; |
||||
|
||||
import { |
||||
SIZES, |
||||
DISPLAY, |
||||
COLORS, |
||||
ALIGN_ITEMS, |
||||
TEXT, |
||||
JUSTIFY_CONTENT, |
||||
} from '../../../helpers/constants/design-system'; |
||||
|
||||
import Box from '../../ui/box/box'; |
||||
|
||||
import { |
||||
ButtonLink, |
||||
ButtonPrimary, |
||||
ButtonSecondary, |
||||
HelpText, |
||||
Icon, |
||||
ICON_NAMES, |
||||
Label, |
||||
Text, |
||||
TEXT_FIELD_SIZES, |
||||
TEXT_FIELD_TYPES, |
||||
} from '..'; |
||||
|
||||
import { FormTextField } from './form-text-field'; |
||||
|
||||
import README from './README.mdx'; |
||||
|
||||
const marginSizeControlOptions = [ |
||||
undefined, |
||||
0, |
||||
1, |
||||
2, |
||||
3, |
||||
4, |
||||
5, |
||||
6, |
||||
7, |
||||
8, |
||||
9, |
||||
10, |
||||
11, |
||||
12, |
||||
'auto', |
||||
]; |
||||
|
||||
export default { |
||||
title: 'Components/ComponentLibrary/FormTextField', |
||||
id: __filename, |
||||
component: FormTextField, |
||||
parameters: { |
||||
docs: { |
||||
page: README, |
||||
}, |
||||
}, |
||||
argTypes: { |
||||
value: { |
||||
control: 'text', |
||||
}, |
||||
onChange: { |
||||
action: 'onChange', |
||||
}, |
||||
labelProps: { |
||||
control: 'object', |
||||
}, |
||||
textFieldProps: { |
||||
control: 'object', |
||||
}, |
||||
helpTextProps: { |
||||
control: 'object', |
||||
}, |
||||
showClearButton: { |
||||
control: 'boolean', |
||||
table: { category: 'text field props' }, |
||||
}, |
||||
clearButtonOnClick: { |
||||
action: 'clearButtonOnClick', |
||||
table: { category: 'text field props' }, |
||||
}, |
||||
clearButtonProps: { |
||||
control: 'object', |
||||
table: { category: 'text field props' }, |
||||
}, |
||||
autoComplete: { |
||||
control: 'boolean', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
autoFocus: { |
||||
control: 'boolean', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
className: { |
||||
control: 'text', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
disabled: { |
||||
control: 'boolean', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
error: { |
||||
control: 'boolean', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
id: { |
||||
control: 'text', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
inputProps: { |
||||
control: 'object', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
leftAccessory: { |
||||
control: 'text', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
maxLength: { |
||||
control: 'number', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
name: { |
||||
control: 'text', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
onBlur: { |
||||
action: 'onBlur', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
onClick: { |
||||
action: 'onClick', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
onFocus: { |
||||
action: 'onFocus', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
onKeyDown: { |
||||
action: 'onKeyDown', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
onKeyUp: { |
||||
action: 'onKeyUp', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
placeholder: { |
||||
control: 'text', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
readOnly: { |
||||
control: 'boolean', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
required: { |
||||
control: 'boolean', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
rightAccessory: { |
||||
control: 'text', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
size: { |
||||
control: 'select', |
||||
options: Object.values(TEXT_FIELD_SIZES), |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
type: { |
||||
control: 'select', |
||||
options: Object.values(TEXT_FIELD_TYPES), |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
truncate: { |
||||
control: 'boolean', |
||||
table: { category: 'text field base props' }, |
||||
}, |
||||
marginTop: { |
||||
options: marginSizeControlOptions, |
||||
control: 'select', |
||||
table: { category: 'box props' }, |
||||
}, |
||||
marginRight: { |
||||
options: marginSizeControlOptions, |
||||
control: 'select', |
||||
table: { category: 'box props' }, |
||||
}, |
||||
marginBottom: { |
||||
options: marginSizeControlOptions, |
||||
control: 'select', |
||||
table: { category: 'box props' }, |
||||
}, |
||||
marginLeft: { |
||||
options: marginSizeControlOptions, |
||||
control: 'select', |
||||
table: { category: 'box props' }, |
||||
}, |
||||
}, |
||||
args: { |
||||
placeholder: 'Form text field', |
||||
label: 'Label', |
||||
id: 'form-text-field', |
||||
helpText: 'Help text', |
||||
}, |
||||
}; |
||||
|
||||
const Template = (args) => { |
||||
const [{ value }, updateArgs] = useArgs(); |
||||
const handleOnChange = (e) => { |
||||
updateArgs({ value: e.target.value }); |
||||
}; |
||||
const handleOnClear = () => { |
||||
updateArgs({ value: '' }); |
||||
}; |
||||
return ( |
||||
<FormTextField |
||||
{...args} |
||||
value={value} |
||||
onChange={handleOnChange} |
||||
clearButtonOnClick={handleOnClear} |
||||
/> |
||||
); |
||||
}; |
||||
|
||||
export const DefaultStory = Template.bind({}); |
||||
DefaultStory.storyName = 'Default'; |
||||
|
||||
export const Id = Template.bind({}); |
||||
Id.args = { |
||||
id: 'accessible-input-id', |
||||
label: 'If label prop exists id prop is required for accessibility', |
||||
helpText: '', |
||||
}; |
||||
|
||||
export const LabelStory = Template.bind({}); |
||||
LabelStory.storyName = 'Label'; // Need to use LabelStory to avoid conflict with Label component
|
||||
LabelStory.args = { |
||||
id: 'input-with-label', |
||||
label: 'Label content appears here', |
||||
helpText: '', |
||||
}; |
||||
|
||||
export const HelpTextStory = (args) => { |
||||
const [{ value }, updateArgs] = useArgs(); |
||||
const handleOnChange = (e) => { |
||||
updateArgs({ value: e.target.value }); |
||||
}; |
||||
const handleOnClear = () => { |
||||
updateArgs({ value: '' }); |
||||
}; |
||||
return ( |
||||
<> |
||||
<FormTextField |
||||
{...args} |
||||
id="input-with-help-text" |
||||
value={value} |
||||
onChange={handleOnChange} |
||||
clearButtonOnClick={handleOnClear} |
||||
marginBottom={4} |
||||
/> |
||||
<FormTextField |
||||
{...args} |
||||
id="input-with-help-text-as-error" |
||||
error |
||||
helpText="When error is true the help text will be rendered as an error message" |
||||
value={value} |
||||
onChange={handleOnChange} |
||||
clearButtonOnClick={handleOnClear} |
||||
/> |
||||
</> |
||||
); |
||||
}; |
||||
HelpTextStory.storyName = 'HelpText'; // Need to use HelpTextStory to avoid conflict with HelpTextStory component
|
||||
HelpTextStory.args = { |
||||
label: '', |
||||
helpText: 'HelpText content appears here', |
||||
}; |
||||
|
||||
export const FormExample = () => { |
||||
const FORM_STATE = { |
||||
DEFAULT: 'default', |
||||
SUCCESS: 'success', |
||||
ERROR: 'error', |
||||
}; |
||||
const VALIDATED_VALUES = { |
||||
NETWORK_NAME: 'network name', |
||||
NEW_RPC_URL: 'new rpc url', |
||||
CHAIN_ID: 'chain id', |
||||
}; |
||||
const ERROR_MESSAGES = { |
||||
NETWORK_NAME: `Please enter "${VALIDATED_VALUES.NETWORK_NAME}"`, |
||||
NEW_RPC_URL: `Please enter "${VALIDATED_VALUES.NEW_RPC_URL}"`, |
||||
CHAIN_ID: `Please enter "${VALIDATED_VALUES.CHAIN_ID}"`, |
||||
}; |
||||
const [submitted, setSubmitted] = useState(FORM_STATE.DEFAULT); |
||||
const [values, setValues] = useState({ |
||||
networkName: '', |
||||
newRpcUrl: '', |
||||
chainId: '', |
||||
}); |
||||
const [errors, setErrors] = useState({ |
||||
networkName: '', |
||||
newRpcUrl: '', |
||||
chainId: '', |
||||
}); |
||||
useEffect(() => { |
||||
setErrors({ |
||||
networkName: |
||||
values.networkName && |
||||
values.networkName.toLowerCase() !== VALIDATED_VALUES.NETWORK_NAME |
||||
? ERROR_MESSAGES.NETWORK_NAME |
||||
: '', |
||||
newRpcUrl: |
||||
values.newRpcUrl && |
||||
values.newRpcUrl.toLowerCase() !== VALIDATED_VALUES.NEW_RPC_URL |
||||
? ERROR_MESSAGES.NEW_RPC_URL |
||||
: '', |
||||
chainId: |
||||
values.chainId && |
||||
values.chainId.toLowerCase() !== VALIDATED_VALUES.CHAIN_ID |
||||
? ERROR_MESSAGES.CHAIN_ID |
||||
: '', |
||||
}); |
||||
}, [ |
||||
values, |
||||
ERROR_MESSAGES.CHAIN_ID, |
||||
ERROR_MESSAGES.NETWORK_NAME, |
||||
ERROR_MESSAGES.NEW_RPC_URL, |
||||
VALIDATED_VALUES.CHAIN_ID, |
||||
VALIDATED_VALUES.NETWORK_NAME, |
||||
VALIDATED_VALUES.NEW_RPC_URL, |
||||
]); |
||||
const handleClearForm = () => { |
||||
setValues({ networkName: '', newRpcUrl: '', chainId: '' }); |
||||
setErrors({ networkName: '', newRpcUrl: '', chainId: '' }); |
||||
setSubmitted(FORM_STATE.DEFAULT); |
||||
}; |
||||
const handleOnChange = (e) => { |
||||
if (submitted === FORM_STATE.ERROR) { |
||||
setErrors({ networkName: '', newRpcUrl: '', chainId: '' }); |
||||
setSubmitted(FORM_STATE.DEFAULT); |
||||
} |
||||
setValues({ |
||||
...values, |
||||
[e.target.name]: e.target.value, |
||||
}); |
||||
}; |
||||
const handleOnSubmit = (e) => { |
||||
e.preventDefault(); |
||||
if (errors.networkName || errors.newRpcUrl || errors.chainId) { |
||||
setSubmitted(FORM_STATE.ERROR); |
||||
} else { |
||||
setSubmitted(FORM_STATE.SUCCESS); |
||||
} |
||||
}; |
||||
return ( |
||||
<> |
||||
<Box |
||||
as="form" |
||||
onSubmit={handleOnSubmit} |
||||
marginBottom={4} |
||||
style={{ width: '100%', maxWidth: '420px' }} |
||||
> |
||||
<FormTextField |
||||
marginBottom={4} |
||||
label="Network name" |
||||
placeholder="Enter 'network name'" |
||||
required |
||||
name="networkName" |
||||
id="networkName" |
||||
onChange={handleOnChange} |
||||
value={values.networkName} |
||||
error={Boolean(submitted === FORM_STATE.ERROR && errors.networkName)} |
||||
helpText={submitted === FORM_STATE.ERROR ? errors.networkName : null} |
||||
/> |
||||
<FormTextField |
||||
marginBottom={4} |
||||
label="New RPC URL" |
||||
placeholder="Enter 'new RPC URL'" |
||||
required |
||||
name="newRpcUrl" |
||||
id="newRpcUrl" |
||||
onChange={handleOnChange} |
||||
value={values.newRpcUrl} |
||||
error={Boolean(submitted === FORM_STATE.ERROR && errors.newRpcUrl)} |
||||
helpText={submitted === FORM_STATE.ERROR ? errors.newRpcUrl : null} |
||||
/> |
||||
<FormTextField |
||||
label="Chain ID" |
||||
marginBottom={4} |
||||
placeholder="Enter 'chain ID'" |
||||
required |
||||
name="chainId" |
||||
id="chainId" |
||||
onChange={handleOnChange} |
||||
value={values.chainId} |
||||
error={Boolean(submitted === FORM_STATE.ERROR && errors.chainId)} |
||||
helpText={submitted === FORM_STATE.ERROR ? errors.chainId : null} |
||||
/> |
||||
<Box display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER} gap={1}> |
||||
<ButtonPrimary type="submit">Submit</ButtonPrimary> |
||||
</Box> |
||||
</Box> |
||||
<ButtonSecondary |
||||
icon={ICON_NAMES.CLOSE_OUTLINE} |
||||
onClick={handleClearForm} |
||||
danger |
||||
> |
||||
Clear form |
||||
</ButtonSecondary> |
||||
{submitted === FORM_STATE.SUCCESS && ( |
||||
<Text |
||||
variant={TEXT.BODY_LG} |
||||
color={COLORS.SUCCESS_DEFAULT} |
||||
marginTop={4} |
||||
> |
||||
Form successfully submitted! |
||||
</Text> |
||||
)} |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export const CustomLabelOrHelpText = () => ( |
||||
<> |
||||
<Text marginBottom={4}> |
||||
Examples of how one might customize the Label or HelpText within the |
||||
FormTextField component |
||||
</Text> |
||||
<Box |
||||
display={DISPLAY.FLEX} |
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN} |
||||
alignItems={ALIGN_ITEMS.FLEX_END} |
||||
> |
||||
<Box display={DISPLAY.FLEX} alignItems={ALIGN_ITEMS.CENTER}> |
||||
{/* If you need a custom label |
||||
or require adding some form of customization |
||||
import the Label component separately */} |
||||
<Label htmlFor="custom-spending-cap" required> |
||||
Custom spending cap |
||||
</Label> |
||||
<Icon |
||||
name={ICON_NAMES.INFO_FILLED} |
||||
size={SIZES.SM} |
||||
marginLeft={1} |
||||
color={COLORS.ICON_ALTERNATIVE} |
||||
/> |
||||
</Box> |
||||
<ButtonLink size={SIZES.AUTO}>Use default</ButtonLink> |
||||
</Box> |
||||
<FormTextField |
||||
id="custom-spending-cap" |
||||
placeholder="Enter a number" |
||||
rightAccessory={<ButtonLink size={SIZES.AUTO}>Max</ButtonLink>} |
||||
marginBottom={4} |
||||
type={TEXT_FIELD_TYPES.NUMBER} |
||||
/> |
||||
<FormTextField |
||||
label="Swap from" |
||||
placeholder="0" |
||||
type={TEXT_FIELD_TYPES.NUMBER} |
||||
/> |
||||
<Box |
||||
display={DISPLAY.FLEX} |
||||
alignItems={ALIGN_ITEMS.FLEX_START} |
||||
justifyContent={JUSTIFY_CONTENT.SPACE_BETWEEN} |
||||
> |
||||
{/* If you need a custom help text |
||||
or require adding some form of customization |
||||
import the HelpText component separately and handle the error |
||||
logic yourself */} |
||||
<HelpText paddingRight={2} marginTop={1}> |
||||
Only enter a number that you're comfortable with the contract |
||||
accessing now or in the future. You can always increase the token limit |
||||
later. |
||||
</HelpText> |
||||
<ButtonLink size={SIZES.AUTO} marginLeft="auto" marginTop={1}> |
||||
Max |
||||
</ButtonLink> |
||||
</Box> |
||||
</> |
||||
); |
@ -0,0 +1,349 @@ |
||||
/* eslint-disable jest/require-top-level-describe */ |
||||
import React from 'react'; |
||||
import { fireEvent, render } from '@testing-library/react'; |
||||
|
||||
import { |
||||
renderControlledInput, |
||||
renderWithUserEvent, |
||||
} from '../../../../test/lib/render-helpers'; |
||||
|
||||
import { SIZES } from '../../../helpers/constants/design-system'; |
||||
|
||||
import { FormTextField } from './form-text-field'; |
||||
|
||||
describe('FormTextField', () => { |
||||
it('should render correctly', () => { |
||||
const { getByRole, container } = render(<FormTextField />); |
||||
expect(getByRole('textbox')).toBeDefined(); |
||||
expect(container).toMatchSnapshot(); |
||||
}); |
||||
// autoComplete
|
||||
it('should render with autoComplete', () => { |
||||
const { getByTestId } = render( |
||||
<FormTextField |
||||
autoComplete |
||||
inputProps={{ 'data-testid': 'form-text-field-auto-complete' }} |
||||
/>, |
||||
); |
||||
expect(getByTestId('form-text-field-auto-complete')).toHaveAttribute( |
||||
'autocomplete', |
||||
'on', |
||||
); |
||||
}); |
||||
// autoFocus
|
||||
it('should render with autoFocus', () => { |
||||
const { getByRole } = render(<FormTextField autoFocus />); |
||||
expect(getByRole('textbox')).toHaveFocus(); |
||||
}); |
||||
// className
|
||||
it('should render with custom className', () => { |
||||
const { getByTestId } = render( |
||||
<FormTextField data-testid="form-text-field" className="test-class" />, |
||||
); |
||||
expect(getByTestId('form-text-field')).toHaveClass('test-class'); |
||||
}); |
||||
// defaultValue
|
||||
it('should render with a defaultValue', () => { |
||||
const { getByRole } = render( |
||||
<FormTextField defaultValue="default value" />, |
||||
); |
||||
expect(getByRole('textbox').value).toBe('default value'); |
||||
}); |
||||
// disabled
|
||||
it('should render in disabled state and not focus or be clickable', async () => { |
||||
const mockOnClick = jest.fn(); |
||||
const mockOnFocus = jest.fn(); |
||||
const { getByRole, user, getByLabelText } = renderWithUserEvent( |
||||
<FormTextField |
||||
label="test label" |
||||
id="test-id" |
||||
disabled |
||||
onFocus={mockOnFocus} |
||||
onClick={mockOnClick} |
||||
/>, |
||||
); |
||||
|
||||
await user.click(getByLabelText('test label')); |
||||
expect(mockOnFocus).toHaveBeenCalledTimes(0); |
||||
await user.type(getByRole('textbox'), 'test value'); |
||||
expect(getByRole('textbox')).not.toHaveValue('test value'); |
||||
|
||||
expect(getByRole('textbox')).toBeDisabled(); |
||||
expect(mockOnClick).toHaveBeenCalledTimes(0); |
||||
expect(mockOnFocus).toHaveBeenCalledTimes(0); |
||||
}); |
||||
// error
|
||||
it('should render with error classNames on TextField and HelpText components when error is true', () => { |
||||
const { getByTestId, getByText } = render( |
||||
<FormTextField |
||||
error |
||||
textFieldProps={{ 'data-testid': 'text-field' }} |
||||
helpText="test help text" |
||||
/>, |
||||
); |
||||
expect(getByTestId('text-field')).toHaveClass('mm-text-field-base--error'); |
||||
expect(getByText('test help text')).toHaveClass( |
||||
'text--color-error-default', |
||||
); |
||||
}); |
||||
// helpText
|
||||
it('should render with helpText', () => { |
||||
const { getByText } = render(<FormTextField helpText="test help text" />); |
||||
expect(getByText('test help text')).toBeDefined(); |
||||
}); |
||||
// helpTextProps
|
||||
it('should render with helpText and helpTextProps', () => { |
||||
const { getByText, getByTestId } = render( |
||||
<FormTextField |
||||
helpText="test help text" |
||||
helpTextProps={{ 'data-testid': 'help-text-test' }} |
||||
/>, |
||||
); |
||||
expect(getByText('test help text')).toBeDefined(); |
||||
expect(getByTestId('help-text-test')).toBeDefined(); |
||||
}); |
||||
// id
|
||||
it('should render the FormTextField with an id and pass it to input and Label as htmlFor. When clicking on Label the input should have focus', async () => { |
||||
const onFocus = jest.fn(); |
||||
const { getByRole, getByLabelText, user } = renderWithUserEvent( |
||||
<FormTextField label="test label" id="test-id" onFocus={onFocus} />, |
||||
); |
||||
expect(getByRole('textbox')).toHaveAttribute('id', 'test-id'); |
||||
await user.click(getByLabelText('test label')); |
||||
expect(onFocus).toHaveBeenCalledTimes(1); |
||||
expect(getByRole('textbox')).toHaveFocus(); |
||||
}); |
||||
// inputProps
|
||||
it('should render with inputProps', () => { |
||||
const { getByTestId } = render( |
||||
<FormTextField inputProps={{ 'data-testid': 'test-id' }} />, |
||||
); |
||||
expect(getByTestId('test-id')).toBeDefined(); |
||||
}); |
||||
// inputRef
|
||||
it('should render with working ref using inputRef prop', () => { |
||||
// Because the 'ref' attribute wont flow down to the DOM
|
||||
// I'm not exactly sure how to test this?
|
||||
const mockRef = jest.fn(); |
||||
const { getByRole } = render(<FormTextField inputRef={mockRef} />); |
||||
expect(getByRole('textbox')).toBeDefined(); |
||||
expect(mockRef).toHaveBeenCalledTimes(1); |
||||
}); |
||||
// label
|
||||
it('should render with a label', () => { |
||||
const { getByLabelText } = render( |
||||
<FormTextField id="test-id" label="test label" />, |
||||
); |
||||
expect(getByLabelText('test label')).toBeDefined(); |
||||
}); |
||||
// labelProps
|
||||
it('should render with a labelProps', () => { |
||||
const { getByTestId, getByLabelText } = render( |
||||
<FormTextField |
||||
label="test label" |
||||
labelProps={{ 'data-testid': 'label-test-id' }} |
||||
id="test-id" |
||||
/>, |
||||
); |
||||
expect(getByLabelText('test label')).toBeDefined(); |
||||
expect(getByTestId('label-test-id')).toBeDefined(); |
||||
}); |
||||
// leftAccessory, // rightAccessory
|
||||
it('should render with right and left accessories', () => { |
||||
const { getByRole, getByText } = render( |
||||
<FormTextField |
||||
leftAccessory={<div>left accessory</div>} |
||||
rightAccessory={<div>right accessory</div>} |
||||
/>, |
||||
); |
||||
expect(getByRole('textbox')).toBeDefined(); |
||||
expect(getByText('left accessory')).toBeDefined(); |
||||
expect(getByText('right accessory')).toBeDefined(); |
||||
}); |
||||
// maxLength;
|
||||
it('should render with maxLength and not allow more than the set characters', async () => { |
||||
const { getByRole, user } = renderWithUserEvent( |
||||
<FormTextField maxLength={5} />, |
||||
); |
||||
const formTextField = getByRole('textbox'); |
||||
await user.type(formTextField, '1234567890'); |
||||
expect(getByRole('textbox')).toBeDefined(); |
||||
expect(formTextField.maxLength).toBe(5); |
||||
expect(formTextField.value).toBe('12345'); |
||||
expect(formTextField.value).toHaveLength(5); |
||||
}); |
||||
// name
|
||||
it('should render with name prop', () => { |
||||
const { getByRole } = render(<FormTextField name="test-name" />); |
||||
expect(getByRole('textbox')).toHaveAttribute('name', 'test-name'); |
||||
}); |
||||
// onBlur, // onFocus
|
||||
it('should render and fire onFocus and onBlur events', async () => { |
||||
const onFocus = jest.fn(); |
||||
const onBlur = jest.fn(); |
||||
const { getByTestId, user } = renderWithUserEvent( |
||||
<FormTextField |
||||
inputProps={{ 'data-testid': 'form-text-field' }} |
||||
onFocus={onFocus} |
||||
onBlur={onBlur} |
||||
/>, |
||||
); |
||||
const formTextField = getByTestId('form-text-field'); |
||||
|
||||
await user.click(formTextField); |
||||
expect(onFocus).toHaveBeenCalledTimes(1); |
||||
fireEvent.blur(formTextField); |
||||
expect(onBlur).toHaveBeenCalledTimes(1); |
||||
}); |
||||
// onChange
|
||||
it('should render and fire onChange event', async () => { |
||||
const onChange = jest.fn(); |
||||
const { user, getByRole } = renderWithUserEvent( |
||||
<FormTextField onChange={onChange} />, |
||||
); |
||||
await user.type(getByRole('textbox'), 'test'); |
||||
expect(onChange).toHaveBeenCalledTimes(4); |
||||
}); |
||||
// placeholder
|
||||
it('should render with placeholder', () => { |
||||
const { getByTestId } = render( |
||||
<FormTextField |
||||
placeholder="test placeholder" |
||||
inputProps={{ 'data-testid': 'form-text-field-auto-complete' }} |
||||
/>, |
||||
); |
||||
expect(getByTestId('form-text-field-auto-complete')).toHaveAttribute( |
||||
'placeholder', |
||||
'test placeholder', |
||||
); |
||||
}); |
||||
// readOnly
|
||||
it('should render with readOnly attr when readOnly is true', async () => { |
||||
const { getByRole, user } = renderWithUserEvent( |
||||
<FormTextField |
||||
readOnly |
||||
value="test value" |
||||
data-testid="read-only" |
||||
inputProps={{ 'data-testid': 'text-field-base-readonly' }} |
||||
/>, |
||||
); |
||||
await user.type(getByRole('textbox'), 'test'); |
||||
expect(getByRole('textbox')).toHaveValue('test value'); |
||||
expect(getByRole('textbox')).toHaveAttribute('readonly', ''); |
||||
}); |
||||
// required
|
||||
it('should render with required asterisk after Label', () => { |
||||
const { getByTestId } = render( |
||||
<FormTextField |
||||
required |
||||
label="test label" |
||||
labelProps={{ 'data-testid': 'label-test-id' }} |
||||
/>, |
||||
); |
||||
expect(getByTestId('label-test-id')).toHaveTextContent('test label*'); |
||||
}); |
||||
// size = SIZES.MD
|
||||
it('should render with different size classes', () => { |
||||
const { getByTestId } = render( |
||||
<> |
||||
<FormTextField |
||||
size={SIZES.SM} |
||||
textFieldProps={{ 'data-testid': 'sm' }} |
||||
/> |
||||
<FormTextField |
||||
size={SIZES.MD} |
||||
textFieldProps={{ 'data-testid': 'md' }} |
||||
/> |
||||
<FormTextField |
||||
size={SIZES.LG} |
||||
textFieldProps={{ 'data-testid': 'lg' }} |
||||
/> |
||||
</>, |
||||
); |
||||
expect(getByTestId('sm')).toHaveClass('mm-text-field-base--size-sm'); |
||||
expect(getByTestId('md')).toHaveClass('mm-text-field-base--size-md'); |
||||
expect(getByTestId('lg')).toHaveClass('mm-text-field-base--size-lg'); |
||||
}); |
||||
// textFieldProps
|
||||
it('should render with textFieldProps', () => { |
||||
const { getByTestId } = render( |
||||
<FormTextField textFieldProps={{ 'data-testid': 'test-text-field' }} />, |
||||
); |
||||
expect(getByTestId('test-text-field')).toBeDefined(); |
||||
}); |
||||
// truncate
|
||||
it('should render with truncate class as true by default and remove it when truncate is false', () => { |
||||
const { getByTestId } = render( |
||||
<> |
||||
<FormTextField textFieldProps={{ 'data-testid': 'truncate' }} /> |
||||
<FormTextField |
||||
truncate={false} |
||||
textFieldProps={{ 'data-testid': 'no-truncate' }} |
||||
/> |
||||
</>, |
||||
); |
||||
expect(getByTestId('truncate')).toHaveClass('mm-text-field-base--truncate'); |
||||
expect(getByTestId('no-truncate')).not.toHaveClass( |
||||
'mm-text-field-base--truncate', |
||||
); |
||||
}); |
||||
// showClearButton
|
||||
it('should render showClearButton button when showClearButton is true and value exists', async () => { |
||||
// As showClearButton is intended to be used with a controlled input we need to use renderControlledInput
|
||||
const { user, getByRole } = renderControlledInput(FormTextField, { |
||||
showClearButton: true, |
||||
}); |
||||
await user.type(getByRole('textbox'), 'test value'); |
||||
expect(getByRole('textbox')).toHaveValue('test value'); |
||||
expect(getByRole('button', { name: /Clear/u })).toBeDefined(); |
||||
}); |
||||
// clearButtonOnClick
|
||||
it('should fire onClick event when passed to clearButtonOnClick when clear button is clicked', async () => { |
||||
// As showClearButton is intended to be used with a controlled input we need to use renderControlledInput
|
||||
const fn = jest.fn(); |
||||
const { user, getByRole } = renderControlledInput(FormTextField, { |
||||
showClearButton: true, |
||||
clearButtonOnClick: fn, |
||||
}); |
||||
await user.type(getByRole('textbox'), 'test value'); |
||||
await user.click(getByRole('button', { name: /Clear/u })); |
||||
expect(fn).toHaveBeenCalledTimes(1); |
||||
}); |
||||
// clearButtonProps,
|
||||
it('should fire onClick event when passed to clearButtonProps.onClick prop', async () => { |
||||
// As showClearButton is intended to be used with a controlled input we need to use renderControlledInput
|
||||
const fn = jest.fn(); |
||||
const { user, getByRole } = renderControlledInput(FormTextField, { |
||||
showClearButton: true, |
||||
clearButtonProps: { onClick: fn }, |
||||
}); |
||||
await user.type(getByRole('textbox'), 'test value'); |
||||
await user.click(getByRole('button', { name: /Clear/u })); |
||||
expect(fn).toHaveBeenCalledTimes(1); |
||||
}); |
||||
// type,
|
||||
it('should render with different types', () => { |
||||
const { getByTestId } = render( |
||||
<> |
||||
<FormTextField inputProps={{ 'data-testid': 'form-text-field-text' }} /> |
||||
<FormTextField |
||||
type="number" |
||||
inputProps={{ 'data-testid': 'form-text-field-number' }} |
||||
/> |
||||
<FormTextField |
||||
type="password" |
||||
inputProps={{ 'data-testid': 'form-text-field-password' }} |
||||
/> |
||||
</>, |
||||
); |
||||
expect(getByTestId('form-text-field-text')).toHaveAttribute('type', 'text'); |
||||
expect(getByTestId('form-text-field-number')).toHaveAttribute( |
||||
'type', |
||||
'number', |
||||
); |
||||
expect(getByTestId('form-text-field-password')).toHaveAttribute( |
||||
'type', |
||||
'password', |
||||
); |
||||
}); |
||||
}); |
@ -0,0 +1 @@ |
||||
export { FormTextField } from './form-text-field'; |
Loading…
Reference in new issue