Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] shallow: add .dive() #618

Merged
merged 3 commits into from
Oct 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions docs/api/ShallowWrapper/dive.md
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<div className="in-bar" />
</div>
);
}
}
```

```jsx
class Foo extends React.Component {
render() {
return (
<div>
<Bar />
</div>
);
}
}
```

```jsx
const wrapper = shallow(<Foo />);
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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect this would fail because wrapper wraps a DOM element and by design, dive (currently?) throws in that case.

Don't you mean expect(wrapper.children().dive().find('.in-bar')).to.have.length(1); instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, thanks, I did mean that :-) fixed in e722625

```
3 changes: 3 additions & 0 deletions docs/api/shallow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
24 changes: 23 additions & 1 deletion src/ShallowWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
isReactElementAlike,
displayNameOfNode,
isFunctionalComponent,
isCustomComponentElement,
} from './Utils';
import {
debugNodes,
Expand All @@ -31,6 +32,7 @@ import {
createShallowRenderer,
renderToStaticMarkup,
batchedUpdates,
isDOMComponentElement,
} from './react-compat';

/**
Expand Down Expand Up @@ -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}
*/
Expand Down Expand Up @@ -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);
});
}
}
6 changes: 5 additions & 1 deletion src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ export function internalInstance(inst) {
}

export function isFunctionalComponent(inst) {
return inst && inst.constructor && inst.constructor.name === 'StatelessComponent';
return !!inst && !!inst.constructor && inst.constructor.name === 'StatelessComponent';
}

export function isCustomComponentElement(inst) {
return !!inst && React.isValidElement(inst) && typeof inst.type === 'function';
}

export function propsOfNode(node) {
Expand Down
5 changes: 5 additions & 0 deletions src/react-compat.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ if (REACT013) {
};
}

function isDOMComponentElement(inst) {
return React.isValidElement(inst) && typeof inst.type === 'string';
}

const {
mockComponent,
isElement,
Expand All @@ -170,6 +174,7 @@ export {
isElement,
isElementOfType,
isDOMComponent,
isDOMComponentElement,
isCompositeComponent,
isCompositeComponentWithType,
isCompositeComponentElement,
Expand Down
70 changes: 69 additions & 1 deletion test/ShallowWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe('shallow', () => {
);

const context = { name: 'foo' };
expect(() => shallow(<SimpleComponent />, { context })).not.to.throw(Error);
expect(() => shallow(<SimpleComponent />, { context })).not.to.throw();
});

it('is instrospectable through context API', () => {
Expand Down Expand Up @@ -3558,4 +3558,72 @@ describe('shallow', () => {
});
});

describe('.dive()', () => {
class RendersDOM extends React.Component {
render() {
return <div><i /></div>;
}
}
class RendersNull extends React.Component {
render() {
return null;
}
}
class RendersMultiple extends React.Component {
render() {
return (
<div>
<RendersNull />
<RendersDOM />
</div>
);
}
}
class WrapsRendersDOM extends React.Component {
render() {
return <RendersDOM />;
}
}
class DoubleWrapsRendersDOM extends React.Component {
render() {
return <WrapsRendersDOM />;
}
}

it('throws on a DOM node', () => {
const wrapper = shallow(<RendersDOM />);
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(<RendersNull />);
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(<RendersMultiple />).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(<DoubleWrapsRendersDOM />);
expect(wrapper.is(WrapsRendersDOM)).to.equal(true);

const underwater = wrapper.dive();
expect(underwater.is(RendersDOM)).to.equal(true);
});
});
});