From 153a6cdf4d6226984a287b80f001a6d0890ec360 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 30 Sep 2019 16:05:17 +0200 Subject: [PATCH 1/2] chore: simplify getUnhandledProps() & getElementType() --- packages/react-bindings/jest.config.js | 5 +++ packages/react-bindings/package.json | 35 +++++++++++++++++++ packages/react-bindings/src/index.ts | 2 ++ .../src/utils/getElementType.ts | 17 +++++++++ .../src/utils/getUnhandledProps.ts} | 12 ++++--- .../test/utils/getUnhandledProps-test.ts | 15 ++++++++ packages/react-bindings/tsconfig.json | 11 ++++++ packages/react/package.json | 1 + packages/react/src/lib/UIComponent.tsx | 1 - .../accessibility/FocusZone/AutoFocusZone.tsx | 12 +++---- .../accessibility/FocusZone/FocusTrapZone.tsx | 10 ++---- .../lib/accessibility/FocusZone/FocusZone.tsx | 10 ++---- ...createComponent.tsx => createComponent.ts} | 1 - packages/react/src/lib/getElementType.tsx | 26 -------------- packages/react/src/lib/index.ts | 2 -- packages/react/src/lib/renderComponent.tsx | 9 ++--- .../test/specs/lib/getUnhandledProps-test.tsx | 26 -------------- 17 files changed, 106 insertions(+), 89 deletions(-) create mode 100644 packages/react-bindings/jest.config.js create mode 100644 packages/react-bindings/package.json create mode 100644 packages/react-bindings/src/index.ts create mode 100644 packages/react-bindings/src/utils/getElementType.ts rename packages/{react/src/lib/getUnhandledProps.tsx => react-bindings/src/utils/getUnhandledProps.ts} (60%) create mode 100644 packages/react-bindings/test/utils/getUnhandledProps-test.ts create mode 100644 packages/react-bindings/tsconfig.json rename packages/react/src/lib/{createComponent.tsx => createComponent.ts} (99%) delete mode 100644 packages/react/src/lib/getElementType.tsx delete mode 100644 packages/react/test/specs/lib/getUnhandledProps-test.tsx diff --git a/packages/react-bindings/jest.config.js b/packages/react-bindings/jest.config.js new file mode 100644 index 0000000000..8e31fdf34f --- /dev/null +++ b/packages/react-bindings/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + ...require('@stardust-ui/internal-tooling/jest'), + name: 'react-bindings', + moduleNameMapper: require('lerna-alias').jest(), +} diff --git a/packages/react-bindings/package.json b/packages/react-bindings/package.json new file mode 100644 index 0000000000..684ea7cf0b --- /dev/null +++ b/packages/react-bindings/package.json @@ -0,0 +1,35 @@ +{ + "name": "@stardust-ui/react-bindings", + "description": "A set of components and hooks to build components libraries and UI kits.", + "version": "0.39.0", + "author": "Oleksandr Fediashov ", + "bugs": "https://github.com/stardust-ui/react/issues", + "dependencies": { + "@babel/runtime": "^7.1.2" + }, + "devDependencies": { + "@stardust-ui/internal-tooling": "^0.39.0", + "lerna-alias": "^3.0.3-0" + }, + "files": [ + "dist" + ], + "homepage": "https://github.com/stardust-ui/react/tree/master/packages/react-bindings", + "jsnext:main": "dist/es/index.js", + "license": "MIT", + "main": "dist/commonjs/index.js", + "module": "dist/es/index.js", + "peerDependencies": { + "react": "^16.8.0", + "react-dom": "^16.8.0" + }, + "publishConfig": { + "access": "public" + }, + "repository": "stardust-ui/react.git", + "scripts": { + "build": "cross-env TS_NODE_PROJECT=../../tsconfig.json gulp bundle:package:no-umd --package react-bindings" + }, + "sideEffects": false, + "types": "dist/es/index.d.ts" +} diff --git a/packages/react-bindings/src/index.ts b/packages/react-bindings/src/index.ts new file mode 100644 index 0000000000..8fe6402f99 --- /dev/null +++ b/packages/react-bindings/src/index.ts @@ -0,0 +1,2 @@ +export { default as getElementType } from './utils/getElementType' +export { default as getUnhandledProps } from './utils/getUnhandledProps' diff --git a/packages/react-bindings/src/utils/getElementType.ts b/packages/react-bindings/src/utils/getElementType.ts new file mode 100644 index 0000000000..1d00de6269 --- /dev/null +++ b/packages/react-bindings/src/utils/getElementType.ts @@ -0,0 +1,17 @@ +import * as React from 'react' + +/** + * Returns a createElement() type based on the props of the Component. + * Useful for calculating what type a component should render as. + * + * @param {object} props A ReactElement props object + * @returns {string|function} A ReactElement type + */ +function getElementType

