Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] Migrate NullableBooleanInput to use useInput #3522

Merged
merged 2 commits into from
Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 80 additions & 98 deletions packages/ra-ui-materialui/src/input/NullableBooleanInput.js
Original file line number Diff line number Diff line change
@@ -1,118 +1,100 @@
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import TextField from '@material-ui/core/TextField';
import MenuItem from '@material-ui/core/MenuItem';
import { withStyles, createStyles } from '@material-ui/core/styles';
import compose from 'recompose/compose';
import { makeStyles } from '@material-ui/core/styles';
import classnames from 'classnames';
import { addField, translate, FieldTitle } from 'ra-core';
import { useInput, useTranslate, FieldTitle } from 'ra-core';

import sanitizeRestProps from './sanitizeRestProps';
import InputHelperText from './InputHelperText';

const styles = theme =>
createStyles({
input: { width: theme.spacing(16) },
});

export class NullableBooleanInput extends Component {
state = {
value: this.props.input.value,
};
const useStyles = makeStyles(theme => ({
input: { width: theme.spacing(16) },
}));

componentWillReceiveProps(nextProps) {
if (nextProps.input.value !== this.props.input.value) {
this.setState({ value: nextProps.input.value });
}
}

handleChange = event => {
this.props.input.onChange(
this.getBooleanFromString(event.target.value)
);
this.setState({ value: event.target.value });
};
const getBooleanFromString = value => {
if (value === 'true') return true;
if (value === 'false') return false;
return null;
};

getBooleanFromString = value => {
if (value === 'true') return true;
if (value === 'false') return false;
return null;
};
const getStringFromBoolean = value => {
if (value === true) return 'true';
if (value === false) return 'false';
return '';
};

getStringFromBoolean = value => {
if (value === true) return 'true';
if (value === false) return 'false';
return '';
};
const NullableBooleanInput = ({
className,
helperText,
label,
onBlur,
onChange,
onFocus,
options,
resource,
source,
validate,
...rest
}) => {
const classes = useStyles();
const translate = useTranslate();

render() {
const {
classes,
className,
isRequired,
label,
meta,
options,
resource,
source,
translate,
helperText,
...rest
} = this.props;
const { touched, error } = meta;
return (
<TextField
select
margin="normal"
value={this.getStringFromBoolean(this.state.value)}
label={
<FieldTitle
label={label}
source={source}
resource={resource}
isRequired={isRequired}
/>
}
error={!!(touched && error)}
helperText={
<InputHelperText
touched={touched}
error={error}
helperText={helperText}
/>
}
className={classnames(classes.input, className)}
{...options}
{...sanitizeRestProps(rest)}
onChange={this.handleChange}
>
<MenuItem value="" />
<MenuItem value="false">
{translate('ra.boolean.false')}
</MenuItem>
<MenuItem value="true">{translate('ra.boolean.true')}</MenuItem>
</TextField>
);
}
}
const {
id,
input,
isRequired,
meta: { error, touched },
} = useInput({
format: getStringFromBoolean,
onBlur,
onChange,
onFocus,
parse: getBooleanFromString,
resource,
source,
type: 'checkbox',
validate,
});
return (
<TextField
id={id}
{...input}
select
margin="normal"
label={
<FieldTitle
label={label}
source={source}
resource={resource}
isRequired={isRequired}
/>
}
error={!!(touched && error)}
helperText={
<InputHelperText
touched={touched}
error={error}
helperText={helperText}
/>
}
className={classnames(classes.input, className)}
{...options}
{...sanitizeRestProps(rest)}
>
<MenuItem value="">{translate('ra.boolean.null')}</MenuItem>
<MenuItem value="false">{translate('ra.boolean.false')}</MenuItem>
<MenuItem value="true">{translate('ra.boolean.true')}</MenuItem>
</TextField>
);
};

NullableBooleanInput.propTypes = {
classes: PropTypes.object,
className: PropTypes.string,
input: PropTypes.object,
isRequired: PropTypes.bool,
label: PropTypes.string,
meta: PropTypes.object,
options: PropTypes.object,
resource: PropTypes.string,
source: PropTypes.string,
translate: PropTypes.func.isRequired,
};

const enhance = compose(
addField,
translate,
withStyles(styles)
);

export default enhance(NullableBooleanInput);
export default NullableBooleanInput;
47 changes: 26 additions & 21 deletions packages/ra-ui-materialui/src/input/NullableBooleanInput.spec.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,40 @@
import { shallow } from 'enzyme';
import React from 'react';
import { fireEvent, render } from '@testing-library/react';
import { Form } from 'react-final-form';

import { NullableBooleanInput } from './NullableBooleanInput';
import NullableBooleanInput from './NullableBooleanInput';

describe('<NullableBooleanInput />', () => {
const defaultProps = {
input: {},
meta: {},
classes: {},
translate: x => x,
source: 'isPublished',
resource: 'posts',
};

it('should give three different choices for true, false or unknown', () => {
const wrapper = shallow(
<NullableBooleanInput source="foo" {...defaultProps} />
let formApi;
const { getByText, getByRole, getAllByRole } = render(
<Form
onSubmit={jest.fn}
render={({ form }) => {
formApi = form;
return <NullableBooleanInput {...defaultProps} />;
}}
/>
);
const MenuItemElements = wrapper.find(
'WithStyles(ForwardRef(MenuItem))'
);
expect(MenuItemElements.length).toEqual(3);
const select = getByRole('button');
fireEvent.click(select);
const options = getAllByRole('option');
expect(options.length).toEqual(3);

const MenuItemElement1 = MenuItemElements.at(0);
expect(MenuItemElement1.prop('value')).toEqual('');
expect(MenuItemElement1.children().length).toEqual(0);
fireEvent.click(getByText('ra.boolean.null'));
expect(formApi.getState().values.isPublished).toBeNull();

const MenuItemElement2 = MenuItemElements.at(1);
expect(MenuItemElement2.prop('value')).toEqual('false');
expect(MenuItemElement2.childAt(0).text()).toEqual('ra.boolean.false');
fireEvent.click(select);
fireEvent.click(getByText('ra.boolean.false'));
expect(formApi.getState().values.isPublished).toEqual(false);

const MenuItemElement3 = MenuItemElements.at(2);
expect(MenuItemElement3.prop('value')).toEqual('true');
expect(MenuItemElement3.childAt(0).text()).toEqual('ra.boolean.true');
fireEvent.click(select);
fireEvent.click(getByText('ra.boolean.true'));
expect(formApi.getState().values.isPublished).toEqual(true);
});
});