Skip to content
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

Use only public api for ReactDOMEventListener-test.js #11327

Merged
merged 6 commits into from
Nov 3, 2017
Merged

Use only public api for ReactDOMEventListener-test.js #11327

merged 6 commits into from
Nov 3, 2017

Conversation

enapupe
Copy link
Contributor

@enapupe enapupe commented Oct 22, 2017

Related to #11299 – Express more tests via public API
Hey! After a couple hours fighting with this one I think I got the idea..
Could you guys check if I'm in the right direction?

BTW, I lost quite some time until I realized this line: https://github.com/facebook/react/blob/master/packages/react-dom/src/__tests__/ReactDOMEventListener-test.js#L32 was messing up with ReactTestUtils.SimulateNative... Funny, that's probably the reason why we are using just the public api 😅

document.createElement('div'),
);

ReactDOM.findDOMNode(component).appendChild(otherNode);
Copy link
Contributor Author

@enapupe enapupe Oct 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this first test, it seems to have lost its initial purpose.

);

ReactDOM.findDOMNode(component).appendChild(otherNode);
ReactTestUtils.SimulateNative.mouseOver(otherNode);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these purposes, I'd like to treat SimulateNative (and all the ReactTestUtils really as an internal API. The reason for that is that they also reach into internals indirectly. They actually bypass some of the implementation details that would make this example not work with normal React.

In normal React this wouldn't work because the container div is not in the document and therefore mouse moves would never be dispatched on top of it. React can't pick it up because it uses event listeners on the document to do it.

Instead, we should insert the container div into the document, dispatch a native event and then remove the div from the document. Like this:
https://github.com/facebook/react/blob/master/packages/react-dom/src/__tests__/ReactDOMInput-test.js#L55-L59

Copy link
Contributor Author

@enapupe enapupe Oct 23, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! I did the requested changes, could you take a look at it?
I'm still not sure if the first test is on spot because before it tested two unrelated components and now they have a parent-child relationship.

Also while we are at this file, I'm not sure how to rewrite should not get confused by disappearing elements.. I think I understand the thinking behind it but I'm not sure if it's possible to accomplish the same result without mocking handleTopLevel. I'd have to unmount a component in the middle of the event chain, which seems impossible.

Thanks!


var container = document.createElement('div');
var stub = ReactDOM.render(<div onMouseOver={mock} />, container);
var node = ReactDOM.findDOMNode(stub);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no "stubs" anymore, you can remove these findDOMNode() calls. Return value of ReactDOM.render(<div />) is the node itself.

var container = document.createElement('div');
var stub = ReactDOM.render(<div onMouseOver={mock} />, container);
var node = ReactDOM.findDOMNode(stub);
var otherComponent = document.createElement('h1');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not call nodes "components". I understand this naming already existed but let's keep it clear in new code. We call DOM nodes "nodes" in newer code (or "containers" when we render into them). So otherNode was a good naming.

var stub = ReactDOM.render(<div onMouseOver={mock} />, container);
var node = ReactDOM.findDOMNode(stub);
var otherComponent = document.createElement('h1');
node.appendChild(otherComponent);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it appended as a child to node? I don't see it in the original test. My impression (based on the test name) is that the test specifically wants to test it still works if the otherComponent is outside the React tree. Whereas this is putting it inside.

otherComponent.dispatchEvent(nativeEvent);

expect(mock).toBeCalled();
document.body.removeChild(container);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the right way to write this test would be something like:

    var mock = jest.fn();

    var container = document.createElement('div');
    var node = ReactDOM.render(<div onMouseEnter={mock} />, container);
    var otherNode = document.createElement('h1');
    document.body.appendChild(container);
    document.body.appendChild(otherNode);

    var nativeEvent = document.createEvent('MouseEvent');
    nativeEvent.initMouseEvent('mouseout', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, node);
    otherNode.dispatchEvent(nativeEvent);
    expect(mock).toBeCalled();

This way we verify that if an event originates outside of React tree (on otherNode) it still fires an event inside a React tree.

I had to use a different event but that doesn't matter for this test.

});
var nativeEvent = document.createEvent('Event');
nativeEvent.initEvent('mouseout', true, true);
ReactDOM.findDOMNode(childControl).dispatchEvent(nativeEvent);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No findDOMNode please.

var parentContainer = document.createElement('div');
var parentControl = <div>Parent</div>;
var parentControl = <div onMouseOut={onMouseOut}>div</div>;
childControl = ReactDOM.render(childControl, childContainer);
parentControl = ReactDOM.render(parentControl, parentContainer);
ReactDOM.findDOMNode(parentControl).appendChild(childContainer);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, too, we can remove all findDOMNodes.

@@ -90,22 +95,25 @@ describe('ReactDOMEventListener', () => {
ReactDOM.findDOMNode(parentControl).appendChild(childContainer);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same re: findDOMNode.

return <div><div id="outer">{inner}</div></div>;
}
}

