From 041d39094ce5ce35ec586f92e782a120822863fb Mon Sep 17 00:00:00 2001 From: Paul Sherman Date: Sun, 4 Feb 2018 16:09:54 -0600 Subject: [PATCH 1/2] Support context components in react-test-renderer --- .../src/ReactTestRenderer.js | 29 +++++++++++++- .../src/__tests__/ReactTestRenderer-test.js | 38 +++++++++++++++++++ .../ReactTestRendererTraversal-test.js | 20 +++++++++- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index 1bf7d4d4adbcf..fd01485c5f735 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -22,6 +22,8 @@ import { HostPortal, HostText, HostRoot, + ContextConsumer, + ContextProvider, } from 'shared/ReactTypeOfWork'; import invariant from 'fbjs/lib/invariant'; @@ -367,6 +369,26 @@ function toTree(node: ?Fiber) { return node.stateNode.text; case Fragment: return childrenToTree(node.child); + case ContextConsumer: + return { + nodeType: 'component', + type: node.type, + props: {...node.pendingProps}, + instance: node.stateNode, + rendered: hasSiblings(node.child) + ? nodeAndSiblingsTrees(node.child) + : toTree(node.child), + }; + case ContextProvider: + return { + nodeType: 'component', + type: node.type, + props: {...node.memoizedProps}, + instance: node.stateNode, + rendered: hasSiblings(node.child) + ? nodeAndSiblingsTrees(node.child) + : toTree(node.child), + }; default: invariant( false, @@ -393,6 +415,8 @@ const validWrapperTypes = new Set([ FunctionalComponent, ClassComponent, HostComponent, + ContextProvider, + ContextConsumer, ]); class ReactTestInstance { @@ -432,7 +456,8 @@ class ReactTestInstance { } get props(): Object { - return this._currentFiber().memoizedProps; + const fiber = this._currentFiber(); + return fiber.memoizedProps || fiber.pendingProps; } get parent(): ?ReactTestInstance { @@ -457,6 +482,8 @@ class ReactTestInstance { case FunctionalComponent: case ClassComponent: case HostComponent: + case ContextConsumer: + case ContextProvider: children.push(wrapFiber(node)); break; case HostText: diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js index f1df422e9321c..7c969d039b3ad 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js @@ -745,6 +745,44 @@ describe('ReactTestRenderer', () => { ); }); + it('toTree() handles ContextConsumer and ContextProvider components', () => { + const {Consumer, Provider} = React.createContext('foo'); + + const renderer = ReactTestRenderer.create( + + {value =>
{value}
}
+
, + ); + + const tree = renderer.toTree(); + + cleanNodeOrArray(tree); + + expect(prettyFormat(tree)).toEqual( + prettyFormat({ + type: Provider, + nodeType: 'component', + instance: null, + props: { + value: 'bar', + }, + rendered: { + type: Consumer, + nodeType: 'component', + instance: null, + props: {}, + rendered: { + type: 'div', + nodeType: 'host', + instance: null, + props: {}, + rendered: ['bar'], + }, + }, + }), + ); + }); + it('root instance and createNodeMock ref return the same value', () => { const createNodeMock = ref => ({node: ref}); let refInst = null; diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js index 587a094f8ab70..68dda3b5f8072 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js @@ -17,9 +17,14 @@ const RCTView = 'RCTView'; const View = props => ; describe('ReactTestRendererTraversal', () => { + let Consumer, Provider; + beforeEach(() => { jest.resetModules(); ReactTestRenderer = require('react-test-renderer'); + const context = React.createContext('quux'); + Consumer = context.Consumer; + Provider = context.Provider; }); class Example extends React.Component { @@ -37,6 +42,11 @@ describe('ReactTestRendererTraversal', () => { + + + {value => } + + ); @@ -54,7 +64,7 @@ describe('ReactTestRendererTraversal', () => { // assert .props, .type and .parent attributes const foo = render.root.find(hasFooProp); - expect(foo.props.children).toHaveLength(7); + expect(foo.props.children).toHaveLength(8); expect(foo.type).toBe(View); expect(render.root.parent).toBe(null); expect(foo.children[0].parent).toBe(foo); @@ -69,6 +79,8 @@ describe('ReactTestRendererTraversal', () => { const hasNullProp = node => node.props.hasOwnProperty('null'); const hasVoidProp = node => node.props.hasOwnProperty('void'); const hasItselfProp = node => node.props.hasOwnProperty('itself'); + const hasProviderProp = node => node.props.hasOwnProperty('provider'); + const hasConsumerProp = node => node.props.hasOwnProperty('consumer'); expect(() => render.root.find(hasFooProp)).not.toThrow(); // 1 match expect(() => render.root.find(hasBarProp)).toThrow(); // >1 matches @@ -76,6 +88,8 @@ describe('ReactTestRendererTraversal', () => { expect(() => render.root.find(hasBingProp)).not.toThrow(); // 1 match expect(() => render.root.find(hasNullProp)).not.toThrow(); // 1 match expect(() => render.root.find(hasVoidProp)).toThrow(); // 0 matches + expect(() => render.root.find(hasProviderProp)).not.toThrow(); // 1 match + expect(() => render.root.find(hasConsumerProp)).not.toThrow(); // 1 match // same assertion as .find(), but confirm length expect(render.root.findAll(hasFooProp, {deep: false})).toHaveLength(1); @@ -119,10 +133,12 @@ describe('ReactTestRendererTraversal', () => { // note: there are clearly multiple in general, but there // is only one being rendered at root node level expect(() => render.root.findByType(ExampleNull)).toThrow(); // 2 matches + expect(() => render.root.findByType(Provider)).not.toThrow(); // 1 match + expect(() => render.root.findByType(Consumer)).not.toThrow(); // 1 match expect(render.root.findAllByType(ExampleFn)).toHaveLength(1); expect(render.root.findAllByType(View, {deep: false})).toHaveLength(1); - expect(render.root.findAllByType(View)).toHaveLength(7); + expect(render.root.findAllByType(View)).toHaveLength(8); expect(render.root.findAllByType(ExampleNull)).toHaveLength(2); const nulls = render.root.findAllByType(ExampleNull); From 14ebb5b1f9bdc4ba35186e32be85d14ab49ba552 Mon Sep 17 00:00:00 2001 From: Paul Sherman Date: Sun, 4 Feb 2018 16:50:50 -0600 Subject: [PATCH 2/2] Treat context components like Fragments --- .../src/ReactTestRenderer.js | 28 ++--------- .../src/__tests__/ReactTestRenderer-test.js | 48 ++++++++++++------- .../ReactTestRendererTraversal-test.js | 20 ++++---- 3 files changed, 43 insertions(+), 53 deletions(-) diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index fd01485c5f735..40358e3fa2cab 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -370,25 +370,8 @@ function toTree(node: ?Fiber) { case Fragment: return childrenToTree(node.child); case ContextConsumer: - return { - nodeType: 'component', - type: node.type, - props: {...node.pendingProps}, - instance: node.stateNode, - rendered: hasSiblings(node.child) - ? nodeAndSiblingsTrees(node.child) - : toTree(node.child), - }; case ContextProvider: - return { - nodeType: 'component', - type: node.type, - props: {...node.memoizedProps}, - instance: node.stateNode, - rendered: hasSiblings(node.child) - ? nodeAndSiblingsTrees(node.child) - : toTree(node.child), - }; + return toTree(node.child); default: invariant( false, @@ -456,8 +439,7 @@ class ReactTestInstance { } get props(): Object { - const fiber = this._currentFiber(); - return fiber.memoizedProps || fiber.pendingProps; + return this._currentFiber().memoizedProps || {}; } get parent(): ?ReactTestInstance { @@ -482,14 +464,14 @@ class ReactTestInstance { case FunctionalComponent: case ClassComponent: case HostComponent: - case ContextConsumer: - case ContextProvider: children.push(wrapFiber(node)); break; case HostText: children.push('' + node.memoizedProps); break; case Fragment: + case ContextConsumer: + case ContextProvider: descend = true; break; default: @@ -572,7 +554,6 @@ function findAll( ): Array { const deep = options ? options.deep : true; const results = []; - if (predicate(root)) { results.push(root); if (!deep) { @@ -633,7 +614,6 @@ const ReactTestRendererFiber = { ); invariant(root != null, 'something went wrong'); TestRenderer.updateContainer(element, root, null, null); - const entry = { root: undefined, // makes flow happy // we define a 'getter' for 'root' below using 'Object.defineProperty' diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js index 7c969d039b3ad..e1ea1c7f083fb 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRenderer-test.js @@ -760,25 +760,11 @@ describe('ReactTestRenderer', () => { expect(prettyFormat(tree)).toEqual( prettyFormat({ - type: Provider, - nodeType: 'component', + type: 'div', + nodeType: 'host', instance: null, - props: { - value: 'bar', - }, - rendered: { - type: Consumer, - nodeType: 'component', - instance: null, - props: {}, - rendered: { - type: 'div', - nodeType: 'host', - instance: null, - props: {}, - rendered: ['bar'], - }, - }, + props: {}, + rendered: ['bar'], }), ); }); @@ -917,4 +903,30 @@ describe('ReactTestRenderer', () => { 'world', ]); }); + + it('can update Providers and Consumers', () => { + const {Consumer, Provider} = React.createContext('foo'); + + const renderer = ReactTestRenderer.create( + + {value =>
{value}
}
+
, + ); + + expect(renderer.toJSON()).toEqual({ + type: 'div', + children: ['bar'], + props: {}, + }); + renderer.update( + + {value =>
{value}
}
+
, + ); + expect(renderer.toJSON()).toEqual({ + type: 'div', + children: ['corge'], + props: {}, + }); + }); }); diff --git a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js index 68dda3b5f8072..d5bf31ad17c5d 100644 --- a/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js +++ b/packages/react-test-renderer/src/__tests__/ReactTestRendererTraversal-test.js @@ -22,7 +22,7 @@ describe('ReactTestRendererTraversal', () => { beforeEach(() => { jest.resetModules(); ReactTestRenderer = require('react-test-renderer'); - const context = React.createContext('quux'); + const context = React.createContext('corge'); Consumer = context.Consumer; Provider = context.Provider; }); @@ -42,10 +42,8 @@ describe('ReactTestRendererTraversal', () => { - - - {value => } - + + {value => } @@ -79,8 +77,8 @@ describe('ReactTestRendererTraversal', () => { const hasNullProp = node => node.props.hasOwnProperty('null'); const hasVoidProp = node => node.props.hasOwnProperty('void'); const hasItselfProp = node => node.props.hasOwnProperty('itself'); - const hasProviderProp = node => node.props.hasOwnProperty('provider'); - const hasConsumerProp = node => node.props.hasOwnProperty('consumer'); + const hasProviderType = node => node.type === Provider; + const hasConsumerType = node => node.type === Consumer; expect(() => render.root.find(hasFooProp)).not.toThrow(); // 1 match expect(() => render.root.find(hasBarProp)).toThrow(); // >1 matches @@ -88,8 +86,8 @@ describe('ReactTestRendererTraversal', () => { expect(() => render.root.find(hasBingProp)).not.toThrow(); // 1 match expect(() => render.root.find(hasNullProp)).not.toThrow(); // 1 match expect(() => render.root.find(hasVoidProp)).toThrow(); // 0 matches - expect(() => render.root.find(hasProviderProp)).not.toThrow(); // 1 match - expect(() => render.root.find(hasConsumerProp)).not.toThrow(); // 1 match + expect(() => render.root.find(hasProviderType)).toThrow(); // 0 matches + expect(() => render.root.find(hasConsumerType)).toThrow(); // 0 matches // same assertion as .find(), but confirm length expect(render.root.findAll(hasFooProp, {deep: false})).toHaveLength(1); @@ -133,8 +131,8 @@ describe('ReactTestRendererTraversal', () => { // note: there are clearly multiple in general, but there // is only one being rendered at root node level expect(() => render.root.findByType(ExampleNull)).toThrow(); // 2 matches - expect(() => render.root.findByType(Provider)).not.toThrow(); // 1 match - expect(() => render.root.findByType(Consumer)).not.toThrow(); // 1 match + expect(() => render.root.findByType(Provider)).toThrow(); // 0 matches + expect(() => render.root.findByType(Consumer)).toThrow(); // 0 matches expect(render.root.findAllByType(ExampleFn)).toHaveLength(1); expect(render.root.findAllByType(View, {deep: false})).toHaveLength(1);