Skip to content

Commit cc3db79

Browse files
minznerjoshljharb
authored andcommitted
[New] mount/shallow: add getWrappingComponent
1 parent 26f5492 commit cc3db79

File tree

10 files changed

+843
-4
lines changed

10 files changed

+843
-4
lines changed

SUMMARY.md

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* [first()](/docs/api/ShallowWrapper/first.md)
4343
* [forEach(fn)](/docs/api/ShallowWrapper/forEach.md)
4444
* [get(index)](/docs/api/ShallowWrapper/get.md)
45+
* [getWrappingComponent()](/docs/api/ShallowWrapper/getWrappingComponent.md)
4546
* [getElement(index)](/docs/api/ShallowWrapper/getElement.md)
4647
* [getElements(index)](/docs/api/ShallowWrapper/getElements.md)
4748
* [hasClass(className)](/docs/api/ShallowWrapper/hasClass.md)
@@ -104,6 +105,7 @@
104105
* [forEach(fn)](/docs/api/ReactWrapper/forEach.md)
105106
* [get(index)](/docs/api/ReactWrapper/get.md)
106107
* [getDOMNode()](/docs/api/ReactWrapper/getDOMNode.md)
108+
* [getWrappingComponent()](/docs/api/ReactWrapper/getWrappingComponent.md)
107109
* [hasClass(className)](/docs/api/ReactWrapper/hasClass.md)
108110
* [hostNodes()](/docs/api/ReactWrapper/hostNodes.md)
109111
* [html()](/docs/api/ReactWrapper/html.md)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# `.getWrappingComponent() => ReactWrapper`
2+
3+
If a `wrappingComponent` was passed in `options`, this methods returns a `ReactWrapper` around the rendered `wrappingComponent`. This `ReactWrapper` can be used to update the `wrappingComponent`'s props, state, etc.
4+
5+
6+
#### Returns
7+
8+
`ReactWrapper`: A `ReactWrapper` around the rendered `wrappingComponent`
9+
10+
11+
12+
#### Examples
13+
14+
```jsx
15+
import { Provider } from 'react-redux';
16+
import { Router } from 'react-router';
17+
import store from './my/app/store';
18+
import mockStore from './my/app/mockStore';
19+
20+
function MyProvider(props) {
21+
const { children, customStore } = props;
22+
23+
return (
24+
<Provider store={customStore || store}>
25+
<Router>
26+
{children}
27+
</Router>
28+
</Provider>
29+
);
30+
}
31+
MyProvider.propTypes = {
32+
children: PropTypes.node,
33+
customStore: PropTypes.shape({}),
34+
};
35+
MyProvider.defaultProps = {
36+
children: null,
37+
customStore: null,
38+
};
39+
40+
const wrapper = mount(<MyComponent />, {
41+
wrappingComponent: MyProvider,
42+
});
43+
const provider = wrapper.getWrappingComponent();
44+
provider.setProps({ customStore: mockStore });
45+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# `.getWrappingComponent() => ShallowWrapper`
2+
3+
If a `wrappingComponent` was passed in `options`, this methods returns a `ShallowWrapper` around the rendered `wrappingComponent`. This `ShallowWrapper` can be used to update the `wrappingComponent`'s props, state, etc.
4+
5+
6+
#### Returns
7+
8+
`ShallowWrapper`: A `ShallowWrapper` around the rendered `wrappingComponent`
9+
10+
11+
12+
#### Examples
13+
14+
```jsx
15+
import { Provider } from 'react-redux';
16+
import { Router } from 'react-router';
17+
import store from './my/app/store';
18+
import mockStore from './my/app/mockStore';
19+
20+
function MyProvider(props) {
21+
const { children, customStore } = props;
22+
23+
return (
24+
<Provider store={customStore || store}>
25+
<Router>
26+
{children}
27+
</Router>
28+
</Provider>
29+
);
30+
}
31+
MyProvider.propTypes = {
32+
children: PropTypes.node,
33+
customStore: PropTypes.shape({}),
34+
};
35+
MyProvider.defaultProps = {
36+
children: null,
37+
customStore: null,
38+
};
39+
40+
const wrapper = shallow(<MyComponent />, {
41+
wrappingComponent: MyProvider,
42+
});
43+
const provider = wrapper.getWrappingComponent();
44+
provider.setProps({ customStore: mockStore });
45+
```

docs/api/mount.md

+5
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ describe('<Foo />', () => {
4949
- `options.context`: (`Object` [optional]): Context to be passed into the component
5050
- `options.attachTo`: (`DOMElement` [optional]): DOM Element to attach the component to.
5151
- `options.childContextTypes`: (`Object` [optional]): Merged contextTypes for all children of the wrapper.
52+
- `options.wrappingComponent`: (`ComponentType` [optional]): A component that will render as a parent of the `node`. It can be used to provide context to the `node`, among other things. See the [`getWrappingComponent()` docs](ReactWrapper/getWrappingComponent.md) for an example. **Note**: `wrappingComponent` _must_ render its children.
53+
- `options.wrappingComponentProps`: (`Object` [optional]): Initial props to pass to the `wrappingComponent` if it is specified.
5254

5355
#### Returns
5456

@@ -177,6 +179,9 @@ Manually sets context of the root component.
177179
#### [`.instance() => ReactComponent|DOMComponent`](ReactWrapper/instance.md)
178180
Returns the wrapper's underlying instance.
179181

182+
#### [`.getWrappingComponent() => ReactWrapper`](ReactWrapper/getWrappingComponent.md)
183+
Returns a wrapper representing the `wrappingComponent`, if one was passed.
184+
180185
#### [`.unmount() => ReactWrapper`](ReactWrapper/unmount.md)
181186
A method that un-mounts the component.
182187

docs/api/shallow.md

+5
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ describe('<MyComponent />', () => {
5050
- `options.disableLifecycleMethods`: (`Boolean` [optional]): If set to true, `componentDidMount`
5151
is not called on the component, and `componentDidUpdate` is not called after
5252
[`setProps`](ShallowWrapper/setProps.md) and [`setContext`](ShallowWrapper/setContext.md). Default to `false`.
53+
- `options.wrappingComponent`: (`ComponentType` [optional]): A component that will render as a parent of the `node`. It can be used to provide context to the `node`, among other things. See the [`getWrappingComponent()` docs](ShallowWrapper/getWrappingComponent.md) for an example. **Note**: `wrappingComponent` _must_ render its children.
54+
- `options.wrappingComponentProps`: (`Object` [optional]): Initial props to pass to the `wrappingComponent` if it is specified.
5355

5456
#### Returns
5557

@@ -187,6 +189,9 @@ Manually sets props of the root component.
187189
#### [`.setContext(context) => ShallowWrapper`](ShallowWrapper/setContext.md)
188190
Manually sets context of the root component.
189191

192+
#### [`.getWrappingComponent() => ShallowWrapper`](ShallowWrapper/getWrappingComponent.md)
193+
Returns a wrapper representing the `wrappingComponent`, if one was passed.
194+
190195
#### [`.instance() => ReactComponent`](ShallowWrapper/instance.md)
191196
Returns the instance of the root component.
192197

packages/enzyme-test-suite/test/ReactWrapper-spec.jsx

+130
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,135 @@ describeWithDOM('mount', () => {
180180
expect(() => wrapper.state('key')).to.throw('ReactWrapper::state("key") requires that `state` not be `null` or `undefined`');
181181
});
182182

183+
describeIf(is('>= 0.14'), 'wrappingComponent', () => {
184+
const realCreateMountRenderer = getAdapter().createMountRenderer;
185+
186+
class More extends React.Component {
187+
render() {
188+
return null;
189+
}
190+
}
191+
192+
class TestProvider extends React.Component {
193+
getChildContext() {
194+
const { value, renderMore } = this.props;
195+
196+
return {
197+
testContext: value || 'Hello world!',
198+
renderMore: renderMore || false,
199+
};
200+
}
201+
202+
render() {
203+
const { children } = this.props;
204+
205+
return children;
206+
}
207+
}
208+
TestProvider.childContextTypes = {
209+
testContext: PropTypes.string,
210+
renderMore: PropTypes.bool,
211+
};
212+
213+
class MyWrappingComponent extends React.Component {
214+
render() {
215+
const { children, contextValue, renderMore } = this.props;
216+
217+
return (
218+
<div>
219+
<TestProvider value={contextValue} renderMore={renderMore}>{children}</TestProvider>
220+
</div>
221+
);
222+
}
223+
}
224+
225+
class MyComponent extends React.Component {
226+
render() {
227+
const { testContext, renderMore } = this.context;
228+
229+
return (
230+
<div>
231+
<div>Context says: {testContext}</div>
232+
{renderMore && <More />}
233+
</div>
234+
);
235+
}
236+
}
237+
MyComponent.contextTypes = TestProvider.childContextTypes;
238+
239+
it('mounts the passed node as the root as per usual', () => {
240+
const wrapper = mount(<MyComponent />, {
241+
wrappingComponent: MyWrappingComponent,
242+
});
243+
expect(wrapper.type()).to.equal(MyComponent);
244+
expect(wrapper.parent().exists()).to.equal(false);
245+
expect(() => wrapper.setProps({ foo: 'bar' })).not.to.throw();
246+
});
247+
248+
it('renders the root in the wrapping component', () => {
249+
const wrapper = mount(<MyComponent />, {
250+
wrappingComponent: MyWrappingComponent,
251+
});
252+
// Context will only be set properly if the root node is rendered as a descendent of the wrapping component.
253+
expect(wrapper.text()).to.equal('Context says: Hello world!');
254+
});
255+
256+
it('supports mounting the wrapping component with initial props', () => {
257+
const wrapper = mount(<MyComponent />, {
258+
wrappingComponent: MyWrappingComponent,
259+
wrappingComponentProps: { contextValue: 'I can be set!' },
260+
});
261+
expect(wrapper.text()).to.equal('Context says: I can be set!');
262+
});
263+
264+
it('throws an error if the wrappingComponent does not render its children', () => {
265+
class BadWrapper extends React.Component {
266+
render() {
267+
return <div />;
268+
}
269+
}
270+
expect(() => mount(<MyComponent />, {
271+
wrappingComponent: BadWrapper,
272+
})).to.throw('`wrappingComponent` must render its children!');
273+
});
274+
275+
wrap()
276+
.withOverrides(() => getAdapter(), () => ({
277+
RootFinder: undefined,
278+
createMountRenderer: (...args) => {
279+
const renderer = realCreateMountRenderer(...args);
280+
delete renderer.getWrappingComponentRenderer;
281+
renderer.getNode = () => null;
282+
return renderer;
283+
},
284+
isCustomComponent: undefined,
285+
}))
286+
.describe('with an old adapter', () => {
287+
it('renders fine when wrappingComponent is not passed', () => {
288+
const wrapper = mount(<MyComponent />);
289+
expect(wrapper.debug()).to.equal('');
290+
});
291+
292+
it('throws an error if wrappingComponent is passed', () => {
293+
expect(() => mount(<MyComponent />, {
294+
wrappingComponent: MyWrappingComponent,
295+
})).to.throw('your adapter does not support `wrappingComponent`. Try upgrading it!');
296+
});
297+
});
298+
});
299+
300+
itIf(is('<=0.13'), 'throws an error if wrappingComponent is passed', () => {
301+
class WrappingComponent extends React.Component {
302+
render() {
303+
const { children } = this.props;
304+
return children;
305+
}
306+
}
307+
expect(() => mount(<div />, {
308+
wrappingComponent: WrappingComponent,
309+
})).to.throw('your adapter does not support `wrappingComponent`. Try upgrading it!');
310+
});
311+
183312
describeIf(is('>= 16.3'), 'uses the isValidElementType from the Adapter to validate the prop type of Component', () => {
184313
const Foo = () => null;
185314
const Bar = () => null;
@@ -711,6 +840,7 @@ describeWithDOM('mount', () => {
711840
'getElements',
712841
'getNode',
713842
'getNodes',
843+
'getWrappingComponent',
714844
'hasClass',
715845
'hostNodes',
716846
'html',

0 commit comments

Comments
 (0)