-
Notifications
You must be signed in to change notification settings - Fork 47.4k
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 createComponentMock option to test renderer #8982
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,9 +19,11 @@ var emptyObject = require('fbjs/lib/emptyObject'); | |
var ReactTypeOfWork = require('ReactTypeOfWork'); | ||
var invariant = require('fbjs/lib/invariant'); | ||
var { | ||
IndeterminateComponent, | ||
FunctionalComponent, | ||
ClassComponent, | ||
HostComponent, | ||
Fragment, | ||
HostText, | ||
HostRoot, | ||
} = ReactTypeOfWork; | ||
|
@@ -41,6 +43,7 @@ type ReactTestRendererNode = ReactTestRendererJSON | string; | |
type Container = {| | ||
children: Array<Instance | TextInstance>, | ||
createNodeMock: Function, | ||
createComponentMock: Function, | ||
tag: 'CONTAINER', | ||
|}; | ||
|
||
|
@@ -217,6 +220,29 @@ var TestRenderer = ReactFiberReconciler({ | |
setTimeout(fn, 0, {timeRemaining: Infinity}); | ||
}, | ||
|
||
mockComponent(component: Fiber, rootContainer: Container) { | ||
invariant( | ||
component._unmockedType === null, | ||
'Trying to mock an already mocked component', | ||
); | ||
const mockedFn = rootContainer.createComponentMock({ | ||
type: component.type, | ||
props: component.pendingProps, | ||
}); | ||
invariant( | ||
typeof mockedFn === 'function', | ||
'createComponentMock() must return a function. Found %s instead.', | ||
typeof mockedFn, | ||
); | ||
if (mockedFn !== component.type) { | ||
component._unmockedType = component.type; | ||
component.type = mockedFn; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should never mutate the Fiber here. We should instead return the mock and let the algorithm initialize it in the appropriate place. |
||
// force the fiber to be indeterminate so that users can mock a class component | ||
// into a functional component and vice versa | ||
component.tag = IndeterminateComponent; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This indicates to me that we're not overriding it soon enough. Maybe we need to do the override earlier? This also doesn't allow you to mock it to a |
||
} | ||
}, | ||
|
||
useSyncScheduling: true, | ||
|
||
getPublicInstance(inst) { | ||
|
@@ -237,6 +263,9 @@ var defaultTestOptions = { | |
createNodeMock: function() { | ||
return null; | ||
}, | ||
createComponentMock: function(component: {type: Function, props: any}) { | ||
return component.type; | ||
}, | ||
}; | ||
|
||
function toJSON(inst: Instance | TextInstance): ReactTestRendererNode { | ||
|
@@ -277,6 +306,46 @@ function nodeAndSiblingsArray(nodeWithSibling: ?Fiber) { | |
return array; | ||
} | ||
|
||
function childrenToTree(node) { | ||
if (!node) { | ||
return null; | ||
} | ||
const children = nodeAndSiblingsArray(node); | ||
if (children.length === 0) { | ||
return null; | ||
} else if (children.length === 1) { | ||
return toTree(children[0]); | ||
} else { | ||
return flatten(children.map(toTree)); | ||
} | ||
} | ||
|
||
function flatten(arr) { | ||
const result = []; | ||
const stack = [{i: 0, array: arr}]; | ||
while (stack.length) { | ||
let n = stack.pop(); | ||
while (n.i < n.array.length) { | ||
const el = n.array[n.i]; | ||
n.i += 1; | ||
if (Array.isArray(el)) { | ||
stack.push(n); | ||
stack.push({i: 0, array: el}); | ||
break; | ||
} | ||
result.push(el); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
function publicType(node: Fiber) { | ||
if (node._unmockedType !== null) { | ||
return node._unmockedType; | ||
} | ||
return node.type; | ||
} | ||
|
||
function toTree(node: ?Fiber) { | ||
if (node == null) { | ||
return null; | ||
|
@@ -287,26 +356,28 @@ function toTree(node: ?Fiber) { | |
case ClassComponent: | ||
return { | ||
nodeType: 'component', | ||
type: node.type, | ||
type: publicType(node), | ||
props: {...node.memoizedProps}, | ||
instance: node.stateNode, | ||
rendered: toTree(node.child), | ||
rendered: childrenToTree(node.child), | ||
}; | ||
case Fragment: // 10 | ||
return childrenToTree(node.child); | ||
case FunctionalComponent: // 1 | ||
return { | ||
nodeType: 'component', | ||
type: node.type, | ||
type: publicType(node), | ||
props: {...node.memoizedProps}, | ||
instance: null, | ||
rendered: toTree(node.child), | ||
rendered: childrenToTree(node.child), | ||
}; | ||
case HostComponent: // 5 | ||
return { | ||
nodeType: 'host', | ||
type: node.type, | ||
props: {...node.memoizedProps}, | ||
instance: null, // TODO: use createNodeMock here somehow? | ||
rendered: nodeAndSiblingsArray(node.child).map(toTree), | ||
rendered: flatten(nodeAndSiblingsArray(node.child).map(toTree)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make this faster? This is super inefficient atm. We have plenty of examples in the codebase of linked list tree traversal without the intermediate arrays. But why are only host components flattened and not other types? |
||
}; | ||
case HostText: // 6 | ||
return node.stateNode.text; | ||
|
@@ -325,9 +396,14 @@ var ReactTestFiberRenderer = { | |
if (options && typeof options.createNodeMock === 'function') { | ||
createNodeMock = options.createNodeMock; | ||
} | ||
var createComponentMock = defaultTestOptions.createComponentMock; | ||
if (options && typeof options.createComponentMock === 'function') { | ||
createComponentMock = options.createComponentMock; | ||
} | ||
var container = { | ||
children: [], | ||
createNodeMock, | ||
createComponentMock, | ||
tag: 'CONTAINER', | ||
}; | ||
var root: ?FiberRoot = TestRenderer.createContainer(container); | ||
|
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 should not be exposing the Fiber data structure to renderers. I have some refactoring ideas where this will break. Instead, pass the individual pieces as arguments.