React hook form unit test - javascript

I have the following component:
const WorkProviderList: React.FC<StepComponentParams> = ({
metadata,
}): JSX.Element => {
const {
control,
setValue,
getValues,
} = useForm({
resolver: yupResolver(validationSchema),
defaultValues: { [CONFIRM_NAME]: true } as any,
mode: "all",
});
...
const isOtherFieldNameVisible = getValues()[FIELD_NAME] === "other";
console.log("isOtherFieldNameVisible: ", isOtherFieldNameVisible);
return (
<>
<Text element="h1" variant="title2Bold">
Which is your work provider?
</Text>
<Text>
If you work with more than one, select the provider that you work with
the most.
</Text>
<fieldset role="group" aria-labelledby={FIELD_NAME}>
<Controller
name={FIELD_NAME}
control={control}
render={() => (
<RadioGroupFields
fields={radioFields}
name={FIELD_NAME}
onChange={(e) => {
setValue(FIELD_NAME, e.target.value, {
shouldValidate: true,
});
}}
/>
)}
/>
</fieldset>
{isOtherFieldNameVisible && (
<div className="py-3">
<FormField
name={OTHER_FIELD_NAME}
label="What is the name of your work provider?"
>
<Controller
name={OTHER_FIELD_NAME}
control={control}
render={({ field: { onChange } }) => (
<TextField
id="other_input"
isFullWidth
name={OTHER_FIELD_NAME}
statusType="info"
onChange={onChange}
/>
)}
/>
</FormField>
</div>
)}
</>
);
};
and the following test:
const { getByLabelText, getByText } = render(
<WorkProviderList {...props} />
);
const input = getByLabelText("I work with a different work provider");
fireEvent.change(input, { target: { value: "other" } });
fireEvent.click(input);
const text = getByText("What is the name of your work provider?");
expect(text).toBeInTheDocument();
even though the console.log in the component returns true, I would expect to find the label I am looking for but I get the following error:
TestingLibraryElementError: Unable to find a label with the text of:
What is the name of your work provider?

Related

Formik field values arn't being passed from React Context

I have a Formik form that is using a progressive stepper and have multiple fields across different components, thus requiring the need to store the values in React Context. However none of the field values are being passed, so when I click submit, all values are empty strings and the validation fails. You can see on each Formik Field i am setting the value as {state.[field]}, which comes from the Context, so I believe something is going wrong here. Can anyone see what I'm doing wrong?
Thanks a lot
Here is my parent component
const AddSongPage = () => {
const { state, dispatch } = useUploadFormContext();
const initialValues = {
name: "",
};
const { mutate: handleCreateTrack } = useCreateSyncTrackMutation(
gqlClient,
{}
);
const handleSubmit = (values: any) => {
handleCreateTrack(
{
...values,
},
{
onSuccess() {
console.log("Track added succesfully");
},
}
);
};
const validate = Yup.object({
name: Yup.string().required("Song name is required"),
description: Yup.string().optional(),
});
return (
<Layout headerBg="brand.blue">
<Formik
onSubmit={(values) => handleSubmit(values)}
initialValues={initialValues}
validationSchema={validate}
>
<Form>
<Box> {state.activeStep === 1 && <Step1 />}</Box>
<Box> {state.activeStep === 2 && <Step2 />}</Box>
<Box> {state.activeStep === 3 && <Step3 />}</Box>
<Button type={"submit"}>Submit</Button>
</Form>
</Formik>
</Layout>
);
};
Here is step 1
const Step1 = () => {
const { state, dispatch } = useUploadFormContext();
const onInputChange = (e: FormEvent<HTMLInputElement>) => {
const inputName = e.currentTarget.name;
dispatch({
type: "SET_UPLOAD_FORM",
payload: {
[inputName]: e.currentTarget.value,
},
});
};
return (
<Stack spacing={4}>
<Field name={"name"}>
{({ field, form }: any) => (
<FormControl isInvalid={form.errors.name && form.touched.name}>
<Input
{...field}
onChange={onInputChange}
value={state.name}
/>
</FormControl>
)}
</Field>
<Field name={"description"}>
{({ field, form }: any) => (
<FormControl isInvalid={form.errors.name && form.touched.name}>
<Input
{...field}
onChange={onInputChange}
value={state.description}
/>
</FormControl>
)}
</Field>
</Stack>
);
};
export default Step1;

Cant Edit dynamic Textfield form graphql data in reactjs

I'm trying to create a dynamic textfield that takes data from gql like this
const { data } = useQuery(DATA_LIST, {
variables: {
param: {
limit: 10,
offset: 0,
sortBy: 'order'
}
}
});
const [state, setState] = useState<any>([]);
useEffect(() => {
if (data) {
setState(data?.dataList?.data);
}}, [data]);
then create a textField like this :
<TextField
name="name"
required
fullWidth
// label="Status Name"
onChange={(event) => handleChange(event, index)}
value={item?.name}
sx={{ marginRight: 5 }}
/>
<TextField
name="category"
required
fullWidth
select
// label="Category"
onChange={(event) => handleChange(event, index)}
value={item?.category}
>
{Category.map((option, index) => (
<MenuItem key={index} value={option.value}>
{option.name}
</MenuItem>
))}
</TextField>
handleChange :
const handleChangeInput = (
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
index: number
) => {
const values = [...state];
values[index][event.target.name] = event.target.value;
console.log(values[index], 'ini values');
setState(values);
};
and call the inputRow component like this (im using drag and drop for textField list) :
{state.map((item: any, index: any) => {
// console.log(statusName[index]);
return (
<Draggable key={item.id} draggableId={String(item.id)} index={index}>
{(provided, snapshot): JSX.Element => (
<div
key={index}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
>
<Box marginRight={2}>
<TypographyComponent text={index + 1} type={'subBody'} />
</Box>
<InputRow index={index} item={item} handleChange={handleChangeInput} />
</div>
)}
</Draggable>
);
})}
but when i try to type the textfield, an error appears that Cannot assign to read only property
error message
This is weird because if I input dummy data, the textfield can be modified, but if I use data from the API the data cannot be modified.

test react form submit with conditions which button clicked

React beginner on testing, i'm testing form submits, i have tested already few form submits successfully, but this one has 'Accept' button AND 'Delete' button and both uses same 'handleSubmit' function so thats why i have conditions inside that function, depending on which clicked. I need some advice on how to test form submit when
”Accept” button is clicked ?
English is not my mother language so could be mistakes. If any question just ask me.
My test will pass only if i delete one of those buttons but i need both.
test:
import React from "react";
import {
render,
screen,
RenderResult,
cleanup,
fireEvent,
getByRole,
getByDisplayValue,
} from "#testing-library/react";
const onFormSubmit = jest.fn();
const currentCamera = "0-0-0-2";
describe("Testing component", () => {
const AddCamera = () =>
render(
<Provider store={store}>
<MemoryRouter>
<CameraForm onSubmit={onFormSubmit} key={"cameraform-" + currentCamera} />
</MemoryRouter>
</Provider>
);
test("Testing Add Camera", () => {
AddCamera();
const AddName = screen.getByTestId(/^AddName/i);
expect(AddName).toBeInTheDocument();
fireEvent.change(AddName, { target: { value: "Camera 2" } });
expect(AddName).toHaveValue("Camera 2");
fireEvent.submit(screen.getByTestId("renderAddForm"));
expect(onFormSubmit).toHaveBeenCalled();
});
});
code:
import {
...
} from "#material-ui/core/";
const handleSubmit = (event) => {
event.preventDefault();
if (state.button === 1) {
if (!Camera) {
return;
}
const { name} = state;
refresh();
dispatch(
addCamera(site.identifier, Camera.identifier, {
name
})
);
externalOnSubmit();
}
if (state.button === 2) {
event.preventDefault();
if (!Camera) {
return;
}
refresh();
dispatch(
deleteCamera(site.identifier, Camera.identifier)
);
externalOnSubmit();
}
};
const renderAdd = () => {
const { helperText, error, name } = state;
return (
<React.Fragment>
<Box sx={boxStyle} data-testid="CameraForm">
<form
data-testid="renderAddForm"
onSubmit={handleSubmit}>
<div className="handle">
<Box>
<Button
onClick={nulll}
aria-label="close-settings-popup">
<Close />
</Button>
</Box>
</div>
<div>
<FormGroup>
<Box>
<FormControl>
<TextField
helperText={helperText.name}
error={error.name}
inputProps={{
"data-testid": "AddName",
}}
InputProps={{
className: classes.underline,
}}
InputLabelProps={{
className: classes.inputLabelColor,
}}
required={true}
type="text"
id="name"
value={name}
onChange={handleChange}
label='Name'
></TextField>
</FormControl>
</Box>
</FormGroup>
</div>
{!state.readOnly ? (
<div>
<Button
onClick={() => (state.button = 2)}
type="submit"
>
<Trans i18nKey="form.delete">Delete</Trans>
</Button>
<Button
data-testid="submitButton"
onClick={() => (state.button = 1)}
type="submit"
variant="contained"
color="primary"
disabled={
!currentSite ||
Object.values(state.error).some((v) => {
return v === true;
})
}
className={classes.button_basic}
startIcon={<Done />}
>
Accept
</Button>
</div>
) : (
""
)}
</form>
</Box>
</React.Fragment>
);
};

Accessing error from react-hook-form using reactstrap

I created a form with reactstrap and react-hook-form. Why are my errors not displaying?
Dummy section which renders text input:
function DummySection() {
const { control } = useForm();
return (
<div>
<Controller
name="dummyName"
control={control}
rules={{ required: true }}
defaultValue=""
render={({ field: { onChange, ref }, fieldState: { error } }) => (
<TextInput onChange={onChange} innerRef={ref} error={error} />
)}
/>
</div>
);
}
Text input with error:
function TextInput({ onChange, value, innerRef, error }) {
const updateText = (e) => {
onChange(e);
// do something else below
};
return (
<div>
<Input
name="dummyName"
type="text"
onChange={(e) => updateText(e)}
value={value}
innerRef={innerRef}
/>
{error && "Cannot be blank"}
</div>
);
}
Submit Button
function SubmitBtn() {
const { handleSubmit } = useForm();
const onSubmit = (data) => console.log(data);
return <Button onClick={() => handleSubmit(onSubmit)()}>Submit</Button>;
}
Thanks.
Code Sandbox
#jamfie, you are using different useForm for each component.
Your form need to have the same instance, you can do that by using useformcontext or by passing props to components.
Also, you do not need Controller for this thing,
here is codesandbox shows how you can use reactstrap with react-hook-form.
You can also follow this answer in github issue.

React Autocomplete with Material UI

I'm implementing a component Autocomplete using Material UI library.
But there's a problem - I'm not sure how to pass value and onChange properly, because I have a custom implementation of TextField that requires value and onChange as well. Should I pass value and onChange twice - to Autocomplete and TextField? Or maybe there's a better solution? Would appreciate any help!
Here's my code:
import { Autocomplete as MuiAutocomplete } from '#material-ui/lab'
import { FormControl } from 'components/_helpers/FormControl'
import { useStyles } from 'components/Select/styles'
import { Props as TextFieldProps, TextField } from 'components/TextField'
export type Props = Omit<TextFieldProps, 'children'> & {
options: Array<any>
value: string
onChange: (value: string) => void
disabled?: boolean
}
export const Autocomplete = (props: Props) => {
const classes = useStyles()
return (
<FormControl
label={props.label}
error={props.error}
helperText={props.helperText}
>
<MuiAutocomplete
options={props.options}
// value={props.value}
// onChange={event =>
// props.onChange((event.target as HTMLInputElement).value as string)
// }
classes={{
option: classes.menuItem,
}}
disabled={props.disabled}
getOptionLabel={option => option.label}
renderInput={params => (
<TextField
{...params}
placeholder={props.placeholder}
value={props.value}
onChange={props.onChange}
/>
)}
renderOption={option => {
return <Typography>{option.label}</Typography>
}}
/>
</FormControl>
)
}```
Material UI has props built in to handle the state of the Autocomplete vs input values.
You can see it in use in the docs here: https://material-ui.com/components/autocomplete/#controllable-states
In your example, you would want to add the inputChange and onInputChange props to the Autocomplete component. These will get passed down to your TextField through the params passed to the renderInput function.
So your final code would look something like the below snippet copied from the linked documentation:
<Autocomplete
value={value}
onChange={(event, newValue) => {
setValue(newValue);
}}
inputValue={inputValue}
onInputChange={(event, newInputValue) => {
setInputValue(newInputValue);
}}
id="controllable-states-demo"
options={options}
style={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Controllable" variant="outlined" />}
/>
import React, { useEffect, useState } from "react";
import { Autocomplete } from "#mui/material/node";
import { Controller, useFormContext } from "react-hook-form";
import { TextField } from "#mui/material";
import PropTypes from "prop-types";
const valueFunc = (arr, id) => {
const temp = arr.length > 0 && arr?.find((element) => element.id === id);
return temp;
};
AutocompleteSearch.propTypes = {
options: PropTypes.arrayOf({
title: PropTypes.string,
id: PropTypes.string,
}),
name: PropTypes.string,
};
export default function AutocompleteSearch({
name,
options,
label,
id,
...other
}) {
const [temp, setTemp] = useState({});
const { control, setValue } = useFormContext();
useEffect(async () => {
const found = valueFunc(options, id);
await setTemp(found);
}, [options, id]);
return (
<Controller
control={control}
name={name}
rules={{ required: true }}
render={({ fieldState: { error } }) => (
<>
<div >
<Autocomplete
id="controllable-states-demo"
onChange={(_, v) => {
setValue(name, v?.id);
setTemp(v);
}}
onBlur={(e) => {
e.target.value == "" && setValue(name, "");
}}
value={temp}
options={options}
getOptionLabel={(item) => (item.title ? item.title : "")}
renderInput={(params) => (
<>
<TextField
{...params}
label={label}
InputLabelProps={{
style: {
fontSize: "14px",
fontWeight: "400",
color: "#FF5B00",
},
}}
size="small"
error={temp === null && !!error}
helperText={temp === null && error?.message}
{...other}
/>
</>
)}
/>
</div>
</>
)}
/>
);
}
<AutocompleteSearch
name="pharmacy_group_title"
label="Pharmacy Group"
options={pharmacyGroups} // Array {id , title}
id={defaultValues?.pharmacy_group_title} // ID
/>

Categories

Resources