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.
I'm walking into this discussion with the following assumptions. Please challenge them if they don't make sense!
shallow() and mount().createContext() and the legacy context API simultaneously, just like react.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 [email protected], as that's the only part of the API that sets this.context in a component.
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.
createContext() APII'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()!
options.context
class ConsumerA extends React.Component {
render() {
return <div>A is: {this.context.foo}</div>;
}
}
ConsumerA.childContextTypes = {
foo: PropTypes.string,
};
class ConsumerB extends React.Component {
render() {
return <div>B is: {this.context.bar}</div>;
}
}
ConsumerB.childContextTypes = {
bar: PropTypes.string,
};
class MyComponent extends Component {
render() {
return (
<div>
<ConsumerA />
<ConsumerB />
</div>
);
}
}
shallow(<MyComponent />, {
context: { foo: 'hello', bar: 'world' },
});
mount(<MyComponent />, {
context: { foo: 'hello', bar: 'world' },
childContextTypes: { foo: PropTypes.string, bar: PropTypes.string },
});
createContext()const A = createContext();
const B = createContext();
class MyComponent extends Component {
render() {
return (
<div>
<A.Consumer>
{value => <div>A is {value}</div>}
</A.Consumer>
<B.Consumer>
{value => <div>B is {value}</div>}
</B.Consumer>
</div>
);
}
}
shallow(
<A.Provider value="hello">
<B.Provider value="world">
<MyComponent />
</B.Provider>
</A.Provider>
).dive().dive(); // dive() through to <MyComponent />
mount(
<A.Provider value="hello">
<B.Provider value="world">
<MyComponent />
</B.Provider>
</A.Provider>
)
setContext()
class MyComponent extends React.Component {
render() {
return <div>Context is: {this.context.someContext}</div>;
}
}
MyComponent.childContextTypes = { someContext: PropTypes.string };
const sWrapper = shallow(<MyComponent />, { context: { someContext: 'foo' } });
sWrapper.setContext({ someContext: 'bar' });
const mWrapper = mount(<MyComponent />, { context: { someContext: 'foo' } });
mWrapper.setContext({ someContext: 'bar' });
createContext()const Context = createContext();
class MyComponent extends React.Component {
render() {
return (
<Context.Consumer>
{value => <div>Context is: {value}</div>}
</Context.Consumer>
)
}
}
const sProvider = shallow(
<Context.Provider value="foo">
<MyComponent />
</Context.Provider>
);
let sWrapper = sProvider.dive();
sWrapper = sProvider.setProps({ value: 'bar' }).dive();
const mWrapper = mount(
<Context.Provider value="foo">
<MyComponent />
</Context.Provider>
);
mWrapper.setProps({ value: 'bar' });
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!
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.
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.
Understood; there's already a bit of discussion on https://github.com/reduxjs/react-redux/issues/1161 about it.
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
Closed by #1960 and #1966.
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.
@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.
Enzyme does not support or recommend snapshot testing.
@ljharb can you cite this?
@ryanirilli please file another issue to discuss that; as for a citation, i just said it :-)
@heath-freenome eg https://airbnb.io/enzyme/docs/api/ShallowWrapper/getWrappingComponent.html?
Most helpful comment
It's perhaps worth noting that version 4 of
styled-componentsis notoriously difficult to test using Enzyme due to the Context API. See e.g.https://github.com/styled-components/jest-styled-components/issues/191