enzymejs / enzyme

JavaScript Testing utilities for React
https://enzymejs.github.io/enzyme/
MIT License
19.95k stars 2.01k forks source link

wrappedComponent doesn't actually pass context values down in tests #2189

Open heath-freenome opened 5 years ago

heath-freenome commented 5 years ago

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

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

ljharb commented 5 years ago

contextType is not yet supported.

heath-freenome commented 5 years ago

Is there an ETA on that?

DavidLozzi commented 5 years ago

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

JawsomeJason commented 5 years ago
const wrapper = render(<FooContext.Provider values={someValues}>

That should be value, no?

mjancarik commented 4 years ago

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

heath-freenome commented 4 years ago

@ljharb Status update?

heath-freenome commented 3 years ago

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

ljharb commented 3 years ago

@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.

heath-freenome commented 3 years ago

@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...

ljharb commented 3 years ago

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.

heath-freenome commented 3 years ago

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

ljharb commented 3 years ago

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

heath-freenome commented 3 years ago

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

forivall commented 3 years ago

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!

ljharb commented 3 years ago

@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).

forivall commented 3 years ago

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

ljharb commented 3 years ago

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

unverbraucht commented 3 years ago

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?

pabloimrik17 commented 3 years ago

Any updates?