diff --git a/docs/api/ReactWrapper/wrapProp.md b/docs/api/ReactWrapper/wrapProp.md
new file mode 100644
index 000000000..7517de7fa
--- /dev/null
+++ b/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();
+```
+
+#### Returns
+
+`ReactWrapper`: A new wrapper that wraps the node from the provided prop.
+
+#### Examples
+
+##### Test Setup
+
+```jsx
+class Inner extends React.Component {
+ render() {
+ return
;
+ }
+}
+
+class Outer extends React.Component {
+ render() {
+ if (!this.props.renderNode) return ;
+ return this.props.node;
+ }
+}
+
+class Container extends React.Component {
+ render() {
+ /*
+ * Just as an example, can render or not the provided prop.
+ * Independent of what it does, you want to test the component given to node.
+ */
+ return } />;
+ }
+}
+```
+
+##### Testing with no arguments
+
+```jsx
+const wrapper = mount()
+ .find(Outer)
+ .wrapProp('node');
+
+expect(wrapper.find('div').equals()).to.equal(true);
+expect(wrapper.html()).to.equal('');
+```
diff --git a/docs/api/ShallowWrapper/wrapProp.md b/docs/api/ShallowWrapper/wrapProp.md
new file mode 100644
index 000000000..748beca58
--- /dev/null
+++ b/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();
+```
+
+#### Returns
+
+`ShallowWrapper`: A new wrapper that wraps the node from the provided prop.
+
+#### Examples
+
+##### Test Setup
+
+```jsx
+class Inner extends React.Component {
+ render() {
+ return ;
+ }
+}
+
+class Outer extends React.Component {
+ render() {
+ if (!this.props.renderNode) return ;
+ return this.props.node;
+ }
+}
+
+class Container extends React.Component {
+ render() {
+ /*
+ * Just as an example, can render or not the provided prop.
+ * Independent of what it does, you want to test the component given to node.
+ */
+ return } />;
+ }
+}
+```
+
+##### Testing with no arguments
+
+```jsx
+const wrapper = shallow()
+ .find(Outer)
+ .wrapProp('node');
+
+expect(wrapper.equals()).to.equal(true);
+```
diff --git a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
index fb246711b..52930e5a6 100644
--- a/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
+++ b/packages/enzyme-test-suite/test/ReactWrapper-spec.jsx
@@ -1077,6 +1077,7 @@ describeWithDOM('mount', () => {
'text',
'unmount',
'wrap',
+ 'wrapProp',
);
describeHooks(
{ Wrap, Wrapper },
diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
index 7c894cf75..bfa35e55c 100644
--- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
+++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
@@ -1285,6 +1285,7 @@ describe('shallow', () => {
'text',
'unmount',
'wrap',
+ 'wrapProp',
);
describeHooks(
{ Wrap, Wrapper },
diff --git a/packages/enzyme-test-suite/test/shared/methods/wrapProp.jsx b/packages/enzyme-test-suite/test/shared/methods/wrapProp.jsx
new file mode 100644
index 000000000..e12631861
--- /dev/null
+++ b/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 ;
+ }
+ }
+ class Outer extends React.Component {
+ render() {
+ return ;
+ }
+ }
+ class Container extends React.Component {
+ render() {
+ const node = this.props.replace || ;
+ return ;
+ }
+ }
+
+ it('returns a wrapper around the node provided by the given prop', () => {
+ const wrapper = Wrap();
+ const wrappedPropWrapper = wrapper.find(Outer).wrapProp('node');
+ expect(wrappedPropWrapper.find('div').equals()).to.equal(true);
+ if (isShallow) expect(wrappedPropWrapper.equals()).to.equal(true);
+ });
+
+ it('throws on a non-string prop name', () => {
+ const wrapper = Wrap();
+ expect(() => wrapper.find(Outer).wrapProp([])).to.throw(
+ TypeError,
+ `${WrapperName}::wrapProp(): \`propName\` must be a string`,
+ );
+ });
+
+ it('throws on a missing prop', () => {
+ const wrapper = Wrap();
+ 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( } />);
+ 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();
+ expect(() => wrapper.find(Outer).wrapProp('foo')).to.throw(RangeError);
+ });
+ });
+}
diff --git a/packages/enzyme/src/ReactWrapper.js b/packages/enzyme/src/ReactWrapper.js
index 856df6609..0dbea795a 100644
--- a/packages/enzyme/src/ReactWrapper.js
+++ b/packages/enzyme/src/ReactWrapper.js
@@ -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.
*
@@ -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));
diff --git a/packages/enzyme/src/ShallowWrapper.js b/packages/enzyme/src/ShallowWrapper.js
index f9821339b..c361ae77b 100644
--- a/packages/enzyme/src/ShallowWrapper.js
+++ b/packages/enzyme/src/ShallowWrapper.js
@@ -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) {
@@ -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', () => {
@@ -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}
@@ -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.
*