diff --git a/.travis.yml b/.travis.yml index bd95283e3..e5c88bfae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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/*" diff --git a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js index 76ad49ac0..f3b21ea99 100644 --- a/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js +++ b/packages/enzyme-adapter-react-16/src/ReactSixteenAdapter.js @@ -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; @@ -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(); diff --git a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx index 7cdfb73a3..b9d067b92 100644 --- a/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx +++ b/packages/enzyme-test-suite/test/ShallowWrapper-spec.jsx @@ -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() { diff --git a/packages/enzyme-test-suite/test/shared/methods/find.jsx b/packages/enzyme-test-suite/test/shared/methods/find.jsx index c511f2973..be3d252b5 100644 --- a/packages/enzyme-test-suite/test/shared/methods/find.jsx +++ b/packages/enzyme-test-suite/test/shared/methods/find.jsx @@ -17,6 +17,7 @@ import { Fragment, forwardRef, memo, + useState, } from '../../_helpers/react-compat'; export default function describeFind({ @@ -767,6 +768,49 @@ export default function describeFind({ }); }); + describeIf(is('>= 16.8'), 'hooks', () => { + it('handles useState', () => { + const ComponentUsingStateHook = () => { + const [count] = useState(0); + return
{count}
; + }; + + const wrapper = Wrap(); + + 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
setCount(count + 1)}>{count}
; + }; + + const wrapper = Wrap(); + 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
setCount(count + 1)}>{count}
; + }; + + const wrapper = Wrap(); + 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. @@ -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
Hello
; - } - } - - class Foo extends React.Component { - render() { - const { foo } = this.props; - return ( -
- -
bar
-
{foo}
-
- ); - } - } - const FooMemo = memo(Foo); - - expect(() => Wrap()).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
Hello
; @@ -907,7 +925,17 @@ export default function describeFind({ const FooMemo = memo(Foo); const wrapper = Wrap(); - expect(wrapper.debug()).to.equal(` + const expectedDebug = isShallow + ? `
+ +
+ bar +
+
+ qux +
+
` + : `
@@ -923,7 +951,8 @@ export default function describeFind({ qux
-
`); +
`; + 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');