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

Rewrite ReactTreeTraversal-test.js using public APIs #11664

Merged
merged 10 commits into from Nov 29, 2017

Conversation

timjacobi
Copy link
Member

@timjacobi timjacobi commented Nov 26, 2017

Hej!

This is a super interesting test to look at since the tests cover 3 different areas when you try to rewrite them using only public APIs. So far I have converted 1/3 of the tests to use public APIs only. The rest I'd like to discuss.

traverseTwoPhase

These were fairly straightforward to rewrite. I could even used the same expectedCalls arrays for the expectations on the mock.

The only thing that could be a bit nicer is the calls to eventHandler. I would like to factor out passing the arguments but #11663 prevents me from passing the event phase dynamically.

traverseEnterLeave

In order to test them in the same fashion as most other public API tests I would have to dispatch onmouseenter and onmouseleave events against the DOM nodes and see which event handlers React calls. Unfortunately dispatching these event types doesn't have an effect and looking at EnterLeaveEventPlugin.js it seems like enter and leave events are implemented using onmouseover and onmouseout.
The tests seem to verify the capturing and bubbling behaviour which are inherently different between onmouseenter and onmouseover as well as onmouseleave and onmouseout. I would be really thank for some ideas on how we could solve this.

getFirstCommonAncestor aka getLowestCommonAncestor

This function is only used by the ResponderEventPlugin which is in turn only used by React Native. This doesn't mean we can't test it only with public APIs but we might struggle with that in our JSDOM environment. Again would be great to discuss alternatives :)

@gaearon
Copy link
Collaborator

gaearon commented Nov 27, 2017

Unfortunately dispatching these event types doesn't have an effect and looking at EnterLeaveEventPlugin.js it seems like enter and leave events are implemented using onmouseover and onmouseout.

This sounds right.

I would be really thank for some ideas on how we could solve this.

I'm not sure I understand the problem. The traversal tests verify that we correctly walk the tree for different "types" of walks (enter-leave is one example). It seems right to me that you would test it by listening on React onMouseEnter and onMouseLeave, but dispatch whatever native events necessary. Maybe EnterLeavePlugin-test could be useful as inspiration? Or could you clarify what is the problem you ran into?

This function is only used by the ResponderEventPlugin which is in turn only used by React Native.

If you search for require('react-native-renderer') you'll find a few places where we load its mock. Maybe some of these tests could be helpful?

@timjacobi
Copy link
Member Author

timjacobi commented Nov 27, 2017

Or could you clarify what is the problem you ran into?

My thoughts were the wrong way like "to simulate a mouseenter I have to dispatch a mousesenter"... I have figured out the events to dispatch now and rewritten all enter/leave tests.

I will look into the inner workings of the ResponderEventPlugin tomorrow to understand how I can trigger getLowestCommonAncestor with the values in the current test. The test renderer looks good, I'm sure I can get it to where I need it.

When I added the react-native-renderer to the test though I got the error Invariant Violation: EventPluginRegistry: Cannot inject event plugin ordering more than once. You are likely trying to load more than one copy of React. which makes sense. Is there any trick to unload React or do I have to put the test in a different file?

@gaearon
Copy link
Collaborator

gaearon commented Nov 27, 2017

do I have to put the test in a different file?

This sounds right. In fact maybe you can move the original test for that part into ResponderEventPlugin-test.internal.js which uses internal APIs anyway. We can look at it later at some point.

@timjacobi timjacobi changed the title [WIP] Rewrite ReactTreeTraversal-test.js using public APIs Rewrite ReactTreeTraversal-test.js using public APIs Nov 28, 2017
@timjacobi
Copy link
Member Author

Sounds good. It looks like ResponderEventPlugin-test.internal.js needs some care anyway.
I have removed the WIP status now. Should be good to review.

@@ -1360,4 +1360,88 @@ describe('ResponderEventPlugin', () => {
run(config, three, nativeEvent);
expect(ResponderEventPlugin._getResponder()).toBe(null);
});

it('should determine the first common ancestor correctly', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since we're not using Responder plugin it's worth adding a comment explaining why the test is here and how it's related to Resonder.

describe('Two phase traversal', () => {
it('should not traverse when target is outside component boundary', () => {
const outerNode = document.createElement('div');
document.body.appendChild(outerNode);
Copy link
Collaborator

Choose a reason for hiding this comment

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

The node is appended here to the document. We should remove it to clean up after the test. I usually put the rest of the test in try/finally, and remove it in the finally block.

var enterNode = document.createElement('div');

document.body.appendChild(leaveNode);
document.body.appendChild(enterNode);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In fact it may be easier to always add these nodes in beforeEach, and then always remove them in afterEach. Some tests will use them, some won't.

var leaveNode = document.createElement('div');
var enterNode = document.getElementById('P_P1_C1__DIV');

document.body.appendChild(leaveNode); // From the window or outside of the React sandbox.
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we change nodes to always be added, tests like this will have to remove one instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

Could you elaborate? I don't follow.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Never mind, I misread the tests.

@timjacobi
Copy link
Member Author

Updated according to comments :)

<div
id={id + '__DIV'}
onClickCapture={e =>
eventHandler(e.currentTarget.id, 'captured', e.type, ARG)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The logic behind choosing to put either ARG or ARG2 here is not clear to me.

In the original tests, it was specific to a test. We're traversing enter/leave, and calling "from" path with ARG and "to" path with ARG2.

Here, we instead encoded ARG and ARG2 right into components. I don't think this is right. This seems to couple them with specific test cases below. So it might be hard or impossible to add other completely valid tests which test the opposite enter/leave paths. This didn't use to be a problem.

I'm not sure what the solution is; just pointing out an issue I see.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah I'm not entirely happy with that either. Will think about a more elegant solution.

Copy link
Member Author

Choose a reason for hiding this comment

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

I wonder if we're good to just remove the ARGs since we're now tracking the event type which we haven't done before. If I get it righttraverseEnterLeave(from, to, fn, argFrom, argTo) will be called with argFrom and argTo set to the events that are dispatched on the target anyway so I feel like extracting the target id and event type will be strict enough.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes this sounds right

@gaearon gaearon mentioned this pull request Nov 28, 2017
26 tasks
these were used for testing the internal API to simulate synthetic
events passed to traverseEnterLeave. since we're now dealing with
actual synthetic events we can remove them.
@timjacobi
Copy link
Member Author

@gaearon the App Veyor test failures seem unrelated.

@gaearon
Copy link
Collaborator

gaearon commented Nov 29, 2017

Yeah, I didn't realize it would start appearing on PRs..

@gaearon gaearon merged commit c1b2a34 into facebook:master Nov 29, 2017
@gaearon
Copy link
Collaborator

gaearon commented Nov 29, 2017

This is good. Thank you!

@timjacobi
Copy link
Member Author

No worries. Was great again!

@timjacobi timjacobi deleted the tree-traversal-public-api branch November 29, 2017 15:17
madeinfree pushed a commit to madeinfree/react that referenced this pull request Nov 30, 2017
* rewrite two phase traversal tests with public APIs

* rewrite enter/leave tests

* lift render into beforeEach, organise variables

* move getLowestCommonAncestor test

* remove internal tree traversal test

* fix linter errors

* move creation of outer nodes into {before,after}Each

* explain why getLowestCommonAncestor test was moved

* remove unnessecary ARG and ARG2 token

these were used for testing the internal API to simulate synthetic
events passed to traverseEnterLeave. since we're now dealing with
actual synthetic events we can remove them.

* run prettier
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.

None yet

3 participants