Skip to content

Commit

Permalink
[New] shallow: add renderProp
Browse files Browse the repository at this point in the history
  • Loading branch information
dferber90 authored and ljharb committed Oct 16, 2018
1 parent 8494d0e commit ec66aaf
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 0 deletions.
85 changes: 85 additions & 0 deletions docs/api/ShallowWrapper/renderProp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# `.renderProp(propName, ...args) => ShallowWrapper`

Calls the current wrapper's property with name `propName` and the `args` provided.
Returns the result in a new wrapper.

NOTE: can only be called on wrapper of a single non-DOM component element node.

#### Arguments

1. `propName` (`String`):
1. `...args` (`Array<Any>`):

This essentially calls `wrapper.prop(propName)(...args)`.

#### Returns

`ShallowWrapper`: A new wrapper that wraps the node returned from the render prop.

#### Examples

##### Test Setup

```jsx
class Mouse extends React.Component {
constructor() {
super();
this.state = { x: 0, y: 0 };
}

render() {
const { render } = this.props;
return (
<div
style={{ height: '100%' }}
onMouseMove={(event) => {
this.setState({
x: event.clientX,
y: event.clientY,
});
}}
>
{render(this.state)}
</div>
);
}
}

Mouse.propTypes = {
render: PropTypes.func.isRequired,
};
```

```jsx
const App = () => (
<div style={{ height: '100%' }}>
<Mouse
render={(x = 0, y = 0) => (
<h1>
The mouse position is ({x}, {y})
</h1>
)}
/>
</div>
);
```

##### Testing with no arguments

```jsx
const wrapper = shallow(<App />)
.find(Mouse)
.renderProp('render');

expect(wrapper.equals(<h1>The mouse position is 0, 0</h1>)).to.equal(true);
```

##### Testing with multiple arguments

```jsx
const wrapper = shallow(<App />)
.find(Mouse)
.renderProp('render', [10, 20]);

expect(wrapper.equals(<h1>The mouse position is 10, 20</h1>)).to.equal(true);
```
3 changes: 3 additions & 0 deletions docs/api/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ Shallow renders the current node and returns a shallow wrapper around it.
#### [`.render() => CheerioWrapper`](ShallowWrapper/render.md)
Returns a CheerioWrapper of the current node's subtree.

#### [`.renderProp(key) => ShallowWrapper`](ShallowWrapper/renderProp.md)
Returns a wrapper of the node rendered by the provided render prop.

#### [`.unmount() => ShallowWrapper`](ShallowWrapper/unmount.md)
A method that un-mounts the component.

Expand Down
62 changes: 62 additions & 0 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4743,6 +4743,68 @@ describe('shallow', () => {
});
});

describe('.renderProp()', () => {
it('returns a wrapper around the node returned from the render prop', () => {
class Foo extends React.Component {
render() {
return <div className="in-foo" />;
}
}
class Bar extends React.Component {
render() {
const { render: r } = this.props;
return <div className="in-bar">{r()}</div>;
}
}

const wrapperA = shallow(<div><Bar render={() => <div><Foo /></div>} /></div>);
const renderPropWrapperA = wrapperA.find(Bar).renderProp('render');
expect(renderPropWrapperA.find(Foo)).to.have.lengthOf(1);

const wrapperB = shallow(<div><Bar render={() => <Foo />} /></div>);
const renderPropWrapperB = wrapperB.find(Bar).renderProp('render');
expect(renderPropWrapperB.find(Foo)).to.have.lengthOf(1);

const stub = sinon.stub().returns(<div />);
const wrapperC = shallow(<div><Bar render={stub} /></div>);
stub.resetHistory();
wrapperC.find(Bar).renderProp('render', 'one', 'two');
expect(stub.args).to.deep.equal([['one', 'two']]);
});

it('throws on host elements', () => {
class Div extends React.Component {
render() {
const { children } = this.props;
return <div>{children}</div>;
}
}

const wrapper = shallow(<Div />);
expect(wrapper.is('div')).to.equal(true);
expect(() => wrapper.renderProp('foo')).to.throw();
});

wrap()
.withOverride(() => getAdapter(), 'wrap', () => undefined)
.it('throws with a react adapter that lacks a `.wrap`', () => {
class Foo extends React.Component {
render() {
return <div className="in-foo" />;
}
}
class Bar extends React.Component {
render() {
const { render: r } = this.props;
return <div className="in-bar">{r()}</div>;
}
}

const wrapper = shallow(<div><Bar render={() => <div><Foo /></div>} /></div>);
expect(() => wrapper.find(Bar).renderProp('render')).to.throw(RangeError);
});
});

describe('lifecycle methods', () => {
describe('disableLifecycleMethods option', () => {
describe('validation', () => {
Expand Down
35 changes: 35 additions & 0 deletions packages/enzyme/src/ShallowWrapper.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import flat from 'array.prototype.flat';
import isEqual from 'lodash.isequal';
import cheerio from 'cheerio';
import has from 'has';

import {
nodeEqual,
Expand Down Expand Up @@ -1033,6 +1034,40 @@ class ShallowWrapper {
return this.props()[propName];
}

/**
* Returns a wrapper of the node rendered by the provided render prop.
*
* @param {String} propName
* @returns {ShallowWrapper}
*/
renderProp(propName, ...args) {
const adapter = getAdapter(this[OPTIONS]);
if (typeof adapter.wrap !== 'function') {
throw new RangeError('your adapter does not support `wrap`. Try upgrading it!');
}

return this.single('renderProp', (n) => {
if (n.nodeType === 'host') {
throw new TypeError('ShallowWrapper::renderProp() can only be called on custom components');
}
if (typeof propName !== 'string') {
throw new TypeError('`propName` must be a string');
}
const props = this.props();
if (!has(props, propName)) {
throw new Error(`no prop called “${propName}“ found`);
}
const propValue = props[propName];
if (typeof propValue !== 'function') {
throw new TypeError(`expected prop “${propName}“ to contain a function, but it holds “${typeof prop}“`);
}

const element = propValue(...args);
const wrapped = adapter.wrap(element);
return this.wrap(wrapped, null, this[OPTIONS]);
});
}

/**
* Returns the key assigned to the current node.
*
Expand Down

0 comments on commit ec66aaf

Please sign in to comment.