Jest testing the event target that was collected onClick - javascript

I am testing that myFunction is called when a button is clicked. myFunction actually just calls another function and passes some stuff to it, including the element that was clicked, which I expect to actually be the img inside the button.
E.g:
<button class="button" onClick=myFunction(someArgs)>
<img class="imageInsideButton" />
/>
myFunction = (someArgs) => (event) =>
this.props.someOtherFunction(
someArgs
event.target
);
In my test I am trying to check that someOtherFunction was called with the expected args, which includes event.target
function render(args, renderer = shallow) {
const component = renderer(<MyComponent {...args} />);
return {
component,
buttons: () => component.find(".button"),
imagesInsideButtons: () => component.find(".imageInsideButton"),
};
}
beforeEach(() => {
myProps = {
myFunction: jest.fn(),
};
});
it("Should call someOtherFunction with the correct args", () => {
const { buttons, imagesInsideButtons } = render(defaultArgs, mount);
const indexClicked = 1;
buttons().at(indexClicked).simulate("click");
expect(myProps.someOtherFunction).toHaveBeenCalledWith(
someArgs, imagesInsideButtons().at(indexClicked)
);
});
So in this test I am simulating a click of one of the buttons() (in this case the second one listed). I'm expecting the event target that was clicked to be the img that was wrapped inside this button.
This does sort of work, but the problem appears to be that my test expects a ReactComponent but instead gets an actual node:
Expected
the other args,
ReactWrapper {}
Received
the other args,
<button class="button"><img class"imageInsideButton" /></button>
It seems like the result is sort of OK, in terms of getting this event target, but the way in which I have written the test means these are not matching. How do I make this test work?

When simulating the click, you could pass a fake target, and expect THAT target.
buttons().at(indexClicked).simulate("click", { target: 999 });
// test
expect(myProps.someOtherFunction).toHaveBeenCalledWith(
someArgs, 999
);

Related

Insufficient Jest coverage

I have the below code:
<ReturnToLastSearch
href={'/listings'}
onClick={(e): void => {
e.preventDefault();
router.back();
}}
/>
Inside ReturnToLastSearch({ href, onClick }: Props) has the following:
<a className="button" onClick={!onClick ? urlOnClick : onClick}>
Everything is working fine, except jest complains that my diff, which adds the onClick prop and the ternary has insufficient tests coverage!
I tried:
it('when onClick is defined uses onClick', () => {
const call = ()=> ({back: jest.fn()});
jest.mock('next/router', call)
const { getByText } = render(<ReturnToLastSearch href="foo" onClick={call}/>);
getByText(i18n.t('favorites.returnToLastSearch') as string).click();
expect(router.back).toHaveBeenCalledWith('/listings')
});
I tried the above, which is the same as the version without onClick except for the expect and the mock of router.back(). However, I got an error telling me that the second argument in the jest.mock() should be an inline function.
What sort of test would make sense and also convince jest to leave me alone?
Thank you
I ended-up with:
it('when onClick is defined uses onClick', () => {
const call = jest.fn();
const { getByText } = render(<ReturnToLastSearch href="foo" onClick={call} />);
getByText(i18n.t('favorites.returnToLastSearch') as string).click();
expect(call.mock.calls.length).toBe(1);
});
It is not the most useful test, in my opinion. Yet, it did get jest off my back.

How can I pass an argument to onCancel from {...bind} using useLongPress.js in React?

