enzyme icon indicating copy to clipboard operation
enzyme copied to clipboard

wrappedComponent doesn't actually pass context values down in tests

Open heath-freenome opened this issue 6 years ago • 19 comments

Given the following:

import { shallow } from 'enzyme';
import React, { Component } from 'react';
import PropTypes from 'prop-types';

const FooContext = React.createContext();

class Foo extends Component {
  static contextType = FooContext;

  render() {
    const { value1, value2 } = this.context;
    return (<div>
      <span>Value 1: {value1}</span>
      <span>Value 2: {value2}</span>
    </div>);
  }
}

const someValues = { value1: '1', value2: '2' };

function FooProvider({ children }) {
  return (<FooContext.Provider values={someValues}>
    {children}
  </FooContext.Provider>);
}

FooProvider.propTypes = {
  children: PropTypes.node
};

describe('Foo', () => {
  let spans;
  beforeAll(() => {
    const wrapper = shallow(<Foo/>, { wrappingComponent: FooProvider });
    spans = wrapper.find('span');
  });
  it('the first is someValues.value1', () => {
    const first = spans.at(0);
    expect(first).toExist();
    expect(first).toHaveText(`Value 1: ${someValues.value1}`);
  });
  it('the second is someValues.value2', () => {
    const second = spans.at(1);
    expect(second).toExist();
    expect(second).toHaveText(`Value 2: ${someValues.value2}`);
  });
});

Current behavior

Foo ✕ the first is someValues.value1 (17ms) ✕ the second is someValues.value2 (13ms)

● Foo › the first is someValues.value1

Expected <span> components text to match (using ===), but it did not.
Expected HTML: "Value 1: 1"
Actual HTML: "Value 1: "

  49 |     const first = spans.at(0);
  50 |     expect(first).toExist();
> 51 |     expect(first).toHaveText(`Value 1: ${someValues.value1}`);
     |                   ^
  52 |   });
  53 |   it('the second is someValues.value2', () => {
  54 |     const second = spans.at(1);

  at Object.toHaveText (app/javascript/tests/temp/foo.test.jsx:51:19)

● Foo › the second is someValues.value2

Expected <span> components text to match (using ===), but it did not.
Expected HTML: "Value 2: 2"
Actual HTML: "Value 2: "

  54 |     const second = spans.at(1);
  55 |     expect(second).toExist();
> 56 |     expect(second).toHaveText(`Value 2: ${someValues.value2}`);
     |                    ^
  57 |   });
  58 | });
  59 | 

Expected behavior

The two tests pass. It seems like the values set into the context in FooProvider don't actually get passed to Foo

Your environment

node: 12.4.0

API

  • [x] shallow
  • [ ] mount
  • [ ] render

Version

library version
enzyme 3.10.0
react 16.8.6
react-dom 16.8.6
react-test-renderer 16.8.6
adapter (below) 1.14.0

Adapter

  • [x] enzyme-adapter-react-16
  • [ ] enzyme-adapter-react-16.3
  • [ ] enzyme-adapter-react-16.2
  • [ ] enzyme-adapter-react-16.1
  • [ ] enzyme-adapter-react-15
  • [ ] enzyme-adapter-react-15.4
  • [ ] enzyme-adapter-react-14
  • [ ] enzyme-adapter-react-13
  • [ ] enzyme-adapter-react-helper
  • [ ] others ( )

heath-freenome avatar Jul 15 '19 20:07 heath-freenome

contextType is not yet supported.

ljharb avatar Jul 16 '19 05:07 ljharb

Is there an ETA on that?

heath-freenome avatar Jul 16 '19 17:07 heath-freenome

My workaround is to add the provider in the test:

const wrapper = render(<FooContext.Provider values={someValues}>
    <Foo/>
  </FooContext.Provider>);

and changing it to a render from shallow, which introduces some other fun to work through

DavidLozzi avatar Aug 01 '19 15:08 DavidLozzi

