-
Notifications
You must be signed in to change notification settings - Fork 46.9k
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
Add toTree() method to stack and fiber TestRenderer #8931
Add toTree() method to stack and fiber TestRenderer #8931
Conversation
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 looks great @lelandrichardson, I'm really excited to hopefullly get this in! Left some feedback/questions. Also I think we need to add toTree
to the ReactTestEmptyComponent
instance: this._instance, | ||
rendered: this._renderedComponent.toTree(), | ||
}; | ||
}, |
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.
Historically we've avoided adding/using test-renderer specific APIs to ReactCompositeComponent
(reference: #7649 (comment)) Could we derive this tree from the instance inside the stack test renderer like you did with Fiber?
Maybe it could be abstracted into a module like ReactInstanceToTree
or something like that?
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.
hmm. This makes sense as a rule. I'm not sure what the cleanest way to do this is but I'll give it a shot
default: | ||
throw new Error( | ||
`toTree() does not yet know how to handle nodes with tag=${node.tag}.` | ||
); |
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.
We'll want to use the invariant
module for error handling to remain consistent.
You can just do invariant(false, '[message]')
// eslint-disable-next-line no-unused-vars | ||
const { children, ...propsWithoutChildren } = element.props; | ||
return { | ||
nodeType: ReactNodeTypes.getType(element), |
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.
ReactNodeTypes
returns an integer representing the type, but in the spec we define nodeType
as a string. Should we update the spec and standardize the numbering, e.g., 0
always refers to a host node?
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 thought about this. I feel like there are two ways of looking at this, and I'm not sure which one is more correct:
- we just export a
nodeType
that makes the most sense based on the data structure available and not relying on the knowledge of the RST node spec. - we export a
nodeType
that is identified by the RST node spec.
The former is easier in terms of the react implementation and makes the "enzyme adapter" more complicated. The latter makes the enzyme adapter simpler, but means that react has to have some knowledge of this "RST node spec".
I'm open to either. Now that I'm saying it out loud, option 2 seems a bit better to me.
@aweary for ReactTestEmptyComponent, how would I test that it is working since the tests i've written so far are apparently working fine without it? |
function toTree(node) { | ||
switch (node.tag) { | ||
case HostRoot: // 3 | ||
return toTree(node.progressedChild); |
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 you want .child
because .progressedChild
refers to something different.
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.
for some reason, .node
on HostRoot
was always null
.
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.
What is .node
?
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.
gah, sorry. i meant .child
.
If i change this line to return toTree(node.child);
, this breaks because node.child
is null
but node.progressedChild
is not. :/
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.
That might be a bug either in Fiber or the test renderer. You should definitely not use progressedChild.
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.
ok good to know. Any advice on what i should dive into in order to try and track down why this is happening?
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.
The problem is that you're looking at the wrong root. There's two. We might also want to switch which one createContainer returns.
For now, use stateNode
to store the "root".
var root = TestRenderer.createContainer(container).stateNode;
This will not be a Fiber object so every time you use it you need to use root.current
to get the current Fiber.
There are two Fibers per root and this will ensure that you get the right one.
6129a08
to
f847289
Compare
@lelandrichardson you should be able to test it by rendering |
@sebmarkbage I just added a commit which refactors the fiber test renderer to use |
a565b14
to
c596e19
Compare
@lelandrichardson Mind rebasing on top of #8934 ? |
@@ -248,12 +307,12 @@ var ReactTestFiberRenderer = { | |||
createNodeMock, | |||
tag: 'CONTAINER', | |||
}; | |||
var root = TestRenderer.createContainer(container); | |||
TestRenderer.updateContainer(element, root, null, null); | |||
var root = TestRenderer.createContainer(container).stateNode; |
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.
After #8934 this doesn't need .stateNode
anymore.
c596e19
to
cc80a77
Compare
@sebmarkbage rebased. Any thoughts on what the next steps for this PR might be? We could refactor |
@lelandrichardson I think just fixing the unit tests would be enough to land this. According to CI you have some failing tests. We can refactor |
OK. Looks like it's just some flow errors. I'll add some flow types to the new code |
@sebmarkbage strange. If i click "Details" for circle CI it shows that everything has passed for the latest commit of this branch. Am i missing something? |
@lelandrichardson you have to re-run the fiber test script and commit those changes since you added a new test |
@aweary ah, makes sense |
@aweary @sebmarkbage tests passing! Let me know if there's anything more i need to do! |
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.
lgtm
@@ -16,8 +16,19 @@ | |||
var ReactFiberReconciler = require('ReactFiberReconciler'); | |||
var ReactGenericBatching = require('ReactGenericBatching'); | |||
var emptyObject = require('emptyObject'); | |||
var ReactTypeOfWork = require('ReactTypeOfWork'); |
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 you confirm that this works in the npm build? I.e. grunt build
puts this file in the npm package. I've had trouble in RN when this isn't reachable from within the npm build?
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.
sorry.... can you explain what you want me to try here? just run npm run build
?
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 verify this file actually ends up in build/packages/react-test-renderer
after you've run npm run build
. Whether it does or not depends on where ReactTypeOfWork
is located. Explanation for our folder structure: https://facebook.github.io/react/contributing/codebase-overview.html#shared-code.
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.
In other words, after npm run build
try cd-ing into build/packages/react-test-renderer
and verify you can require()
it without crashing.
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've confirmed that build/packages/react-test-renderer/lib/ReactTypeOfWork.js
exists
✨ |
Really happy to see this land! I'm looking to do a followup PR to add in a |
Not sure it's the best way but you'd likely want to make hostConfig available in ReactFiber so that you can delegate to it (hostConfig.isHost) when deciding whether something HostComponent or not. Host config is the object with methods that you pass to ReactFiberReconciler to create the renderer. Does this help? |
@gaearon i think that might give me enough to start playing around. Thanks! |
What @gaearon said about hostConfig but please don't make a |
Then in user space you can build isHost: mockComponent = (component) => {
if (isHost(component)) {
const x = (props) => props.children;
x.displayName = component.displayName || component.name;
return x;
}
return component;
}; This just works because Fiber supports fragments. |
This is what I proposed in enzymejs/enzyme#742 (comment), is my understanding right? One thing I'm not sure about is how exactly to model shallow rendering with this (I think my comment actually gets it wrong). For shallow rendering the type is not enough because you want the (composite) root to become expanded but any composite children to get mocked. So either some notion of depth, or element equality, is necessary. WDYT? |
@gaearon my understanding was that for shallow rendering we would just make any composite component a "mocked" host component that just renders children. Since children would be elements (of both composite and host types), the hosts would work like normal and the composites would also be mocked, so their corresponding |
A composite may not be using Additionally, that wouldn't render the very root which is the problem @gaearon is concerned about. You really want to render var isFirst = true;
function mockComponent(c) {
if (isFirst) {
isFirst = false;
return c;
}
return function() { return null; };
}
ReactShallowRenderer.render = function(children) {
isFirst = true;
Renderer.updateContainer(root, children);
} We could also just write a shallow renderer from scratch. It's not hard. |
Hah, I didn't think about local variables 😄 . This looks like it would work if we forbid passing a fragment or a host component to the shallow renderer (since host could have multiple composite children). Which kinda makes sense anyway. I feel like mocking without hijacking the module system is generally valuable so I'd like to see this explored within our existing system in Fiber, without a separate rewrite. Although I'm not sure if this is going to have unwanted side effects like calling |
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.
Yrs
to: @gaearon @aweary @iamdustan
This PR adds a
toTree()
method toReactTestRenderer
. This method is a result of the discussions from enzymejs/enzyme#742. Related discussion also in #8808.This API will help us accomplish the goal of enzyme not needing to refer to any react internals at all.
This PR isn't complete, but I think it's a good start. I was hoping for some feedback on how to get this to the finish line. The main things I can think of are:
toJSON()
as a function that usestoTree()
under the hood in order to reduce amount of code?toTree()
the right name?.child
or.progressedChild
or.return
, etc..toEqual
matcher, it was succeeding at times where it shouldn't, which really concerned me. I ended up changingexpect(foo).toEqual(bar)
toexpect(prettyFormat(foo)).toEqual(prettyFormat(bar)
in order to make sure things were working properly.instance
on the nodes in tests is kind of wonkychildren
in theprops
object of these elements? I think we should now that I think about it more, but we might have to do more hacks to make the tests easier again.