Skip to content

Commit

Permalink
[enzyme-adapter-react-16] [fix] shallow: avoid wrapping component f…
Browse files Browse the repository at this point in the history
…or hooks
  • Loading branch information
rodrigopr authored and ljharb committed Feb 8, 2019
1 parent 6f60a00 commit f695b6a
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 40 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit f695b6a

Please sign in to comment.