From 3dd8111899a0bb66c59c84d311972fabd8ea0deb Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 20 Sep 2016 23:30:41 -0700 Subject: [PATCH] [New] `shallow`: add `.dive()` --- docs/api/ShallowWrapper/dive.md | 52 ++++++++++++++++++++++++++++ docs/api/shallow.md | 3 ++ src/ShallowWrapper.js | 24 ++++++++++++- test/ShallowWrapper-spec.jsx | 60 ++++++++++++++++++++++++++++++++- 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 docs/api/ShallowWrapper/dive.md diff --git a/docs/api/ShallowWrapper/dive.md b/docs/api/ShallowWrapper/dive.md new file mode 100644 index 000000000..83c7d77fa --- /dev/null +++ b/docs/api/ShallowWrapper/dive.md @@ -0,0 +1,52 @@ +# `.dive([options]) => ShallowWrapper` + +Shallow render the one non-DOM child of the current wrapper, and return a wrapper around the result. + +NOTE: can only be called on wrapper of a single non-DOM component element node. + + +#### Arguments + +1. `options` (`Object` [optional]): +- `options.context`: (`Object` [optional]): Context to be passed into the component + + + +#### Returns + +`ShallowWrapper`: A new wrapper that wraps the current node after it's been shallow rendered. + + + +#### Examples + +```jsx +class Bar extends React.Component { + render() { + return ( +
+
+
+ ); + } +} +``` + +```jsx +class Foo extends React.Component { + render() { + return ( +
+ +
+ ); + } +} +``` + +```jsx +const wrapper = shallow(); +expect(wrapper.find('.in-bar')).to.have.length(0); +expect(wrapper.find(Bar)).to.have.length(1); +expect(wrapper.dive().find('.in-bar')).to.have.length(1); +``` diff --git a/docs/api/shallow.md b/docs/api/shallow.md index f9468739d..2e31a3b31 100644 --- a/docs/api/shallow.md +++ b/docs/api/shallow.md @@ -207,3 +207,6 @@ Returns whether or not all of the nodes in the wrapper match the provided select #### [`.everyWhere(predicate) => Boolean`](ShallowWrapper/everyWhere.md) Returns whether or not all of the nodes in the wrapper pass the provided predicate function. + +#### [`.dive([options]) => ShallowWrapper`](ShallowWrapper/dive.md) +Shallow render the one non-DOM child of the current wrapper, and return a wrapper around the result. diff --git a/src/ShallowWrapper.js b/src/ShallowWrapper.js index fe0284643..13e49d95d 100644 --- a/src/ShallowWrapper.js +++ b/src/ShallowWrapper.js @@ -15,6 +15,7 @@ import { isReactElementAlike, displayNameOfNode, isFunctionalComponent, + isCustomComponentElement, } from './Utils'; import { debugNodes, @@ -31,6 +32,7 @@ import { createShallowRenderer, renderToStaticMarkup, batchedUpdates, + isDOMComponentElement, } from './react-compat'; /** @@ -698,7 +700,7 @@ export default class ShallowWrapper { /** * Returns the type of the current node of this wrapper. If it's a composite component, this will - * be the component constructor. If it's native DOM node, it will be a string. + * be the component constructor. If it's a native DOM node, it will be a string. * * @returns {String|Function} */ @@ -959,4 +961,24 @@ export default class ShallowWrapper { intercepter(this); return this; } + + /** + * Primarily useful for HOCs (higher-order components), this method may only be + * run on a single, non-DOM node, and will return the node, shallow-rendered. + * + * @param options object + * @returns {ShallowWrapper} + */ + dive(options) { + const name = 'dive'; + return this.single(name, (n) => { + if (isDOMComponentElement(n)) { + throw new TypeError(`ShallowWrapper::${name}() can not be called on DOM components`); + } + if (!isCustomComponentElement(n)) { + throw new TypeError(`ShallowWrapper::${name}() can only be called on components`); + } + return new ShallowWrapper(n, null, options); + }); + } } diff --git a/test/ShallowWrapper-spec.jsx b/test/ShallowWrapper-spec.jsx index cc07faa68..887f3157c 100644 --- a/test/ShallowWrapper-spec.jsx +++ b/test/ShallowWrapper-spec.jsx @@ -69,7 +69,7 @@ describe('shallow', () => { ); const context = { name: 'foo' }; - expect(() => shallow(, { context })).not.to.throw(Error); + expect(() => shallow(, { context })).not.to.throw(); }); it('is instrospectable through context API', () => { @@ -3558,4 +3558,62 @@ describe('shallow', () => { }); }); + describe('.dive()', () => { + function RendersDOM() { + return
; + } + function RendersNull() { + return null; + } + function RendersMultiple() { + return ( +
+ + +
+ ); + } + function WrapsRendersDOM() { + return ; + } + function DoubleWrapsRendersDOM() { + return ; + } + + it('throws on a DOM node', () => { + const wrapper = shallow(); + expect(wrapper.is('div')).to.equal(true); + + expect(() => { wrapper.dive(); }).to.throw( + TypeError, + 'ShallowWrapper::dive() can not be called on DOM components' + ); + }); + + it('throws on a non-component', () => { + const wrapper = shallow(); + expect(wrapper.type()).to.equal(null); + + expect(() => { wrapper.dive(); }).to.throw( + TypeError, + 'ShallowWrapper::dive() can only be called on components' + ); + }); + + it('throws on multiple children found', () => { + const wrapper = shallow().find('div').children(); + expect(() => { wrapper.dive(); }).to.throw( + Error, + 'Method “dive” is only meant to be run on a single node. 2 found instead.' + ); + }); + + it('dives + shallow-renders when there is one component child', () => { + const wrapper = shallow(); + expect(wrapper.is(WrapsRendersDOM)).to.equal(true); + + const underwater = wrapper.dive(); + expect(underwater.is(RendersDOM)).to.equal(true); + }); + }); });