react-testing-library for material ui Text input - reactjs

My Text Input is:
<TextField
className={classes.textField}
data-testid={name}
variant="outlined"
error={false}
required
onChange={(element) => {
if (onTextChange) {
onTextChange(name, element.target.value);
}
}}
disabled={!editEnable}
name={name}
label={label}
defaultValue={values}
fullWidth
/>;
and UI:
How to change the value of this text element in the React testing library?

In my case it works like this
it('should render input ', () => {
const field = screen.getByTestId('search-text-field').querySelector('input')
expect(field ).toBeInTheDocument()
fireEvent.change(field , {target: { value: 'google it'}});
expect(field.value).toBe('google it');
});

I don't think getting the input by the display value is a good idea as if that changes the whole test will fail. Instead you should get the label of the input field.
screen.getByLabelText(/^label/i)
Update
Just realised that my way only works if you include an id to the TextField and the ID must match the name. This does seem to be the preferred way to get the input via Material UI as you don't need to include a test-id or by the value.
<TextField
name={name}
id={name}
label={label}
{...otherProps}
/>

I often struggle to get Material UI and react-testing-library working. But if you know your "recipes" it's always the same.
Here is an example of an TextField
import * as React from 'react';
import { render, fireEvent } from '#testing-library/react';
import { TextField } from '#material-ui/core';
const inputMock = jest.fn();
const Test = () => (
<TextField
data-testid={name}
variant="outlined"
error={false}
required
onChange={inputMock}
name={name}
label={'label'}
defaultValue={'4711'}
placeholder={'Enter Number'}
fullWidth
/>
);
test('Input', () => {
const container = render(<Test />);
const input = container.getByDisplayValue('4711') as HTMLInputElement;
fireEvent.change(input, { target: { value: '42' } });
expect(input.value).toBe('42');
expect(inputMock.mock.calls).toHaveLength(1);
});
Here are some advises which selectors to use. So you can try a "better" one.
https://testing-library.com/docs/guide-which-query
Cheers
Thomas

Related

React testing by input type

I am trying to test my below component in which I am returning dynamic control based upon the switch condition:
const DataTableFormControl: FC<DataTableFormControlProp> = ({ displayName, isRequired, dataType, content, cellId }) => {
return (
(() => {
switch (dataType) {
case DataType.NUMBER:
return <TextField
id={cellId}
label={displayName}
defaultValue={content}
required={isRequired}
type="number"
/>
case DataType.DATE:
return <TextField
id={cellId}
label={displayName}
defaultValue={content}
type="date"
required={isRequired}
/>
case DataType.BOOLEAN:
return <FormControl variant="standard">
<Typography>{displayName}</Typography>
<Switch color="primary" defaultChecked={content === 'true'} />
</FormControl>
case DataType.PERCENT:
return <FormControl variant="standard">
<Typography>{displayName}</Typography>
<Input
id={cellId}
defaultValue={content}
required={isRequired}
endAdornment={<InputAdornment position="end">%</InputAdornment>}
/>
</FormControl>
default:
return <TextField
id={cellId}
label={displayName}
type="text"
required={isRequired}
defaultValue={content}
/>
}
})()
)
};
export { DataTableFormControl };
I tried below way to find the item but, how I can run an assertion based upon the TextField type and check if we are getting or have endAdornment property?
describe('DataTableFormControl', () => {
test('should render component with text dataType', () => {
const { container } = renderControl(DataType.TEXT);
const textElement = container.querySelector(`#${cellId}`);
expect(textElement).toBeInTheDocument();
// How we can run find if this element is type of text, date, or number.
});
});
In general React testing library discourages testing props just for the sake of checking that props passed to a component. The whole idea of React testing library is writing tests that translate to "how this is effecting the user?". In other words, a better test would check how does the existence of a prop affect the DOM (and by extension, the user), instead of just checking if a prop was passed or not.
I would also recommend avoiding using querySelector and to use getBy... queries which should be able to find any item you are looking for. In your case, since you are looking for inputs getByRole would be best. Depending on the type of the input you can do getByRole('textbox'), getByRole('checkbox') and so on.

latest react-hook-form error handling with material-ui TextField

I have difficulties, using react-hook-form with material-ui.
I prepared a codesandbox example.
import { TextField } from "#material-ui/core";
import React from "react";
import { useForm } from "react-hook-form";
import "./styles.css";
interface IMyForm {
vasarlo: string;
}
export default function App() {
const {
handleSubmit,
formState: { errors },
register
} = useForm<IMyForm>();
const onSubmit = (data: IMyForm) => {
alert(JSON.stringify(data));
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>First Name</label>
<TextField
variant="outlined"
margin="none"
label="Test"
{...register("vasarlo", {
required: "error text"
})}
error={errors?.vasarlo ? true : false}
helperText={errors?.vasarlo ? errors.vasarlo.message : null}
/>
<input type="submit" />
</form>
);
}
How can I properly use the register function, to get error message, write to input field, also the onSubmit function to work?
I couldn't find answer in the documentation for this scenario.
In react-hook-form v7, this is how you register an input:
<input {...register('name')} />
Calling register() will return necessary props for your input like onChange, onBlur and ref. These props make it possible for react-hook-form to keep track of your form data. Now when you use register with Material-UI TextField like this:
<TextField {...register('name')} />
You pass the ref property directly to the TextField while the correct place to put it is in the inputRef:
<TextField inputRef={ref} />
So you have to modify your code like this:
const { ref: inputRef, ...inputProps } = register("vasarlo", {
required: "error text"
});
<TextField inputRef={inputRef} {...inputProps} />
How can I properly use the register function, to get error message
There is nothing wrong with your error handling code. Though you can shorten you code a bit more using Typescript's optional chaining operator ?.:
<TextField
error={!!errors.vasarlo}
helperText={errors?.vasarlo?.message}
inputRef={ref}
{...inputProps}
/>
Live Demo
You're misusing Controller. react-hook-form's default functionality is using uncontrolled inputs. Remove <Controller/> and put this on your TextField
inputRef={register({
required: 'This is required',
validate: (data) => myValidationFunction(data)
})}
You'll only want to use a controller if you NEED to modify/intercept/format a value that is being displayed in the TextField that is different from what a user is typing, i.e a phone number getting shown as (xxx)-xxx-xxxx when only typing the digits.

