Fix to call a setState callback after finishing the the render#1453
Fix to call a setState callback after finishing the the render#1453ljharb merged 6 commits intoenzymejs:masterfrom
Conversation
| this.update(); | ||
| // call the setState callback | ||
| if (callback) { | ||
| callback.call(instance); |
There was a problem hiding this comment.
are we sure that React calls the callback with zero arguments, and the instance as the receiver?
There was a problem hiding this comment.
are we sure that React calls the callback with zero arguments, and the instance as the receiver?
I think so.
There was a problem hiding this comment.
That implementation doesn't show where update.callback is assigned; but your demo is convincing (for the latest version of React).
However, in React ^15.4, it seems to get one argument set to undefined in your demo (whereas in <= 15.3, and >= 16, it seems to get zero arguments). We should maintain that behavior via adapters.
There was a problem hiding this comment.
Thanks! I'll work on to support < 15.3
There was a problem hiding this comment.
I've tested for arguments of setState callback.
setState({foo: 'bar'}, function(...args) {
console.log(args);
});- 0.13.3 ...
[] - 0.14.9 ...
[] - 15.4.2 ...
[ undefined ] - 15.6.2 ...
[ undefined ] - 16.2.0 ...
[]
The arguments seem to be [undefined] only in v15.
There was a problem hiding this comment.
It's more than just painful; enzyme itself must not know about React at all, given that non-React adapters need to be able to work.
In other words, this functionality needs to be something that every adapter implements, and enzyme merely calls into.
There was a problem hiding this comment.
enzyme itself must not know about React at all, given that non-React adapters need to be able to work.
I agree with you. But calling setState callback without argument is depending on current React implementation.
Spying setState callback and using the result later is to reproduce this functionality so I think it can be applied any adapters which are including non-React adapters.
instance.setState(state, spy);
:
if (callback) {
// replay setState callbacks
spy.getCalls().forEach((spyCall) => {
const context = spyCall.thisValue;
const args = spyCall.args;
callback.apply(context, args);
});
}There was a problem hiding this comment.
Your spy suggestion is interesting, but I was suggesting something like adapter.invokeSetStateCallback(instance, callback) - I'm not sure which is better.
There was a problem hiding this comment.
Thanks, I got it.
Are you ok to implement a default behavior into EnzymeAdapter? If not, it will be a breaking change.
There was a problem hiding this comment.
Yes, I think that would make sense.
279cbf6 to
d3c6cf1
Compare
| "eslint-plugin-react": "^7.5.1" | ||
| } | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
let's revert this file; every file should always have a trailing newline
There was a problem hiding this comment.
The diff is added again😉 I'm not digging into that but I guess it is added by a script to run tests.
There was a problem hiding this comment.
ah, weird. maybe npm itself. not a big deal.
| } | ||
| instance.setState(state, callback); | ||
| // We don't pass the setState callback here | ||
| // to guarantee to call the callback after finishing the render |
There was a problem hiding this comment.
can we add a test case for this? or does expect(wrapper.find('div').prop('className')).to.eql('bar'); cover that
There was a problem hiding this comment.
Yes, expect(wrapper.find('div').prop('className')).to.eql('bar'); is a test for this.
40540e4 to
4afbef7
Compare
|
|
||
|
|
||
| // some React versions pass undefined as an argument of setState callback. | ||
| const CALLING_SETSTATE_CALLBACK_WITH_UNDEFINED = REACT154 || REACT155 || REACT156; |
There was a problem hiding this comment.
could this be made a bit more robust by changing it to !REACT15 && !REACT150 && !REACT151 && !REACT152 && !REACT153, since those are the only 4 versions of 15 that will ever not have this behavior?
| if (MINOR_VERSION >= 4) { | ||
| callback.call(instance, undefined); | ||
| } else { | ||
| callback.call(instance); |
There was a problem hiding this comment.
maybe this could do super.invokeSetStateCallback(instance, callback)?
|
Thank you for your review! |
|
Tests seem to be failing in React 15, both locally and in travis. I'm going to see what I can do to figure it out now. |
6ea171e to
751b1f7
Compare
| '^15.4', | ||
| () => { callback.call(instance, undefined); }, | ||
| () => { super.invokeSetStateCallback(instance, callback); }, | ||
| ); |
There was a problem hiding this comment.
ifReact returns a function, not calling it so you have to call a function which is returned from ifReact.
https://github.com/airbnb/enzyme/blob/master/packages/enzyme-adapter-react-helper/src/ifReact.js#L7
There was a problem hiding this comment.
lol, oops. shouldn't tests have failed?
There was a problem hiding this comment.
I think so we should add a test to guarantee to call a setState callback.
Currently, we don't have a test for it.
| const BATCHING = !REACT16; | ||
|
|
||
| // some React versions pass undefined as an argument of setState callback. | ||
| const CALLING_SETSTATE_CALLBACK_WITH_UNDEFINED = REACT15 && !REACT150_4; |
There was a problem hiding this comment.
I guess this condition doesn't cover a case that React v15.4 calls setState callback with undefined.
But all tests are passing in my environment. The reason is ifReact is checking with React v16.2 because enzyme-adapter-react-helper has React v16.2 into its node_modules.
I'm not sure why enzyme-adapter-react-helper has React v16.2 into its node_modules.
There was a problem hiding this comment.
it has a peer dep/dev dep on react >= 0.13, so that'd install the latest, but i'm not sure why that would do it. let me try something tho
There was a problem hiding this comment.
Thanks! I'll dig into it later.
There was a problem hiding this comment.
enzyme-adapter-react-helper shouldn't have react as a devDependencies?
https://github.com/airbnb/enzyme/blob/master/packages/enzyme-adapter-react-helper/package.json#L50
There was a problem hiding this comment.
since it's a peer dep, it also needs to be a dev dep.
There was a problem hiding this comment.
Other packages don't have react as a devDeps. Why does only this package have it?
There was a problem hiding this comment.
That's a bug leftover from the v3 migration; they all should - every peer dep in every npm package anywhere must always have the identical peer dep string as a dep or a dev dep.
There was a problem hiding this comment.
I've almost got a fix PR with a regression test; i'll put it up shortly.
- [fix] actually invoke the setState callback (#1465, #1453) - [fix] add missing support for animation events (#1569) - [fix] call ref for a root element (#1541) - [fix] Allow empty strings as key props (#1524) - [fix] correct the adapter class name - [fix] `mount`: make sure it works with native arrow functions (#1667) - [refactor]: add “lifecycles” adapter option (#1696) - [refactor] use `react-is` package - [deps] remove unneeded `lodash` dep; update `prop-types`, `enzyme-adapter-utils`, `object.assign` - [dev deps] update `babel-cli`, `babel-core`, `babel-plugin-transform-replace-object-assign`, `babel-preset-airbnb`, `chai`, `coveralls`, `eslint`, `eslint-config-airbnb`, `eslint-plugin-import`, `eslint-plugin-jsx-a11y`, `eslint-plugin-react`, `lerna`, `json-loader`, `karma-firefox-launcher`, `prop-types`, `lerna`, `rimraf`, `webpack`
- [fix] actually invoke the setState callback (#1465, #1453) - [fix] add missing support for animation events (#1569) - [fix] call ref for a root element (#1541) - [fix] Allow empty strings as key props (#1524) - [fix] correct the adapter class name - [fix] `mount`: make sure it works with native arrow functions (#1667) - [refactor]: add “lifecycles” adapter option (#1696) - [refactor] use `react-is` package - [deps] remove unneeded `lodash` dep; update `prop-types`, `enzyme-adapter-utils`, `object.assign` - [dev deps] update `babel-cli`, `babel-core`, `babel-plugin-transform-replace-object-assign`, `babel-preset-airbnb`, `chai`, `coveralls`, `eslint`, `eslint-config-airbnb`, `eslint-plugin-import`, `eslint-plugin-jsx-a11y`, `eslint-plugin-react`, `lerna`, `json-loader`, `karma-firefox-launcher`, `prop-types`, `lerna`, `rimraf`, `webpack`
This PR is to fix #1451.
Currently, a setState callback is called before finishing the render because enzyme is passing the callback into
instance.setState.So I've fixed it to call the callback after
this.update()