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

[Labs/Popover2] Ease Popover2 migration with prop shims #1581

Merged
merged 4 commits into from Sep 27, 2017
Merged

Conversation

brieb
Copy link
Contributor

@brieb brieb commented Sep 20, 2017

Fixes #1553

Checklist

  • Include tests
  • Update documentation

@blueprint-bot
Copy link

Add tests

Preview: documentation
Coverage: core | datetime

Copy link
Contributor

@cmslewis cmslewis left a comment

Choose a reason for hiding this comment

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

Looks good! Requesting a filename change.

child
</Popover2>,
);
{
Copy link
Contributor

Choose a reason for hiding this comment

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

whoa, nifty syntax trick

Copy link
Contributor

Choose a reason for hiding this comment

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

what is this trick called? closures?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok wow this is pretty cool! Makes the setup/assertion/setup/assertion pattern very clear.

But might this still be better accomplished with a helper function? Instead of repeating these 5 lines many times.
assertPlacement(wrapper, expected)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -0,0 +1,57 @@
/*
Copy link
Contributor

Choose a reason for hiding this comment

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

I vote to rename this file to popoverMigrationUtils.ts. Suffixing bags of functions with -Utils.ts ensures we know exactly what that file is at a glance, and the consistency is nice too.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@giladgray giladgray left a comment

Choose a reason for hiding this comment

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

Nice work!

* @param candidates the ordered list of value candidates
* @param defaultValue the fallback value
*/
export function resolvePropValue<T>(candidates: Array<T | undefined>, defaultValue: T): T {
Copy link
Contributor

Choose a reason for hiding this comment

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

How about (defaultValue, ...candidates)? Won't need to clone the array before reversing.

case Position.LEFT_TOP:
return "left-start";
default:
return assertNever(position);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a fan of one line when it's this simple
case X: return Y;

Copy link
Contributor

Choose a reason for hiding this comment

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

I bet prettier is going to have an opinion.

@@ -0,0 +1,57 @@
/*
Copy link
Contributor

Choose a reason for hiding this comment

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

👍


export const POPOVER_WARN_DEPRECATED_IS_DISABLED = `${deprec} <Popover2> isDisabled is deprecated. Use disabled.`;
export const POPOVER_WARN_DEPRECATED_IS_MODAL = `${deprec} <Popover2> isModal is deprecated. Use hasBackdrop.`;
export const POPOVER_WARN_DEPRECATED_POSITION = `${deprec} <Popover2> position is deprecated. Use placement.`;
Copy link
Contributor

Choose a reason for hiding this comment

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

POPOVER2

* Migrate a legacy position into a placement.
* @param position the position to convert
*/
export function migratePosition(position: Position): Placement {
Copy link
Contributor

Choose a reason for hiding this comment

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

positionToPlacement?

child
</Popover2>,
);
{
Copy link
Contributor

Choose a reason for hiding this comment

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

Ok wow this is pretty cool! Makes the setup/assertion/setup/assertion pattern very clear.

But might this still be better accomplished with a helper function? Instead of repeating these 5 lines many times.
assertPlacement(wrapper, expected)

@brieb
Copy link
Contributor Author

brieb commented Sep 21, 2017

So, I had some second thoughts on this. I don't like how easy it is to bypass the special getters, and access the props directly. Seems like it will really hurt maintainability. So, what if we added an HOC what transformed the props before they were consumed by the component. This would allow us to safely use this.props... and defaultProps.

/**
 * React component decorator that transforms incoming props according to the given transformation function.
 *
 * Example usage:
 * @transformProps(props => {
 *     let someProp = someDefaultValue;
 *     if (props.someProp !== undefined) {
 *         someProp = props.someProp;
 *     } else if (props.someDeprecatedProp !== undefined) {
 *         someProp = props.someDeprecatedProp;
 *     }
 * 
 *     return {
 *         ...props,
 *         someProp,
 *     };
 * })
 * class MyComponent extends React.Component<MyComponentProps, {}> {
 *   public render() {
 *     // Here, this.props is the transformed version
 *     // ...
 *   }
 * }
 * 
 * @param transformer a function that converts the input props into the desired output props
 * @returns a decorator that wraps the target component in a higher-order stateless functional component which
 *  transforms the props before they are given to the created element
 */
export function transformProps<P>(transformer: (props: P) => P) {
    return (target: any): any => {
        return (props: P) => React.createElement(target, transformer(props));
    };
}

Only issue I can foresee with this is with enzyme. Doesn't sound like it plays well with HOCs since our tests check the state of the mounted component and enzyme only lets you get the state of the root component. If we decorate the component, the root component becomes the wrapper component (no longer the one that has the state). Sounds like shallow might help us here:
enzymejs/enzyme#98 (comment)
enzymejs/enzyme#431
enzymejs/enzyme#1025
But, some of the tests rely on dom mounting, though those aren't the same ones that check the state. So, maybe a hybrid is possible...
I am still learning enzyme and testing best practices, so just thought I'd ask before venturing into a seemingly deep dark rabbit hole.

@adidahiya
Copy link
Contributor

@brieb that sounds promising! I like the @transformProps API as long as we can get good type safety. And a mix of shallow / deep rendering for tests sounds fine if you're testing different things.

@blueprint-bot
Copy link

Address PR feedback

Preview: documentation | table
Coverage: core | datetime

@brieb
Copy link
Contributor Author

brieb commented Sep 22, 2017

Getting the tests working with the HOC proved to be quite challenging. We can maybe revisit this later on. Instead, I changed this to use a derived state value. There is some precedence for this elsewhere in the code when we derive internal values for controlled / uncontrolled components. So, it seemed like a less foreign pattern to get the final value off the state than to be sure to use a random getter every time you access the value. So, not quite what I was going for with the decorator, but a step in the right direction. Thoughts?

@blueprint-bot
Copy link

Use state for derived migrated prop values

Preview: documentation | table
Coverage: core | datetime

@cmslewis
Copy link
Contributor

@brieb That sounds good to me.

@brieb
Copy link
Contributor Author

brieb commented Sep 27, 2017

Says "You're not authorized to merge this pull request."
Is this good to go? If so, can someone with permissions merge it?
Thanks!

@adidahiya adidahiya merged commit d90adf5 into master Sep 27, 2017
@adidahiya adidahiya deleted the bb/1553 branch September 27, 2017 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants