I am attempting to test that a callback function is being called by submitting a form. I have mocked an onSubmit function which is passed into react-final-form. As shown in the codesandbox and below, I've got a simple form with an onSubmit callback.
export const MyForm = ({ onSubmit }) => (
<Form
onSubmit={onSubmit}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit} autoComplete="off">
<Field
label="Email"
component={Input}
name="email"
type="email"
autoComplete="off"
/>
<button>Submit</button>
</form>
)}
/>
);
When I simulate a click event on the button, I expect it to call the mocked function.
it("should call onSubmit when the button is click", () => {
const button = wrapper.find("button");
expect(button.length).toBeGreaterThan(0);
button.at(0).simulate("click");
expect(mockSubmit).toHaveBeenCalled();
});
Any assistance would be greatly appreciated.
Codesandbox
You will need to simulate submit in order to submit the form.
As for the Warning: An update to ReactFinalForm inside a test was not wrapped in act(...)., you are using a promise in your submit handler in the test which causes the form validation, submit, and state updates to be async.
act() provides a scope around expected component updates, and you will get this warning when an component does something outside of this scope. Since in the test the submit handler is async, the updates will happen outside of the act() function and will give you this error.
There are two ways to fix this, make the submit handler sync via jest.fn().
const mockSubmit = jest.fn();
If you need to keep this async, you will need to act/await over a the submit promise. This would mean you would need to create a resolved promise value and have a mock function resolve it.
const promise = Promise.resolve();
const mockSubmit = jest.fn(() => promise);
beforeEach(() => {
wrapper = mount(<MyForm onSubmit={mockSubmit} />);
});
it("should call onSubmit when the button is click 2", async () => {
const button = wrapper.find("form");
expect(button.length).toBeGreaterThan(0);
button.at(0).simulate("submit");
expect(mockSubmit).toHaveBeenCalled();
await act(() => promise);
});
My preferred method is to use <button type="submit">Submit</button> and then fireEvent.click(getByText('Submit')), like this.
Related
React Hook Forms detect that I change the value of text input when I type something (onChange). But can it also detect the change if I enter data to the input by value={value}?
const validator = register(name);
I need something like
onValueSet={(e) => {
validator.onChange(e);
}}
I mean if I just set data by value={value} I get an error that this input is required. I don't want this error, because data was set by value={value}.
Here is my input:
<StyledInput
name={name}
maxLength={100}
value={value}
onChange={(e) => {
validator.onChange(e);
}}
/>
and my validation schema:
const validationSchema = Yup.object().shape({
first_name: Yup.string()
.required("required"),
});
You have to use reset here and call it when you received your initial form data. I assume you're doing an api call and want to set the result to the form. You can do so with useEffect, watching when you're data has resolved and then reset the form with the actual values. This way you don't have to set the value via the value prop and let RHF manage the state of your inputs. You also don't need to set name prop, as RHF's register call returns this prop as well.
const Component = (props) => {
const { result } = useApiCall();
const { register, reset } = useForm();
useEffect(() => {
reset(result)
}, [result]);
return (
...
<StyledInput
{...register('first_name')}
maxLength={100}
/>
...
)
}
Here is a little CodeSandbox demonstrating your use case:
You can define an onfocus or onkeyup event and call the validation function there instead of onChange event.
<StyledInput
name={name}
maxLength={100}
value={value}
onfocus={(e) => {
validator.onChange(e);
}}
/>
Instead of triggering the validator when input changes, you can instead call your validator through the onBlur event. And I am not sure how you are using react-hook-form .. but the useForm hook has a config mode (onChange | onBlur | onSubmit | onTouched | all = 'onSubmit') on when to trigger the validation:
The whole dispatch(addproductStart.....) section yields red lines with Expected an assignment or function call and instead saw an expression.eslintno-unused-expressions
I'm wondering what's wrong with the function.
It is essentially a handleSubmit function that takes the Forminput of an Image set to productThumbnail State, then gets put into Firebase.Storage(), then getDownloadURL() gets put into a new State to be used within the Dispatched Redux function.
It must be a syntax thing.
Thanks!
const handleSubmit = (e) => {
e.preventDefault();
const storageRef= storage.ref(productThumbnail.name);
storageRef.put(productThumbnail).then(() => {
const postproductThumbnail = storageRef.getDownloadURL();
setPostProductThumbnail(postproductThumbnail);
},
)
dispatch(
addProductStart({
productCategory,
productName,
postproductThumbnail,
productPrice,
productDesc,
})
),
resetForm()
}
return (
<h1> Manage Products</h1>
<FormInput
className="field"
label="Name"
type="text"
value={productName}
handleChange={(e) =>
setProductName(
e.target.value
)
}
/>
<FormInput
className="field"
label="Upload Product Image File"
type="file"
value={productThumbnail}
handleChange={(e) =>
setProductThumbnail(
e.target.value
)
}
/>
Both storageRef.put and storageRef.getDownloadURL are asynchronous operations. This means that your addProductStart call that uses postproductThumbnail is called before postproductThumbnail = storageRef.getDownloadURL() ever runs. You can most easily check this by adding some console.log statements, or setting breakpoints on these lines and running in a debugger.
On top of this problem your const postproductThumbnail won't be visible in the call to addProductStart because of where you declared it, but the main issue is the asynchronous nature of the calls.
To fix this:
Wait for the asynchronous result of storageRef.getDownloadURL with another then().
Move the call to dispatch(addProductStart(...)) into the callback.
So something like:
const storageRef= storage.ref(productThumbnail.name);
storageRef.put(productThumbnail).then(() => {
storageRef.getDownloadURL().then((postproductThumbnail) => {
setPostProductThumbnail(postproductThumbnail);
dispatch(
addProductStart({
productCategory,
productName,
postproductThumbnail,
productPrice,
productDesc,
})
)
})
I am testing a react component which contains a form(using formik) and I need to test if on submit button clicked, whether submit function is called or not.
at the moment, the test is failing.
now, the form has required fields schema too using yup
so, I was wondering whether I need to fill up all the fields before testing it.
because at the moment, it doesnt submit until the form has errors i.e. if the required fieldss are empty. so does that obstruct the testing of the button click and the function being called or not?
describe('submitform', () => {
let wrapper = '';
const handleSubmit = jest.fn();
beforeEach(() => {
wrapper = mount(<ExampleButton >
<span className="visible-sm">Next</span>
<span className="visible-xs font-entity">
›
</span>
</ExampleButton>
);
});
afterEach(() => {
wrapper.unmount();
});
it('call function on click',async ()=> {
// let btn = wrapper.find('#btnEx').find('button').find('#btnEx');
let btn = wrapper.find('button').simulate('click');
console.log('wrapper : ',btn.debug());
// btn.props().onClick();
expect(handleSubmit).toHaveBeenCalled();
});
})
how do I fill up the fields then, before testing? or is it even required for me to fill up the fields before testing on click?
You need a way to pass your mock handleSubmit function to your ExampleButton
If ExampleButton has onSubmit event handler prop, this is easier:
// ExampleButton.jsx
const ExampleButton = ({ onSubmit }) => <button type="submit" onClick={onSubmit} />;
// ExampleButton.test.jsx
const handleSubmit = jest.fn();
...
wrapper = mount(<ExampleButton onSubmit={handleSubmit} />);
If ExampleButton has inner event handler function, kinda tricky
// ExampleButton.jsx
const ExampleButton = () => {
const handleSubmit = (params) => {...}
return <button type="submit" onClick={handleSubmit} />;
}
// ExampleButton.test.jsx
wrapper = mount(<ExampleButton onSubmit={handleSubmit} />);
wrapper.find('button').simulate('click', mockParams);
Given the source code for a simple login form, see below. You see I want to use the username text field's value when I click the form's submit button. Since I need a reference to the actual DOM node to retrieve the value, I'm setting the usernameElement variable to that node.
const Login = ({ onLogin }) => {
let usernameElement
const handleSubmit = event => {
event.preventDefault()
onLogin(usernameElement.value)
}
return <form onSubmit={handleSubmit}>
<input
type="text"
name="username"
ref={node => { usernameElement = node }}
/>
<button type="submit">Login</button>
</form>
}
So, how would I make an functional approach to that problem, or simply get rid of the let variable?
Apparently, the correct approach to this is to make use of the component's state, meaning you need a stateful component instead of the stateless one.
// untested
class Login extends Component {
state = { username: '' }
handleChange = event => {
this.setState({ username: event.target.value })
}
handleSubmit = event => {
event.preventDefault()
this.props.onLogin(this.state.username)
}
render = () =>
<form onSubmit={this.handleSubmit}>
<input
type="text"
name="username"
value={this.state.username}
onChange={this.handleChange}
/>
<button type="submit">Login</button>
</form>
}
Using this, the username is always bound to this.state.username. The handleSubmit method can just use the username from the component's state.
See this page for further information: https://facebook.github.io/react/docs/forms.html
I have an issue where if I click the submit on redux-form (v.6) twice, very quickly, it will send two actions and then -> ajax requests to my server which will create a duplicate request.
What is the best way to disable the 'submit' button while the entry from the form is being saved in the database and enable it again after the entry has been stored. I tried the ( disabled={submitting} ) and it doesn't seem to work as an example below. There are not a lot of information on it so am I using it incorrectly?
class Timesheet extends Component {
...
onSubmit(props) {
console.log('submitting', props);
createTimesheet(props);
}
render() {
const {handleSubmit, reset, submitting} = this.props;
return(
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
... form details
<button type="submit" disabled={submitting} className="btn btn-primary">Submit</button>
</form>
)
}
}
...
const TimesheetForm = reduxForm({
form: 'TimesheetNewForm',
enableReinitialize: true
}
, null, {createTimesheet})(Timesheet);
...
Your onSubmit function should return a promise.
In that case this.props.submitting will become true before the promise is resolved.
You Can use promise like..
onSubmit={values => {
return new Promise(resolve => {
method(){
return resolve
}
});
});