So I have an element that has a listener that uses currentTarget for some stuff and while I was doing some test for it I saw that when using Simulate to trigger an event no matter that you specify the currentTarget it will always be the original element.
There is a dumb example here: http://jsbin.com/sazovikiga/1/edit?js,console,output
If you click in the h1 it will log the target tagName and the currentTarget tagName after doing:
Simulate.keyUp(element, {target: {tagName: 'TARGET'}, currentTarget: {tagName: 'CURRENT'}});
So i was expecting that in the listener if I checked the currentTarget.tagName i would get "CURRENT" but I get the actual element while for target I got the (I think right value of) "TARGET" name instead of the element.
You shouldn't be able to specify currentTarget; it should get set automatically to correspond to the event handler receiving the event. If that's not working then it's probably just a bug.
I understand the the currentTarget is set automatically in the regular events, as the target does, but I thought that the point of Simulate was dispatching events that I can customise so I can use it for testing.
If I have a component that uses currentTarget in a listener and I want to write a unit test for it, how can I test it? If I can't specify a currentTarget in simulate then I will never be able to test that method, and certainly changing it to target is not an option as they are quite different things.
It also doesn't appear to work with checkbox inputs. Simulating a change event on the checkbox does not modify its checked status and you're not able to modify the currentTarget (as previously outlined) to update this manually.
Sorry for the delay here. currentTarget is set as the element bubbles and is always going to point to the DOM node that has the listener attached, no matter what element you dispatch the event to. Simulate does the bubbling like real events, so this can't be changed. So if you do
<div onClick={foo}>
<span />
</div>
and do Simulate.click(span)
, the div should receive a click event with .currentTarget
pointing to the div DOMElement and .target
pointing to the span. If that's not happening then there is a bug but if you are seeing this behavior then it is expected.
I understand what you are saying but the same principle apply to the target, it gets assigned based on the element the listener is attached to, but Simulate allow us to override it and how its done is an implementation detail that shouldn´t matter from the point of view of the user writing the test.
So If I cannot override the currentTarget using Simulate then it´s impossible to test some event listeners right? Is there another way to do it that I´m not aware? If not I'm almost sure that this is a bug and should be solved...
Can you give an example of a component you're struggling to test?
Well right now I´m on holidays and I don´t have the code in front of me but it was a simple listener, a click if I´m not mistaken:
onClick = event => {
if (event.currentTarget.tagName === 'UL') {
....
}
}
Something like that, we wanted to listent o clicks and if its inside an ul, if it was a part of the list, do something that I don't remember exactly.
That condition can only be true if you add it to a ul like <ul onClick={onClick} />
, and if you dispatch an event to that ul or any of its descendants, then event.currentTarget
WILL point to the ul
node when it bubbles up to the ul
event handler.
Would also love to see this. To give an example of my current problem:
onFileChange(event) {
const target = event.currentTarget;
const files = target.files;
// rest of method.
}
Because I can't programmatically set files, and I can't override currentTarget, I have an untestable event handler.
Ups, completely forgot about this, sorry @spicyj!
So yeah my problem was that I have the onClick in the 'ul' and I want to acce'ss DOM of the 'ul' when I click the 'li'. I really think this is a valid case and I understand that in an ideal world you will use the component instance to access whatever you want to access but we still need to dig into the DOM sometimes :D
@cam-stitt I think that in your case you only need to attach the event listener to the input and use 'event.target' instead of 'event.currentTarget' and that probably do it as in that case both will be the same, at least based on the code you pasted!
There is more to my code that doesn't allow me to use target, but I'll see
what I can do.
On Friday, 18 March 2016, Adrián Seijo [email protected] wrote:
Ups, completely forgot about this, sorry @spicyj
https://github.com/spicyj!So yeah my problem was that I have the onClick in the
and I want to acce'ss DOM of the
when I click the
- . I really think this is a valid case and I understand that in an
ideal world you will use the component instance to access whatever you want
to access but we still need to dig into the DOM sometimes :D@cam-stitt <https://github.com/cam-stitt> I think that in your case you only need to attach the event listener to the input and use 'event.target' instead of 'event.currentTarget' and that probably do it as in that case both will be the same, at least based on the code you pasted!
—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/facebook/react/issues/4950#issuecomment-198270675
If you have onClick on the ul, then when you simulate an event on the li, e.currentTarget
will point to the ul. Same as for non-simulated events.
So what you are saying is that if I want to do it I should Simulate a click event in the <li>
itself and that will automatically propagate and do everything I need using synthetic events and it will eventually trigger the onClick in the <ul>
?
That's actually quite nice, but I don't think it neccessary work in all the cases. In this case the original issue was with a custom dropdown component where the list itself render the individual list items as custom component so they were completely opaque to the component. I don't think I should try to poke the DOM and try to use them just to get the right event for the onClick handler as that will effectively make the test dependant on the internals of the children and defeat a little beat the point of the unit test. What do you think?
The target will always be the element that receives the event, and currentTarget will be the element that has the event handler. This is true whether or not you're simulating events. I'm not quite sure the use case you're describing. Maybe you could provide a code example?
Its a bit late (I'm in the UK :P) and I don't have the example right now but I think that vaguely resemble something like:
class Test extends Component {
onClick(event) {
const {currentTarget, target} = event;
const {parentNode} = currentTarget;
const index = parentNode.children.indexOf(currentTarget);
// Do some calculations and stuff, dont quite remember...
// This wasn't probably like this....
// I remember that we wanted to know which one was clicked por something like that...
actionFromSomewhereElse(index, target.getAttribute('data-value'));
}
renderDropdown(options) {
return (
<SomeNiceDropdownThatItsAnUl onClick={this.onClick}>
{options.map(option => <CoolOption value={option.value}>{option.name}</CoolOption>)}
</SomeNiceDropdownThatItsAnUl>
);
}
render() {
const {dropdowns} = this.props;
return (
<div>
{dropdowns.map(dropdown => renderDropdown(dropdown.options))}
</div>
);
}
}
The onclick was doing some stuff that I don't remember and the point was that we wan't to write some unit test for it.
Oh almost forgot, jsut in case you suggest that :P the more immediate solution would be to bind the onClick and use it to pass everything instead of relaying in the DOM but we actually don't allow binds in render methods in general so that's not really an option for us (because the whole creating a function every time you render, etc, which can be considered too excessive I know, that's a topic for another day I think :D)
How are you simulating events on the option at all? It's not obvious to me how you would with that structure.
I am a bit late to the party but I have also problems with this.
I am trying to do something similar to what @hatched said: I created my own checkbox component and I want to test it.
The checkbox is something similar to this (more or less, details are stripped for simplicity):
render() { return (<div className="whatever>
<input type="checkbox" onChange={this.onChange} checked={this.props.checked} defaultChecked={this.props.defaultChecked}
</div>); }
onChange = (event) => this.props.onChange(event.currentTarget.checked);
Now I am writing some unit tests to test the behaviour, and I cannot find a way to test the following:
1)the checkbox with defaultChecked=false is not checked, then I execute a simulated change event, and then the ckeckbox will be checked
2)the checkbox with checked=false is not checked, then I execute a simulated change event and my onChange callback passed as props will be called with argument "true".
The problem is that simulate.change(element) doesn't actually modify the checked state of the checkbox (so scenario 1 is not testable), and simulate.change(element, {currentTarget:{checked: true}}) doesn't work to test scenario 2 because it is not possible to pass some property to currentTarget as other people have mentioned in this thread.
Note: if my checkbox would be listening to target instead of currentTarget, then simulate.change(element, target:{checked:true}}) would actually work. However it doesn't for currentTarget.
You are correct that Simulate.change does not change the state of any inputs; it merely triggers onChange
event listeners. React relies on the browser to change the input (or follow a link in the case of a click, etc) and anything we did in this avenue would just be reimplementing that behavior in an incompatible way.
My suggestion has been to mutate .checked
on the actual DOM node, then call Simulate.change – your event handler should run properly.
Thank you for the explanation, it makes a lot of sense.
Unfortunately I am actually using Enzyme (that as far as I know underlying uses the testUtils Simulate) to write the tests and as far as I know it doesn't allow to mutate the actual dom node.
Even if Simulate doesn't really change the DOM state of inputs, it still allows to pass custom data to the event.target object with Simulate.change(node, {target:{value:"whatever"}}). Wouldn't be possible to allow simulate to do the same to currentTarget? That at least would allow to at least test the event handlers, since now it is just impossible in my scenario.
(Ps: I am using currentTarget instead of target in my event handlers mainly because I am using typescript and if I know that I am interested in the events on a html input I can at compile time check event.currentTarget.value in a typesafe manner, since target by definition is dynamic and cannot be known at compiletime. )
@sophiebits Bhabi hai tumhari
@MastroLindus
If you are working with Typescript then I recommend using the following actions. (combination of the comments written below)
const inputElement = input.getDOMNode() as HTMLInputElement;
inputElement.value = "another_new_value";
input.update();
input.simulate("change");
https://github.com/airbnb/enzyme/issues/218#issuecomment-388481390
https://github.com/airbnb/enzyme/issues/1850#issuecomment-456791477
thank you @Joshua-rose
i was trying to simulate checking a checkbox. using your sample, this ended up working for me
await act(async () => {
const domNode = wrapper.find(selector).getDOMNode() as HTMLInputElement;
domNode.checked = true;
wrapper.find(selector).simulate('change');
});
Most helpful comment
If you are working with Typescript then I recommend using the following actions. (combination of the comments written below)
https://github.com/airbnb/enzyme/issues/218#issuecomment-388481390
https://github.com/airbnb/enzyme/issues/1850#issuecomment-456791477