Skip to content

Commit d19f13e

Browse files
authored
Merge pull request #1832 from SinHouse/master
- [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.
2 parents 9ea33d7 + 3254521 commit d19f13e

File tree

5 files changed

+192
-0
lines changed

5 files changed

+192
-0
lines changed

packages/enzyme-adapter-react-16.3/src/ReactSixteenThreeAdapter.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import TestUtils from 'react-dom/test-utils';
1111
import {
1212
isElement,
1313
isPortal,
14+
isForwardRef,
1415
isValidElementType,
1516
AsyncMode,
1617
Fragment,
@@ -483,6 +484,13 @@ class ReactSixteenThreeAdapter extends EnzymeAdapter {
483484
return typeOfNode(fragment) === Fragment;
484485
}
485486

487+
isCustomComponentElement(inst) {
488+
if (!inst || !this.isValidElement(inst)) {
489+
return false;
490+
}
491+
return typeof inst.type === 'function' || isForwardRef(inst);
492+
}
493+
486494
createElement(...args) {
487495
return React.createElement(...args);
488496
}

packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import TestUtils from 'react-dom/test-utils';
1111
import {
1212
isElement,
1313
isPortal,
14+
isForwardRef,
1415
isValidElementType,
1516
AsyncMode,
1617
Fragment,
@@ -525,6 +526,13 @@ class ReactSixteenAdapter extends EnzymeAdapter {
525526
return typeOfNode(fragment) === Fragment;
526527
}
527528

529+
isCustomComponentElement(inst) {
530+
if (!inst || !this.isValidElement(inst)) {
531+
return false;
532+
}
533+
return typeof inst.type === 'function' || isForwardRef(inst);
534+
}
535+
528536
createElement(...args) {
529537
return React.createElement(...args);
530538
}

packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6923,6 +6923,33 @@ describe('shallow', () => {
69236923
expect(underwater.is(RendersDOM)).to.equal(true);
69246924
});
69256925

6926+
describeIf(is('>=16.3.0'), 'forwardRef Elements', () => {
6927+
const ForwardRefWrapsRendersDOM = forwardRef && forwardRef(() => <WrapsRendersDOM />);
6928+
const NestedForwarRefsWrapsRendersDom = forwardRef
6929+
&& forwardRef(() => <ForwardRefWrapsRendersDOM />);
6930+
6931+
if (forwardRef) {
6932+
NestedForwarRefsWrapsRendersDom.contextTypes = { foo: PropTypes.string };
6933+
ForwardRefWrapsRendersDOM.contextTypes = { foo: PropTypes.string };
6934+
}
6935+
6936+
it('dives + shallow-renders a forwardRef component', () => {
6937+
const wrapper = shallow(<ForwardRefWrapsRendersDOM />);
6938+
expect(wrapper.is(WrapsRendersDOM)).to.equal(true);
6939+
6940+
const underwater = wrapper.dive();
6941+
expect(underwater.is(RendersDOM)).to.equal(true);
6942+
});
6943+
6944+
it('dives + shallow-renders a with nested forwardRefs component', () => {
6945+
const wrapper = shallow(<NestedForwarRefsWrapsRendersDom />);
6946+
expect(wrapper.is(ForwardRefWrapsRendersDOM)).to.equal(true);
6947+
6948+
const underwater = wrapper.dive();
6949+
expect(underwater.is(WrapsRendersDOM)).to.equal(true);
6950+
});
6951+
});
6952+
69266953
it('merges and pass options through', () => {
69276954
const wrapper = shallow(<ContextWrapsRendersDOM />, { context: { foo: 'hello' } });
69286955
expect(wrapper.context()).to.deep.equal({ foo: 'hello' });

packages/enzyme-test-suite/test/Utils-spec.jsx

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
displayNameOfNode,
1010
spyMethod,
1111
nodeHasType,
12+
isCustomComponentElement,
1213
} from 'enzyme/build/Utils';
1314
import getAdapter from 'enzyme/build/getAdapter';
1415
import {
@@ -639,6 +640,151 @@ describe('Utils', () => {
639640
});
640641
});
641642

643+
describe('isCustomComponentElement()', () => {
644+
const adapter = getAdapter();
645+
646+
wrap()
647+
.withOverride(() => adapter, 'isCustomComponentElement', () => undefined)
648+
.describe('with an adapter lacking `.isCustomComponentElement`', () => {
649+
describe('given a valid CustomComponentElement', () => {
650+
it('returns true', () => {
651+
class Foo extends React.Component {
652+
render() { return <div />; }
653+
}
654+
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
655+
});
656+
657+
describeIf(is('> 0.13'), 'stateless function elements', () => {
658+
it('returns true', () => {
659+
const Foo = () => <div />;
660+
661+
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
662+
});
663+
});
664+
665+
describeIf(is('>=16.3.0'), 'forwardRef Elements', () => {
666+
it('returns false', () => {
667+
const Foo = React.forwardRef(() => <div />);
668+
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(false);
669+
});
670+
});
671+
});
672+
673+
describe('given an invalid CustomComponentElement', () => {
674+
it('returns false for HTML elements', () => {
675+
expect(isCustomComponentElement(<div />, adapter)).to.equal(false);
676+
});
677+
678+
it('returns false for non-Components', () => {
679+
[
680+
class Foo {},
681+
{},
682+
() => {},
683+
'div',
684+
'Foo',
685+
null,
686+
].forEach((nonComponent) => {
687+
expect(isCustomComponentElement(nonComponent, adapter)).to.equal(false);
688+
});
689+
});
690+
});
691+
});
692+
693+
wrap()
694+
.withOverride(() => adapter, 'isCustomComponentElement', () => () => false)
695+
.describe('with an adapter that has `.isCustomComponentElement` that always returns false', () => {
696+
describe('given a valid CustomComponentElement', () => {
697+
it('returns false', () => {
698+
class Foo extends React.Component {
699+
render() { return <div />; }
700+
}
701+
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(false);
702+
});
703+
704+
describeIf(is('> 0.13'), 'stateless function elements', () => {
705+
it('returns false', () => {
706+
const Foo = () => <div />;
707+
708+
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(false);
709+
});
710+
});
711+
712+
describeIf(is('>=16.3.0'), 'forwardRef Elements', () => {
713+
it('returns false', () => {
714+
const Foo = React.forwardRef(() => <div />);
715+
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(false);
716+
});
717+
});
718+
});
719+
720+
describe('given an invalid CustomComponentElement', () => {
721+
it('returns false for HTML elements', () => {
722+
expect(isCustomComponentElement(<div />, adapter)).to.equal(false);
723+
});
724+
725+
it('returns false for non-Components', () => {
726+
[
727+
class Foo {},
728+
{},
729+
() => {},
730+
'div',
731+
'Foo',
732+
null,
733+
].forEach((nonComponent) => {
734+
expect(isCustomComponentElement(nonComponent, adapter)).to.equal(false);
735+
});
736+
});
737+
});
738+
});
739+
740+
wrap()
741+
.withOverride(() => adapter, 'isCustomComponentElement', () => () => true)
742+
.describe('with an adapter that has `.isCustomComponentElement` that always returns true', () => {
743+
describe('given a valid CustomComponentElement', () => {
744+
it('returns true', () => {
745+
class Foo extends React.Component {
746+
render() { return <div />; }
747+
}
748+
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
749+
});
750+
751+
describeIf(is('> 0.13'), 'stateless function elements', () => {
752+
it('returns true', () => {
753+
const Foo = () => <div />;
754+
755+
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
756+
});
757+
});
758+
759+
describeIf(is('>=16.3.0'), 'forwardRef Elements', () => {
760+
it('returns true', () => {
761+
const Foo = React.forwardRef(() => <div />);
762+
expect(isCustomComponentElement(<Foo />, adapter)).to.equal(true);
763+
});
764+
});
765+
});
766+
767+
describe('given an invalid CustomComponentElement', () => {
768+
it('returns true for HTML elements', () => {
769+
expect(isCustomComponentElement(<div />, adapter)).to.equal(true);
770+
});
771+
772+
it('returns true for non-Components', () => {
773+
[
774+
class Foo {},
775+
{},
776+
() => {},
777+
'div',
778+
'Foo',
779+
null,
780+
].forEach((nonComponent) => {
781+
expect(isCustomComponentElement(nonComponent, adapter)).to.equal(true);
782+
});
783+
});
784+
});
785+
});
786+
});
787+
642788
wrap()
643789
.withOverride(() => getAdapter(), 'displayNameOfNode', () => undefined)
644790
.describe('nodeHasType', () => {

packages/enzyme/src/Utils.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ export function makeOptions(options) {
4242
}
4343

4444
export function isCustomComponentElement(inst, adapter) {
45+
if (adapter.isCustomComponentElement) {
46+
return !!adapter.isCustomComponentElement(inst);
47+
}
4548
return !!inst && adapter.isValidElement(inst) && typeof inst.type === 'function';
4649
}
4750

0 commit comments

Comments
 (0)