Skip to content

Commit

Permalink
Merge pull request #339 from milesj/add-isnull-check
Browse files Browse the repository at this point in the history
Add an `isEmptyRender()` method to both `ShallowWrapper` and `ReactWrapper`
  • Loading branch information
aweary committed May 25, 2016
2 parents 11250b5 + 411bee2 commit 6845529
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 4 deletions.
20 changes: 20 additions & 0 deletions src/ReactWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
instEqual,
treeFilter,
getNode,
internalInstanceOrComponent,
} from './MountedTraversal';
import {
renderWithOptions,
Expand All @@ -30,6 +31,7 @@ import {
import {
debugInsts,
} from './Debug';
import { REACT15 } from './version';

/**
* Finds all nodes in the current wrapper nodes' render trees that match the provided predicate
Expand Down Expand Up @@ -388,6 +390,24 @@ export default class ReactWrapper {
return this.single(n => predicate(n));
}

/**
* Returns true if the component rendered nothing, i.e., null or false.
*
* @returns {boolean}
*/
isEmptyRender() {
return this.single((n) => {
// Stateful components and stateless function components have different internal structures,
// so we need to find the correct internal instance, and validate the rendered node type
// equals 2, which is the `ReactNodeTypes.EMPTY` value.
if (REACT15) {
return internalInstanceOrComponent(n)._renderedNodeType === 2;
}

return findDOMNode(n) === null;
});
}

/**
* Returns a new wrapper instance with only the nodes of the current wrapper instance that match
* the provided predicate function.
Expand Down
9 changes: 9 additions & 0 deletions src/ShallowWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@ export default class ShallowWrapper {
return this.single(predicate);
}

/**
* Returns true if the component rendered nothing, i.e., null or false.
*
* @returns {boolean}
*/
isEmptyRender() {
return this.type() === null;
}

/**
* Returns a new wrapper instance with only the nodes of the current wrapper instance that match
* the provided predicate function. The predicate should receive a wrapped node as its first
Expand Down
43 changes: 41 additions & 2 deletions test/ReactWrapper-spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describeWithDOM, describeIf } from './_helpers';
import { describeWithDOM, describeIf, itWithData, generateEmptyRenderData } from './_helpers';
import React from 'react';
import { expect } from 'chai';
import {
Expand All @@ -7,7 +7,7 @@ import {
ReactWrapper,
} from '../src/';
import sinon from 'sinon';
import { REACT013 } from '../src/version';
import { REACT013, REACT15 } from '../src/version';

describeWithDOM('mount', () => {

Expand Down Expand Up @@ -1240,6 +1240,45 @@ describeWithDOM('mount', () => {
});
});

describe('.isEmptyRender()', () => {
const emptyRenderValues = generateEmptyRenderData();

itWithData(emptyRenderValues, 'when a React class component returns: ', (data) => {
const Foo = React.createClass({
render() {
return data.value;
},
});
const wrapper = mount(<Foo />);
expect(wrapper.isEmptyRender()).to.equal(data.expectResponse);
});

itWithData(emptyRenderValues, 'when an ES2015 class component returns: ', (data) => {
class Foo extends React.Component {
render() {
return data.value;
}
}
const wrapper = mount(<Foo />);
expect(wrapper.isEmptyRender()).to.equal(data.expectResponse);
});

it('should not return true for HTML elements', () => {
const wrapper = mount(<div className="bar baz" />);
expect(wrapper.isEmptyRender()).to.equal(false);
});

describeIf(REACT15, 'stateless function components', () => {
itWithData(emptyRenderValues, 'when a component returns: ', (data) => {
function Foo() {
return data.value;
}
const wrapper = mount(<Foo />);
expect(wrapper.isEmptyRender()).to.equal(data.expectResponse);
});
});
});

describe('.not(selector)', () => {
it('filters to things not matching a selector', () => {
const wrapper = mount(
Expand Down
43 changes: 41 additions & 2 deletions test/ShallowWrapper-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from 'react';
import { expect } from 'chai';
import { shallow, render, ShallowWrapper } from '../src/';
import sinon from 'sinon';
import { describeIf } from './_helpers';
import { REACT013 } from '../src/version';
import { describeIf, itWithData, generateEmptyRenderData } from './_helpers';
import { REACT013, REACT15 } from '../src/version';

describe('shallow', () => {

Expand Down Expand Up @@ -1127,6 +1127,45 @@ describe('shallow', () => {
});
});

describe('.isEmptyRender()', () => {
const emptyRenderValues = generateEmptyRenderData();

itWithData(emptyRenderValues, 'when a React class component returns: ', (data) => {
const Foo = React.createClass({
render() {
return data.value;
},
});
const wrapper = shallow(<Foo />);
expect(wrapper.isEmptyRender()).to.equal(data.expectResponse);
});

itWithData(emptyRenderValues, 'when an ES2015 class component returns: ', (data) => {
class Foo extends React.Component {
render() {
return data.value;
}
}
const wrapper = shallow(<Foo />);
expect(wrapper.isEmptyRender()).to.equal(data.expectResponse);
});

it('should not return true for HTML elements', () => {
const wrapper = shallow(<div className="bar baz" />);
expect(wrapper.isEmptyRender()).to.equal(false);
});

describeIf(REACT15, 'stateless function components', () => {
itWithData(emptyRenderValues, 'when a component returns: ', (data) => {
function Foo() {
return data.value;
}
const wrapper = shallow(<Foo />);
expect(wrapper.isEmptyRender()).to.equal(data.expectResponse);
});
});
});

describe('.not(selector)', () => {
it('filters to things not matching a selector', () => {
const wrapper = shallow(
Expand Down
38 changes: 38 additions & 0 deletions test/_helpers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* globals jsdom */

import React from 'react';

/**
* Simple wrapper around mocha describe which allows a boolean to be passed in first which
* determines whether or not the test will be run
Expand All @@ -24,6 +26,20 @@ export function itIf(test, a, b) {
}
}

/**
* Simple wrapper around mocha it which allows an array of possible values to test against.
* Each test will be wrapped in a try/catch block to handle any errors.
*
* @param {Object[]} data
* @param {String} message
* @param {Function} factory
*/
export function itWithData(data, message, factory) {
data.forEach((testCase) => {
it(`${message} ${testCase.message}`, () => factory(testCase));
});
}

function only(a, b) {
describe('(uses jsdom)', () => {
if (typeof jsdom === 'function') {
Expand Down Expand Up @@ -62,3 +78,25 @@ export function describeWithDOM(a, b) {
describeWithDOM.only = only;
describeWithDOM.skip = skip;

/**
* React component used for testing.
*/
class TestHelper extends React.Component {
render() {
return <div />;
}
}

/**
* Possible values for React render() checks.
*/
export function generateEmptyRenderData() {
return [
// Returns true for empty
{ message: 'false', value: false, expectResponse: true },
{ message: 'null', value: null, expectResponse: true },
// Returns false for empty, valid returns
{ message: 'React component', value: <TestHelper />, expectResponse: false },
{ message: 'React element', value: <span />, expectResponse: false },
];
}

0 comments on commit 6845529

Please sign in to comment.