Skip to content

Commit

Permalink
Merge pull request #1832 from SinHouse/master
Browse files Browse the repository at this point in the history
 - [fix] `shallow`: A nested ForwardRef is not recognized as a valid component by `dive()`
 - [enzyme-adapter-react-{16,16.3}] [new] add `isCustomComponentElement`

Fixes #1830.
  • Loading branch information
ljharb authored Oct 2, 2018
2 parents 9ea33d7 + 3254521 commit d19f13e
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TestUtils from 'react-dom/test-utils';
import {
isElement,
isPortal,
isForwardRef,
isValidElementType,
AsyncMode,
Fragment,
Expand Down Expand Up @@ -483,6 +484,13 @@ class ReactSixteenThreeAdapter extends EnzymeAdapter {
return typeOfNode(fragment) === Fragment;
}

isCustomComponentElement(inst) {
if (!inst || !this.isValidElement(inst)) {
return false;
}
return typeof inst.type === 'function' || isForwardRef(inst);
}

createElement(...args) {
return React.createElement(...args);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TestUtils from 'react-dom/test-utils';
import {
isElement,
isPortal,
isForwardRef,
isValidElementType,
AsyncMode,
Fragment,
Expand Down Expand Up @@ -525,6 +526,13 @@ class ReactSixteenAdapter extends EnzymeAdapter {
return typeOfNode(fragment) === Fragment;
}

isCustomComponentElement(inst) {
if (!inst || !this.isValidElement(inst)) {
return false;
}
return typeof inst.type === 'function' || isForwardRef(inst);
}

createElement(...args) {
return React.createElement(...args);
}
Expand Down
27 changes: 27 additions & 0 deletions packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6923,6 +6923,33 @@ describe('shallow', () => {
expect(underwater.is(RendersDOM)).to.equal(true);
});

describeIf(is('>=16.3.0'), 'forwardRef Elements', () => {
const ForwardRefWrapsRendersDOM = forwardRef && forwardRef(() => <WrapsRendersDOM />);
const NestedForwarRefsWrapsRendersDom = forwardRef
&& forwardRef(() => <ForwardRefWrapsRendersDOM />);

if (forwardRef) {
NestedForwarRefsWrapsRendersDom.contextTypes = { foo: PropTypes.string };
ForwardRefWrapsRendersDOM.contextTypes = { foo: PropTypes.string };
}

it('dives + shallow-renders a forwardRef component', () => {
const wrapper = shallow(<ForwardRefWrapsRendersDOM />);
expect(wrapper.is(WrapsRendersDOM)).to.equal(true);

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

it('dives + shallow-renders a with nested forwardRefs component', () => {
const wrapper = shallow(<NestedForwarRefsWrapsRendersDom />);
expect(wrapper.is(ForwardRefWrapsRendersDOM)).to.equal(true);

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

it('merges and pass options through', () => {
const wrapper = shallow(<ContextWrapsRendersDOM />, { context: { foo: 'hello' } });
expect(wrapper.context()).to.deep.equal({ foo: 'hello' });
Expand Down
146 changes: 146 additions & 0 deletions packages/enzyme-test-suite/test/Utils-spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
displayNameOfNode,
spyMethod,
nodeHasType,
isCustomComponentElement,
} from 'enzyme/build/Utils';
import getAdapter from 'enzyme/build/getAdapter';
import {
Expand Down Expand Up @@ -639,6 +640,151 @@ describe('Utils', () => {
});
});

describe('isCustomComponentElement()', () => {
const adapter = getAdapter();

wrap()
.withOverride(() => adapter, 'isCustomComponentElement', () => undefined)
.describe('with an adapter lacking `.isCustomComponentElement`', () => {
describe('given a valid CustomComponentElement', () => {
it('returns true', () => {
class Foo extends React.Component {
render() { return <div />; }
}
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
});

describeIf(is('> 0.13'), 'stateless function elements', () => {
it('returns true', () => {
const Foo = () => <div />;

expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
});
});

describeIf(is('>=16.3.0'), 'forwardRef Elements', () => {
it('returns false', () => {
const Foo = React.forwardRef(() => <div />);
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(false);
});
});
});

describe('given an invalid CustomComponentElement', () => {
it('returns false for HTML elements', () => {
expect(isCustomComponentElement(<div />, adapter)).to.equal(false);
});

it('returns false for non-Components', () => {
[
class Foo {},
{},
() => {},
'div',
'Foo',
null,
].forEach((nonComponent) => {
expect(isCustomComponentElement(nonComponent, adapter)).to.equal(false);
});
});
});
});

wrap()
.withOverride(() => adapter, 'isCustomComponentElement', () => () => false)
.describe('with an adapter that has `.isCustomComponentElement` that always returns false', () => {
describe('given a valid CustomComponentElement', () => {
it('returns false', () => {
class Foo extends React.Component {
render() { return <div />; }
}
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(false);
});

describeIf(is('> 0.13'), 'stateless function elements', () => {
it('returns false', () => {
const Foo = () => <div />;

expect(isCustomComponentElement(<Foo />, adapter)).to.equal(false);
});
});

describeIf(is('>=16.3.0'), 'forwardRef Elements', () => {
it('returns false', () => {
const Foo = React.forwardRef(() => <div />);
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(false);
});
});
});

describe('given an invalid CustomComponentElement', () => {
it('returns false for HTML elements', () => {
expect(isCustomComponentElement(<div />, adapter)).to.equal(false);
});

it('returns false for non-Components', () => {
[
class Foo {},
{},
() => {},
'div',
'Foo',
null,
].forEach((nonComponent) => {
expect(isCustomComponentElement(nonComponent, adapter)).to.equal(false);
});
});
});
});

wrap()
.withOverride(() => adapter, 'isCustomComponentElement', () => () => true)
.describe('with an adapter that has `.isCustomComponentElement` that always returns true', () => {
describe('given a valid CustomComponentElement', () => {
it('returns true', () => {
class Foo extends React.Component {
render() { return <div />; }
}
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
});

describeIf(is('> 0.13'), 'stateless function elements', () => {
it('returns true', () => {
const Foo = () => <div />;

expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
});
});

describeIf(is('>=16.3.0'), 'forwardRef Elements', () => {
it('returns true', () => {
const Foo = React.forwardRef(() => <div />);
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
});
});
});

describe('given an invalid CustomComponentElement', () => {
it('returns true for HTML elements', () => {
expect(isCustomComponentElement(<div />, adapter)).to.equal(true);
});

it('returns true for non-Components', () => {
[
class Foo {},
{},
() => {},
'div',
'Foo',
null,
].forEach((nonComponent) => {
expect(isCustomComponentElement(nonComponent, adapter)).to.equal(true);
});
});
});
});
});

wrap()
.withOverride(() => getAdapter(), 'displayNameOfNode', () => undefined)
.describe('nodeHasType', () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/enzyme/src/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export function makeOptions(options) {
}

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

Expand Down

0 comments on commit d19f13e

Please sign in to comment.