Skip to content

Commit

Permalink
[New] shallow/mount: Add wrapProp() method
Browse files Browse the repository at this point in the history
  • Loading branch information
Juliano Penna authored and ljharb committed Oct 5, 2019
1 parent 1eb020e commit d7f216a
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 4 deletions.
60 changes: 60 additions & 0 deletions docs/api/ReactWrapper/wrapProp.md
@@ -0,0 +1,60 @@
# `.wrapProp(propName[, options]) => ReactWrapper`

Returns a new wrapper around the component provided to the original wrapper's prop `propName`.`

#### Arguments

1. `propName` (`String`): Name of the prop to be wrapped
1. `options` (`Object` [optional]): Will be passed to the renderer constructor.
Refer to the [`mount()` options](https://enzymejs.github.io/enzyme/docs/api/mount.html#arguments).

This essentially does:

```jsx
const Node = () => wrapper.prop('node');
const node = mount(<Node />);
```

#### Returns

`ReactWrapper`: A new wrapper that wraps the node from the provided prop.

#### Examples

##### Test Setup

```jsx
class Inner extends React.Component {
render() {
return <div className="inner" />;
}
}

class Outer extends React.Component {
render() {
if (!this.props.renderNode) return <div />;
return this.props.node;
}
}

class Container extends React.Component {
render() {
/*
* Just as an example, <Outer> can render or not the provided prop.
* Independent of what it does, you want to test the component given to node.
*/
return <Outer renderNode={false} node={<Inner />} />;
}
}
```

##### Testing with no arguments

```jsx
const wrapper = mount(<Container />)
.find(Outer)
.wrapProp('node');

expect(wrapper.find('div').equals(<div className="inner" />)).to.equal(true);
expect(wrapper.html()).to.equal('<div class="inner"></div>');
```
59 changes: 59 additions & 0 deletions docs/api/ShallowWrapper/wrapProp.md
@@ -0,0 +1,59 @@
# `.wrapProp(propName[, options]) => ShallowWrapper`

Returns a new wrapper around the component provided to the original wrapper's prop `propName`.`

#### Arguments

1. `propName` (`String`): Name of the prop to be wrapped
1. `options` (`Object` [optional]): Will be passed to the renderer constructor.
Refer to the [`shallow()` options](https://enzymejs.github.io/enzyme/docs/api/shallow.html#arguments).

This essentially does:

```jsx
const Node = () => wrapper.prop('node');
const node = shallow(<Node />);
```

#### Returns

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

#### Examples

##### Test Setup

```jsx
class Inner extends React.Component {
render() {
return <div className="inner" />;
}
}

class Outer extends React.Component {
render() {
if (!this.props.renderNode) return <div />;
return this.props.node;
}
}

class Container extends React.Component {
render() {
/*
* Just as an example, <Outer> can render or not the provided prop.
* Independent of what it does, you want to test the component given to node.
*/
return <Outer renderNode={false} node={<Inner />} />;
}
}
```

##### Testing with no arguments

```jsx
const wrapper = shallow(<Container />)
.find(Outer)
.wrapProp('node');

expect(wrapper.equals(<div className="inner" />)).to.equal(true);
```
1 change: 1 addition & 0 deletions packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
Expand Up @@ -1077,6 +1077,7 @@ describeWithDOM('mount', () => {
'text',
'unmount',
'wrap',
'wrapProp',
);
describeHooks(
{ Wrap, Wrapper },
Expand Down
1 change: 1 addition & 0 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Expand Up @@ -1285,6 +1285,7 @@ describe('shallow', () => {
'text',
'unmount',
'wrap',
'wrapProp',
);
describeHooks(
{ Wrap, Wrapper },
Expand Down
71 changes: 71 additions & 0 deletions packages/enzyme-test-suite/test/shared/methods/wrapProp.jsx
@@ -0,0 +1,71 @@
import React from 'react';
import { expect } from 'chai';
import wrap from 'mocha-wrap';

import getAdapter from 'enzyme/build/getAdapter';

export default function describeWrapProp({
Wrap,
WrapRendered,
WrapperName,
isShallow,
}) {
wrap()
.withConsoleThrows()
.describe('.wrapProp()', () => {
class Inner extends React.Component {
render() {
return <div className="inner" />;
}
}
class Outer extends React.Component {
render() {
return <div />;
}
}
class Container extends React.Component {
render() {
const node = this.props.replace || <Inner />;
return <Outer node={node} />;
}
}

it('returns a wrapper around the node provided by the given prop', () => {
const wrapper = Wrap(<Container />);
const wrappedPropWrapper = wrapper.find(Outer).wrapProp('node');
expect(wrappedPropWrapper.find('div').equals(<div className="inner" />)).to.equal(true);
if (isShallow) expect(wrappedPropWrapper.equals(<div className="inner" />)).to.equal(true);
});

it('throws on a non-string prop name', () => {
const wrapper = Wrap(<Container />);
expect(() => wrapper.find(Outer).wrapProp([])).to.throw(
TypeError,
`${WrapperName}::wrapProp(): \`propName\` must be a string`,
);
});

it('throws on a missing prop', () => {
const wrapper = Wrap(<Container />);
expect(() => wrapper.find(Outer).wrapProp('missing')).to.throw(
Error,
`${WrapperName}::wrapProp(): no prop called "missing" found`,
);
});

it('throws on an invalid element prop value', () => {
const wrapper = Wrap(<Container replace={() => <div />} />);
expect(() => wrapper.find(Outer).wrapProp('node')).to.throw(
TypeError,
`${WrapperName}::wrapProp(): prop "node" does not contain a valid element`,
);
});

wrap()
.withOverride(() => getAdapter(), 'wrap', () => undefined)
.it('throws with a react adapter that lacks a `.wrap`', () => {
const wrapper = Wrap(<Container />);
expect(() => wrapper.find(Outer).wrapProp('foo')).to.throw(RangeError);
});
});
}
32 changes: 31 additions & 1 deletion packages/enzyme/src/ReactWrapper.js
Expand Up @@ -886,6 +886,36 @@ class ReactWrapper {
});
}

/**
* Returns a new `ReactWrapper` around the node provided to the prop
*
* @param {String} propName
* @param {Object} options
* @returns {ReactWrapper}
*/
wrapProp(propName, options) {
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('wrapProp', () => {
if (typeof propName !== 'string') {
throw new TypeError('ReactWrapper::wrapProp(): `propName` must be a string');
}
const props = this.props();
if (!has(props, propName)) {
throw new Error(`ReactWrapper::wrapProp(): no prop called "${propName}" found`);
}
const node = props[propName];
if (!adapter.isValidElement(node)) {
throw new TypeError(`ReactWrapper::wrapProp(): prop "${propName}" does not contain a valid element`);
}

return new ReactWrapper(node, null, options);
});
}

/**
* Returns the key assigned to the current node.
*
Expand Down Expand Up @@ -1010,7 +1040,7 @@ class ReactWrapper {
*
* @param {Number} begin
* @param {Number} end
* @returns {ShallowWrapper}
* @returns {ReactWrapper}
*/
slice(begin, end) {
return this.wrap(this.getNodesInternal().slice(begin, end));
Expand Down
36 changes: 33 additions & 3 deletions packages/enzyme/src/ShallowWrapper.js
Expand Up @@ -706,7 +706,7 @@ class ShallowWrapper {
throw new Error('ShallowWrapper::setProps() can only be called on the root');
}
if (arguments.length > 1 && typeof callback !== 'function') {
throw new TypeError('ReactWrapper::setProps() expects a function as its second argument');
throw new TypeError('ShallowWrapper::setProps() expects a function as its second argument');
}
this.rerender(props);
if (callback) {
Expand Down Expand Up @@ -736,7 +736,7 @@ class ShallowWrapper {
throw new Error('ShallowWrapper::setState() can only be called on class components');
}
if (arguments.length > 1 && typeof callback !== 'function') {
throw new TypeError('ReactWrapper::setState() expects a function as its second argument');
throw new TypeError('ShallowWrapper::setState() expects a function as its second argument');
}

this.single('setState', () => {
Expand Down Expand Up @@ -1313,7 +1313,7 @@ class ShallowWrapper {

/**
* Used to invoke a function prop.
* Will invoke an function prop and return its value.
* Will invoke a function prop and return its value.
*
* @param {String} propName
* @returns {Any}
Expand Down Expand Up @@ -1368,6 +1368,36 @@ class ShallowWrapper {
});
}

/**
* Returns a new `ShallowWrapper` around the node provided to the prop
*
* @param {String} propName
* @param {Object} options
* @returns {ShallowWrapper}
*/
wrapProp(propName, options) {
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('wrapProp', () => {
if (typeof propName !== 'string') {
throw new TypeError('ShallowWrapper::wrapProp(): `propName` must be a string');
}
const props = this.props();
if (!has(props, propName)) {
throw new Error(`ShallowWrapper::wrapProp(): no prop called "${propName}" found`);
}
const node = props[propName];
if (!adapter.isValidElement(node)) {
throw new TypeError(`ShallowWrapper::wrapProp(): prop "${propName}" does not contain a valid element`);
}

return new ShallowWrapper(node, null, options);
});
}

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

0 comments on commit d7f216a

Please sign in to comment.