How to fire and test a real paste event (not simulated by calling the prop) in Jest and Enzyme - reactjs

I'm trying to unit test a very simple feature in a React app where I'm blocking the user from pasting into a textarea by adding an event.preventDefault() in the event handler, like so:
function handlePaste(event) {
event.preventDefault();
}
// ... pass it down as props
<TextareaComponent onPaste={handlePaste} />
The problem I'm having is that every method I've found of dispatching events in Jest or Enzyme just "simulates" the event by getting the function passed to the onPaste prop and calling it directly with a mock event object. That's not what I'm interested in testing.
Ideally I want to do something like this, testing that the actual value of the input hasn't changed after pasting:
const wrapper = mount(<ParentComponent inputValue="Prefilled text" />);
const input = wrapper.find(TextareaComponent);
expect(input.value).toEqual("Prefilled text")
input.doAPaste("Pasted text")
expect(input.value).not.toEqual("Pasted text")
expect(input.value).toEqual("Prefilled text")
But haven't been able to find a method that works. Any help would be appreciated!

Since you're just testing against a synthetic event (and not some sort of secondary action -- like a pop up that warns the user that pasting is disabled), then the easiest and correct solution is to simulate a paste event, pass it a mocked preventDefault function, and then assert that the mocked function was called.
Attempting to make assertions against a real paste event is pointless as this a React/Javascript implementation (for example, making assertions that a callback function is called when an onPaste/onChange event is triggered). Instead, you'll want to test against what happens as a result of calling the callback function (in this example, making assertions that event.preventDefault was called -- if it wasn't called, then we know the callback function was never executed!).
Working example (click the Tests tab to run the assertions):
To keep it simple, I'm just asserting that the input is initially empty and then only updates the value if an onChange event was triggered. This can very easily be adapted to have some sort of passed in prop influence the default input's value.
App.js
import React, { useCallback, useState } from "react";
const App = () => {
const [value, setValue] = useState("");
const handleChange = useCallback(
({ target: { value } }) => setValue(value),
[]
);
const handlePaste = useCallback((e) => {
e.preventDefault();
}, []);
const resetValue = useCallback(() => {
setValue("");
}, []);
const handleSubmit = useCallback(
(e) => {
e.preventDefault();
console.log(`Submitted value: ${value}`);
setValue("");
},
[value]
);
return (
<form onSubmit={handleSubmit}>
<label htmlFor="foo">
<input
id="foo"
type="text"
data-testid="test-input"
value={value}
onPaste={handlePaste}
onChange={handleChange}
/>
</label>
<br />
<button data-testid="reset-button" type="button" onClick={resetValue}>
Reset
</button>
<button type="submit">Submit</button>
</form>
);
};
export default App;
App.test.js
import React from "react";
import { configure, mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import App from "./App";
configure({ adapter: new Adapter() });
const value = "Hello";
describe("App", () => {
let wrapper;
let inputNode;
beforeEach(() => {
wrapper = mount(<App />);
// finding the input node by a 'data-testid'; this is not required, but easier
// when working with multiple form elements and can be easily removed
// when the app is compiled for production
inputNode = () => wrapper.find("[data-testid='test-input']");
});
it("initially displays an empty input", () => {
expect(inputNode()).toHaveLength(1);
expect(inputNode().props().value).toEqual("");
});
it("updates the input's value", () => {
inputNode().simulate("change", { target: { value } });
expect(inputNode().props().value).toEqual(value);
});
it("prevents the input's value from updating from a paste event", () => {
const mockPreventDefault = jest.fn();
const prefilledText = "Goodbye";
// updating input with prefilled text
inputNode().simulate("change", { target: { value: prefilledText } });
// simulating a paste event with a mocked preventDefault
// the target.value isn't required, but included for illustration purposes
inputNode().simulate("paste", {
preventDefault: mockPreventDefault,
target: { value }
});
// asserting that "event.preventDefault" was called
expect(mockPreventDefault).toHaveBeenCalled();
// asserting that the input's value wasn't changed
expect(inputNode().props().value).toEqual(prefilledText);
});
it("resets the input's value", () => {
inputNode().simulate("change", { target: { value } });
wrapper.find("[data-testid='reset-button']").simulate("click");
expect(inputNode().props().value).toEqual("");
});
it("submits the input's value", () => {
inputNode().simulate("change", { target: { value } });
wrapper.find("form").simulate("submit");
expect(inputNode().props().value).toEqual("");
});
});

Related

How can I test an input with Jest

I've been trying to figure out how to test different input methods but since I am new to this test methodology, I cannot get even close to the answer. Here is what I have:
const App = (props) => {
const newGame = props.newGame;
const [typeracertext, setTyperacertext] = useState(props.typeracertext);
const [wholeText, setWholeText] = useState("");
const onChange = (e) => {
//here I have code that read the input and is comparing it with variable - typeracertext and if so, it sets the property wholeText to that value
};
return (
<input ref={(node) => this.textInput = node} placeholder="Message..." onChange={onChange}></input>
);
}
so what I am trying to figure out is a test that should set the typeracertext to a certain value (for example "This is a test), and set the input value to "This" so if it passes the onChange() check it should set wholeText to "This". I hope that makes sense.
This is the best I could get and I don't have an idea what should I write on "expect".
test('Test the input value', () => {
const node = this.textInput;
node.value = 'This';
ReactTestUtils.Simulate.change(node);
expect()
});
Since this is a react app, I'll advice you take advantage of react testing library to make this easy
import React from 'react';
import { fireEvent, render, screen } from '#testing-library/react';
import userEvent from '#testing-library/user-event';
// In describe block
test('Test input component', () => {
const onChange = jest.fn();
render(<InputComponent onChange={onChange} data-test-id="input" />);
const input = screen.getByTestId('input');
fireEvent.change(input, { target: { value: 'a value' } });
// You can also do this with userEvent
userEvent.type(input, 'test')
// Check if change event was fired
expect((input as HTMLInputElement).onchange).toHaveBeenCalled();
});
See documentation here

Mock function doesn't get called when inside 'if' statement - React app testing with jest and enzyme?

I am writing a test case for my react app and I'm trying to simulate a button click with a mock function. I'm passing the mock function as a prop and I'm calling the function inside an 'if' statement but the mock function doesn't get called and the test fails but if i call the function without the 'if' statement it gets called and the test passes. Why is this happening?
Form.js
const Form = ({ text, incompleteList, setIncompleteList }) => {
const submitTodoHandler = (e) => {
e.preventDefault()
if (text !== '') {
setIncompleteList([...incompleteList, { name: text, id: Math.random() * 1000 }])
}
}
return (
<form action='' autoComplete='off'>
<button type='submit' className='todo-button' onClick={submitTodoHandler}>
add
</button>
</form>
)
}
export default Form
Form.test.js
import Enzyme, { shallow, mount } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
import Form from '../components/Form'
Enzyme.configure({ adapter: new Adapter() })
test('Form calls setIncompleteList prop on add button onClick event', () => {
const mockfn = jest.fn()
const wrapper = mount(<Form setIncompleteList={mockfn} />)
wrapper.find('button').simulate('click')
expect(mockfn).toHaveBeenCalled()
})
I'm using react 16.
The problem was I did not pass the 'text' props to the form component and the comparison failed to take place that's why the mock doesn't get called and the test failed.
<Form text='mock' setIncompleteList={mockfn} />
Pass value and incompleteList while mounting the component
test('Form calls setIncompleteList prop on add button onClick event', () => {
const mockfn = jest.fn()
const wrapper = mount(<Form text='mock'
incompleteList={[{name: 'sarun', id: 1001}]} setIncompleteList={mockfn} />)
wrapper.find('button').simulate('click')
expect(mockfn).toHaveBeenCalled()
})
you can also set a default value for incompletelist like below so that no need to pass incompletelist while mounting the component,
const Form = ({ text, incompleteList = [], setIncompleteList }) => {
}

Testing mouse event listener added using ref in React functional component

Hi I have a functional component as shown below:
import React, { useRef, useEffect, useState } from 'react';
const SomeComponent = ({ prop1, ...otherProps}) => {
const divRef = useRef();
useEffect(() => {
divRef.current.addEventListener('mousedown', mouseDownFunc);
}, []);
const mouseDownFunc = () => {
document.addEventListener('mousemove', (el) => {
// call some parent function
});
}
return (
<div
className='test-div'
ref={ divRef }>
</div>
);
};
How do I test a react functional component wherein addEventListener is added using ref inside useEffect which when triggered calls mouseDownFunc.
I'm new to react jest testing, little confused on how to do it.
Testing this sort of component can be tricky, but using #testing-library/react I think I was able to come up with something useful.
I did have to make some changes to your component to expose the API a bit, and I also made some changes so that it stops listening to the events on mouseup which may not be the specific event you want.
Here's the modified component:
// MouseDownExample.js
import React, { useEffect, useState } from "react";
export default ({ onMouseMoveWhileDown }) => {
const [x, setX] = useState(null);
const [listening, setListening] = useState();
// Replaced with mouse move function, should make sure we're unlistening as well
useEffect(() => {
if (listening) {
const onMouseMove = (event) => {
// call some parent function
onMouseMoveWhileDown(event);
console.log(event.clientX);
// purely for testing purposes
setX(event.clientX);
};
const onMouseUp = (event) => {
// stop listening on mouse up
// - you should pick whatever event you want to stop listening
// - this is global so it also stops when the mouse is outside the box
setListening(false);
};
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
return () => {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
};
}
}, [listening, onMouseMoveWhileDown]);
return (
<div
style={{
backgroundColor: "red",
width: 200,
height: 200
}}
className="test-div"
onMouseDown={() => {
// moved this inline, so no ref
setListening(true);
}}
>
X Position: {x}
</div>
);
};
I called out in comments the main differences.
And here's an example test:
// MouseDownExample.test.js
import React from "react";
import { fireEvent, render } from "#testing-library/react";
import MouseDownExample from "./MouseDownExample";
it("shouldn't trigger onMouseMoveWhileDown when mouse isn't down", () => {
const onMouseMoveWhileDown = jest.fn();
const { container } = render(
<MouseDownExample onMouseMoveWhileDown={onMouseMoveWhileDown} />
);
// Note: normally I would use `screen.getByRole` but divs don't have a useful role
const subject = container.firstChild;
fireEvent.mouseMove(
document,
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent
{
clientX: 200
}
);
// hasn't gone down yet
expect(onMouseMoveWhileDown).not.toHaveBeenCalled();
fireEvent.mouseDown(subject);
fireEvent.mouseUp(subject);
// went down then up before moving
fireEvent.mouseMove(document, {
clientX: 200
});
expect(onMouseMoveWhileDown).not.toHaveBeenCalled();
});
it("should trigger onMouseMoveWhileDown when mouse is down", () => {
const onMouseMoveWhileDown = jest.fn();
const { container } = render(
<MouseDownExample onMouseMoveWhileDown={onMouseMoveWhileDown} />
);
// Note: normally I would use `screen.getByRole` but divs don't have a useful role
const subject = container.firstChild;
fireEvent.mouseDown(subject);
fireEvent.mouseMove(document, {
clientX: 200
});
expect(onMouseMoveWhileDown).toHaveBeenCalledWith(
expect.objectContaining({ clientX: 200 })
);
});
What's happening here, is we're rendering the component, then firing events to ensure the onMouseMoveWhileDown function prop is called when we expect.
We have to do expect.objectContaining rather than just the object because it's called with a MouseEvent which contains other properties.
Another test we might want to add is an unmount test to ensure the listeners are no longer triggering events.
You can look at/experiment with this Code Sandbox with this component and the tests. Hope this helps 👍

Jest/Enzyme Shallow testing RFC - not firing jest.fn()

I'm trying to test the onChange prop (and the value) of an input on an RFC. On the tests, trying to simulate the event doesn't fire the jest mock function.
The actual component is connected (with redux) but I'm exporting it also as an unconnected component so I can do a shallow unit test. I'm also using some react-spring hooks for animation.
I've also tried to mount instead of shallow the component but I still get the same problem.
MY Component
export const UnconnectedSearchInput: React.FC<INT.IInputProps> = ({ scrolled, getUserInputRequest }): JSX.Element => {
const [change, setChange] = useState<string>('')
const handleChange = (e: InputVal): void => {
setChange(e.target.value)
}
const handleKeyUp = (): void => {
getUserInputRequest(change)
}
return (
<animated.div
className="search-input"
data-test="component-search-input"
style={animateInputContainer}>
<animated.input
type="text"
name="search"
className="search-input__inp"
data-test="search-input"
style={animateInput}
onChange={handleChange}
onKeyUp={handleKeyUp}
value={change}
/>
</animated.div>
)
}
export default connect(null, { getUserInputRequest })(UnconnectedSearchInput);
My Tests
Here you can see the test that is failing. Commented out code is other things that I-ve tried so far without any luck.
describe('test input and dispatch action', () => {
let changeValueMock
let wrapper
const userInput = 'matrix'
beforeEach(() => {
changeValueMock = jest.fn()
const props = {
handleChange: changeValueMock
}
wrapper = shallow(<UnconnectedSearchInput {...props} />).dive()
// wrapper = mount(<UnconnectedSearchInput {...props} />)
})
test('should update input value', () => {
const input = findByTestAttr(wrapper, 'search-input').dive()
// const component = findByTestAttr(wrapper, 'search-input').last()
expect(input.name()).toBe('input')
expect(changeValueMock).not.toHaveBeenCalled()
input.props().onChange({ target: { value: userInput } }) // not geting called
// input.simulate('change', { target: { value: userInput } })
// used with mount
// act(() => {
// input.props().onChange({ target: { value: userInput } })
// })
// wrapper.update()
expect(changeValueMock).toBeCalledTimes(1)
// expect(input.prop('value')).toBe(userInput);
})
})
Test Error
Nothing too special here.
expect(jest.fn()).toBeCalledTimes(1)
Expected mock function to have been called one time, but it was called zero times.
71 | // wrapper.update()
72 |
> 73 | expect(changeValueMock).toBeCalledTimes(1)
Any help would be greatly appreciated since it's been 2 days now and I cn't figure this out.
you don't have to interact with component internals; instead better use public interface: props and render result
test('should update input value', () => {
expect(findByTestAttr(wrapper, 'search-input').dive().props().value).toEqual('');
findByTestAttr(wrapper, 'search-input').dive().props().onChange({ target: {value: '_test_'} });
expect(findByTestAttr(wrapper, 'search-input').dive().props().value).toEqual('_test_');
}
See you don't need to check if some internal method has been called, what's its name or argument. If you get what you need - and you require to have <input> with some expected value - it does not matter how it happened.
But if function is passed from the outside(through props) you will definitely want to verify if it's called at some expected case
test('should call getUserInputRequest prop on keyUp event', () => {
const getUserInputRequest = jest.fn();
const mockedEvent = { target: { key: 'A' } };
const = wrapper = shallow(<UnconnectedSearchInput getUserInputRequest={getUserInputRequest } />).dive()
findByTestAttr(wrapper, 'search-input').dive().props().onKeyUp(mockedEvent)
expect(getUserInputRequest).toHaveBeenCalledTimes(1);
expect(getUserInputRequest).toHaveBeenCalledWith(mockedEvent);
}
[UPD] seems like caching selector in interm variable like
const input = findByTestAttr(wrapper, 'search-input').dive();
input.props().onChange({ target: {value: '_test_'} });
expect(input.props().value).toEqual('_test_');
does not pass since input refers to stale old object where value does not update.
At enzyme's github I've been answered that it's expected behavior:
This is intended behavior in enzyme v3 - see https://github.com/airbnb/enzyme/blob/master/docs/guides/migration-from-2-to-3.md#calling-props-after-a-state-change.
So yes, exactly - everything must be re-found from the root if anything has changed.

How to test props that are updated by an onChange handler in react testing library?

I've got an onChange handler on an input that I'm trying to test based on what I've read in the Dom Testing Library docs here and here.
One difference in my code is that rather than using local state to control the input value, I'm using props. So the onChange function is actually calling another function (also received via props), which updates the state which has been "lifted up" to another component. Ultimately, the value for the input is received as a prop by the component and the input value is updated.
I'm mocking the props and trying to do a few simple tests to prove that the onChange handler is working as expected.
I expect that the function being called in the change handler to be called the same number of times that fireEvent.change is used in the test, and this works with:
const { input } = setup();
fireEvent.change(input, { target: { value: "" } });
expect(handleInstantSearchInputChange).toHaveBeenCalledTimes(1);
I expect that the input.value is read from the original mock prop setup, and this works with:
const { input } = setup();
expect(input.value).toBe("bacon");
However, I'm doing something stupid (not understanding mock functions at all, it would seem), and I can't figure out why the following block does not update the input.value, and continues to read the input.value setup from the original mock prop setup.
This fails with expecting "" / received "bacon" <= set in original prop
fireEvent.change(input, { target: { value: "" } });
expect(input.value).toBe("");
QUESTION: How can I write a test to prove that the input.value has been changed given the code below? I assume that I need the mock handleInstantSearchInputChange function to do something, but I don't really know what I'm doing here quite yet.
Thanks for any advice on how to do and/or better understand this.
Test File
import React from "react";
import InstantSearchForm from "../../components/InstantSearchForm";
import { render, cleanup, fireEvent } from "react-testing-library";
afterEach(cleanup);
let handleInstantSearchInputChange, props;
handleInstantSearchInputChange = jest.fn();
props = {
foodSearch: "bacon",
handleInstantSearchInputChange: handleInstantSearchInputChange
};
const setup = () => {
const utils = render(<InstantSearchForm {...props} />);
const input = utils.getByLabelText("food-search-input");
return {
input,
...utils
};
};
it("should render InstantSearchForm correctly with provided foodSearch prop", () => {
const { input } = setup();
expect(input.value).toBe("bacon");
});
it("should handle change", () => {
const { input } = setup();
fireEvent.change(input, { target: { value: "" } });
expect(input.value).toBe("");
fireEvent.change(input, { target: { value: "snickerdoodle" } });
expect(input.value).toBe("snickerdoodle");
});
Component
import React from "react";
import PropTypes from "prop-types";
const InstantSearchForm = props => {
const handleChange = e => {
props.handleInstantSearchInputChange(e.target.value);
};
return (
<div className="form-group">
<label className="col-form-label col-form-label-lg" htmlFor="food-search">
What did you eat, fatty?
</label>
<input
aria-label="food-search-input"
className="form-control form-control-lg"
onChange={handleChange}
placeholder="e.g. i ate bacon and eggs for breakfast with a glass of whole milk."
type="text"
value={props.foodSearch}
/>
</div>
);
};
InstantSearchForm.propTypes = {
foodSearch: PropTypes.string.isRequired,
handleInstantSearchInputChange: PropTypes.func.isRequired
};
export default InstantSearchForm;
The way you are thinking about your tests is slightly incorrect. The behavior of this component is purely the following:
When passed a text as a prop foodSearch renders it correctly.
Component calls the appropriate handler on change.
So only test for the above.
What happens to the foodSearch prop after the change event is triggered is not the responsibility of this component(InstantSearchForm). That responsibility lies with the method that handles that state. So, you would want to test that handler method specifically as a separate test.

Resources