var instance = ReactTestUtils.renderIntoDocument(<Wrapper />);
ReactTestUtils.SimulateNative.mouseOut(instance.getInner());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's rewrite this one not to use SimulateNative either. Can we?

@gaearon gaearon mentioned this pull request Oct 26, 2017
26 tasks
@enapupe
Copy link
Contributor Author

enapupe commented Oct 26, 2017

Thanks for the feedback!
The changes were addressed but there are still 2 tests left to go. I'm not sure how to proceed here because both tests change handleTopLevel behaviour.
One seems to test if ReactDOMEventListener can handle an element being unmounted in the middle of an event chain.
The other I'm not even sure if I understand, it mentions two different roots enqueuing events, but the current test only dispatches one event so I'm not really sure what's that about. THanks!

document.body.appendChild(container);
document.body.appendChild(otherNode);

var nativeEvent = document.createEvent('MouseEvent');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw another PR using new MouseEvent(type, args), could you check if that one's going to be shorter?

Copy link
Contributor Author

@enapupe enapupe Oct 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it seems

new MouseEvent('mouseout', {
  bubbles: true,
  cancelable: true,
  relatedTarget: node,
})

works just fine.

@gaearon
Copy link
Collaborator

gaearon commented Oct 27, 2017

One seems to test if ReactDOMEventListener can handle an element being unmounted in the middle of an event chain.

I think it just wants to test that if you unmountComponentAtNode() during a React event (e.g. in child component's handler), the parent still receives the event too. Does that seem to make sense? Can you try to write a test for it? It might also help to view the blame for these lines, find the code that “fixed” this case (when this code was introduced), and then “undo” the fix to see if your test catches the regression. This would tell you if you’re testing for the same thing it was originally written for or not.

The other I'm not even sure if I understand, it mentions two different roots enqueuing events, but the current test only dispatches one event so I'm not really sure what's that about. THanks!

React batches updates occurring inside the same browser event. Means it applies all changes when it exists the top-level browser event handler (instead of when it exits component handler). This test verifies this behavior is true even if two components live in separate React trees (but in the same document).

In other words it wants to verify that if you do

var mock = jest.fn();

and then, inside child (in a separate root) event handler:

ReactDOM.render(<div>1</div>, childContainer);
mock(childNode.textContent);

and inside parent (in a separate root) event handler:

ReactDOM.render(<div>2</div>, childContainer);
mock(childNode.textContent);

Both calls to mock should've been called with 'Child' because both updates are batched together and neither one is applied yet inside React event handlers. You should, however, check that when after dispatching is done, childNode.textContent is now set to '2'. (Now that updates are flushed.)

@gaearon
Copy link
Collaborator

gaearon commented Oct 31, 2017

Looks like a test is still failing. Would you like to continue working on this?

@enapupe
Copy link
Contributor Author

enapupe commented Oct 31, 2017

Yeah there's just one missing.
I'll try to do it until tomorrow, otherwise I'll be traveling for a few days and will be back only after Nov 5th, feel free to take over if you must.

@gaearon
Copy link
Collaborator

gaearon commented Oct 31, 2017

Thanks, sounds good!

@enapupe
Copy link
Contributor Author

enapupe commented Nov 1, 2017

So, I think I was able to reproduce the same test case but I couldn't find the code that introduced that fix you mentioned, so confirmation is still necessary. The blame for that lines seem to be sort of detached from the git tree..

There was no need to extract React elements into separate variables.
I could not get it to fail on master so it was probably testing something specific to Stack implementation details. It was also already broken because it didn't look at the right argument and never actually called `unmountComponentAtNode`.

Instead I replaced it with original repro case from #1105 which is when it was introduced.
<div>{topLevelTarget === childNode ? '1' : '2'}</div>,
childContainer,
);
// Since we're batching, neither update should yet have gone through.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments like this are valuable because they're not obvious.

@gaearon gaearon changed the title (WIP) Use only public api for ReactDOMEventListener-test.js Use only public api for ReactDOMEventListener-test.js Nov 3, 2017
@gaearon
Copy link
Collaborator

gaearon commented Nov 3, 2017

This looks great! Thanks. I made a few small tweaks.

@gaearon gaearon merged commit bfa2690 into facebook:master Nov 3, 2017
Ethan-Arrowood pushed a commit to Ethan-Arrowood/react that referenced this pull request Dec 8, 2017
* Use only public api for ReactDOMEventListener-test.js

* Use less confusing naming

There was no need to extract React elements into separate variables.

* Replace the "disappearance" test

I could not get it to fail on master so it was probably testing something specific to Stack implementation details. It was also already broken because it didn't look at the right argument and never actually called `unmountComponentAtNode`.

Instead I replaced it with original repro case from facebook#1105 which is when it was introduced.

* Tweak naming and add comments

* Missed this one
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants