mirror of
https://github.com/strapi/strapi.git
synced 2026-01-01 01:33:50 +00:00
Merge pull request #12861 from strapi/fix/number-clear
GenericInput: Allow number inputs to be cleared
This commit is contained in:
commit
7cbf20c762
@ -38,7 +38,7 @@ const GenericInput = ({
|
||||
required,
|
||||
step,
|
||||
type,
|
||||
value,
|
||||
value: defaultValue,
|
||||
isNullable,
|
||||
...rest
|
||||
}) => {
|
||||
@ -47,6 +47,10 @@ const GenericInput = ({
|
||||
|
||||
const CustomInput = customInputs ? customInputs[type] : null;
|
||||
|
||||
// the API always returns null, which throws an error in React,
|
||||
// therefore we cast this case to undefined
|
||||
const value = defaultValue ?? undefined;
|
||||
|
||||
if (CustomInput) {
|
||||
return (
|
||||
<CustomInput
|
||||
@ -109,7 +113,7 @@ const GenericInput = ({
|
||||
|
||||
return (
|
||||
<ToggleInput
|
||||
checked={value === null ? null : value || false}
|
||||
checked={defaultValue === null ? null : defaultValue || false}
|
||||
disabled={disabled}
|
||||
hint={hint}
|
||||
label={label}
|
||||
@ -170,7 +174,7 @@ const GenericInput = ({
|
||||
onClear={() => onChange({ target: { name, value: null, type } })}
|
||||
placeholder={formattedPlaceholder}
|
||||
required={required}
|
||||
value={value ? new Date(value) : null}
|
||||
value={value && new Date(value)}
|
||||
selectedDateLabel={formattedDate => `Date picker, current is ${formattedDate}`}
|
||||
/>
|
||||
);
|
||||
@ -216,12 +220,12 @@ const GenericInput = ({
|
||||
hint={hint}
|
||||
name={name}
|
||||
onValueChange={value => {
|
||||
onChange({ target: { name, value, type } });
|
||||
onChange({ target: { name, value: value ?? null, type } });
|
||||
}}
|
||||
placeholder={formattedPlaceholder}
|
||||
required={required}
|
||||
step={step}
|
||||
value={value ?? undefined}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -240,7 +244,7 @@ const GenericInput = ({
|
||||
placeholder={formattedPlaceholder}
|
||||
required={required}
|
||||
type="email"
|
||||
value={value || ''}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -261,7 +265,7 @@ const GenericInput = ({
|
||||
placeholder={formattedPlaceholder}
|
||||
required={required}
|
||||
type="text"
|
||||
value={value || ''}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -303,7 +307,7 @@ const GenericInput = ({
|
||||
placeholder={formattedPlaceholder}
|
||||
required={required}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={value || ''}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -322,7 +326,7 @@ const GenericInput = ({
|
||||
}}
|
||||
placeholder={formattedPlaceholder}
|
||||
required={required}
|
||||
value={value || ''}
|
||||
value={value}
|
||||
>
|
||||
{options.map(({ metadatas: { intlLabel, disabled, hidden }, key, value }) => {
|
||||
return (
|
||||
@ -348,7 +352,7 @@ const GenericInput = ({
|
||||
required={required}
|
||||
placeholder={formattedPlaceholder}
|
||||
type={type}
|
||||
value={value || ''}
|
||||
value={value}
|
||||
>
|
||||
{value}
|
||||
</Textarea>
|
||||
@ -415,7 +419,7 @@ GenericInput.defaultProps = {
|
||||
required: false,
|
||||
options: [],
|
||||
step: 1,
|
||||
value: '',
|
||||
value: undefined,
|
||||
};
|
||||
|
||||
GenericInput.propTypes = {
|
||||
|
||||
@ -0,0 +1,311 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`GenericInput number renders and matches the snapshot 1`] = `
|
||||
.c13 {
|
||||
border: 0;
|
||||
-webkit-clip: rect(0 0 0 0);
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.c8 {
|
||||
padding-right: 12px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.c10 {
|
||||
color: #8e8ea9;
|
||||
}
|
||||
|
||||
.c2 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c5 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
-webkit-align-items: center;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c1 {
|
||||
font-weight: 600;
|
||||
color: #32324d;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.33;
|
||||
}
|
||||
|
||||
.c3 {
|
||||
color: #d02b20;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.43;
|
||||
}
|
||||
|
||||
.c4 {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.c7 {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding-left: 16px;
|
||||
padding-right: 0;
|
||||
color: #32324d;
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
display: block;
|
||||
width: 100%;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.c7::-webkit-input-placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c7::-moz-placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c7:-ms-input-placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c7::placeholder {
|
||||
color: #8e8ea9;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.c7[aria-disabled='true'] {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.c7:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.c6 {
|
||||
border: 1px solid #dcdce4;
|
||||
border-radius: 4px;
|
||||
background: #ffffff;
|
||||
height: 2.5rem;
|
||||
outline: none;
|
||||
box-shadow: 0;
|
||||
-webkit-transition-property: border-color,box-shadow,fill;
|
||||
transition-property: border-color,box-shadow,fill;
|
||||
-webkit-transition-duration: 0.2s;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.c6:focus-within {
|
||||
border: 1px solid #4945ff;
|
||||
box-shadow: #4945ff 0px 0px 0px 2px;
|
||||
}
|
||||
|
||||
.c0 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.c0 > * {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.c0 > * + * {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.c11 path {
|
||||
fill: #8e8ea9;
|
||||
}
|
||||
|
||||
.c9 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
height: 1rem;
|
||||
-webkit-align-items: flex-end;
|
||||
-webkit-box-align: flex-end;
|
||||
-ms-flex-align: flex-end;
|
||||
align-items: flex-end;
|
||||
-webkit-transform: translateY(-2px);
|
||||
-ms-transform: translateY(-2px);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.c9 svg {
|
||||
display: block;
|
||||
height: 0.25rem;
|
||||
-webkit-transform: rotateX(180deg);
|
||||
-ms-transform: rotateX(180deg);
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
|
||||
.c12 {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
height: 1rem;
|
||||
-webkit-align-items: flex-start;
|
||||
-webkit-box-align: flex-start;
|
||||
-ms-flex-align: flex-start;
|
||||
align-items: flex-start;
|
||||
-webkit-transform: translateY(2px);
|
||||
-ms-transform: translateY(2px);
|
||||
transform: translateY(2px);
|
||||
}
|
||||
|
||||
.c12 svg {
|
||||
display: block;
|
||||
height: 0.25rem;
|
||||
}
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
class="c0"
|
||||
spacing="1"
|
||||
>
|
||||
<label
|
||||
class="c1"
|
||||
for="number"
|
||||
required=""
|
||||
>
|
||||
<div
|
||||
class="c2"
|
||||
>
|
||||
Default label
|
||||
<span
|
||||
class="c3 c4"
|
||||
>
|
||||
*
|
||||
</span>
|
||||
</div>
|
||||
</label>
|
||||
<div
|
||||
class="c5 c6"
|
||||
>
|
||||
<input
|
||||
aria-disabled="false"
|
||||
aria-invalid="false"
|
||||
class="c7"
|
||||
id="number"
|
||||
name="number"
|
||||
placeholder="Default placeholder"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
class="c8"
|
||||
>
|
||||
<button
|
||||
aria-hidden="true"
|
||||
class="c9"
|
||||
data-testid="ArrowUp"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
class="c10 c11"
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 14 8"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M14 .889a.86.86 0 01-.26.625L7.615 7.736A.834.834 0 017 8a.834.834 0 01-.615-.264L.26 1.514A.861.861 0 010 .889c0-.24.087-.45.26-.625A.834.834 0 01.875 0h12.25c.237 0 .442.088.615.264a.86.86 0 01.26.625z"
|
||||
fill="#32324D"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
aria-hidden="true"
|
||||
class="c12"
|
||||
data-testid="ArrowDown"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
class="c10 c11"
|
||||
fill="none"
|
||||
height="1em"
|
||||
viewBox="0 0 14 8"
|
||||
width="1em"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M14 .889a.86.86 0 01-.26.625L7.615 7.736A.834.834 0 017 8a.834.834 0 01-.615-.264L.26 1.514A.861.861 0 010 .889c0-.24.087-.45.26-.625A.834.834 0 01.875 0h12.25c.237 0 .442.088.615.264a.86.86 0 01.26.625z"
|
||||
fill="#32324D"
|
||||
fill-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="c13"
|
||||
>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-log"
|
||||
role="log"
|
||||
/>
|
||||
<p
|
||||
aria-live="polite"
|
||||
aria-relevant="all"
|
||||
id="live-region-status"
|
||||
role="status"
|
||||
/>
|
||||
<p
|
||||
aria-live="assertive"
|
||||
aria-relevant="all"
|
||||
id="live-region-alert"
|
||||
role="alert"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -0,0 +1,123 @@
|
||||
import React from 'react';
|
||||
import { ThemeProvider, lightTheme } from '@strapi/design-system';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
|
||||
import GenericInput from '../index';
|
||||
|
||||
function ComponentFixture(props) {
|
||||
return (
|
||||
<IntlProvider locale="en" messages={{}}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<GenericInput {...props} />
|
||||
</ThemeProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function setupNumber(props) {
|
||||
const NUMBER_FIXTURE_PROPS = {
|
||||
type: 'number',
|
||||
name: 'number',
|
||||
intlLabel: {
|
||||
id: 'label.test',
|
||||
defaultMessage: 'Default label',
|
||||
},
|
||||
placeholder: {
|
||||
id: 'placeholder.test',
|
||||
defaultMessage: 'Default placeholder',
|
||||
},
|
||||
hint: 'Hint message',
|
||||
value: null,
|
||||
required: true,
|
||||
onChange: jest.fn,
|
||||
...props,
|
||||
};
|
||||
|
||||
const rendered = render(<ComponentFixture {...NUMBER_FIXTURE_PROPS} />);
|
||||
const input = rendered.container.querySelector('input');
|
||||
|
||||
return {
|
||||
...rendered,
|
||||
input,
|
||||
};
|
||||
}
|
||||
|
||||
describe('GenericInput', () => {
|
||||
describe('number', () => {
|
||||
test('renders and matches the snapshot', () => {
|
||||
const { container } = setupNumber();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('renders an error message', () => {
|
||||
const { getByText } = setupNumber({ error: 'Error message' });
|
||||
expect(getByText('Error message')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('renders a number (int) value', () => {
|
||||
const { container } = setupNumber({ value: 1 });
|
||||
expect(container.querySelector('[type="text"]').value).toBe('1');
|
||||
});
|
||||
|
||||
test('renders a number (float) value', () => {
|
||||
const { container } = setupNumber({ value: 1.3333 });
|
||||
expect(container.querySelector('input').value).toBe('1.3333');
|
||||
});
|
||||
|
||||
test('does not call onChange callback on first render', () => {
|
||||
const spy = jest.fn();
|
||||
setupNumber({ value: null, onChange: spy });
|
||||
expect(spy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('does not call onChange callback if the value does not change', () => {
|
||||
const spy = jest.fn();
|
||||
const { input } = setupNumber({ value: 23, onChange: spy });
|
||||
|
||||
fireEvent.change(input, { target: { value: 23 } });
|
||||
|
||||
expect(spy).not.toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
test('does call onChange callback with number (int) value', () => {
|
||||
const spy = jest.fn();
|
||||
const { input } = setupNumber({ value: null, onChange: spy });
|
||||
|
||||
fireEvent.change(input, { target: { value: '23' } });
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({ target: { name: 'number', type: 'number', value: 23 } });
|
||||
});
|
||||
|
||||
test('does call onChange callback with number (float) value', () => {
|
||||
const spy = jest.fn();
|
||||
const { input } = setupNumber({ value: null, onChange: spy });
|
||||
|
||||
fireEvent.change(input, { target: { value: '1.3333' } });
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
target: { name: 'number', type: 'number', value: 1.3333 },
|
||||
});
|
||||
});
|
||||
|
||||
test('does call onChange callback with number (0) value', () => {
|
||||
const spy = jest.fn();
|
||||
const { input } = setupNumber({ value: null, onChange: spy });
|
||||
|
||||
fireEvent.change(input, { target: { value: '0' } });
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
target: { name: 'number', type: 'number', value: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
test('does call onChange callback with null on empty value', () => {
|
||||
const spy = jest.fn();
|
||||
const { input } = setupNumber({ value: 1, onChange: spy });
|
||||
|
||||
fireEvent.change(input, { target: { value: '' } });
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({ target: { name: 'number', type: 'number', value: null } });
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user