From ca8982fe6cea3cf50da700cb3a8dc7d434539264 Mon Sep 17 00:00:00 2001 From: Ivan Vezhnavets Date: Thu, 25 Mar 2021 13:51:37 +0300 Subject: [PATCH 01/10] feat: implement falsyNode realisation --- .../falsyNode/falsyNode.component.tsx | 63 +++++++++++++++++++ src/components/devsupport/index.ts | 1 + src/components/ui/card/card.component.tsx | 7 ++- 3 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/components/devsupport/components/falsyNode/falsyNode.component.tsx diff --git a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx new file mode 100644 index 000000000..223efcc40 --- /dev/null +++ b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx @@ -0,0 +1,63 @@ +import React from "react"; + +export type RenderProp = (props?: Props) => React.ReactElement; + +export type FalsyNodeProps = Props & { + fallback?: React.ReactElement; + children?: React.ReactElement; + style?: React.CSSProperties | React.CSSProperties[]; +}; + +/** + * Helper component for optional properties that should render a component. + * + * Accepts props of a component that is expected to be rendered, + * and `component` which may be a string, a function, null or undefined. + * + * If it is a function, will call it with props passed to this component. + * Otherwise, will return null. + * + * @property {RenderProp} children - React jsx component to be rendered. + * @property {React.ReactElement} fallback - Element to render if children is null or undefined. + * + * @example Will render nothing. + * ``` + * + * ``` + * + * @example Will render red title. + * ``` + * const Title = () => ( + * Title} + * /> + * ); + * ``` + */ + +type ChildElement = React.ReactElement; +type ChildrenProp = ChildElement | ChildElement[]; + +export class FalsyNode extends React.Component> { + + private renderChildElement = (source: ChildElement): ChildElement => { + return React.cloneElement(source, { + style: [this.props?.style, source.props.style], + }); + }; + + private renderComponentChildren = (source: ChildrenProp): ChildElement[] => { + return React.Children.map(source, this.renderChildElement); + }; + + public render(): React.ReactElement { + const { children, fallback, ...props } = this.props; + + if (!children) { + return fallback || null; + } + + return <>{this.renderComponentChildren(children)}; + } +} diff --git a/src/components/devsupport/index.ts b/src/components/devsupport/index.ts index ccb1338c1..f320cf270 100644 --- a/src/components/devsupport/index.ts +++ b/src/components/devsupport/index.ts @@ -3,6 +3,7 @@ export { RenderProp, } from './components/falsyFC/falsyFC.component'; export { FalsyText } from './components/falsyText/falsyText.component'; +export { FalsyNode } from './components/falsyNode/falsyNode.component'; export { TouchableWithoutFeedback, TouchableWithoutFeedbackProps, diff --git a/src/components/ui/card/card.component.tsx b/src/components/ui/card/card.component.tsx index c90eb04da..8e5e73f7e 100644 --- a/src/components/ui/card/card.component.tsx +++ b/src/components/ui/card/card.component.tsx @@ -14,6 +14,7 @@ import { import { EvaStatus, FalsyFC, + FalsyNode, RenderProp, TouchableWeb, TouchableWebElement, @@ -33,7 +34,7 @@ type CardStyledProps = Overwrite; export interface CardProps extends TouchableWebProps, CardStyledProps { - children?: React.ReactNode; + children?: React.ReactElement; header?: RenderProp; footer?: RenderProp; accent?: RenderProp; @@ -150,9 +151,9 @@ export class Card extends React.Component { component={header} /> {header && } - + {children} - + {footer && } Date: Thu, 25 Mar 2021 14:18:26 +0300 Subject: [PATCH 02/10] fix: typofix --- .../components/falsyNode/falsyNode.component.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx index 223efcc40..fe8445597 100644 --- a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx +++ b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx @@ -9,13 +9,12 @@ export type FalsyNodeProps = Props & { }; /** - * Helper component for optional properties that should render a component. + * Helper component for optional properties that should render cloned component. * * Accepts props of a component that is expected to be rendered, - * and `component` which may be a string, a function, null or undefined. + * and `children` which may be React Element only. * - * If it is a function, will call it with props passed to this component. - * Otherwise, will return null. + * If it is a React Element, will call it with props passed to this component. * * @property {RenderProp} children - React jsx component to be rendered. * @property {React.ReactElement} fallback - Element to render if children is null or undefined. @@ -52,7 +51,7 @@ export class FalsyNode extends React.Component }; public render(): React.ReactElement { - const { children, fallback, ...props } = this.props; + const { children, fallback } = this.props; if (!children) { return fallback || null; From e442300a2fa719177f32b92df456e9b28e38a984 Mon Sep 17 00:00:00 2001 From: Ivan Vezhnavets Date: Thu, 25 Mar 2021 14:53:25 +0300 Subject: [PATCH 03/10] fix: type fix --- .../devsupport/components/falsyNode/falsyNode.component.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx index fe8445597..3c191528f 100644 --- a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx +++ b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx @@ -1,11 +1,12 @@ import React from "react"; +import { StyleType } from '../../../theme'; export type RenderProp = (props?: Props) => React.ReactElement; export type FalsyNodeProps = Props & { fallback?: React.ReactElement; children?: React.ReactElement; - style?: React.CSSProperties | React.CSSProperties[]; + style?: StyleType; }; /** From 8066fc3d601a4ab44a5c5543cc10e64f05f8658b Mon Sep 17 00:00:00 2001 From: Ivan Vezhnavets Date: Thu, 25 Mar 2021 16:06:06 +0300 Subject: [PATCH 04/10] fix: extend source props --- .../components/falsyNode/falsyNode.component.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx index 3c191528f..e4815dd8c 100644 --- a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx +++ b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx @@ -41,23 +41,24 @@ type ChildrenProp = ChildElement | ChildElement[]; export class FalsyNode extends React.Component> { - private renderChildElement = (source: ChildElement): ChildElement => { + private renderChildElement = (source: ChildElement, props: Props): ChildElement => { return React.cloneElement(source, { + ...props, style: [this.props?.style, source.props.style], }); }; - private renderComponentChildren = (source: ChildrenProp): ChildElement[] => { - return React.Children.map(source, this.renderChildElement); + private renderComponentChildren = (source: ChildrenProp, props: Props): ChildElement[] => { + return React.Children.map(source, child => this.renderChildElement(child, props)); }; public render(): React.ReactElement { - const { children, fallback } = this.props; + const { children, fallback, ...props } = this.props; if (!children) { return fallback || null; } - return <>{this.renderComponentChildren(children)}; + return <>{this.renderComponentChildren(children, props)}; } } From cdfbf914146d05ec565b2fa7d8e9c3c43bbb6b9c Mon Sep 17 00:00:00 2001 From: Ivan Vezhnavets Date: Thu, 25 Mar 2021 16:08:12 +0300 Subject: [PATCH 05/10] fix: type fix --- .../devsupport/components/falsyNode/falsyNode.component.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx index e4815dd8c..7e3ca1fec 100644 --- a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx +++ b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx @@ -41,14 +41,14 @@ type ChildrenProp = ChildElement | ChildElement[]; export class FalsyNode extends React.Component> { - private renderChildElement = (source: ChildElement, props: Props): ChildElement => { + private renderChildElement = (source: ChildElement, props: any): ChildElement => { return React.cloneElement(source, { ...props, style: [this.props?.style, source.props.style], }); }; - private renderComponentChildren = (source: ChildrenProp, props: Props): ChildElement[] => { + private renderComponentChildren = (source: ChildrenProp, props: any): ChildElement[] => { return React.Children.map(source, child => this.renderChildElement(child, props)); }; From 957c0917c8780cd692574d53d8d6fa32f190b14c Mon Sep 17 00:00:00 2001 From: Ivan Vezhnavets Date: Thu, 25 Mar 2021 16:20:00 +0300 Subject: [PATCH 06/10] fix: dont miss source props --- .../devsupport/components/falsyNode/falsyNode.component.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx index 7e3ca1fec..a4a66c473 100644 --- a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx +++ b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx @@ -44,6 +44,7 @@ export class FalsyNode extends React.Component private renderChildElement = (source: ChildElement, props: any): ChildElement => { return React.cloneElement(source, { ...props, + ...source.props, style: [this.props?.style, source.props.style], }); }; From bd3f78ec7c47f17df68912cdb8582b7005952229 Mon Sep 17 00:00:00 2001 From: Ivan Vezhnavets Date: Mon, 29 Mar 2021 22:13:43 +0300 Subject: [PATCH 07/10] feat(falsyNode): add tests, update falsyNode logic, create merge method --- .vscode/launch.json | 21 ++++++ .../falsyNode/falsyNode.component.tsx | 17 +++-- .../components/falsyNode/falsyNode.spec.tsx | 71 +++++++++++++++++++ .../services/props/props.service.ts | 17 +++++ .../devsupport/services/props/props.spec.ts | 11 +++ src/components/ui/card/card.component.tsx | 7 +- 6 files changed, 132 insertions(+), 12 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 src/components/devsupport/components/falsyNode/falsyNode.spec.tsx diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..58bc917e4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "pwa-node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/src/components/devsupport/components/falsyNode/falsyNode.spec.tsx", + "preLaunchTask": "tsc: build - tsconfig.json", + "outFiles": [ + "${workspaceFolder}/dist/tsc-out/**/*.js" + ] + } + ] +} \ No newline at end of file diff --git a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx index a4a66c473..4d579a4a5 100644 --- a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx +++ b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx @@ -1,11 +1,11 @@ import React from "react"; +import { PropsService } from '../../services/props/props.service'; import { StyleType } from '../../../theme'; export type RenderProp = (props?: Props) => React.ReactElement; export type FalsyNodeProps = Props & { - fallback?: React.ReactElement; - children?: React.ReactElement; + component?: React.ReactElement; style?: StyleType; }; @@ -17,7 +17,7 @@ export type FalsyNodeProps = Props & { * * If it is a React Element, will call it with props passed to this component. * - * @property {RenderProp} children - React jsx component to be rendered. + * @property {RenderProp} component - React jsx component to be rendered. * @property {React.ReactElement} fallback - Element to render if children is null or undefined. * * @example Will render nothing. @@ -40,12 +40,11 @@ type ChildElement = React.ReactElement; type ChildrenProp = ChildElement | ChildElement[]; export class FalsyNode extends React.Component> { - private renderChildElement = (source: ChildElement, props: any): ChildElement => { return React.cloneElement(source, { ...props, ...source.props, - style: [this.props?.style, source.props.style], + style: PropsService.mergeStyles([this.props?.style, source.props?.style]), }); }; @@ -54,12 +53,12 @@ export class FalsyNode extends React.Component }; public render(): React.ReactElement { - const { children, fallback, ...props } = this.props; + const { component, ...props } = this.props; - if (!children) { - return fallback || null; + if (!component) { + return null; } - return <>{this.renderComponentChildren(children, props)}; + return <>{this.renderComponentChildren(component, props)}; } } diff --git a/src/components/devsupport/components/falsyNode/falsyNode.spec.tsx b/src/components/devsupport/components/falsyNode/falsyNode.spec.tsx new file mode 100644 index 000000000..e1f693777 --- /dev/null +++ b/src/components/devsupport/components/falsyNode/falsyNode.spec.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import { Text } from "react-native"; +import { fireEvent, render } from "react-native-testing-library"; +import { FalsyNode } from "./falsyNode.component"; + +it("should render nothing", function () { + const component = render(); + expect(component.toJSON()).toEqual(null); +}); + +it("should render provided React Element", () => { + const component = render( + I love Babel} /> + ); + + const textComponent = component.getByText("I love Babel"); + + expect(textComponent).toBeTruthy(); + expect(textComponent.props.style).toEqual({ + color: "red", + }); +}); + +it("should render provided React Element with overwritten styles", () => { + const renderComponent = I love Babel; + + const component = render( + + ); + + const textComponent = component.getByText("I love Babel"); + + expect(textComponent).toBeTruthy(); + expect(textComponent.props.style).toEqual({ + color: "blue", + backgroundColor: "black", + }); +}); + +it('should keep props passed in FalsyNode component', function () { + const onPress = jest.fn(); + + const component = render( + I love Babel} + />, + ); + + fireEvent.press(component.queryByText('I love Babel')); + expect(onPress).toBeCalledTimes(1); +}); + +it('should override props passed in FalsyNode component', function () { + const onPress = jest.fn(); + const onInnerPress = jest.fn(); + + const component = render( + I love Babel} + />, + ); + + fireEvent.press(component.queryByText('I love Babel')); + expect(onPress).toBeCalledTimes(0); + expect(onInnerPress).toBeCalledTimes(1); +}); diff --git a/src/components/devsupport/services/props/props.service.ts b/src/components/devsupport/services/props/props.service.ts index fbb546d38..23d0af0b8 100644 --- a/src/components/devsupport/services/props/props.service.ts +++ b/src/components/devsupport/services/props/props.service.ts @@ -102,6 +102,8 @@ export interface RestProps { export type AllOfProps = Partial; export type AllWithRestProps = Partial & RestProps; +type StylesToMap = StyleType[] | [StyleType[]]; + class NativePropsService { /** * Retrieves all props included in `from` array @@ -159,6 +161,21 @@ class NativePropsService { }; }, {}); } + + /** + * Merge styles passed to array parameter. + * + * @param {Partial} styles - array styles which need to be merged. + * + * @return {StyleType} - merged object with styles inside. + */ + public mergeStyles = (styles: Partial): StyleType => { + return Object.assign({}, + ...styles.map(currentStyle => + Array.isArray(currentStyle) + ? Object.assign({}, ...currentStyle.map(style => style)) + : currentStyle)); + }; } export const PropsService = new NativePropsService(); diff --git a/src/components/devsupport/services/props/props.spec.ts b/src/components/devsupport/services/props/props.spec.ts index ed8428765..ca2d84f39 100644 --- a/src/components/devsupport/services/props/props.spec.ts +++ b/src/components/devsupport/services/props/props.spec.ts @@ -51,4 +51,15 @@ describe('@props: service checks', () => { }); }); + it('should merge styles in appropriate style object', () => { + const stylesArray = [{color: 'blue'}, {backgroundColor: 'black'}]; + const styles = {color: 'red'}; + + const mergedStyles = PropsService.mergeStyles([stylesArray, styles]); + expect(mergedStyles).toEqual({ + color: 'red', + backgroundColor: 'black', + }) + }); + }); diff --git a/src/components/ui/card/card.component.tsx b/src/components/ui/card/card.component.tsx index 8e5e73f7e..c9e9b3004 100644 --- a/src/components/ui/card/card.component.tsx +++ b/src/components/ui/card/card.component.tsx @@ -151,9 +151,10 @@ export class Card extends React.Component { component={header} /> {header && } - - {children} - + {footer && } Date: Mon, 29 Mar 2021 22:19:16 +0300 Subject: [PATCH 08/10] update: rename propsService method to standalone method --- .../falsyNode/falsyNode.component.tsx | 2 +- .../devsupport/services/props/props.service.ts | 18 +++++++++--------- .../devsupport/services/props/props.spec.ts | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx index 4d579a4a5..608990a3a 100644 --- a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx +++ b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx @@ -44,7 +44,7 @@ export class FalsyNode extends React.Component return React.cloneElement(source, { ...props, ...source.props, - style: PropsService.mergeStyles([this.props?.style, source.props?.style]), + style: PropsService.mergeObjectsWithArrays([this.props?.style, source.props?.style]), }); }; diff --git a/src/components/devsupport/services/props/props.service.ts b/src/components/devsupport/services/props/props.service.ts index 23d0af0b8..a10aabcdd 100644 --- a/src/components/devsupport/services/props/props.service.ts +++ b/src/components/devsupport/services/props/props.service.ts @@ -102,7 +102,7 @@ export interface RestProps { export type AllOfProps = Partial; export type AllWithRestProps = Partial & RestProps; -type StylesToMap = StyleType[] | [StyleType[]]; +type ObjectsToMerge = Object[] | [Object[]]; class NativePropsService { /** @@ -163,18 +163,18 @@ class NativePropsService { } /** - * Merge styles passed to array parameter. + * Merge objects & array of objects passed to array parameter. * - * @param {Partial} styles - array styles which need to be merged. + * @param {Partial} objects - array which needs to be merged. * - * @return {StyleType} - merged object with styles inside. + * @return {Object} - merged object with merged values inside. */ - public mergeStyles = (styles: Partial): StyleType => { + public mergeObjectsWithArrays = (objects: Partial): Object => { return Object.assign({}, - ...styles.map(currentStyle => - Array.isArray(currentStyle) - ? Object.assign({}, ...currentStyle.map(style => style)) - : currentStyle)); + ...objects.map(currentObject => + Array.isArray(currentObject) + ? Object.assign({}, ...currentObject.map(obj => obj)) + : currentObject)); }; } diff --git a/src/components/devsupport/services/props/props.spec.ts b/src/components/devsupport/services/props/props.spec.ts index ca2d84f39..16c1e6ea6 100644 --- a/src/components/devsupport/services/props/props.spec.ts +++ b/src/components/devsupport/services/props/props.spec.ts @@ -51,11 +51,11 @@ describe('@props: service checks', () => { }); }); - it('should merge styles in appropriate style object', () => { + it('should merge object & array of objects in appropriate object', () => { const stylesArray = [{color: 'blue'}, {backgroundColor: 'black'}]; const styles = {color: 'red'}; - const mergedStyles = PropsService.mergeStyles([stylesArray, styles]); + const mergedStyles = PropsService.mergeObjectsWithArrays([stylesArray, styles]); expect(mergedStyles).toEqual({ color: 'red', backgroundColor: 'black', From 518396d7a2cbc0138d48990cb3c63b51185a7b4a Mon Sep 17 00:00:00 2001 From: Ivan Date: Mon, 29 Mar 2021 22:20:03 +0300 Subject: [PATCH 09/10] Delete launch.json --- .vscode/launch.json | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 58bc917e4..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "pwa-node", - "request": "launch", - "name": "Launch Program", - "skipFiles": [ - "/**" - ], - "program": "${workspaceFolder}/src/components/devsupport/components/falsyNode/falsyNode.spec.tsx", - "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": [ - "${workspaceFolder}/dist/tsc-out/**/*.js" - ] - } - ] -} \ No newline at end of file From fbf4fdf5becd71f1461c5b0f95de56ed59a90733 Mon Sep 17 00:00:00 2001 From: Ivan Vezhnavets Date: Tue, 30 Mar 2021 22:36:22 +0300 Subject: [PATCH 10/10] update: update types --- .../components/falsyNode/falsyNode.component.tsx | 11 +++++------ src/components/ui/card/card.component.tsx | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx index 608990a3a..09143cf15 100644 --- a/src/components/devsupport/components/falsyNode/falsyNode.component.tsx +++ b/src/components/devsupport/components/falsyNode/falsyNode.component.tsx @@ -2,10 +2,10 @@ import React from "react"; import { PropsService } from '../../services/props/props.service'; import { StyleType } from '../../../theme'; -export type RenderProp = (props?: Props) => React.ReactElement; +export type RenderComponent = React.ReactElement | React.ReactElement[]; export type FalsyNodeProps = Props & { - component?: React.ReactElement; + component?: RenderComponent; style?: StyleType; }; @@ -13,12 +13,11 @@ export type FalsyNodeProps = Props & { * Helper component for optional properties that should render cloned component. * * Accepts props of a component that is expected to be rendered, - * and `children` which may be React Element only. + * and `component` which may be React Element only. * * If it is a React Element, will call it with props passed to this component. * - * @property {RenderProp} component - React jsx component to be rendered. - * @property {React.ReactElement} fallback - Element to render if children is null or undefined. + * @property {RenderComponent} component - React jsx component to be rendered. * * @example Will render nothing. * ``` @@ -30,7 +29,7 @@ export type FalsyNodeProps = Props & { * const Title = () => ( * Title} + * component={Title} * /> * ); * ``` diff --git a/src/components/ui/card/card.component.tsx b/src/components/ui/card/card.component.tsx index c9e9b3004..92897e7ba 100644 --- a/src/components/ui/card/card.component.tsx +++ b/src/components/ui/card/card.component.tsx @@ -34,7 +34,7 @@ type CardStyledProps = Overwrite; export interface CardProps extends TouchableWebProps, CardStyledProps { - children?: React.ReactElement; + children?: React.ReactElement | React.ReactElement[]; header?: RenderProp; footer?: RenderProp; accent?: RenderProp;