const wrapper = render(<FooContext.Provider values={someValues}>

That should be value, no?

JawsomeJason avatar Sep 22 '19 20:09 JawsomeJason

I created module for workaround https://www.npmjs.com/package/shallow-with-context. The module works well in our projects.

mjancarik avatar Dec 13 '19 18:12 mjancarik

@ljharb Status update?

heath-freenome avatar Apr 22 '20 16:04 heath-freenome

Wow, 1.5 years later and it seems like this has stalled. No wonder some people have been saying enzyme isn't the best testing library for React anymore

heath-freenome avatar Feb 10 '21 23:02 heath-freenome

@heath-freenome that's really not called for or helpful. PRs are quite welcome, and if your company's business depends on this project that's maintained for free by a single developer, perhaps there's some business value in investing resources in improving it.

ljharb avatar Feb 10 '21 23:02 ljharb

@ljharb Sorry about that. I'm just frustrated. For the most part, I've switched the bulk of my implementations to using the useContext() hook inside of stateless functional components and implemented test-only version of that hook which I mock in using jest. I've just run into a case where I need to use contextType within a class component while building server-side rendering and just crashed into this issue again...

heath-freenome avatar Feb 11 '21 00:02 heath-freenome

I would love to have full contextType support, but the release of npm 7 broke tests on master, so I'm scrambling to fix that so everything else is unblocked. In the meantime, a PR would be very helpful.

ljharb avatar Feb 11 '21 00:02 ljharb

I don't know enough about enzyme to be helpful in anyway right now. I'm also debating solving this current problem using a hooks approach

heath-freenome avatar Feb 11 '21 00:02 heath-freenome

Hooks don't work in class components, so i'm not sure that's going to help you much :-/

ljharb avatar Feb 11 '21 00:02 ljharb

The idea is to avoid needing a class component... Or at least contexts inside of components

heath-freenome avatar Feb 11 '21 00:02 heath-freenome

Right now, i have a dirty solution using patch-package:

patches/enzyme-adapter-react-16+1.15.6.patch

diff --git a/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js b/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js
index 5fc24a5..cfb2bbb 100644
--- a/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js
+++ b/node_modules/enzyme-adapter-react-16/build/ReactSixteenAdapter.js
@@ -847,7 +847,13 @@ var ReactSixteenAdapter = /*#__PURE__*/function (_EnzymeAdapter) {
             });
             var _renderedEl = renderedEl,
                 Component = _renderedEl.type;
-            var context = (0, _enzymeAdapterUtils.getMaskedContext)(Component.contextTypes, unmaskedContext);
+            var context;
+            if (Component.contextType) {
+              var Provider = adapter.getProviderFromConsumer(Component.contextType);
+              context = providerValues.has(Provider) ? providerValues.get(Provider) : getProviderDefaultValue(Provider);
+            } else {
+              context = (0, _enzymeAdapterUtils.getMaskedContext)(Component.contextTypes, unmaskedContext);
+            }
 
             if (isMemo(el.type)) {
               var _el$type = el.type,

patches/react-test-renderer+16.14.0.patch

diff --git a/node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js b/node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js
index df5c5d4..081bd45 100644
--- a/node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js
+++ b/node_modules/react-test-renderer/cjs/react-test-renderer-shallow.development.js
@@ -770,7 +770,7 @@ function () {
     var previousElement = this._element;
     this._rendering = true;
     this._element = element;
-    this._context = getMaskedContext(elementType.contextTypes, context); // Inner memo component props aren't currently validated in createElement.
+    this._context = elementType.contextType ? context : getMaskedContext(elementType.contextTypes, context);
 
     if (reactIs.isMemo(element) && elementType.propTypes) {
       currentlyValidatingElement = element;

I'm not sure if this is the correct semantics, as i've never used the legacy contextTypes api, but i think this is what a PR would need to do. Seems like some change to react-shallow-renderer would also be needed, and an update of enzyme to use the extracted react-shallow-renderer directly, instead of react-test-renderer/shallow (seems like this is also waiting on https://github.com/NMinhNguyen/react-shallow-renderer/issues/16 too?)

thanks for the hard work ljharb!

forivall avatar Mar 11 '21 03:03 forivall

@forivall with a test case, that seems like most of a PR to enzyme already; we don't have to wait for react-test-renderer to update - we can patch it at runtime in the adapter (we already do things like this for a few cases where react itself is broken).

ljharb avatar Mar 11 '21 04:03 ljharb

awesome. i'll submit a PR when i have a few more extra cycles in the next few days (hopefully i remember).

forivall avatar Mar 11 '21 04:03 forivall

More than happy to help with the final parts of the fix once the tests are good (and failing)

ljharb avatar Mar 11 '21 14:03 ljharb

Hi all, I added the patch posted by @forivall (thanks!) and a simple test case in #2507, please have a look. @ljharb I have no clue on how to patch react-test-renderer, I manually patched the locally installed shallow-renderer as mentioned by @forivall, can you assist here?

unverbraucht avatar Mar 14 '21 13:03 unverbraucht

Any updates?

pabloimrik17 avatar Jul 09 '21 11:07 pabloimrik17