-
Notifications
You must be signed in to change notification settings - Fork 47.6k
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
Rewrite ReactBrowserEventEmitter-test #11713
Rewrite ReactBrowserEventEmitter-test #11713
Conversation
e807142
to
69dced2
Compare
beforeEach(() => { | ||
LISTENER.mockClear(); | ||
|
||
EventPluginHub = require('events/EventPluginHub'); |
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.
Event*
are internal modules. They shouldn't be here.
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.
Let's move any tests related to TapEventPlugin to a separate file, e.g. TapEventPlugin-test.internal.js
.
f30ba20
to
d5bb17d
Compare
@@ -63,6 +64,7 @@ describe('ReactBrowserEventEmitter', () => { | |||
EventPluginRegistry = require('events/EventPluginRegistry'); | |||
React = require('react'); | |||
ReactDOM = require('react-dom'); | |||
ReactDOMComponentTree = require('react-dom/src/client/ReactDOMComponentTree'); |
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 is also an internal module, and tests shouldn't rely on it.
I rewrite the test, but remove the EventPluginRegistry cause the last test fail, do I write the test here ? In the test, use Component instance and ref to test, I didn't find any document way to find the instance by stateless function component, like internal api
|
Don't try to just "convert" the tests, some can't be written the same way. Think about what from user's perspective is being tested. Then come up with a way to test the same thing, but perhaps in a different way. |
Thanks for tips ! I need som time to think how to rewrite it ~ |
Hi @gaearon I rewrite the test, please help me review it, thank you ! and a question the last test |
* @return {Component Instance} Child instance | ||
*/ | ||
getChildInstance = inst => { | ||
return ReactTestUtils.findRenderedComponentWithType(inst, Child); |
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.
Can we use refs and avoid the need for this method?
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.
oh ! I missing refs can use with Component Class before I read the document again, I remove the get* method and only use refs
ReactBrowserEventEmitter.setEnabled(false); | ||
const child = getChildInstance(inst); | ||
ReactTestUtils.SimulateNative.click(ReactDOM.findDOMNode(child)); | ||
ReactTestUtils.SimulateNative.click(ReactDOM.findDOMNode(CHILD)); |
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.
Suggestion: let's remove any use of Simulate
and SimulateNative
in this file.
Instead, make sure that we add container
to document.body
in beforeEach
, remove it from document.body
in afterEach
, and then use browser native dispatch:
node.dispatchEvent(
new MouseEvent('click', {bubbles: true, cancelable: true}),
);
OK, I remove all Simulate and use new MouseEvent to instead, but I found the toThrowError in test can't catch the error log Edit: |
|
||
React = require('react'); | ||
ReactDOM = require('react-dom'); | ||
ReactBrowserEventEmitter = require('react-dom/src/events/ReactBrowserEventEmitter'); |
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 is still a private API. The point of this rewrite is to remove reliance on private APIs.
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.
Please remove remaining dependencies on ReactBrowserEventEmitter
. It doesn't matter how it's imported: it's still an internal module.
@@ -54,7 +40,6 @@ describe('ReactBrowserEventEmitter', () => { | |||
jest.resetModules(); | |||
LISTENER.mockClear(); | |||
|
|||
// TODO: can we express this test with only public API? | |||
EventPluginHub = require('events/EventPluginHub'); |
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'm not sure if I was being clear enough.
Anything except react
or react-dom
themselves is considered private API. Nothing that starts with events/
or has /src/
in the path is public.
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.
Seems like there were more tests earlier? If you meant to delete some of them intentionally please explain why.
I’d love to get closer to accepting this. But if you’re struggling with something, you need to say. Just deleting tests makes my job harder because now I need to verify which ones you deleted and whether there was a good reason (or if you just couldn’t convert them). It is better if you post comments explaining your reasoning, and if you have problems, point them out and we can brainstorm them together.
Hi, @gaearon thank for your reply ~ I saw your reply yesterday and I'm not ready this PR yet, I would have comment some question after I get off work but I just thinking how could I ask 😟 some confused about test for me from test for //... test code
ReactBrowserEventEmitter.setEnabled(false);
//... test code the test for spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
ReactBrowserEventEmitter.listenTo('onClick', document);
ReactBrowserEventEmitter.listenTo('onClick', document);
expect(EventTarget.prototype.addEventListener.calls.count()).toBe(1); Edit: test for // ...test code
var module = EventPluginRegistry.registrationNameModules['onChange'];
// ...test code I confused in this line I reference some rewrite PR and found some public API doc but I still confused now, I'll keep to thinking how could I do, if have any suggestion please let me know 🙏🙏🙏, Super thanks to you, Dan. |
Try to find when it gets set :-) Then simulate the same situation. If I recall correctly it gets set when we’re already inside an event handler. So you probably want to fire an event, and then fire another event inside an event handler. |
OK I'll keep going ! |
The event system is also disabled while a component is being unmounted. |
Sorry, I'm busy on my work this week, I'll keep to rewrite next week ~~ |
@madeinfree Thank you so much for looking into that! I've seen your comment in the umbrella issue: #11299 (comment). It's absolutely no problem if you have no more time to work on it. I'm sure a future contributor will find this PR very helpful and can take over from there. Do you think we can close your PR or do you plan to give it another try? |
Thanks @philipp-spiess |
@madeinfree Awesome! So from a quick glimpse you can try to do the following to simulate the behavior where the react browser emitter is disabled:
There are also some merge conflicts so you might want to rebase first. |
bf0bd7c
to
c212ff9
Compare
* remove legacy code.
* comment and preapre to separate TapEventPlugin-test.internal.js
* merge rebase code
* a little change
* remove all old version test * rewrite all test
* remove get* method * only use refs
* appendChild container on document.body * remove container after each test * remove all Simulate * use new MouseEvent to dispatch native browser Can not catch the error from new MouseEvent in test 'toThrowError'.
* remove ReactBrowserEventEmitter internal lib * add ReactBrowserEventEmitter is disabled test
* update ReactBrowserEventEmitter is disabled test * use unmount to test it
* plugins test * dependencies test * complete all test
* compare order and new
* move jest.fn to beforeEach
* remove unnecessary comment
* just use node.click() * rewrite ReactBrowserEventEmitter disable test
* remove resetModules
* remove updateInstance comment
* PARENT -> parentRef * CHILD -> childRef
* remove createInitialInstance comment
* createInitialInstance always return parent so we can remove parentRef
* LISTENER.mock.calls.length -> toHaveBeenCalledTimes
* use push instead concat
eb585cd
to
78ce4ef
Compare
Details of bundled changes.Comparing: 42d1231...979262b schedule
Generated by 🚫 dangerJS |
* fix eslint
Hi @philipp-spiess I finish the check, please review again when you have time, thanks ! :-) |
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 started with the review up until (and including) "should bubble simply". It would be great if you work on getting those tests ready until I have more time to review the rest.
Ideally we can get rid of the abstraction of using createInitialInstance()
as well as updateInstance()
and just set up the smallest possible component tree in each test. E.g. a test like "should invoke a simple handler registered on a node" does not need more than one <div onClick={listener}>
. Everything else makes it a lot harder to understand what's going on.
Thanks for your patience here - I really appreciate your work on this!
|
||
function Child(props) { | ||
return <div ref={c => (CHILD = c)} {...props} />; | ||
class Child extends React.PureComponent { |
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.
Have you found out why this PureComponent is needed? It was also set on the wrapper of Child previously - Maybe to avoid the Child from receiving updates?
|
||
describe('ReactBrowserEventEmitter', () => { | ||
beforeEach(() => { | ||
jest.resetModules(); | ||
LISTENER.mockClear(); | ||
LISTENER = jest.fn(); |
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 think we don't need to have LISTENER
as an variable in the closure and can just call jest.fn()
whenever we want a listener :-)
<ChildWrapper {...CHILD_PROPS} /> | ||
</div> | ||
</div>, | ||
createInitialInstance = () => { |
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.
Can we remove this abstraction (as well as updateInstance()
) and use ReactDOM.render()
directly in each test? This way we can make sure to use the least amount of setup for each test and make it a lot easier to follow.
onClick: LISTENER, | ||
}, | ||
}); | ||
expect(childRef.props.onClick).toBe(LISTENER); |
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 test is not asserting the same thing as the previous one. The previous test used to call the internal API ReactDOMComponentTree.getInstanceFromNode(node);
to find out if React internally added an event listener. The new code is asserting that the onClick prop is set which is what you do in the line above. I think this would be green even if we remove the event system.
I think this test can safely be removed though. We have a lot of coverage for simple onClick
cases.
onClick: LISTENER, | ||
}, | ||
}); | ||
expect(childRef.props.onClick).toBe(LISTENER); |
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 test is a duplicate to the one above, check my comments there.
parentProps: {}, | ||
childProps: {}, | ||
}); | ||
expect(childRef.props.onClick).toBe(undefined); |
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 test is again only asserting the props that you set. I think you can use ReactDOM.render() with an onClick
listener and then another ReactDOM.render() without the listener, then fire a native event, and assert that the handler is not called. This way we know that React has cleaned up the listeners internally.
} | ||
// The ReactBrowserEventEmitter is disabled when WillUnmount lifecycle work | ||
componentWillUnmount() { | ||
willUnmountCalls(); |
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 can be removed.
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution. |
Closing this pull request after a prolonged period of inactivity. If this issue is still present in the latest release, please ask for this pull request to be reopened. Thank you! |
ref: #11299