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

Support nonRenderPhase updates in ShallowRenderer #14841

Conversation

trueadm
Copy link
Contributor

@trueadm trueadm commented Feb 13, 2019

Addresses the issue with #14840.

Following up from the fix in #14802 – which although somewhat correct, it didn't make sense when aligned with the comments. I've addressed the comments and approach so it should fix the original issue whilst keeping non-render phase updates and render-phase updates separate.

@sizebot
Copy link

sizebot commented Feb 13, 2019

Details of bundled changes.

Comparing: 0e4135e...705f5e1

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% 485.03 KB 485.03 KB 102.9 KB 102.9 KB UMD_DEV
react-test-renderer.production.min.js 0.0% 0.0% 63.75 KB 63.75 KB 19.53 KB 19.53 KB UMD_PROD
react-test-renderer.development.js 0.0% 0.0% 479.47 KB 479.47 KB 101.6 KB 101.6 KB NODE_DEV
react-test-renderer.production.min.js 0.0% 0.0% 63.41 KB 63.41 KB 19.21 KB 19.21 KB NODE_PROD
ReactTestRenderer-dev.js 0.0% 0.0% 489.51 KB 489.51 KB 101.26 KB 101.26 KB FB_WWW_DEV
react-test-renderer-shallow.development.js +3.8% +3.0% 37.2 KB 38.62 KB 9.5 KB 9.78 KB UMD_DEV
react-test-renderer-shallow.production.min.js 🔺+6.1% 🔺+4.6% 11.12 KB 11.8 KB 3.35 KB 3.5 KB UMD_PROD
react-test-renderer-shallow.development.js +4.5% +3.2% 31.5 KB 32.92 KB 8.14 KB 8.4 KB NODE_DEV
react-test-renderer-shallow.production.min.js 🔺+5.7% 🔺+3.7% 11.77 KB 12.44 KB 3.65 KB 3.79 KB NODE_PROD
ReactShallowRenderer-dev.js +5.6% +4.0% 29.91 KB 31.58 KB 7.57 KB 7.87 KB FB_WWW_DEV

Generated by 🚫 dangerJS

// A sanity check to ensure two maps do not get used together
invariant(
this._didScheduleRenderPhaseUpdate === false,
'non-render phase updates should never occur in combination with render phase updates',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why would they not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I couldn't think of a case where that would happen, at least in the case they do this invariant would hit. If you can think of a test for it I can enable it and add the logic for that code path :)

}
updatesMap = this._renderPhaseUpdates;
} else if (componentIdentity === this._previousComponentIdentity) {
// This means an update has happened after the function component has
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it also rerender in this case?

It would be inline with class component behavior, covering tests like

rendered.props.onClick();
expect(shallowRenderer.getRenderOutput()).toEqual(... updated ...);

which is what I think a lib like enzyme would do internally

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure how Enzyme works, but in this case, it's triggering an update to the update queue. There is no re-render, which seems more in line with how shallow renderer works (you explicitly call render).

Copy link
Contributor

@rodrigopr rodrigopr Feb 13, 2019

Choose a reason for hiding this comment

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

But don't shallow-render Updater do this already? (immediately change the state and rerender)

this._renderer._newState = {
...currentState,
...partialState,
};
this._renderer.render(this._renderer._element, this._renderer._context);

(i'm not sure who calls enqueueSetState, so I might be missing something)

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah setState should trigger a render.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, my mistake. I've changed the PR to do a re-render.

@ericaprieto
Copy link

ericaprieto commented Feb 14, 2019

useState seems to be working with this but useEffect don't, is this intended?

@gaearon
Copy link
Collaborator

gaearon commented Feb 14, 2019

AFAIK even old React lifecycle methods like componentDidMount aren't triggered by shallow renderer. They rely on DOM too often.

In general I wouldn't recommend using shallow renderer for anything more involved than asserting on the result. If you want to test useEffect, please follow the recommended testing approach.

@ericaprieto
Copy link

That approach feels weird to me... I need to know the internals of every sub component to be able to make assertions, and that becomes really messy as you go up in the tree. If a sub component changes, than my unit test has to change too... I don't know, maybe I'm missing something, but I've never seen an example showing how this approach scales.

@gaearon
Copy link
Collaborator

gaearon commented Feb 14, 2019

This is a long discussion and I'm not sure I'm ready to do it in this PR. :-)

need to know the internals of every sub component to be able to make assertions

You need to test what matters to the user. This approach is described here: https://testing-library.com/docs/react-testing-library/intro.

The user doesn’t care how you split your app into components. If you refactor a Panel into PanelBorder and PanelContent that shouldn’t break your tests because from user’s perspective, nothing changed. Shallow rendering encourages writing tests that are too fragile and that don’t really test much — in many cases they mostly test that React itself works. That’s not super useful.

Of course you’re right that you don’t usually want a single test to get too deep. That’s where mocking helps. In Jest, you can do

jest.mock('../MyButton', () => 'button')

or

jest.mock('../SomeContent', () => (props) => '(My content)')

and that would shim the rendered component with a fake version. That allows you to pick where your test “ends“. Other test runners may or may not provide similar capabilities.

In either case, this is a long discussion that’s irrelevant to this PR — so let’s have it elsewhere as long as we have a clear question and goals in mind. I’d rather this not turn into a giant argument about testing because people tend to be very opinionated about that.

@ericaprieto
Copy link

I get what your saying, I thought about that a lot and eventually got to this mocking approach, but I always feel I'm doing wrong, you know? I guess I'm too obsessed with unit testing, maybe I need to change how I think about tests.

Anyway, you are right, this is not the place for this and thank you very much for answering!

@lmerotta-zz
Copy link

Any news on when this will be merged ?

@trueadm
Copy link
Contributor Author

trueadm commented Mar 15, 2019

Fixed in #15120

@trueadm trueadm closed this Mar 15, 2019
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.

7 participants