How can we set default value for React RRule Generator in react-admin?

I am using react-admin and react-rrule-generator (https://github.com/Fafruch/react-rrule-generator). Create / Adding records is working fine while using rrule widget. But whenever I try to edit a record, the widget should have its values automatically filled based on the record's values. But the value is always the default one provided by the widget itself. Here is my code:
main_file.jsx
export const JobCreate = (props) => {
return (
<Create {...props}>
<SimpleForm>
<CustomRRuleInput name="recurrency" label="Recurrency" />
</SimpleForm>
</Create>
)
}
recurrency_field.jsx
export const CustomRRuleInput = (props) => {
const {
input: { onChange },
meta: { touched, error },
} = useInput(props)
return (
<Labeled label={props.label}>
<RRuleGenerator
onChange={onChange}
name={props.name}
/>
</Labeled>
)
}
If I add value={props.record.recurrency} in RRuleGenerator component, I can't change values because I kind of fixed / hardcoded its value which is constant even if I try to change them. If this widget had a prop called defaultValue then it would have worked out!
How can I achieve this?
If you check closely the documentation's Inputs/Writing your own input part you will notice that custom input compoenents using either useField or useInput hooks still receive the source prop which is passed inside the input as part of the hook parameters.
Try this:
Inside main_file.jsx
<CustomRRuleInput source="recurrency" label="Recurrency" />
Inside recurrency_field.jsx
const {
input: { name, onChange },
meta: { touched, error },
} = useInput(props)
return (
<Labeled label={props.label}>
<RRuleGenerator
onChange={onChange}
name={name}
/>
</Labeled>
)
Never mind I did it! I can use this for creation as well as updating records. I also used rrule library for converting rrule to human readable text which gets displayed in TextInput field just below RRule widget. The text dynamically changes when you change data in RRule widget.
recurrency_field.jsx
import RRuleGenerator from "react-rrule-generator"
import React, { useState } from "react"
import { useInput, Labeled, TextInput } from "react-admin"
import { rrulestr } from "rrule"
export const CustomRRuleInput = (props) => {
const record = props.record
const {
input: { onChange },
} = useInput(props)
const [state, setState] = useState(record[props.name])
return (
<>
<Labeled label={props.label}>
<RRuleGenerator
onChange={(val) => {
setState(val)
onChange(val)
}}
value={state}
name={props.name}
/>
</Labeled>
<TextInput
fullWidth
disabled
label={"RRule Text"}
value={state ? rrulestr(state).toText() : ""}
/>
</>
)
}
main_file.jsx
<CustomRRuleInput name="recurrency" label="Recurrency(r rule)" />

Focus Select element

I'm trying to programatically focus a select element in Material-UI in a useEffect block.
Trying to pass a ref to the component (ref={my ref}) itself does nothing, and trying to pass any variety of inputProps={{ ref: myRef }}, inputProps={{ inputRef: myRef }} throws an error for displayNode being undefined when I call focus() on the ref.
I am sure there is something obvious I am missing, what would be the correct syntax/way to do this?
const MyCard = ({
disabled,
}) => {
const inputField = React.useRef(null);
React.useEffect(() => {
if (!disabled && inputField.current) {
inputField.current.focus();
}
}, [inputField, disabled]);
return (
<Select
disabled={disabled}
inputProps={{ ref: inputField }}
required
id="answer"
name="answer"
autoComplete='off'
autoFocus
>
<MenuItem value="option1">option1</MenuItem>
<MenuItem value="option2">option2</MenuItem>
</Select>
)
}
Additional info from OP:
I need to be able to programmatically focus the select. Autofocus
works for me on the initial render, but not subsequently, specifically
I am trying to refocus the input after removing the disabled prop.
Make sure you check that it is defined before calling focus:
useEffect(() => {
if (myRef.current) {
myRef.current.focus()
}
}, [myRef])

Material-UI - TextField - select text programmatically

Material-UI V1 beta.
Could not find the answer in the Docs.
How do I select text of a TextField component?
Create a ref to it, then call the value of the ref. Something like this:
<TextField ref="myTextField" />
// Call this in the component that contains the text field so 'this' is set properly
function getTextFieldValue() {
return this.refs.myTextField.getValue();
}
This is known as an uncontrolled react component. An alternative would be to use a controlled component and save the value in your state. Here is some info on the difference between controlled and uncontrolled components: https://reactjs.org/docs/uncontrolled-components.html
if you are using a stateless functional component then you can use react hooks.
Also make sure you are using inputRef
import React, { useState, useRef } from "react";
let MyFunctional = props => {
let textInput = useRef(null);
return (
<div>
<Button
onClick={() => {
setTimeout(() => {
console.log(textInput.current.value);
}, 100);
}}
>
Focus TextField
</Button>
<TextField
fullWidth
required
inputRef={textInput}
name="firstName"
type="text"
placeholder="Enter Your First Name"
label="First Name"
/>
</div>
);
};

Resources