>(props: P): React.ElementType { + // ---------------------------------------- + // use defaultProp or 'div' + + return props.as || 'div' +} + +export default getElementType diff --git a/packages/react/src/lib/getUnhandledProps.tsx b/packages/react-bindings/src/utils/getUnhandledProps.ts similarity index 60% rename from packages/react/src/lib/getUnhandledProps.tsx rename to packages/react-bindings/src/utils/getUnhandledProps.ts index 2afb0cd4a1..af3a0d7b80 100644 --- a/packages/react/src/lib/getUnhandledProps.tsx +++ b/packages/react-bindings/src/utils/getUnhandledProps.ts @@ -1,14 +1,16 @@ /** * Returns an object consisting of props beyond the scope of the Component. * Useful for getting and spreading unknown props from the user. - * @param {function} Component A function or ReactClass. + * + * @param {string[]} handledProps An array with names of props * @param {object} props A ReactElement props object * @returns {{}} A shallow copy of the prop object */ -const getUnhandledProps = (Component, props) => { - const { handledProps = [] } = Component - - return Object.keys(props).reduce((acc, prop) => { +function getUnhandledProps

>( + handledProps: (keyof P)[], + props: P, +): Partial

{ + return Object.keys(props).reduce>((acc, prop) => { if (handledProps.indexOf(prop) === -1) acc[prop] = props[prop] return acc diff --git a/packages/react-bindings/test/utils/getUnhandledProps-test.ts b/packages/react-bindings/test/utils/getUnhandledProps-test.ts new file mode 100644 index 0000000000..8df98c4237 --- /dev/null +++ b/packages/react-bindings/test/utils/getUnhandledProps-test.ts @@ -0,0 +1,15 @@ +import { getUnhandledProps } from '@stardust-ui/react-bindings' + +describe('getUnhandledProps', () => { + test('leaves props that are not defined in handledProps', () => { + expect(getUnhandledProps([], { 'data-leave-this': 'it is unhandled' })).toHaveProperty( + 'data-leave-this', + ) + }) + + test('removes props defined in handledProps', () => { + expect( + getUnhandledProps(['data-remove-me'], { 'data-remove-me': 'it is handled' }), + ).not.toHaveProperty('data-remove-me') + }) +}) diff --git a/packages/react-bindings/tsconfig.json b/packages/react-bindings/tsconfig.json new file mode 100644 index 0000000000..8603c4cc3d --- /dev/null +++ b/packages/react-bindings/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../build/tsconfig.common", + "compilerOptions": { + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "noUnusedParameters": true, + "strictNullChecks": true + }, + "include": ["src", "test"] +} diff --git a/packages/react/package.json b/packages/react/package.json index 82467b3236..21165fde09 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -7,6 +7,7 @@ "dependencies": { "@babel/runtime": "^7.1.2", "@stardust-ui/accessibility": "^0.39.0", + "@stardust-ui/react-bindings": "^0.39.0", "@stardust-ui/react-component-event-listener": "^0.39.0", "@stardust-ui/react-component-nesting-registry": "^0.39.0", "@stardust-ui/react-component-ref": "^0.39.0", diff --git a/packages/react/src/lib/UIComponent.tsx b/packages/react/src/lib/UIComponent.tsx index c7462e3255..fac690eaac 100644 --- a/packages/react/src/lib/UIComponent.tsx +++ b/packages/react/src/lib/UIComponent.tsx @@ -55,7 +55,6 @@ class UIComponent extends React.Component { return renderComponent( { className: this.childClass.className, - defaultProps: this.childClass.defaultProps, displayName: this.childClass.displayName, handledProps: this.childClass.handledProps, props: this.props, diff --git a/packages/react/src/lib/accessibility/FocusZone/AutoFocusZone.tsx b/packages/react/src/lib/accessibility/FocusZone/AutoFocusZone.tsx index 87964d8c7f..4f0c3d45fc 100644 --- a/packages/react/src/lib/accessibility/FocusZone/AutoFocusZone.tsx +++ b/packages/react/src/lib/accessibility/FocusZone/AutoFocusZone.tsx @@ -1,3 +1,4 @@ +import { getElementType, getUnhandledProps } from '@stardust-ui/react-bindings' import { Ref } from '@stardust-ui/react-component-ref' import * as React from 'react' import * as PropTypes from 'prop-types' @@ -6,8 +7,6 @@ import * as _ from 'lodash' import { getNextElement, focusAsync } from './focusUtilities' import { AutoFocusZoneProps } from './AutoFocusZone.types' -import getUnhandledProps from '../../getUnhandledProps' -import getElementType from '../../getElementType' import callable from '../../callable' /** AutoFocusZone is used to focus inner element on mount. */ @@ -19,19 +18,16 @@ export default class AutoFocusZone extends React.Component { firstFocusableSelector: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), } - static handledProps = _.keys(AutoFocusZone.propTypes) + static handledProps = _.keys(AutoFocusZone.propTypes) as any componentDidMount(): void { this.findElementAndFocusAsync() } render(): JSX.Element { - const unhandledProps = getUnhandledProps( - { handledProps: AutoFocusZone.handledProps }, - this.props, - ) + const unhandledProps = getUnhandledProps(AutoFocusZone.handledProps, this.props) - const ElementType = getElementType({}, this.props) as React.ComponentClass + const ElementType = getElementType(this.props) return ( diff --git a/packages/react/src/lib/accessibility/FocusZone/FocusTrapZone.tsx b/packages/react/src/lib/accessibility/FocusZone/FocusTrapZone.tsx index 776c0e1a51..ef86e0e69a 100644 --- a/packages/react/src/lib/accessibility/FocusZone/FocusTrapZone.tsx +++ b/packages/react/src/lib/accessibility/FocusZone/FocusTrapZone.tsx @@ -1,3 +1,4 @@ +import { getElementType, getUnhandledProps } from '@stardust-ui/react-bindings' import { EventListener } from '@stardust-ui/react-component-event-listener' import * as React from 'react' import * as ReactDOM from 'react-dom' @@ -15,8 +16,6 @@ import { } from './focusUtilities' import { FocusTrapZoneProps } from './FocusTrapZone.types' -import getUnhandledProps from '../../getUnhandledProps' -import getElementType from '../../getElementType' /** FocusTrapZone is used to trap the focus in any html element placed in body * and hide other elements outside of Focus Trap Zone from accessibility tree. @@ -130,11 +129,8 @@ export default class FocusTrapZone extends React.Component implement render() { const { className } = this.props - const ElementType = getElementType({ defaultProps: FocusZone.defaultProps }, this.props) - const unhandledProps = getUnhandledProps( - { handledProps: [..._.keys(FocusZone.propTypes)] }, - this.props, - ) + const ElementType = getElementType(this.props) + const unhandledProps = getUnhandledProps(_.keys(FocusZone.propTypes) as any, this.props) // Note, right before rendering/reconciling proceeds, we need to record if focus // was in the zone before the update. This helper will track this and, if focus diff --git a/packages/react/src/lib/createComponent.tsx b/packages/react/src/lib/createComponent.ts similarity index 99% rename from packages/react/src/lib/createComponent.tsx rename to packages/react/src/lib/createComponent.ts index dbc10ec3f6..b64d2eecad 100644 --- a/packages/react/src/lib/createComponent.tsx +++ b/packages/react/src/lib/createComponent.ts @@ -49,7 +49,6 @@ const createComponent =

= any>({ return renderComponent( { className, - defaultProps, displayName, handledProps: _.keys(propTypes).concat(handledProps), props, diff --git a/packages/react/src/lib/getElementType.tsx b/packages/react/src/lib/getElementType.tsx deleted file mode 100644 index 926fe55a0d..0000000000 --- a/packages/react/src/lib/getElementType.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react' -import { Props } from '../types' - -/** - * Returns a createElement() type based on the props of the Component. - * Useful for calculating what type a component should render as. - * - * @param {function} Component A function or ReactClass. - * @param {object} props A ReactElement props object - * @returns {string|function} A ReactElement type - */ -function getElementType(Component: { defaultProps?: Props }, props: Props): React.ElementType { - const { defaultProps = {} } = Component - - // ---------------------------------------- - // user defined "as" element type - - if (props.as && props.as !== defaultProps.as) return props.as - - // ---------------------------------------- - // use defaultProp or 'div' - - return defaultProps.as || 'div' -} - -export default getElementType diff --git a/packages/react/src/lib/index.ts b/packages/react/src/lib/index.ts index c89d4ee17b..025cf65673 100644 --- a/packages/react/src/lib/index.ts +++ b/packages/react/src/lib/index.ts @@ -13,8 +13,6 @@ export { default as getOrGenerateIdFromShorthand } from './getOrGenerateIdFromSh export * from './factories' export { default as callable } from './callable' export { default as constants } from './constants' -export { default as getElementType } from './getElementType' -export { default as getUnhandledProps } from './getUnhandledProps' export { default as mergeThemes } from './mergeThemes' export { default as mergeProviderContexts } from './mergeProviderContexts' diff --git a/packages/react/src/lib/renderComponent.tsx b/packages/react/src/lib/renderComponent.tsx index b515726dc3..a94d97533a 100644 --- a/packages/react/src/lib/renderComponent.tsx +++ b/packages/react/src/lib/renderComponent.tsx @@ -4,13 +4,12 @@ import { FocusZoneDefinition, Accessibility, } from '@stardust-ui/accessibility' +import { getElementType, getUnhandledProps } from '@stardust-ui/react-bindings' import cx from 'classnames' import * as React from 'react' import * as _ from 'lodash' import callable from './callable' -import getElementType from './getElementType' -import getUnhandledProps from './getUnhandledProps' import logProviderMissingWarning from './providerMissingHandler' import { ComponentStyleFunctionParam, @@ -46,7 +45,6 @@ export type RenderComponentCallback

= (config: RenderResultConfig

) => any export interface RenderConfig

{ className?: string - defaultProps?: { [key: string]: any } displayName: string handledProps: string[] props: PropsWithVarsAndStyles @@ -160,7 +158,6 @@ const renderComponent =

( ): React.ReactElement

=> { const { className, - defaultProps, displayName, handledProps, props, @@ -177,7 +174,7 @@ const renderComponent =

( const { disableAnimations = false, renderer = null, rtl = false, theme = emptyTheme } = context || {} - const ElementType = getElementType({ defaultProps }, props) as React.ReactType

+ const ElementType = getElementType(props) as React.ReactType

const stateAndProps = { ...state, ...props } // Resolve variables for this component, allow props.variables to override @@ -205,7 +202,7 @@ const renderComponent =

( rtl, ) - const unhandledProps = getUnhandledProps({ handledProps }, props) + const unhandledProps = getUnhandledProps(handledProps, props) const styleParam: ComponentStyleFunctionParam = { displayName, diff --git a/packages/react/test/specs/lib/getUnhandledProps-test.tsx b/packages/react/test/specs/lib/getUnhandledProps-test.tsx deleted file mode 100644 index 565128c2f9..0000000000 --- a/packages/react/test/specs/lib/getUnhandledProps-test.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import * as React from 'react' -import { shallow } from 'enzyme' -import { getUnhandledProps } from 'src/lib' - -// We spread the unhandled props onto the rendered result. -// Then, we can test the props of the rendered result. -// This is the intended usage of the util. -const TestComponent: any = props => { - return

-} - -describe('getUnhandledProps', () => { - test('leaves props that are not defined in handledProps', () => { - expect(shallow().props()).toHaveProperty( - 'data-leave-this', - ) - }) - - test('removes props defined in handledProps', () => { - TestComponent.handledProps = ['data-remove-me'] - expect(shallow().props()).not.toHaveProperty( - 'data-remove-me', - 'thanks', - ) - }) -}) From 690414359338cff5b42582fbb5f9900e50218a15 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Thu, 3 Oct 2019 16:14:16 +0200 Subject: [PATCH 2/2] add missing UT --- .../react-bindings/test/utils/getElementType-test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 packages/react-bindings/test/utils/getElementType-test.ts diff --git a/packages/react-bindings/test/utils/getElementType-test.ts b/packages/react-bindings/test/utils/getElementType-test.ts new file mode 100644 index 0000000000..d09b5e8ef3 --- /dev/null +++ b/packages/react-bindings/test/utils/getElementType-test.ts @@ -0,0 +1,11 @@ +import { getElementType } from '@stardust-ui/react-bindings' + +describe('getElementType', () => { + test('takes a value from "as" prop', () => { + expect(getElementType({ as: 'span' })).toBe('span') + }) + + test('defaults to "div"', () => { + expect(getElementType({})).toBe('div') + }) +})