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

Don't wrap functional components in react >=16.5 to shallow render #2014

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Expand Up @@ -26,6 +26,8 @@ matrix:
env: REACT=16
- node_js: "6"
env: REACT=16
- node_js: "lts/*"
env: REACT=16.8.5 RENDERER=16.8.5
- node_js: "lts/*"
env: REACT=16.8.3
- node_js: "lts/*"
Expand Down
40 changes: 30 additions & 10 deletions packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js
Expand Up @@ -405,6 +405,27 @@ class ReactSixteenAdapter extends EnzymeAdapter {
const renderer = new ShallowRenderer();
let isDOM = false;
let cachedNode = null;

let lastComponent = null;
let wrappedComponent = null;

// Wrap functional components on versions prior to 16.5,
// to avoid inadvertently pass a `this` instance to it.
const wrapFunctionalComponent = (Component) => {
if (is165) {
return Component;
}

if (lastComponent !== Component) {
wrappedComponent = Object.assign(
(...args) => Component(...args), // eslint-disable-line new-cap
Component,
);
lastComponent = Component;
}
return wrappedComponent;
};

return {
render(el, unmaskedContext) {
cachedNode = el;
Expand All @@ -424,20 +445,19 @@ class ReactSixteenAdapter extends EnzymeAdapter {

if (!isStateful && isMemo(el.type)) {
const InnerComp = el.type.type;
const wrappedEl = Object.assign(
(...args) => InnerComp(...args), // eslint-disable-line new-cap
InnerComp,
);
return withSetStateAllowed(() => renderer.render({ ...el, type: wrappedEl }, context));
return withSetStateAllowed(() => renderer.render(
{ ...el, type: wrapFunctionalComponent(InnerComp) },
context,
));
}

if (!isStateful && typeof Component === 'function') {
const wrappedEl = Object.assign(
(...args) => Component(...args), // eslint-disable-line new-cap
Component,
);
return withSetStateAllowed(() => renderer.render({ ...el, type: wrappedEl }, context));
return withSetStateAllowed(() => renderer.render(
{ ...el, type: wrapFunctionalComponent(Component) },
context,
));
}

if (isStateful) {
// fix react bug; see implementation of `getEmptyStateValue`
const emptyStateValue = getEmptyStateValue();
Expand Down
2 changes: 1 addition & 1 deletion packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx
Expand Up @@ -643,7 +643,7 @@ describe('shallow', () => {
});
});

describeIf(is('>= 16.8'), 'hooks', () => {
describeIf(is('>= 16.8.5'), 'hooks', () => {
// TODO: enable when the shallow renderer fixes its bug
it.skip('works with `useEffect`', (done) => {
function ComponentUsingEffectHook() {
Expand Down
87 changes: 58 additions & 29 deletions packages/enzyme-test-suite/test/shared/methods/find.jsx
Expand Up @@ -17,6 +17,7 @@ import {
Fragment,
forwardRef,
memo,
useState,
} from '../../_helpers/react-compat';

export default function describeFind({
Expand Down Expand Up @@ -767,6 +768,49 @@ export default function describeFind({
});
});

describeIf(is('>= 16.8'), 'hooks', () => {
it('handles useState', () => {
const ComponentUsingStateHook = () => {
const [count] = useState(0);
return <div>{count}</div>;
};

const wrapper = Wrap(<ComponentUsingStateHook />);

expect(wrapper.find('div')).to.have.lengthOf(1);
expect(wrapper.find('div').text()).to.equal('0');
});

it('handles setState returned from useState', () => {
const ComponentUsingStateHook = () => {
const [count, setCount] = useState(0);
return <div onClick={() => setCount(count + 1)}>{count}</div>;
};

const wrapper = Wrap(<ComponentUsingStateHook />);
wrapper.simulate('click'); // FIXME: avoid simulate

expect(wrapper.find('div').text()).to.equal('1');
});

it('handles keep hook state for same component type', () => {
const ComponentUsingStateHook = () => {
const [count, setCount] = useState(0);
return <div onClick={() => setCount(count + 1)}>{count}</div>;
};

const wrapper = Wrap(<ComponentUsingStateHook />);
wrapper.simulate('click'); // FIXME: avoid simulate
expect(wrapper.find('div').text()).to.equal('1');

wrapper.setProps({ newProp: 1 });
expect(wrapper.find('div').text()).to.equal('1');

wrapper.simulate('click'); // FIXME: avoid simulate
expect(wrapper.find('div').text()).to.equal('2');
});
});

describeWithDOM('find DOM elements by constructor', () => {
// in React 0.13 and 0.14, these HTML tags get moved around by the DOM, and React fails
// they're tested in `shallow`, and in React 15+, so we can skip them here.
Expand Down Expand Up @@ -859,33 +903,7 @@ export default function describeFind({
expect(wrapper.find('.qoo').text()).to.equal('qux');
});

// TODO; reevaluate
itIf(isShallow, 'throws with a class component', () => {
class InnerComp extends React.Component {
render() {
return <div><span>Hello</span></div>;
}
}

class Foo extends React.Component {
render() {
const { foo } = this.props;
return (
<div>
<InnerComp />
<div className="bar">bar</div>
<div className="qoo">{foo}</div>
</div>
);
}
}
const FooMemo = memo(Foo);

expect(() => Wrap(<FooMemo foo="qux" />)).to.throw(TypeError);
});

// FIXME: fix for shallow
itIf(!isShallow, 'works with a class component', () => {
it('works with a class component', () => {
class InnerComp extends React.Component {
render() {
return <div><span>Hello</span></div>;
Expand All @@ -907,7 +925,17 @@ export default function describeFind({
const FooMemo = memo(Foo);

const wrapper = Wrap(<FooMemo foo="qux" />);
expect(wrapper.debug()).to.equal(`<Foo foo="qux">
const expectedDebug = isShallow
? `<div>
<InnerComp />
<div className="bar">
bar
</div>
<div className="qoo">
qux
</div>
</div>`
: `<Foo foo="qux">
<div>
<InnerComp>
<div>
Expand All @@ -923,7 +951,8 @@ export default function describeFind({
qux
</div>
</div>
</Foo>`);
</Foo>`;
expect(wrapper.debug()).to.equal(expectedDebug);
expect(wrapper.find('InnerComp')).to.have.lengthOf(1);
expect(wrapper.find('.bar')).to.have.lengthOf(1);
expect(wrapper.find('.qoo').text()).to.equal('qux');
Expand Down