-
-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
shallow wrapper .simulate() SyntheticEvent and event propagation #368
Conversation
This is not a complete solution to all the shallow wrapper's event shortcomings (still no propagation) but to me it looks like a change that should be compatible with whatever ends up being done for propagation, since all it does is call event handlers with an object with an event-like interface |
I've added a POC for event propagation :) let me know what you think |
src/ShallowWrapper.js
Outdated
} | ||
simulate(event, mock, ...args) { | ||
const eventProp = propFromEvent(event); | ||
const e = Object.assign(new SyntheticEvent(undefined, undefined, { type: event }), mock); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the mock replaces the stopPropagation
method event propagation stops working as intended so this Object.assign
is probably not a great idea (also would have to be the object.assign
npm module because of old versions of node)
src/ShallowWrapper.js
Outdated
@@ -26,6 +26,7 @@ import { | |||
createShallowRenderer, | |||
renderToStaticMarkup, | |||
} from './react-compat'; | |||
import SyntheticEvent from 'react/lib/SyntheticEvent'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should likely be re-exported from ./react-compat
, not directly imported from react/lib
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah i realised that afterwards, ill change it if this goes forward
@nfcampos thanks for this contribution! I think that this is on the right track. I have a couple of things though:
There are a couple of questions this raises:
Unfortunately my first @ljharb do you have any thoughts? My thinking is that this will have to be a breaking change, and that perhaps we need to provide an additional way for users to specify not using the synthetic event. |
@lelandrichardson I've pulled the import of SyntheticEvent into I think there's no way around this being a breaking change, especially because of the event propagation this introduces, it will simply do stuff in your tests that it didn't do before. A couple questions:
|
another thing, I think there should be a command in |
src/react-compat.js
Outdated
@@ -134,6 +134,8 @@ if (REACT013) { | |||
}; | |||
} | |||
|
|||
import SyntheticEvent from 'react/lib/SyntheticEvent'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: please move this to the top of the file. import
statements get executed before anything else when transpiled by babel anyway (and in the spec), so putting this here can be misleading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. I've moved it to the top
@nfcampos all good points. I think you are correct, the only way out of this is to create another method that handles non-standard event simulation. To reiterate your points, I think we should move forward with the following guides:
Further questions i have:
Note: I also agree that this should be wrapped inside of |
@lelandrichardson is this b4014e0 what you mean? If yes, yeah that still works. |
If you think it's worth it, I can add that, it's doable, do you think it's worth it? |
I think that if we decide we need it later that it will need to be another breaking change, so I think it's worth it for us to get it right this time. |
Yeah I tend to agree. Then there is one other thing we should think about now: events that have exotic behaviour (I'm not sure what to call it) eg. calling |
^ I Think @ljharb will have some thoughts on this. The main thing we are going to face is that it is extremely hard to emulate this behavior reliably on a shallow wrapper because by design we are looking at an incomplete picture of the DOM. Thinking about this further, we are realistically going to have the same problem with click/capture event phases because we have no way of knowing if a parent component ends up preventing the capture phase or not. |
Hmm, re: capturing I think it's doable, think of eg. this tree <div onClickCapture={e => e.stopPropagation()}>
<a onClickCapture={innerOnClick}>foo</a>
</div> If Obviously we only know whether a parent node that you actually rendered stops propagation or not, when you end up using the component you're testing in your app it might be inside a component that stops propagation. But that's the whole point of unit testing components individually, no? |
Yes the capturing will work so long as you're not worries about effects from outside your component. This is reasonable, but is definitely not the whole picture. This is an instance where there is a sort of implicit public contract of the component that we are not able to test. In reality we could provide an API to capture events at the root node of the wrapper, and then the tester could mock out the "response" of the parent component, which could be preventing the capture phase. These are definitely corner cases, but perhaps something we should consider nonetheless. |
So this is in a way similar to context in which a parent component changes the behaviour of its children implicitly (ie. through something other than props), right? |
Yeah, although props and context are somewhat similar (props are just less explicit) in that they are top => down. Event bubbling is in the opposite direction and seems trickier to formulate an API that makes sense around it. |
Bubbling is not that hard to test, the only way an outside component can mess with a component's behaviour through event bubbling is if that component accepts |
The API I was thinking would be something like:
|
@lelandrichardson actually in that example I think neither |
I'll take another look shortly; either way let's hold off merging in this breaking change (and the others queued up) until we've merged in all the non-majors and cut a release. |
docs/api/ShallowWrapper/invoke.md
Outdated
const { count } = this.state; | ||
return ( | ||
<div> | ||
<div className={`clicks-${count}`}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not actually a blocker ofc, but i'd prefer to use a data attribute here, so that the example follows best practices :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I've changed it
Non-majors keep coming in. Maybe we make a project of what to batch in the next major release so we can actually act on this. |
- invokes event handlers on selected node with custom arguments and no propagation - added tests for invoke - added invoke to docs
@ljharb I was trying to rebase this and it seems with react 15.5 SyntheticEvent is no longer importable from react, so I'm not sure where to go from here. |
@nfcampos they likely moved it to another package? if not, then we should file an issue on react asking for it to be exported. |
@ljharb thinking about this some more, I don't think we want to start depending on SyntheticEvent, seeing as that is React-specific and we're going in the opposite direction in v3. What do you suggest we do with this, drop it or somehow not use SyntheticEvent? |
I've come to believe that |
Why problematic? |
It conveys a false promise - it doesn't actually simulate anything. There's not necessarily an event, there's not necessarily bubbling, there's not any chain of events (like if you simulate a change, you don't get all the interim key events). Whereas if you use |
@ljharb I tend to agree, closing |
What do you guys think about this? I just added an empty React SyntheticEvent as the first argument that
.simulate
calls event handlers with.This provides the regular event methods (
stopPropagation
and friends) by default but still allows the user to replace them eg. with spies, exactly the same way they do now.todo
stopPropagation
and friends on event object passed to event handlersthis.single