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

Implement spyMethod and use it to inspect the result of shouldComponentUpdate #1192

Merged
merged 2 commits into from
Jul 5, 2018

Conversation

koba04
Copy link
Contributor

@koba04 koba04 commented Sep 28, 2017

This PR is to follow up #1133 (comment)

if (props) this[UNRENDERED] = cloneElement(adapter, this[UNRENDERED], props);
this[RENDERER].render(this[UNRENDERED], nextContext);
if (spy) {
shouldRender = spy.getCall(0).returnValue;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we care about calling shouldComponentUpdate multiple times?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe it should get the last call, not the first?

if (props) this[UNRENDERED] = cloneElement(adapter, this[UNRENDERED], props);
this[RENDERER].render(this[UNRENDERED], nextContext);
if (spy) {
shouldRender = spy.getCall(0).returnValue;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe it should get the last call, not the first?

}
instance.setState(state, callback);
if (spy) {
shouldRender = spy.getCall(0).returnValue;
Copy link
Member

Choose a reason for hiding this comment

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

Also here

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've fixed it!

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

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

Seems good to me!

Before we merge this, can we confirm what the oldest JS engine sinon supports is?

@koba04
Copy link
Contributor Author

koba04 commented Sep 29, 2017

I'm not sure what the oldest JS engine sinon supports is.
But according to the CI results of sinon, sinon supports Node v4 or later.
In browser environment, according to its sourcelabs results, sinon supports IE11.

@ljharb ljharb requested a review from a team September 29, 2017 14:20
@lelandrichardson
Copy link
Collaborator

I'm not sure I agree with this change. This seems like a huge (and often problematic) dependency to take on for very little benefit.

@ljharb
Copy link
Member

ljharb commented Sep 30, 2017

Fair point (not sure about problematic tho; sinon's always worked fine in my experience) the benefit I see is ensuring that the property descriptor remains the same after restoring, which is tedious to do correctly.

@lelandrichardson
Copy link
Collaborator

Honestly I would prefer that we do our own thing here instead of pulling in sinon. If we need to do this in a more robust way and build a util function, or depend on a package that does just that, then that's fine. I'm also wondering if this would have strange conflicts with people using sinon on their own with shouldComponentUpdate and sinon handling sinon-spied functions in a different way in that case.

@lelandrichardson
Copy link
Collaborator

@ljharb when i say "problematic" i mean that I know that in the past webpack and browserify both had problems bundling sinon. I think that might have been fixed in a more recent version though.

@ljharb
Copy link
Member

ljharb commented Sep 30, 2017

Those are all valid concerns. Let's definitely hold off on merging this for now.

@koba04
Copy link
Contributor Author

koba04 commented Oct 14, 2017

I'm working on to implement own spyMethod instead of sinon.spy.

@koba04 koba04 changed the title Use sinon.spy to inspect the result of shouldComponentUpdate Implement spyMethod and use it to inspect the result of shouldComponentUpdate Oct 17, 2017
@koba04
Copy link
Contributor Author

koba04 commented Oct 17, 2017

I've implemented a custom spy function, which is a minimum implementation needed by enzyme.

export function spyMethod(instance, methodName) {
let lastReturnValue;
const originalMethod = instance[methodName];
const hasOwn = Object.hasOwnProperty.call(instance, methodName);
Copy link
Member

Choose a reason for hiding this comment

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

this should use the has package

restore() {
if (hasOwn) {
/* eslint-disable no-param-reassign */
instance[methodName] = originalMethod;
Copy link
Member

Choose a reason for hiding this comment

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

this won't actually restore the original property descriptor; to do this properly, it needs to use Object.getOwnPropertyDescriptor and Object.defineProperty when available, and fall back to normal reading and assignment.

/* eslint-enable no-param-reassign */
}
},
get lastReturnValue() {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this needs to be, or should be, a getter - can it just be a function?

Copy link
Member

@ljharb ljharb left a comment

Choose a reason for hiding this comment

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

Seems good, thanks

@koba04
Copy link
Contributor Author

koba04 commented Feb 12, 2018

@ljharb

React has plans for v16.3. facebook/react#12152
They include API changes for lifecycle methods.

  • Add UNSAFE_ prefix lifecycle methods
    • UNSAFE_componentWillMount, UNSAFE_compnentWillReceiveProps, UNSAFE_componentWillUpdate
  • Add static getDerivedStateFromProps

Currently, ShallowWrapper overrides componentWillReceiveProps and shouldComponentUpdate to get the result of shouldComponentUpdate.
It means enzyme have to deal with these changes at ShallowWrapper.

This PR will solve that.
This PR avoid to override these lifecycle methods by using a spy function.
So if this PR is merged, we don't have to care about the changes.

@koba04
Copy link
Contributor Author

koba04 commented Mar 30, 2018

React v16.3 has been released.
The current implementation of shallow doesn't cover the case that UNSAFE_componentWillReceiveProps should be called when shouldComponentUpdate returns false because enzyme overrides the implementation.
This PR will resolve the case.

https://github.com/airbnb/enzyme/pull/1192/files#diff-fa27c82ee4fde075bae6173848e17c82L263

@koba04 koba04 force-pushed the use-sinon branch 2 times, most recently from 8795acb to 50ddc88 Compare July 2, 2018 02:11
@koba04 koba04 force-pushed the use-sinon branch 2 times, most recently from 312df6b to d5dc160 Compare July 2, 2018 08:12
@koba04
Copy link
Contributor Author

koba04 commented Jul 3, 2018

I'm not sure why CI is failing...

https://travis-ci.org/airbnb/enzyme/jobs/399026371

@koba04
Copy link
Contributor Author

koba04 commented Jul 4, 2018

All checks have passed 🎉

if (
!this[OPTIONS].disableLifecycleMethods &&
instance
lifecycles.getSnapshotBeforeUpdate
Copy link
Member

Choose a reason for hiding this comment

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

this no longer seems to be checking disableLifecycleMethods?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

lifecycles.getSnapshotBeforeUpdate
&& typeof instance.getSnapshotBeforeUpdate === 'function'
) {
const snapshot = instance.getSnapshotBeforeUpdate(prevProps, state);
Copy link
Member

Choose a reason for hiding this comment

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

i have a mild preference for splitting the "snapshot" and "context" paths entirely, instead of trying to interleave them with let snapshot

}
Object.defineProperty(instance, methodName, {
configurable: true,
enumerable: false,
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't this be !descriptor || !!descriptor.enumerable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But shouldn't an instance method be enumerable?

Copy link
Member

Choose a reason for hiding this comment

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

the replacement should be enumerable, or not, to match the one it's spying on

if (typeof instance.getSnapshotBeforeUpdate === 'function') {
snapshot = instance.getSnapshotBeforeUpdate(prevProps, state);
}
if (typeof instance.componentDidUpdate === 'function') {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should I check lifecycles.componentDidUpdate?

Copy link
Member

Choose a reason for hiding this comment

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

yes, that'd be great

let shouldRender = true;
// dirty hack:
// make sure that componentWillReceiveProps is called before shouldComponentUpdate
Copy link
Member

Choose a reason for hiding this comment

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

do we no longer need this hack?

Copy link
Member

Choose a reason for hiding this comment

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

Can you elaborate on why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The current version calls shouldComponentUpdate manually to get the result.
But componentWillReceiveProps should be called before shouldComponentUpdate so we should call componentWillReceiveProps manually before shouldComponentUpdate.

In this PR, we no longer call shouldComponentUpdate manually because we can spy the method.
As the result, we can remove the dirty hack because we don't need to call componentWillReceiveProps and shouldComponentUpdate manually.

Copy link
Member

Choose a reason for hiding this comment

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

Perfect, thanks.

@ljharb ljharb merged commit f1cf02b into enzymejs:master Jul 5, 2018
@koba04
Copy link
Contributor Author

koba04 commented Jul 5, 2018

Thanks 🎉

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.

3 participants