I have some code like this in React. I'd like to intercept the single click and tap, and the long click e long press so I used useLongPress.js.
So, I thought to remove onClick={() => start(preset)} from the button and call start(preset) from shortPress function. But how can I pass the preset argument as well?
const Presets = ({presetsList: presets, presetStart: start}) => {
const longPress = () => {
console.log('long click or long press');
// Some other code...
}
const shortPress = event => {
console.log('single click or tap')
// start(preset)
}
const bind = useLongPress(longPress, {onCancel: shortPress} )
return (
<section onContextMenu={(e)=> e.preventDefault()}>
{presets.map( preset => {
return <button
key={presets.indexOf(preset)}
onClick={() => start(preset)}
{...bind}
type="button"
>
<span>{preset.time}</span>
</button>
})}
</section>
)
}
export default Presets;
Basically, I'd need to pass an argument to onCancel callback from {...bind}. Is there a way to do it?
TL;DR
{presets.map(preset => {
const bind = useLongPress(longPress, { onCancel: shortPress.bind(null, preset) })
return (
<button
key={presets.indexOf(preset)}
onClick={() => start(preset)}
type="button"
{...bind}
>
<span>{preset.time}</span>
</button>
})}
Please read:
If you take a look in the code of that hook library, the hook is returning a bunch of event handlers for you to spread on your element. And it relies on these event handlers to know when the long press happens or stops, etc.
For a few of these event listeners and handlers, a callback will be called that performs some logic and conditionally runs the function you provide as onCancel for you to do your own logic.
Unfortunately, the package you mentioned does not return the actual cancel function for you to use, but what it does return is an object that has the cancel function as the value of some of its properties.
This object contains the event handlers we talked about earlier (the ones you are spreading on your element) and it looks like this:
{
onMouseDown: start as MouseEventHandler<Target>,
onMouseMove: handleMove as MouseEventHandler<Target>,
onMouseUp: cancel as MouseEventHandler<Target>,
onMouseLeave: cancel as MouseEventHandler<Target>,
onTouchStart: start as TouchEventHandler<Target>,
onTouchMove: handleMove as TouchEventHandler<Target>,
onTouchEnd: cancel as TouchEventHandler<Target>,
}
But the problem is the fact that this cancel function is where useLongPress.js performs its logic and calls your onCancel method. This is also using a useCallback. So really the only place where you can bind your argument to your function is when you are calling the useLongPress hook.
In conclusion, as a dirty workaround you can try to write your code like this, but this is not necessarily the most performant or pretty looking code. So my suggestion to you is to change your library. (It's actually pretty simple, maybe even create your own hook?)
{presets.map(preset => {
const bind = useLongPress(longPress, { onCancel: shortPress.bind(null, preset) })
return (
<button
key={presets.indexOf(preset)}
onClick={() => start(preset)}
type="button"
{...bind}
>
<span>{preset.time}</span>
</button>
})}

How can I pass other arguments to event handler in React?

I was trying to pass both the event and some other data (the index of an element) to an event handler in a React App. I found here that I should make a higher-order function, but I don't get a pleasant result. Here is a snippet of my code:
const changeNameHandler = (index) => {
return (event) => {
console.log(index, event);
};
};
let persons = null;
if(showValuesState) {
persons = (
<div>
{
personsState.map((person, index) => {
return <Person
name = {person.name}
age = {person.age}
click = {() => deletePersonHandler(index)}
key = {index}
changeName = {() => changeNameHandler(index)()}/>
})
}
</div>
);
}
I am trying to pass the index to changeNameHandler function while also getting the event, but the value of the event variable from that console.log(index, event) statement is still undefined.
when ever you are calling a function from inside your jsx, if you wanted to pass extra values rather that the event, you have to consider doing like below:
changeName = {() => changeNameHandler(index)}/>
Also the important point is that you are getting the function hooked up on the changeName prop of your Person component but you don't capture the event value from it, so if you need to access the event value inside of your handler you have to do it like below:
changeName = {(evt) => changeNameHandler(evt, index)}/>
and by doing this you will get the event as first argument and he index value as second argument in your handler.
const changeNameHandler = (evt, index) => {
// do what ever you want here with values
}

React-jest-enzyme: testing callback of child component that calls another function first before calling the callback

I am testing wether a callback that is passed to a child component is called after my button in the child component is clicked. I simulate the react-bootstrap button, <Button></Button>, by using the .simulate('click') function.
The problem is that the onClick() function of my button calls another function called update() and that function calls the handleSave callback passed to my child component. The onKeyPress function of the <FormControl/> element also calls the update function of my component. Here is how I have my child component setup:
update(event) {
//Have to check to see if the key stroke is the enter key or if it comes from the button click.
if(event.charCode === 13 || event.type === 'react-click'){
// Have to use this get the ref value because this.refs.input.value doesn't work.
var input = ReactDOM.findDOMNode(this.refs.input);
input.value = '';
this.props.handleSave();
}
}
render(){
return(
<Form>
<FormControl type="text" ref="input" onKeyPress={this.update.bind(this)} placeholder="Enter Note" />
<Button onClick={this.update.bind(this)}>Submit </Button>
</Form>
)
}
That is why my update() function has a check to see if came from charCode==13, that is the charCode for the enter key, or the button click because both save the info that is in the <FormControl />
I have my test setup this way:
describe('Input', () => {
const mockHandleText = jest.fn();
const mockHandleSave = jest.fn();
const props = {handleSave: mockHandleSave}
let input = shallow(<Input {...props} />);
describe('when entering a note', () => {
beforeEach(() => {
input.find('Button').simulate('click', {
charCode: 13
});
});
it('adds the note to state', () => {
expect(props.handleSave).toHaveBeenCalled();
});
});
});
A weird thing is that I have to pass an object as a second parameter to the .simulate() function because if I don't it will give me an error saying cannot read charCode of undefined but when a pass an object, the object doesn't even have to have an event property, then it just says
expect(jest.fn()).toHaveBeenCalled()
Expected mock function to have been called.
Also If I don't pass in the object with some property then it also breaks my other test that I have for a callback on the onChange function of my element. I left it out of the code sample for the sake of simplicity and just uploaded the code that is giving me problems. I am also using a bootstrap form with and . The full code is on my github at github.com/Alebron23.
Enzyme's shallow method doesn't render the whole DOM tree, just the most shallow level. You'll not be able to find nested children using it. In the docs for shallow (https://github.com/airbnb/enzyme/blob/master/docs/api/shallow.md), they discuss that if you need to assert any behavior on child components, you'll have to use something other than shallow().
Your other options are to either use render(), or more likely- since render() is static and you want to test side effects- to fully mount()
the component (https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md) instead.

React onClick - pass event with parameter

Without Parameter
function clickMe(e){
//e is the event
}
<button onClick={this.clickMe}></button>
With Parameter
function clickMe(parameter){
//how to get the "e" ?
}
<button onClick={() => this.clickMe(someparameter)}></button>
I want to get the event. How can I get it?
Try this:
<button
onClick={(e) => {
this.clickMe(e, someParameter);
}}
>
Click Me!
</button>
And in your function:
function clickMe(event, someParameter){
//do with event
}
With the ES6, you can do in a shorter way like this:
const clickMe = (parameter) => (event) => {
// Do something
}
And use it:
<button onClick={clickMe(someParameter)} />
Solution 1
function clickMe(parameter, event){
}
<button onClick={(event) => {this.clickMe(someparameter, event)}></button>
Solution 2
Using the bind function is considered better, than the arrow function way, in solution 1.
Note, that the event parameter should be the last parameter in the handler function
function clickMe(parameter, event){
}
<button onClick={this.clickMe.bind(this, someParameter)}></button>
Currying with ES6 example:
const clickHandler = param => event => {
console.log(param); // your parameter
console.log(event.type); // event type, e.g.: click, etc.
};
Our button, that toggles handler:
<button onClick={(e) => clickHandler(1)(e)}>Click me!</button>
If you want to call this function expression without an event object, then you'd call it this way:
clickHandler(1)();
Also, since react uses synthetic events (a wrapper for native events), there's an event pooling thing, which means, if you want to use your event object asynchronously, then you'd have to use event.persist():
const clickHandler = param => event => {
event.persist();
console.log(event.target);
setTimeout(() => console.log(event.target), 1000); // won't be null, otherwise if you haven't used event.persist() it would be null.
};
Here's live example: https://codesandbox.io/s/compassionate-joliot-4eblc?fontsize=14&hidenavigation=1&theme=dark
To solve the creating new callback issue completely, utilize the data-* attributes in HTML5 is the best solution IMO.
Since in the end of the day, even if you extract a sub-component to pass the parameters, it still creates new functions.
For example,
const handleBtnClick = e => {
const { id } = JSON.parse(e.target.dataset.onclickparam);
// ...
};
<button onClick={handleBtnClick} data-onclickparam={JSON.stringify({ id: 0 })}>
See https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes for using data-* attributes.
<Button onClick={(e)=>(console.log(e)}>Click Me</Button>

Categories