enzymejs / enzyme

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

How should enzyme support the createContext() API #1973

Closed minznerjosh closed 5 years ago

minznerjosh commented 5 years ago

It's been quite some time since the createContext() API shipped in react, and I'd love to help out in implementing "full support" for that API in enzyme ASAP. (Before the libraries many of us depend on [react-router, react-redux, etc] switch from using the deprecated, legacy context API to the new, fully-supported API.)

However, based on my reading of many createContext()-related issues in this repo, I don't have a clear idea of what "full support" for createContext() in enzyme looks like. So I've created this issue so we can hammer it out.

Assumptions

I'm walking into this discussion with the following assumptions. Please challenge them if they don't make sense!

  1. The API should work the same in both shallow() and mount().
  2. The API should let you provide the context of multiple providers, because a single component could read from multiple consumers. (This is how enzyme's legacy context API works.)
  3. The API should be able to support createContext() and the legacy context API simultaneously, just like react.

Enzyme's Current (Legacy) Context APIs

I've seen some issues (#1913, #1840) that imply that options.context should somehow work with the createContext() API in the future. However, as per my assumptions, I don't understand how this API could support createContext(). For example, how would this work?

const ContextA = createContext();
const ContextB = createContext();
function MyComponent() {
  return (
    <ContextA.Consumer>
      {a => (
        <ContextB.Consumer>
          {b => (
            <div>
              A: {a}, B: {b}
            </div>
          )}
        </ContextB.Consumer>
      )}
    </ContextA.Consumer>
  );
}

const wrapper = mount(<MyComponent />, {
  context: "hello!" // which context are we providing here?
});
wrapper.setContext("foo"); // same here!

The only API I could imagine still making sense for createContext() is wrapper.context(). But even then, it would only be for class components that are using the contextType feature added in react@16.6, as that's the only part of the API that sets this.context in a component.

An Idea for Moving Forward

The current context APIs

I think we should move forward with the idea that the existing context APIs (options.context, .setContext(), perhaps .context()) are only for legacy context, and will never have anything to do with the createContext() API. We should update the docs to reflect this immediately.

The createContext() API

I'm of the opinion that enzyme shouldn't actually add any new APIs for createContext(). createContext() is all about just rendering components! It doesn't involve component methods like getChildContext() or static properties like contextTypes and childContextTypes. If enzyme can handle rendering createContext()'s <Consumer /> and <Provider />, it doesn't need to do anything else!

For clarification, here's how one would translate the use of the legacy context APIs using createContext()!

The only problem I see with this approach is that, if you must wrap your component in <Provider />s to get your context, your component will never be the root, and it is therefore not possible to call .setProps() on it. To address this, I'm working on #1960.

Thanks for reading! Looking forward to discussing!

ryanirilli commented 5 years ago

I am experiencing an issue with this as well as it relates to snapshot testing with Redux connected children. The below mock works well

const mockContext = jest.fn(/* mock values returned here */);
jest.mock('some-module', () => ({
    FooConsumer: ({ children }) => children(mockContext())
}));

and snapshots look great for general React components

shallow(<Test />).dive();

However when the child is a Redux connected component, I get this for a snapshot

<Connect(FooComponent)
  /* props */
>
  <FooConsumer>
    <Component />
  </FooConsumer>
</Connect(FooComponent)>

switching to using mount in this case and creating a mock store causes jest or enzyme to hang indefinitely.

buddyp450 commented 5 years ago

Just as an aside, react-redux has already made their change to the new context api as of v6.0.0 which released just last month so consider me as the first of a flood of people that will be running into this issue shortly. :)

Downgrading to react-redux v5.1.1 in the meantime.

ljharb commented 5 years ago

Understood; there's already a bit of discussion on https://github.com/reduxjs/react-redux/issues/1161 about it.

csvan commented 5 years ago

It's perhaps worth noting that version 4 of styled-components is notoriously difficult to test using Enzyme due to the Context API. See e.g.

https://github.com/styled-components/jest-styled-components/issues/191

ljharb commented 5 years ago

Closed by #1960 and #1966.

vKongv commented 5 years ago

Hi, I am not sure this related to this issue but when I tried to mount a styled component I get this weird DOM output from enzyme using debug() function.

<StyledComponent type="search" onClick={[undefined]} roundCorner={true} forwardedComponent={{...}} forwardedRef={{...}}>

I would expect it to mount into actual DOM but this doesn't seems the case. As mentioned by @csvan , this has created issue when come to snapshot testing a styled component as it doesn't output the actual DOM here.

ljharb commented 5 years ago

@vKongv definitely entirely unrelated to this issue; .debug never shows the mounted html, only the enzyme tree. Enzyme does not support or recommend snapshot testing.

ryanirilli commented 5 years ago

Enzyme does not support or recommend snapshot testing. @ljharb can you cite this?

ljharb commented 5 years ago

@ryanirilli please file another issue to discuss that; as for a citation, i just said it :-)

heath-freenome commented 5 years ago

Is there documentation on how to use the features provided by #1960 and #1966?

ljharb commented 5 years ago

@heath-freenome eg https://airbnb.io/enzyme/docs/api/ShallowWrapper/getWrappingComponent.html?