-
Notifications
You must be signed in to change notification settings - Fork 47.3k
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
Test renderer traversal #7516
Test renderer traversal #7516
Conversation
If you do ``` x = ReactTestRenderer.create(<a />); x.update(<b />); x.getType(); ``` then you probably expect to get `'b'` back. But if you were to do ``` div = ReactTestRenderer.create(<div><a /></div>); x = div.getChildren()[0]; div.update(<div><b /></div>); x.getType(); ``` then since `x` points directly to the original `<a />` child, there's no possible way we could return `'b'`. So rather than have a confusing inconsistency between the top-level node and all the lower ones, we outlaw changing type and key so that you always have the same instance. I'm not actually sure I like this. It might make more sense to distinguish between the top-level instance and the children in the API -- since it also makes sense to call .update and .unmount at the top but nowhere else.
This looks good to me. Were you guys thinking of providing a |
I think we could add some sort of find APIs officially. @sebmarkbage Do you feel good about the approach here of having .update() and .unmount() on every test instance but having them work only at the root (and outlawing key/type changes at the root)? Not sure if we should differentiate the so that the container/root is a different object from the rest. |
I see. Yea, maybe separating them would be better even though it adds some mental overhead in the common case. This should probably not be the ideal end-user API anyway, maybe? |
What should be the ideal end-user API? :) |
I think that it would be ideal to be able to do something like:
that way people can drive interactions and make sure state changes can be captured in a snapshot. I think we should restrict search to non-host elements so that people don't write brittle tests, like making sure a span is at the right spot in the tree etc. This stuff should be captured in the snapshot. |
It makes sense to just return an instance of
Would it be enough to just document that traversal methods like // DO: call the traversal method each time your root updates
var tree = ReactTestRenderer.create(<Foo bar="bar" />);
expect(tree.find(Button).getProps().bar).toEqual("bar");
tree.update(<Foo bar="qux"/>);
expect(tree.find(Button).getProps().bar).toEqual("qux"); // <- GOOD
// DONT: try to reference a child instance after an update has been
// made without updating the reference
var tree = ReactTestRenderer.create(<Foo bar="bar" />);
var button = tree.find(Button);
expect(button.getProps().bar).toEqual("bar");
tree.update(<Foo bar="qux"/>);
// button still refers to the button found on the outdated root
expect(button.getProps().bar).toEqual("qux"); // <- BAD
// would be fine if they did `button = tree.find(Button)` beforehand Otherwise I think this is good and we should get the conflicts resolved and get this merged so we can work on the actual traversal API. |
I'm not sure if this is a good argument. If you are using a type checker than a static type error is even better, which is only possible if they are different types. |
Well, that's assuming you're using a static type system and that we're exporting the correct type definitions. As long as you also throw an error on that separate type for |
@spicyj what's the use case for I understanding updating the props (if that was in fact a proposed feature) by why would you want to replace the whole component or its children? Shouldn't updating of children be the job of the component in how it responds to state and props changes? And should replacing the top level component be the job of a separate test? What am I missing? That said, it might be nice to have an |
if (typeof el.type === 'function') { | ||
children = [instance._renderedComponent]; | ||
} else { | ||
children = Object.keys(this._renderedChildren) |
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.
Should this be instance._renderedChildren
?
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.
Yes.
👋 Just noting, that i've done a bunch of work on instance/element traversal for testing here: https://github.com/jquense/bill (heavily tested against the last 3 versions of react). Happy to offer any thoughts, or help to get better api's for this into react core. |
I think this is unnecessary now that |
Hm. |
All of the traversal methods defined in #7409 can be implemented using What do you think? |
@aweary that looks awesome, I'm gonna give it a try. Can you explain more what you mean by:
|
I haven't had a chance to really dig in, but it seemed like toTree might be lacking two things – ability to pull out just part of a tree rather than serializing the entire thing from scratch, and the ability to have persistent component identity and "hold onto" a reference to a component when updating. Do you know how that compares? |
So
@faceyspacey there's a new version of the test renderer |
This got merged in #10377. |
If you do
then you probably expect to get
'b'
back. But if you were to dothen since
x
points directly to the original<a />
child, there's no possible way we could return'b'
. So rather than have a confusing inconsistency between the top-level node and all the lower ones, @gaearon suggested outlawing changing type and key so that you always have the same instance.I'm not actually sure I like this. It might make more sense to distinguish between the top-level instance and the children in the API -- since it also makes sense to call .update and .unmount at the top but nowhere else.
cc @sebmarkbage @rafeca #7148