Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] Allow shallow renderer to work with new API #14329

Closed
wants to merge 3 commits into from

Conversation

mprobber
Copy link

Before submitting a pull request, please make sure the following is done:

  1. Fork the repository and create your branch from master.
  2. Run yarn in the repository root.
  3. If you've fixed a bug or added code that should be tested, add tests!
  4. Ensure the test suite passes (yarn test). Tip: yarn test --watch TestName is helpful in development.
  5. Run yarn test-prod to test in the production environment. It supports the same options as yarn test.
  6. If you need a debugger, run yarn debug-test --watch TestName, open chrome://inspect, and press "Inspect".
  7. Format your code with prettier (yarn prettier).
  8. Make sure your code lints (yarn lint). Tip: yarn linc to only check changed files.
  9. Run the Flow typechecks (yarn flow).
  10. If you haven't already, complete the CLA.

Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html

Hi,
I really like the new contextType API in 16.6, but the shallow renderer doesn't seem to provide support for it. This pull request allows you to pass in an object for context, and if contextType is defined, will shallow render the component with the provided context, as it did for the old context API.

@@ -143,7 +143,12 @@ class ReactShallowRenderer {

this._rendering = true;
this._element = element;
this._context = getMaskedContext(element.type.contextTypes, context);

if (element.type.contextType) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only be enabled for class components.

this._context = getMaskedContext(element.type.contextTypes, context);

if (element.type.contextType) {
this._context = context;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the legacy context object. It has no relation to the new context API you're implementing.

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't implement the new context API. Your test should verify that this.context is equal to 'hello world' (because it's the default context value). I also don't think we want to support passing second value to shallowRenderer.render for new context because which context component subscribes to is an implementation detail. Maybe we should support rendering <Provider value={...}><Thing /></Provider> though. Not sure.

@mprobber
Copy link
Author

@gaearon

I agree with you that the shallow renderer should use the value of the default context if no context is provided, but I'm not really sure about rendering two levels deep and not being transparent about it in the shallow renderer for a select few components.

Looking at

there is no way for a component to support both the contextType and the legacy contextTypes APIs, so it doesn't seem like it would be problematic to use the same argument to set context on a component (just an object, used to set this.context). I don't have too much context (ba-dum-tss) about implementation details though, so I'm curious if there's something I'm missing. I'll update the PR so the default context renders if none is provided.

@sizebot
Copy link

sizebot commented Nov 28, 2018

Details of bundled changes.

Comparing: 409066a...1f656b3

react-test-renderer

File Filesize Diff Gzip Diff Prev Size Current Size Prev Gzip Current Gzip ENV
react-test-renderer.development.js 0.0% 0.0% 443.03 KB 443.03 KB 95.65 KB 95.65 KB UMD_DEV
react-test-renderer.production.min.js 0.0% 0.0% 56.15 KB 56.15 KB 17.23 KB 17.23 KB UMD_PROD
react-test-renderer.development.js 0.0% 0.0% 438.24 KB 438.24 KB 94.5 KB 94.5 KB NODE_DEV
react-test-renderer.production.min.js 0.0% 0.0% 55.82 KB 55.82 KB 17.08 KB 17.08 KB NODE_PROD
ReactTestRenderer-dev.js 0.0% 0.0% 446.12 KB 446.12 KB 93.72 KB 93.72 KB FB_WWW_DEV
react-test-renderer-shallow.development.js +1.5% +1.0% 25.79 KB 26.17 KB 7.04 KB 7.11 KB UMD_DEV
react-test-renderer-shallow.production.min.js 🔺+1.1% 🔺+1.4% 7.28 KB 7.36 KB 2.38 KB 2.41 KB UMD_PROD
react-test-renderer-shallow.development.js +1.9% +1.3% 20.18 KB 20.56 KB 5.61 KB 5.68 KB NODE_DEV
react-test-renderer-shallow.production.min.js 🔺+1.0% 🔺+1.0% 7.96 KB 8.04 KB 2.64 KB 2.67 KB NODE_PROD
ReactShallowRenderer-dev.js +2.1% +1.5% 18.13 KB 18.51 KB 4.75 KB 4.82 KB FB_WWW_DEV

Generated by 🚫 dangerJS

@jfirebaugh
Copy link

It doesn't seem like it would be problematic to use the same argument to set context on a component (just an object, used to set this.context).

Coming here from enzymejs/enzyme#1553, via https://stackoverflow.com/questions/53711166/unable-to-set-context-in-an-enzyme-test, and that's pretty much exactly how I expected it to work.

@minznerjosh
Copy link

To @gaearon's point, using the second argument to support contextType would create some funkiness in enzyme.

It seems reasonable at first because, with the plain react shallow renderer, you're only ever rendering one component at a time, so you have a lot of control over what context you pass when you render a component; you know not to pass legacy context if the component you're testing is using contextType.

But Enzyme has the concept of dive()ing, allowing you to render components further down in the tree. And, most importantly, when you dive() a component, the legacy context of any providers further up in the tree will be passed as context (via the second argument to .render()).

So, if this change were to be released, and no changes in Enzyme were made, if you were to dive() a component that uses contextType you would end up with a bunch of legacy context as this.context in your component.

Now, of course, Enzyme could be updated to inspect the component being dive()d to determine whether new or legacy context should be passed. But I still think it's worth calling out that this change is a little more "break-y" then it appears at first. And I think this potentially odd behavior is a decent smell for the fact that new context and legacy context are actually totally different APIs, and we maybe shouldn't overload the second arg to .render() to handle both.

@mprobber
Copy link
Author

mprobber commented Feb 8, 2019

@minznerjosh

The only thing coming to mind to that would avoid overloading the second argument, and supporting contextType with that use case would be we passing a Map of React.Context, value pairs, but that seems very, very clunky.

I'd think that a follow-up change should be made to enzyme regardless if this PR gets merged. Currently, if you shallow render a component with contextType, this.context is the empty object, as opposed to the default value of the context. So, while this would make dive() behave bizarrely for components that use contextType, they already behaves incorrectly under enzyme. Regardless, I'd be happy to craft a pull request for enzyme when any changes are made.

Let me know if you come up with an API that doesn't overload the second argument to render that's not super clunky. I'd be happy to implement it.

@gaearon
Copy link
Collaborator

gaearon commented Mar 15, 2019

But Enzyme has the concept of dive()ing

Oh my. This is the first time I'm hearing and reading about this API 😳
This seems like a real can of worms.

@ljharb
Copy link
Contributor

ljharb commented Mar 15, 2019

.dive() is just sugar for a feature enzyme has always had since v1, the ability to find an arbitrary custom component and create a new shallow wrapper from that point.

@gaearon
Copy link
Collaborator

gaearon commented Mar 15, 2019

The confusing thing to me is that the updates in the parent component wouldn't "reflect" in that "dived" version for shallow. But they would for mount. Unless I'm missing something.

@minznerjosh
Copy link

@gaearon I'm pretty sure you're correct about this. If you change state/props in the parent, you'd need to re-dive to see the changes reflected in the "dived" version. This, however, probably isn't super surprising to users given the immutable nature of non-root enzyme wrappers.

However, it does present itself if you ever want to test component lifecycle methods in the dived child by manipulating the props/state of the parent.

@ljharb
Copy link
Contributor

ljharb commented Mar 15, 2019

That's also the case for both shallow and mount for any derived wrapper - ie, any .find-produced wrapper is not updated if the parent updates, you always have to re-find from the root.

@gaearon
Copy link
Collaborator

gaearon commented Mar 15, 2019

Thanks for explaining!

@DavyJohnes
Copy link

Guys, is there any workaround to pass context to component which uses new context API?

@d4wei
Copy link

d4wei commented Jun 7, 2019

@mprobber @gaearon Any chance we might move forward with this, or decide on an alternative API in the near future? Having shallow rendering support the new contextType API would be really useful!

If we are thinking about alternatives, would passing a map of Provider/value pairs when creating an instance of shallowRenderer(instead of passing it when render is called) be a less clunky way to do this while avoiding the problems @minznerjosh described? I think in a certain sense, non-default values for contexts can also be interpreted as "configurations" for a shallowRenderer instance.

Guys, is there any workaround to pass context to component which uses new context API?

@DaveVoyles If the context your component uses happens to be an object, you can use the legacy API to test it for now by setting an appropriate value for contextTypes on your component at test time. I've documented this workaround in the Stack Overflow question that led me here.

@comp615
Copy link

comp615 commented Aug 30, 2019

Wanted to throw my hat into the ring too, having a solution here would greatly help unblocking our upgrade path to remove old-style, legacy context. Right now we code-modded most of it away and the code is functioning, but tests are an absolute mess. Can explore the workaround detailed above, but that feels a bit worse.

@mjancarik
Copy link

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

@stale
Copy link

stale bot commented Mar 12, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contribution.

@stale stale bot added the Resolution: Stale Automatically closed due to inactivity label Mar 12, 2020
@ljharb
Copy link
Contributor

ljharb commented Mar 12, 2020

bump

@stale stale bot removed the Resolution: Stale Automatically closed due to inactivity label Mar 12, 2020
@gaearon
Copy link
Collaborator

gaearon commented Mar 14, 2020

The shallow renderer has been extracted so please port this change to https://github.com/NMinhNguyen/react-shallow-renderer if you'd like. Thank you!

@gaearon gaearon closed this Mar 14, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.