From 19a397acf80399ddd109ca337c49e5edce2e5142 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 24 Aug 2020 22:45:00 -0700 Subject: [PATCH 01/12] feat #65 - Form Component Closes 65 --- package-lock.json | 13 +- package.json | 3 +- src/__snapshots__/storybook.test.ts.snap | 162 +++++++++++++----- src/components/Button/Button.stories.tsx | 3 + src/components/Button/Button.test.tsx | 24 +++ src/components/Button/index.tsx | 24 ++- src/components/Form/FieldContext.ts | 10 ++ .../Form/FieldLabel/FieldLabel.test.tsx | 42 +++++ src/components/Form/FieldLabel/index.tsx | 46 +++++ src/components/Form/Form.stories.tsx | 19 ++ src/components/Form/Form.test.tsx | 57 ++++++ .../Form/FormButton/FormButton.test.tsx | 46 +++++ src/components/Form/FormButton/index.tsx | 22 +++ .../Form/FormInput/FormInput.test.tsx | 52 ++++++ src/components/Form/FormInput/index.tsx | 58 +++++++ src/components/Form/index.tsx | 57 ++++++ .../Input.stories.tsx} | 8 +- .../Input.test.tsx} | 70 +++----- .../{InputField => Input}/index.tsx | 51 +----- src/components/Link/Link.test.tsx | 6 + src/components/index.ts | 3 +- 21 files changed, 626 insertions(+), 150 deletions(-) create mode 100644 src/components/Form/FieldContext.ts create mode 100644 src/components/Form/FieldLabel/FieldLabel.test.tsx create mode 100644 src/components/Form/FieldLabel/index.tsx create mode 100644 src/components/Form/Form.stories.tsx create mode 100644 src/components/Form/Form.test.tsx create mode 100644 src/components/Form/FormButton/FormButton.test.tsx create mode 100644 src/components/Form/FormButton/index.tsx create mode 100644 src/components/Form/FormInput/FormInput.test.tsx create mode 100644 src/components/Form/FormInput/index.tsx create mode 100644 src/components/Form/index.tsx rename src/components/{InputField/InputField.stories.tsx => Input/Input.stories.tsx} (74%) rename src/components/{InputField/InputField.test.tsx => Input/Input.test.tsx} (50%) rename src/components/{InputField => Input}/index.tsx (70%) diff --git a/package-lock.json b/package-lock.json index bac03f3e..84b08449 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@dassana-io/web-components", - "version": "0.2.3", + "version": "0.2.4", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -12610,9 +12610,9 @@ } }, "eslint-plugin-react": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.5.tgz", - "integrity": "sha512-ajbJfHuFnpVNJjhyrfq+pH1C0gLc2y94OiCbAXT5O0J0YCKaFEHDV8+3+mDOr+w8WguRX+vSs1bM2BDG0VLvCw==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz", + "integrity": "sha512-kidMTE5HAEBSLu23CUDvj8dc3LdBU0ri1scwHBZjI41oDv4tjsWZKU7MQccFzH1QYPYhsnTF2ovh7JlcIcmxgg==", "dev": true, "requires": { "array-includes": "^3.1.1", @@ -20675,6 +20675,11 @@ "shallowequal": "^1.1.0" } }, + "react-hook-form": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.5.0.tgz", + "integrity": "sha512-DRziWoDvmwNIwBk6tkBN/fPeCgbtYHr1tIlVVd0ihmQg9Fv+gjOs0BU0D3o7HrKDeKwDA8pnp4tuEwxe8qg9TA==" + }, "react-hotkeys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz", diff --git a/package.json b/package.json index 98011794..509e58fe 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "classnames": "^2.2.6", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-hook-form": "^6.5.0", "react-jss": "^10.4.0", "react-scripts": "^3.4.3", "typescript": "^3.9.7" @@ -77,7 +78,7 @@ "chromatic": "^5.1.0", "enzyme": "^3.11.0", "enzyme-adapter-react-16": "^1.15.2", - "eslint-plugin-react": "^7.20.5", + "eslint-plugin-react": "^7.20.6", "normalize.css": "^8.0.1", "react-test-renderer": "^16.13.1", "rollup": "^2.26.4", diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap index 7be44637..ba8b8020 100644 --- a/src/__snapshots__/storybook.test.ts.snap +++ b/src/__snapshots__/storybook.test.ts.snap @@ -59,6 +59,43 @@ exports[`Storyshots Button Google 1`] = ` `; +exports[`Storyshots Button Loading 1`] = ` + +`; + exports[`Storyshots Button Primary 1`] = ` + + +`; + exports[`Storyshots Icon Custom 1`] = ` https://dummyimage.com/600x400/000/fff&text=Dassana `; -exports[`Storyshots InputField Default 1`] = ` +exports[`Storyshots Input Default 1`] = `
-
- Field Label -
`; -exports[`Storyshots InputField Error 1`] = ` +exports[`Storyshots Input Error 1`] = `
-
- Field Label -
`; -exports[`Storyshots InputField Full Width 1`] = ` +exports[`Storyshots Input Full Width 1`] = `
-
- Field Label -
`; -exports[`Storyshots InputField Loading 1`] = ` +exports[`Storyshots Input Loading 1`] = `
- -   - -
-
  @@ -231,7 +309,7 @@ exports[`Storyshots Link Href 1`] = ` exports[`Storyshots Skeleton Circle 1`] = `   @@ -240,27 +318,27 @@ exports[`Storyshots Skeleton Circle 1`] = ` exports[`Storyshots Skeleton Count 1`] = ` Array [   ,   ,   ,   ,   , @@ -269,7 +347,7 @@ Array [ exports[`Storyshots Skeleton Default 1`] = `   diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx index 7807b52d..e3874322 100644 --- a/src/components/Button/Button.stories.tsx +++ b/src/components/Button/Button.stories.tsx @@ -25,6 +25,9 @@ Default.args = { children: 'Default' } export const Disabled = Template.bind({}) Disabled.args = { children: 'Disabled', disabled: true } +export const Loading = Template.bind({}) +Loading.args = { children: 'Loading', loading: true } + export const Primary = Template.bind({}) Primary.args = { children: 'Primary', primary: true } diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx index db8f2994..9d59b77e 100644 --- a/src/components/Button/Button.test.tsx +++ b/src/components/Button/Button.test.tsx @@ -1,5 +1,6 @@ import { Button as AntDButton } from 'antd' import React from 'react' +import Skeleton from '../Skeleton' import Button, { ButtonProps } from '.' import { shallow, ShallowWrapper } from 'enzyme' @@ -35,6 +36,18 @@ describe('Button', () => { }) }) +describe('rendering', () => { + it('renders a skeleton', () => { + wrapper = shallow( + + ) + + expect(wrapper.find(Skeleton)).toHaveLength(1) + }) +}) + describe('Disabled Button', () => { it('has correct prop "disabled" and correct class "disabled"', () => { wrapper = shallow( @@ -45,3 +58,14 @@ describe('Disabled Button', () => { expect(wrapper.props().disabled).toBeTruthy() }) }) + +describe('Loading Button', () => { + it('has correct prop "loading"', () => { + wrapper = shallow( + + ) + expect(wrapper.props().loading).toBeTruthy() + }) +}) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index badeeae6..c1bfce25 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -2,6 +2,7 @@ import 'antd/lib/button/style/index.css' import { Button as AntDButton } from 'antd' import { ButtonProps as AntDButtonProps } from 'antd/es/button' import classnames from 'classnames' +import Skeleton from '../Skeleton' import React, { FC, ReactNode } from 'react' export interface ButtonProps { @@ -21,6 +22,14 @@ export interface ButtonProps { * Adds the disabled attribute and styles (opacity, gray scale filter, no pointer events). */ disabled?: boolean + /** + * Renders an animated loading icon next to the children. + */ + loading?: boolean + /** + * Renders a skeleton for the button + */ + rendering?: boolean /** * Array of classes to pass to button. */ @@ -29,19 +38,26 @@ export interface ButtonProps { const Button: FC = ({ children, - onClick, - primary = false, + classes = [], disabled = false, - classes = [] + loading = false, + onClick, + rendering = false, + primary = false }: ButtonProps) => { const antDProps: AntDButtonProps = { className: classnames(classes), disabled, + loading, onClick, type: primary ? 'primary' : 'default' } - return {children} + return rendering ? ( + + ) : ( + {children} + ) } export default Button diff --git a/src/components/Form/FieldContext.ts b/src/components/Form/FieldContext.ts new file mode 100644 index 00000000..ae3e7178 --- /dev/null +++ b/src/components/Form/FieldContext.ts @@ -0,0 +1,10 @@ +import { createContext } from 'react' + +interface FieldContextProps { + loading: boolean + onSubmit: (data: any) => void +} + +const FieldContext = createContext({} as FieldContextProps) + +export default FieldContext diff --git a/src/components/Form/FieldLabel/FieldLabel.test.tsx b/src/components/Form/FieldLabel/FieldLabel.test.tsx new file mode 100644 index 00000000..171caa7d --- /dev/null +++ b/src/components/Form/FieldLabel/FieldLabel.test.tsx @@ -0,0 +1,42 @@ +import React from 'react' +import Skeleton from '../../Skeleton' +import FieldLabel, { FieldLabelProps } from './index' +import { mount, shallow, ShallowWrapper } from 'enzyme' + +let wrapper: ShallowWrapper + +const MOCK_LABEL = 'Field Label' + +const baseMockProps = { + label: MOCK_LABEL +} + +beforeEach(() => { + wrapper = shallow() +}) + +describe('Field Label', () => { + it('renders', () => { + expect(wrapper).toHaveLength(1) + }) + + it('correctly renders the label', () => { + expect(wrapper.text()).toContain(MOCK_LABEL) + }) + + it('applies the correct class if required is passed as true', () => { + const fieldLabel = mount() + + expect(fieldLabel.getDOMNode().classList.toString()).toContain( + 'required' + ) + }) + + describe('loading', () => { + it('renders a skeleton if loading prop is passed as true', () => { + wrapper = shallow() + + expect(wrapper.find(Skeleton)).toHaveLength(1) + }) + }) +}) diff --git a/src/components/Form/FieldLabel/index.tsx b/src/components/Form/FieldLabel/index.tsx new file mode 100644 index 00000000..5d0b5d88 --- /dev/null +++ b/src/components/Form/FieldLabel/index.tsx @@ -0,0 +1,46 @@ +import cn from 'classnames' +import { createUseStyles } from 'react-jss' +import Skeleton from '../../Skeleton' +import React, { FC } from 'react' + +const useStyles = createUseStyles({ + container: { + fontSize: '14px', + paddingBottom: 5 + }, + required: { + '&::after': { + color: 'red', + // eslint-disable-next-line quotes + content: "'*'", + paddingLeft: '5px' + } + } +}) + +export interface FieldLabelProps { + label: string + loading?: boolean + required?: boolean +} + +const FieldLabel: FC = ({ + label, + loading = false, + required = false +}: FieldLabelProps) => { + const classes = useStyles() + + return ( +
+ {loading ? : label} +
+ ) +} + +export default FieldLabel diff --git a/src/components/Form/Form.stories.tsx b/src/components/Form/Form.stories.tsx new file mode 100644 index 00000000..d3ae4352 --- /dev/null +++ b/src/components/Form/Form.stories.tsx @@ -0,0 +1,19 @@ +import { action } from '@storybook/addon-actions' +import Form from './index' +import React from 'react' +import { Meta, Story } from '@storybook/react/types-6-0' + +export default { + component: Form, + title: 'Form' +} as Meta + +const Template: Story = args => ( +
+ + + + +) + +export const Default = Template.bind({}) diff --git a/src/components/Form/Form.test.tsx b/src/components/Form/Form.test.tsx new file mode 100644 index 00000000..0d580ce3 --- /dev/null +++ b/src/components/Form/Form.test.tsx @@ -0,0 +1,57 @@ +import FieldContext from './FieldContext' +import FormButton from './FormButton' +import React from 'react' +import Form, { FormProps } from './index' +import { mount, ReactWrapper, shallow, ShallowWrapper } from 'enzyme' + +jest.mock('react-hook-form', () => ({ + ...jest.requireActual('react-hook-form'), + useForm: () => ({ + handleSubmit: jest.fn(), + reset: mockReset + }) +})) + +let wrapper: ReactWrapper | ShallowWrapper + +const mockOnSubmit = jest.fn() +const mockReset = jest.fn() + +beforeEach(() => { + wrapper = shallow( +
+ + + ) +}) + +describe('Form', () => { + it('renders', () => { + expect(wrapper).toHaveLength(1) + }) + + it('renders children properly', () => { + expect(wrapper.find(FormButton)).toHaveLength(1) + }) + + it('passes the correct props to FieldContext provider', () => { + expect(wrapper.find(FieldContext.Provider).props().value).toMatchObject( + { + loading: false, + onSubmit: mockOnSubmit + } + ) + }) + + it('sets initial values into the form', () => { + const mockInitialValues = { foo: 'bar' } + wrapper = mount( +
+ + + ) + + expect(mockReset).toHaveBeenCalledTimes(1) + expect(mockReset).toHaveBeenCalledWith(mockInitialValues) + }) +}) diff --git a/src/components/Form/FormButton/FormButton.test.tsx b/src/components/Form/FormButton/FormButton.test.tsx new file mode 100644 index 00000000..663ad5db --- /dev/null +++ b/src/components/Form/FormButton/FormButton.test.tsx @@ -0,0 +1,46 @@ +import Button from '../../Button' +import FieldContext from '../FieldContext' +import React from 'react' +import FormButton, { FormButtonProps } from './index' +import { mount, ReactWrapper } from 'enzyme' + +jest.mock('react-hook-form', () => ({ + ...jest.requireActual('react-hook-form'), + useFormContext: () => ({ + handleSubmit: (onSubmit: Function) => onSubmit() + }) +})) + +let wrapper: ReactWrapper + +const mockOnSubmit = jest.fn() + +beforeEach(() => { + wrapper = mount( + + + + ) +}) + +afterEach(() => { + jest.resetAllMocks() +}) + +describe('FormButton', () => { + it('renders', () => { + expect(wrapper).toHaveLength(1) + }) + + it('calls onSubmit when clicked', () => { + wrapper.simulate('click') + + expect(mockOnSubmit).toHaveBeenCalledTimes(1) + }) + + it('correctly passes loading from field context', () => { + expect(wrapper.find(Button).props().rendering).toBe(true) + }) +}) diff --git a/src/components/Form/FormButton/index.tsx b/src/components/Form/FormButton/index.tsx new file mode 100644 index 00000000..84ea501c --- /dev/null +++ b/src/components/Form/FormButton/index.tsx @@ -0,0 +1,22 @@ +import FieldContext from '../FieldContext' +import { useFormContext } from 'react-hook-form' +import Button, { ButtonProps } from '../../Button' +import React, { FC, useContext } from 'react' + +export type FormButtonProps = Omit< + ButtonProps, + 'rendering' | 'onClick' | 'children' +> + +const FormButton: FC = (props: FormButtonProps) => { + const { handleSubmit } = useFormContext() + const { loading, onSubmit } = useContext(FieldContext) + + return ( + + ) +} + +export default FormButton diff --git a/src/components/Form/FormInput/FormInput.test.tsx b/src/components/Form/FormInput/FormInput.test.tsx new file mode 100644 index 00000000..a7e0072d --- /dev/null +++ b/src/components/Form/FormInput/FormInput.test.tsx @@ -0,0 +1,52 @@ +import { Controller } from 'react-hook-form' +import FieldContext from '../FieldContext' +import React from 'react' +import FormInput, { FormInputProps } from './index' +import { mount, ReactWrapper } from 'enzyme' + +jest.mock('react-hook-form', () => ({ + ...jest.requireActual('react-hook-form'), + Controller: () =>
, + useFormContext: () => ({ + control: jest.fn(), + handleSubmit: (onSubmit: Function) => onSubmit() + }) +})) + +let wrapper: ReactWrapper + +const mockOnSubmit = jest.fn() + +beforeEach(() => { + wrapper = mount( + + + + ) +}) + +afterEach(() => { + jest.resetAllMocks() +}) + +describe('FormInput', () => { + it('renders', () => { + expect(wrapper).toHaveLength(1) + }) + + it('correctly passes validation rules if required', () => { + wrapper = mount( + + + + ) + + expect(wrapper.find(Controller).props().rules).toMatchObject({ + required: true + }) + }) +}) diff --git a/src/components/Form/FormInput/index.tsx b/src/components/Form/FormInput/index.tsx new file mode 100644 index 00000000..282caafb --- /dev/null +++ b/src/components/Form/FormInput/index.tsx @@ -0,0 +1,58 @@ +import FieldContext from '../FieldContext' +import FieldLabel from '../FieldLabel' +import { Controller, useFormContext, ValidationRules } from 'react-hook-form' +import Input, { InputProps } from '../../Input' +import React, { FC, useContext } from 'react' + +interface BaseFieldProps { + label?: string + name: string + required?: boolean + rules?: ValidationRules +} + +export interface FormInputProps extends BaseFieldProps, InputProps {} + +const FormInput: FC = ({ + label, + name, + required, + rules = {}, + ...rest +}: FormInputProps) => { + const { control, errors } = useFormContext() + const { loading } = useContext(FieldContext) + + if (required) { + rules.required = true + } + + return ( +
+ {label && ( + + )} + ( + + )} + rules={rules} + /> +
+ ) +} + +export default FormInput diff --git a/src/components/Form/index.tsx b/src/components/Form/index.tsx new file mode 100644 index 00000000..31635edf --- /dev/null +++ b/src/components/Form/index.tsx @@ -0,0 +1,57 @@ +import { createUseStyles } from 'react-jss' +import FieldContext from './FieldContext' +import FormButton from './FormButton' +import FormInput, { FormInputProps } from './FormInput' +import { FormProvider, useForm } from 'react-hook-form' +import React, { FC, ReactNode, useEffect } from 'react' + +const useStyles = createUseStyles({ + container: { + '& >div:not(:last-child)': { + paddingBottom: 15 + } + } +}) + +export interface FormProps { + children: ReactNode + initialValues?: object + loading?: boolean + onSubmit: (data: any) => void +} + +interface FormSubComponents { + Button: FC + Input: FC +} + +const Form: FC & FormSubComponents = ({ + children, + initialValues = {}, + loading = false, + onSubmit +}: FormProps) => { + const classes = useStyles() + const methods = useForm() + + const { handleSubmit, reset } = methods + + useEffect(() => { + reset(initialValues) + }, [initialValues]) + + return ( + +
+ +
{children}
+
+
+
+ ) +} + +Form.Button = FormButton +Form.Input = FormInput + +export default Form diff --git a/src/components/InputField/InputField.stories.tsx b/src/components/Input/Input.stories.tsx similarity index 74% rename from src/components/InputField/InputField.stories.tsx rename to src/components/Input/Input.stories.tsx index 0e1e1078..7d37be79 100644 --- a/src/components/InputField/InputField.stories.tsx +++ b/src/components/Input/Input.stories.tsx @@ -1,5 +1,5 @@ import React from 'react' -import InputField, { InputFieldProps } from './index' +import Input, { InputProps } from './index' import { Meta, Story } from '@storybook/react/types-6-0' export default { @@ -8,11 +8,11 @@ export default { fieldLabel: { defaultValue: 'Field Label' }, value: { control: { disable: true } } }, - component: InputField, - title: 'InputField' + component: Input, + title: 'Input' } as Meta -const Template: Story = args => +const Template: Story = args => export const Default = Template.bind({}) diff --git a/src/components/InputField/InputField.test.tsx b/src/components/Input/Input.test.tsx similarity index 50% rename from src/components/InputField/InputField.test.tsx rename to src/components/Input/Input.test.tsx index a6c70961..d72fe041 100644 --- a/src/components/InputField/InputField.test.tsx +++ b/src/components/Input/Input.test.tsx @@ -1,80 +1,64 @@ -import { Input } from 'antd' +import { Input as AntDInput } from 'antd' +import Input from './index' import React from 'react' import Skeleton from '../Skeleton' -import InputField, { InputFieldProps } from './index' import { mount, ReactWrapper, shallow } from 'enzyme' let wrapper: ReactWrapper -const mockProps: InputFieldProps = { - fieldLabel: 'Field Label' -} - beforeEach(() => { - wrapper = mount() + wrapper = mount() }) -describe('InputField', () => { +describe('Input', () => { it('renders', () => { - const inputField = wrapper.find(InputField) - - expect(inputField).toHaveLength(1) - }) + const input = wrapper.find(Input) - it('renders a label if one exists', () => { - const inputField = wrapper.find(InputField) - - expect(inputField.text()).toContain('Field Label') + expect(input).toHaveLength(1) }) it('throws an error if value is passed without an onClick', () => { - expect(() => shallow()).toThrow() + expect(() => shallow()).toThrow() }) it('should pass onChange and value to the input component if the props exist', () => { const mockOnChange = jest.fn() - wrapper = mount() + wrapper = mount() - expect(wrapper.find(Input).props().onChange).toEqual(mockOnChange) - expect(wrapper.find(Input).props().value).toEqual('abc') + expect(wrapper.find(AntDInput).props().onChange).toEqual(mockOnChange) + expect(wrapper.find(AntDInput).props().value).toEqual('abc') }) it('correctly passes the disabled prop', () => { - wrapper = mount() + wrapper = mount() - expect(wrapper.find(Input).props().disabled).toBeTruthy() + expect(wrapper.find(AntDInput).props().disabled).toBeTruthy() }) it('correctly passes the placeholder prop', () => { - wrapper = mount() + wrapper = mount() - expect(wrapper.find(Input).props().placeholder).toEqual('Testing') + expect(wrapper.find(AntDInput).props().placeholder).toEqual('Testing') }) describe('type', () => { it('defaults to type text if no type is specified', () => { - expect(wrapper.find(Input).props().type).toEqual('text') + expect(wrapper.find(AntDInput).props().type).toEqual('text') }) it('correctly passes the input type prop', () => { - wrapper = mount() + wrapper = mount() - expect(wrapper.find(Input).props().type).toEqual('password') + expect(wrapper.find(AntDInput).props().type).toEqual('password') }) }) describe('loading', () => { it('renders a loading skeleton', () => { - wrapper = mount() + wrapper = mount() expect(wrapper.find(Skeleton)).toHaveLength(1) }) - - it('renders a loading skeleton for the label if there is one', () => { - wrapper = mount() - - expect(wrapper.find(Skeleton)).toHaveLength(2) - }) }) describe('fullWidth', () => { @@ -94,7 +78,7 @@ describe('InputField', () => { }) it('renders a container that will span the width of its parent container if set to true', () => { - wrapper = mount(, { + wrapper = mount(, { attachTo: document.getElementById('container') }) @@ -104,7 +88,7 @@ describe('InputField', () => { }) it('does not render a container that will span the width of its parent container by default', () => { - wrapper = mount(, { + wrapper = mount(, { attachTo: document.getElementById('container') }) @@ -114,21 +98,11 @@ describe('InputField', () => { }) }) - describe('required', () => { - it('passes the correct required class to field label container', () => { - wrapper = mount() - - expect(wrapper.getDOMNode().children[0].className).toContain( - 'required' - ) - }) - }) - describe('error', () => { it('passes the correct error class to input', () => { - wrapper = mount() + wrapper = mount() - expect(wrapper.find(Input).hasClass(/error-*/)).toBeTruthy() + expect(wrapper.find(AntDInput).hasClass(/error-*/)).toBeTruthy() }) }) }) diff --git a/src/components/InputField/index.tsx b/src/components/Input/index.tsx similarity index 70% rename from src/components/InputField/index.tsx rename to src/components/Input/index.tsx index 6da0bdba..3c123914 100644 --- a/src/components/InputField/index.tsx +++ b/src/components/Input/index.tsx @@ -14,8 +14,6 @@ const useStyles = createUseStyles({ } }, container: { - display: 'flex', - flexDirection: 'column', width: props => (props.fullWidth ? '100%' : '300px') }, error: { @@ -25,41 +23,23 @@ const useStyles = createUseStyles({ input: { border: '1px solid #DEDEDF', borderRadius: '4px', - padding: '8.5px 14px' - }, - label: { - fontSize: '14px', - paddingBottom: '5px' - }, - required: { - '&::after': { - color: 'red', - // eslint-disable-next-line quotes - content: "'*'", - paddingLeft: '5px' - } + padding: '6px 14px' } }) -const InputFieldSkeleton: FC = (props: InputFieldProps) => { - const { fieldLabel } = props +const InputFieldSkeleton: FC = (props: InputProps) => { const classes = useStyles(props) return (
- {fieldLabel && ( -
- -
- )}
- +
) } -export interface InputFieldProps { +export interface InputProps { /** * Array of classes to pass to input * @default [] @@ -75,10 +55,6 @@ export interface InputFieldProps { * @default false */ error?: boolean - /** - * Adds a label above the input - */ - fieldLabel?: string /** * Whether or not input spans the full width of the parent container * @default false @@ -98,11 +74,6 @@ export interface InputFieldProps { * Describes expected value of input */ placeholder?: string - /** - * Adds an asterisk to the label that indicates field is required - * @default false - */ - required?: boolean /** * Type of input (ex: text, password) * @default text @@ -114,16 +85,14 @@ export interface InputFieldProps { value?: string } -const InputField: FC = (props: InputFieldProps) => { +const InputField: FC = (props: InputProps) => { const { classes = [], disabled = false, onChange, error = false, - fieldLabel = '', loading = false, placeholder = '', - required = false, type = 'text', value } = props @@ -154,16 +123,6 @@ const InputField: FC = (props: InputFieldProps) => { ) : (
- {fieldLabel && ( -
- {fieldLabel} -
- )} { mockProps.target ) }) + + it('has a default target of _self if none is passed in', () => { + wrapper = mount(Test) + + expect(wrapper.getDOMNode().getAttribute('target')).toBe('_self') + }) }) diff --git a/src/components/index.ts b/src/components/index.ts index be529c46..9ec6464c 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,5 +1,6 @@ export { default as Button } from './Button' -export { default as InputField } from './InputField' +export { default as Form } from './Form' +export { default as Input } from './Input' export { default as Icon } from './Icon' export { default as Link } from './Link' export { default as Skeleton } from './Skeleton' From a7969c7198ed994d5268a980456de7a595bf7b64 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 25 Aug 2020 10:57:14 -0700 Subject: [PATCH 02/12] feat #65 - Update hooks for form and Input exports --- src/components/Form/index.tsx | 6 +++--- src/components/Input/index.tsx | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Form/index.tsx b/src/components/Form/index.tsx index 31635edf..72501f23 100644 --- a/src/components/Form/index.tsx +++ b/src/components/Form/index.tsx @@ -1,6 +1,6 @@ import { createUseStyles } from 'react-jss' import FieldContext from './FieldContext' -import FormButton from './FormButton' +import FormButton, { FormButtonProps } from './FormButton' import FormInput, { FormInputProps } from './FormInput' import { FormProvider, useForm } from 'react-hook-form' import React, { FC, ReactNode, useEffect } from 'react' @@ -21,7 +21,7 @@ export interface FormProps { } interface FormSubComponents { - Button: FC + Button: FC Input: FC } @@ -38,7 +38,7 @@ const Form: FC & FormSubComponents = ({ useEffect(() => { reset(initialValues) - }, [initialValues]) + }, [initialValues, reset]) return ( diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index 3c123914..b3333d7a 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -1,7 +1,7 @@ import 'antd/lib/input/style/index.css' +import { Input as AntDInput } from 'antd' import cn from 'classnames' import { createUseStyles } from 'react-jss' -import { Input } from 'antd' import Skeleton from '../Skeleton' import React, { FC } from 'react' @@ -85,7 +85,7 @@ export interface InputProps { value?: string } -const InputField: FC = (props: InputProps) => { +const Input: FC = (props: InputProps) => { const { classes = [], disabled = false, @@ -123,7 +123,7 @@ const InputField: FC = (props: InputProps) => { ) : (
- = (props: InputProps) => { ) } -export default InputField +export default Input From 1b83e44b17f7dc7ca063bde303a7e64da8e31d34 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 25 Aug 2020 11:04:01 -0700 Subject: [PATCH 03/12] feat #65 - Add argTypes to fix Form stories --- src/components/Form/Form.stories.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/Form/Form.stories.tsx b/src/components/Form/Form.stories.tsx index d3ae4352..a8497af9 100644 --- a/src/components/Form/Form.stories.tsx +++ b/src/components/Form/Form.stories.tsx @@ -4,6 +4,12 @@ import React from 'react' import { Meta, Story } from '@storybook/react/types-6-0' export default { + argTypes: { + initialValues: { + control: 'object', + defaultValue: { firstName: 'First Name' } + } + }, component: Form, title: 'Form' } as Meta From 6196914dbcb3188d9db01ac57141f10697133492 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 25 Aug 2020 12:20:16 -0700 Subject: [PATCH 04/12] feat #65 - Fix infinite calls with react hooks --- src/components/Form/FieldContext.ts | 3 ++- src/components/Form/FormInput/index.tsx | 10 +++++++--- src/components/Form/index.tsx | 14 ++++++-------- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/Form/FieldContext.ts b/src/components/Form/FieldContext.ts index ae3e7178..e56bb792 100644 --- a/src/components/Form/FieldContext.ts +++ b/src/components/Form/FieldContext.ts @@ -1,6 +1,7 @@ import { createContext } from 'react' -interface FieldContextProps { +export interface FieldContextProps { + initialValues: Record loading: boolean onSubmit: (data: any) => void } diff --git a/src/components/Form/FormInput/index.tsx b/src/components/Form/FormInput/index.tsx index 282caafb..2433a789 100644 --- a/src/components/Form/FormInput/index.tsx +++ b/src/components/Form/FormInput/index.tsx @@ -1,6 +1,6 @@ -import FieldContext from '../FieldContext' import FieldLabel from '../FieldLabel' import { Controller, useFormContext, ValidationRules } from 'react-hook-form' +import FieldContext, { FieldContextProps } from '../FieldContext' import Input, { InputProps } from '../../Input' import React, { FC, useContext } from 'react' @@ -21,12 +21,16 @@ const FormInput: FC = ({ ...rest }: FormInputProps) => { const { control, errors } = useFormContext() - const { loading } = useContext(FieldContext) + const { initialValues, loading } = useContext( + FieldContext + ) if (required) { rules.required = true } + const defaultValue = (initialValues[name] as string) || '' + return (
{label && ( @@ -38,7 +42,7 @@ const FormInput: FC = ({ )} ( loading?: boolean onSubmit: (data: any) => void } @@ -34,16 +34,14 @@ const Form: FC & FormSubComponents = ({ const classes = useStyles() const methods = useForm() - const { handleSubmit, reset } = methods - - useEffect(() => { - reset(initialValues) - }, [initialValues, reset]) + const { handleSubmit } = methods return (
- +
{children}
From 76626f8933710db7161e7d89ef7565e71475a65d Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 25 Aug 2020 18:54:09 -0700 Subject: [PATCH 05/12] feat #65 - Update Typescript types --- src/components/Form/FieldContext.ts | 6 ++++-- src/components/Form/Form.stories.tsx | 12 ++++++++---- src/components/Form/index.tsx | 26 +++++++++++--------------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/components/Form/FieldContext.ts b/src/components/Form/FieldContext.ts index e56bb792..11674868 100644 --- a/src/components/Form/FieldContext.ts +++ b/src/components/Form/FieldContext.ts @@ -1,9 +1,11 @@ import { createContext } from 'react' +import { FieldValues } from 'react-hook-form/dist/types/form' +import { SubmitHandler } from 'react-hook-form' export interface FieldContextProps { - initialValues: Record + initialValues: FieldValues loading: boolean - onSubmit: (data: any) => void + onSubmit: SubmitHandler } const FieldContext = createContext({} as FieldContextProps) diff --git a/src/components/Form/Form.stories.tsx b/src/components/Form/Form.stories.tsx index a8497af9..c3eb7160 100644 --- a/src/components/Form/Form.stories.tsx +++ b/src/components/Form/Form.stories.tsx @@ -1,6 +1,5 @@ -import { action } from '@storybook/addon-actions' -import Form from './index' import React from 'react' +import Form, { FormProps } from './index' import { Meta, Story } from '@storybook/react/types-6-0' export default { @@ -14,8 +13,13 @@ export default { title: 'Form' } as Meta -const Template: Story = args => ( -
+interface UserModel { + firstName: string + lastName?: string +} + +const Template: Story> = (args: FormProps) => ( + diff --git a/src/components/Form/index.tsx b/src/components/Form/index.tsx index f43e9ca3..e12ff161 100644 --- a/src/components/Form/index.tsx +++ b/src/components/Form/index.tsx @@ -1,9 +1,10 @@ import { createUseStyles } from 'react-jss' import FieldContext from './FieldContext' -import FormButton, { FormButtonProps } from './FormButton' -import FormInput, { FormInputProps } from './FormInput' -import { FormProvider, useForm } from 'react-hook-form' -import React, { FC, ReactNode } from 'react' +import { FieldValues } from 'react-hook-form/dist/types/form' +import FormButton from './FormButton' +import FormInput from './FormInput' +import { FormProvider, SubmitHandler, useForm } from 'react-hook-form' +import React, { ReactNode } from 'react' const useStyles = createUseStyles({ container: { @@ -13,24 +14,19 @@ const useStyles = createUseStyles({ } }) -export interface FormProps { +export interface FormProps { children: ReactNode - initialValues?: Record + initialValues?: Model loading?: boolean - onSubmit: (data: any) => void + onSubmit: SubmitHandler } -interface FormSubComponents { - Button: FC - Input: FC -} - -const Form: FC & FormSubComponents = ({ +function Form({ children, - initialValues = {}, + initialValues = {} as Model, loading = false, onSubmit -}: FormProps) => { +}: FormProps) { const classes = useStyles() const methods = useForm() From 395c879490a994510b9c12c88a3c849a8b426b8a Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 25 Aug 2020 20:31:51 -0700 Subject: [PATCH 06/12] feat #65 - Update failing tests --- src/__snapshots__/storybook.test.ts.snap | 40 +++++++++---------- src/components/Form/Form.test.tsx | 28 ++++++++----- .../Form/FormInput/FormInput.test.tsx | 18 +++++++-- src/components/Form/FormInput/index.tsx | 4 +- 4 files changed, 53 insertions(+), 37 deletions(-) diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap index ba8b8020..306c43fb 100644 --- a/src/__snapshots__/storybook.test.ts.snap +++ b/src/__snapshots__/storybook.test.ts.snap @@ -61,35 +61,31 @@ exports[`Storyshots Button Google 1`] = ` exports[`Storyshots Button Loading 1`] = `
Loading @@ -147,7 +143,7 @@ exports[`Storyshots Form Default 1`] = ` onKeyDown={[Function]} placeholder="" type="text" - value="" + value="First Name" />
diff --git a/src/components/Form/Form.test.tsx b/src/components/Form/Form.test.tsx index 0d580ce3..9f95e34a 100644 --- a/src/components/Form/Form.test.tsx +++ b/src/components/Form/Form.test.tsx @@ -7,19 +7,24 @@ import { mount, ReactWrapper, shallow, ShallowWrapper } from 'enzyme' jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), useForm: () => ({ - handleSubmit: jest.fn(), - reset: mockReset + handleSubmit: jest.fn() }) })) -let wrapper: ReactWrapper | ShallowWrapper +interface MockForm { + foo: string +} +let wrapper: + | ReactWrapper> + | ShallowWrapper> + +const mockInitialValues = { foo: 'bar' } const mockOnSubmit = jest.fn() -const mockReset = jest.fn() beforeEach(() => { wrapper = shallow( - + ) @@ -37,21 +42,22 @@ describe('Form', () => { it('passes the correct props to FieldContext provider', () => { expect(wrapper.find(FieldContext.Provider).props().value).toMatchObject( { + initialValues: mockInitialValues, loading: false, onSubmit: mockOnSubmit } ) }) - it('sets initial values into the form', () => { - const mockInitialValues = { foo: 'bar' } - wrapper = mount( -
+ it('correctly defaults initial values if there are none', () => { + wrapper = shallow( + ) - expect(mockReset).toHaveBeenCalledTimes(1) - expect(mockReset).toHaveBeenCalledWith(mockInitialValues) + expect( + wrapper.find(FieldContext.Provider).props().value + ).toMatchObject({ initialValues: {} }) }) }) diff --git a/src/components/Form/FormInput/FormInput.test.tsx b/src/components/Form/FormInput/FormInput.test.tsx index a7e0072d..dbffac6f 100644 --- a/src/components/Form/FormInput/FormInput.test.tsx +++ b/src/components/Form/FormInput/FormInput.test.tsx @@ -9,7 +9,7 @@ jest.mock('react-hook-form', () => ({ Controller: () =>
, useFormContext: () => ({ control: jest.fn(), - handleSubmit: (onSubmit: Function) => onSubmit() + errors: () => ({ foo: true }) }) })) @@ -20,7 +20,11 @@ const mockOnSubmit = jest.fn() beforeEach(() => { wrapper = mount( @@ -36,10 +40,18 @@ describe('FormInput', () => { expect(wrapper).toHaveLength(1) }) + it('correctly passes a default value from initial values if it exists', () => { + expect(wrapper.find(Controller).props().defaultValue).toEqual('bar') + }) + it('correctly passes validation rules if required', () => { wrapper = mount( diff --git a/src/components/Form/FormInput/index.tsx b/src/components/Form/FormInput/index.tsx index 2433a789..2c8ab90a 100644 --- a/src/components/Form/FormInput/index.tsx +++ b/src/components/Form/FormInput/index.tsx @@ -11,7 +11,9 @@ interface BaseFieldProps { rules?: ValidationRules } -export interface FormInputProps extends BaseFieldProps, InputProps {} +export interface FormInputProps + extends BaseFieldProps, + Omit {} const FormInput: FC = ({ label, From dc2ce5743e5d9a83b351e70a01a54f638e1b7e5e Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 25 Aug 2020 21:10:55 -0700 Subject: [PATCH 07/12] feat #65 - Abstract out loading state from button --- package-lock.json | 6 +-- package.json | 1 + src/__snapshots__/storybook.test.ts.snap | 50 +++++++++++++++--------- src/components/Button/Button.test.tsx | 4 +- src/components/Button/index.tsx | 18 +++++++-- src/components/Form/Form.stories.tsx | 8 ++-- src/components/Form/index.tsx | 8 +++- src/components/Input/index.tsx | 4 +- 8 files changed, 63 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 84b08449..bd1c5123 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16722,9 +16722,9 @@ } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "lodash._reinterpolate": { "version": "3.0.0", diff --git a/package.json b/package.json index 509e58fe..7c6c44cc 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "directory": "@dassana-io/web-components" }, "dependencies": { + "@ant-design/icons": "^4.2.2", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap index 306c43fb..45e1e6db 100644 --- a/src/__snapshots__/storybook.test.ts.snap +++ b/src/__snapshots__/storybook.test.ts.snap @@ -66,26 +66,38 @@ exports[`Storyshots Button Loading 1`] = ` onClick={[Function]} type="button" > -
- +
- - - - - -
+ + + +
+ Loading diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx index 9d59b77e..63f14b39 100644 --- a/src/components/Button/Button.test.tsx +++ b/src/components/Button/Button.test.tsx @@ -1,4 +1,4 @@ -import { Button as AntDButton } from 'antd' +import { Button as AntDButton, Spin } from 'antd' import React from 'react' import Skeleton from '../Skeleton' import Button, { ButtonProps } from '.' @@ -66,6 +66,6 @@ describe('Loading Button', () => { Test ) - expect(wrapper.props().loading).toBeTruthy() + expect(wrapper.find(Spin)).toHaveLength(1) }) }) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index c1bfce25..8aeda0ed 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -1,8 +1,10 @@ import 'antd/lib/button/style/index.css' -import { Button as AntDButton } from 'antd' +import 'antd/lib/spin/style/index.css' import { ButtonProps as AntDButtonProps } from 'antd/es/button' import classnames from 'classnames' +import { LoadingOutlined } from '@ant-design/icons' import Skeleton from '../Skeleton' +import { Button as AntDButton, Spin } from 'antd' import React, { FC, ReactNode } from 'react' export interface ButtonProps { @@ -48,7 +50,6 @@ const Button: FC = ({ const antDProps: AntDButtonProps = { className: classnames(classes), disabled, - loading, onClick, type: primary ? 'primary' : 'default' } @@ -56,7 +57,18 @@ const Button: FC = ({ return rendering ? ( ) : ( - {children} + + {loading && ( + + + } + /> + + )} + {children} + ) } diff --git a/src/components/Form/Form.stories.tsx b/src/components/Form/Form.stories.tsx index c3eb7160..e3cf4fcf 100644 --- a/src/components/Form/Form.stories.tsx +++ b/src/components/Form/Form.stories.tsx @@ -4,10 +4,8 @@ import { Meta, Story } from '@storybook/react/types-6-0' export default { argTypes: { - initialValues: { - control: 'object', - defaultValue: { firstName: 'First Name' } - } + initialValues: { control: { disable: true } }, + onSubmit: { control: { disable: true } } }, component: Form, title: 'Form' @@ -19,7 +17,7 @@ interface UserModel { } const Template: Story> = (args: FormProps) => ( -
+ diff --git a/src/components/Form/index.tsx b/src/components/Form/index.tsx index e12ff161..300de4df 100644 --- a/src/components/Form/index.tsx +++ b/src/components/Form/index.tsx @@ -4,7 +4,7 @@ import { FieldValues } from 'react-hook-form/dist/types/form' import FormButton from './FormButton' import FormInput from './FormInput' import { FormProvider, SubmitHandler, useForm } from 'react-hook-form' -import React, { ReactNode } from 'react' +import React, { ReactNode, useEffect } from 'react' const useStyles = createUseStyles({ container: { @@ -30,7 +30,11 @@ function Form({ const classes = useStyles() const methods = useForm() - const { handleSubmit } = methods + const { handleSubmit, reset } = methods + + useEffect(() => { + reset(initialValues) + }, [initialValues, reset]) return ( diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx index b3333d7a..94a99a48 100644 --- a/src/components/Input/index.tsx +++ b/src/components/Input/index.tsx @@ -27,7 +27,7 @@ const useStyles = createUseStyles({ } }) -const InputFieldSkeleton: FC = (props: InputProps) => { +const InputSkeleton: FC = (props: InputProps) => { const classes = useStyles(props) return ( @@ -120,7 +120,7 @@ const Input: FC = (props: InputProps) => { } return loading ? ( - + ) : (
Date: Wed, 26 Aug 2020 12:49:10 -0700 Subject: [PATCH 08/12] feat #65 - Fix lint errors --- src/__snapshots__/storybook.test.ts.snap | 117 +++++++++++++++++++++-- src/components/Button/Button.test.tsx | 2 +- src/components/Form/Form.test.tsx | 2 +- 3 files changed, 110 insertions(+), 11 deletions(-) diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap index 45e1e6db..6ae865d7 100644 --- a/src/__snapshots__/storybook.test.ts.snap +++ b/src/__snapshots__/storybook.test.ts.snap @@ -66,7 +66,13 @@ exports[`Storyshots Button Loading 1`] = ` onClick={[Function]} type="button" > - +
@@ -77,7 +83,6 @@ exports[`Storyshots Button Loading 1`] = ` style={ Object { "fontSize": 16, - "paddingRight": 8, } } > @@ -281,6 +286,100 @@ exports[`Storyshots Input Loading 1`] = `
`; +exports[`Storyshots InputField Default 1`] = ` +
+
+ Field Label +
+ +
+`; + +exports[`Storyshots InputField Error 1`] = ` +
+
+ Field Label +
+ +
+`; + +exports[`Storyshots InputField Full Width 1`] = ` +
+
+ Field Label +
+ +
+`; + +exports[`Storyshots InputField Loading 1`] = ` +
+
+ +   + +
+
+ +   + +
+
+`; + exports[`Storyshots Link Click 1`] = `  
@@ -326,27 +425,27 @@ exports[`Storyshots Skeleton Circle 1`] = ` exports[`Storyshots Skeleton Count 1`] = ` Array [   ,   ,   ,   ,   , @@ -355,7 +454,7 @@ Array [ exports[`Storyshots Skeleton Default 1`] = `   diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx index 63f14b39..e9bec8bc 100644 --- a/src/components/Button/Button.test.tsx +++ b/src/components/Button/Button.test.tsx @@ -1,6 +1,6 @@ -import { Button as AntDButton, Spin } from 'antd' import React from 'react' import Skeleton from '../Skeleton' +import { Button as AntDButton, Spin } from 'antd' import Button, { ButtonProps } from '.' import { shallow, ShallowWrapper } from 'enzyme' diff --git a/src/components/Form/Form.test.tsx b/src/components/Form/Form.test.tsx index 9f95e34a..bdb3dac3 100644 --- a/src/components/Form/Form.test.tsx +++ b/src/components/Form/Form.test.tsx @@ -2,7 +2,7 @@ import FieldContext from './FieldContext' import FormButton from './FormButton' import React from 'react' import Form, { FormProps } from './index' -import { mount, ReactWrapper, shallow, ShallowWrapper } from 'enzyme' +import { ReactWrapper, shallow, ShallowWrapper } from 'enzyme' jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), From 1b04e9c464fc2515915a3ff334b1d1c584dc824d Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 26 Aug 2020 12:54:54 -0700 Subject: [PATCH 09/12] feat #65 - Update failing snapshots --- src/__snapshots__/storybook.test.ts.snap | 108 ++--------------------- 1 file changed, 7 insertions(+), 101 deletions(-) diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap index 6ae865d7..9be68867 100644 --- a/src/__snapshots__/storybook.test.ts.snap +++ b/src/__snapshots__/storybook.test.ts.snap @@ -286,100 +286,6 @@ exports[`Storyshots Input Loading 1`] = `
`; -exports[`Storyshots InputField Default 1`] = ` -
-
- Field Label -
- -
-`; - -exports[`Storyshots InputField Error 1`] = ` -
-
- Field Label -
- -
-`; - -exports[`Storyshots InputField Full Width 1`] = ` -
-
- Field Label -
- -
-`; - -exports[`Storyshots InputField Loading 1`] = ` -
-
- -   - -
-
- -   - -
-
-`; - exports[`Storyshots Link Click 1`] = `
  @@ -425,27 +331,27 @@ exports[`Storyshots Skeleton Circle 1`] = ` exports[`Storyshots Skeleton Count 1`] = ` Array [   ,   ,   ,   ,   , @@ -454,7 +360,7 @@ Array [ exports[`Storyshots Skeleton Default 1`] = `   From 10e182088edf65ed3f384b1e28bbbf778a0a76e0 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 26 Aug 2020 15:14:22 -0700 Subject: [PATCH 10/12] feat #65 - Update button prop to be consistent and update tests --- src/__snapshots__/storybook.test.ts.snap | 6 +++--- src/components/Button/Button.stories.tsx | 4 ++-- src/components/Button/Button.test.tsx | 17 ++++++++++++----- src/components/Button/index.tsx | 14 +++++++------- src/components/Form/Form.test.tsx | 8 +++----- .../Form/FormButton/FormButton.test.tsx | 4 ++-- src/components/Form/FormButton/index.tsx | 4 ++-- 7 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap index 9be68867..e7ae0895 100644 --- a/src/__snapshots__/storybook.test.ts.snap +++ b/src/__snapshots__/storybook.test.ts.snap @@ -59,10 +59,10 @@ exports[`Storyshots Button Google 1`] = ` `; -exports[`Storyshots Button Loading 1`] = ` +exports[`Storyshots Button Pending 1`] = `
- Loading + Pending `; diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/Button.stories.tsx index e3874322..fd497f0f 100644 --- a/src/components/Button/Button.stories.tsx +++ b/src/components/Button/Button.stories.tsx @@ -25,8 +25,8 @@ Default.args = { children: 'Default' } export const Disabled = Template.bind({}) Disabled.args = { children: 'Disabled', disabled: true } -export const Loading = Template.bind({}) -Loading.args = { children: 'Loading', loading: true } +export const Pending = Template.bind({}) +Pending.args = { children: 'Pending', pending: true } export const Primary = Template.bind({}) Primary.args = { children: 'Primary', primary: true } diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx index e9bec8bc..65beb21d 100644 --- a/src/components/Button/Button.test.tsx +++ b/src/components/Button/Button.test.tsx @@ -36,10 +36,10 @@ describe('Button', () => { }) }) -describe('rendering', () => { +describe('loading', () => { it('renders a skeleton', () => { wrapper = shallow( - ) @@ -59,13 +59,20 @@ describe('Disabled Button', () => { }) }) -describe('Loading Button', () => { - it('has correct prop "loading"', () => { +describe('Pending Button', () => { + beforeEach(() => { wrapper = shallow( - ) + }) + + it('renders a Spin component', () => { expect(wrapper.find(Spin)).toHaveLength(1) }) + + it('automatically disables the button', () => { + expect(wrapper.find(AntDButton).props().disabled).toBeTruthy() + }) }) diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 8aeda0ed..2c2c93dd 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -25,13 +25,13 @@ export interface ButtonProps { */ disabled?: boolean /** - * Renders an animated loading icon next to the children. + * Renders a skeleton for the button */ loading?: boolean /** - * Renders a skeleton for the button + * Renders an animated loading icon next to the children. */ - rendering?: boolean + pending?: boolean /** * Array of classes to pass to button. */ @@ -44,21 +44,21 @@ const Button: FC = ({ disabled = false, loading = false, onClick, - rendering = false, + pending = false, primary = false }: ButtonProps) => { const antDProps: AntDButtonProps = { className: classnames(classes), - disabled, + disabled: pending || disabled, onClick, type: primary ? 'primary' : 'default' } - return rendering ? ( + return loading ? ( ) : ( - {loading && ( + {pending && ( ({ ...jest.requireActual('react-hook-form'), @@ -15,9 +15,7 @@ interface MockForm { foo: string } -let wrapper: - | ReactWrapper> - | ShallowWrapper> +let wrapper: ShallowWrapper> const mockInitialValues = { foo: 'bar' } const mockOnSubmit = jest.fn() @@ -49,7 +47,7 @@ describe('Form', () => { ) }) - it('correctly defaults initial values if there are none', () => { + it('correctly defaults initial values to empty object if none is passed in', () => { wrapper = shallow( diff --git a/src/components/Form/FormButton/FormButton.test.tsx b/src/components/Form/FormButton/FormButton.test.tsx index 663ad5db..9caa0d5e 100644 --- a/src/components/Form/FormButton/FormButton.test.tsx +++ b/src/components/Form/FormButton/FormButton.test.tsx @@ -18,7 +18,7 @@ const mockOnSubmit = jest.fn() beforeEach(() => { wrapper = mount( @@ -41,6 +41,6 @@ describe('FormButton', () => { }) it('correctly passes loading from field context', () => { - expect(wrapper.find(Button).props().rendering).toBe(true) + expect(wrapper.find(Button).props().loading).toBe(true) }) }) diff --git a/src/components/Form/FormButton/index.tsx b/src/components/Form/FormButton/index.tsx index 84ea501c..ca66baa2 100644 --- a/src/components/Form/FormButton/index.tsx +++ b/src/components/Form/FormButton/index.tsx @@ -5,7 +5,7 @@ import React, { FC, useContext } from 'react' export type FormButtonProps = Omit< ButtonProps, - 'rendering' | 'onClick' | 'children' + 'loading' | 'onClick' | 'children' > const FormButton: FC = (props: FormButtonProps) => { @@ -13,7 +13,7 @@ const FormButton: FC = (props: FormButtonProps) => { const { loading, onSubmit } = useContext(FieldContext) return ( - ) From 077964f1653d780d85451f43946ed9801b9db605 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 26 Aug 2020 17:27:29 -0700 Subject: [PATCH 11/12] feat #65 - Update form test, allow user to customize skeleton dimensions for label and button --- src/__snapshots__/storybook.test.ts.snap | 48 ++++++++++++++---------- src/components/Button/Button.stories.tsx | 3 ++ src/components/Button/Button.test.tsx | 2 +- src/components/Button/index.tsx | 16 ++++++-- src/components/Form/FieldLabel/index.tsx | 9 +++-- src/components/Form/Form.test.tsx | 18 ++++++++- src/components/Form/FormInput/index.tsx | 3 ++ src/components/assets/styleguide.ts | 1 + 8 files changed, 71 insertions(+), 29 deletions(-) diff --git a/src/__snapshots__/storybook.test.ts.snap b/src/__snapshots__/storybook.test.ts.snap index e7ae0895..85a1a102 100644 --- a/src/__snapshots__/storybook.test.ts.snap +++ b/src/__snapshots__/storybook.test.ts.snap @@ -28,7 +28,7 @@ exports[`Storyshots Button Disabled 1`] = ` exports[`Storyshots Button Google 1`] = `
) } diff --git a/src/components/Form/Form.test.tsx b/src/components/Form/Form.test.tsx index c3b8b553..d9fdb0b0 100644 --- a/src/components/Form/Form.test.tsx +++ b/src/components/Form/Form.test.tsx @@ -2,12 +2,13 @@ import FieldContext from './FieldContext' import FormButton from './FormButton' import React from 'react' import Form, { FormProps } from './index' -import { shallow, ShallowWrapper } from 'enzyme' +import { mount, shallow, ShallowWrapper } from 'enzyme' jest.mock('react-hook-form', () => ({ ...jest.requireActual('react-hook-form'), useForm: () => ({ - handleSubmit: jest.fn() + handleSubmit: jest.fn(), + reset: mockReset }) })) @@ -19,6 +20,7 @@ let wrapper: ShallowWrapper> const mockInitialValues = { foo: 'bar' } const mockOnSubmit = jest.fn() +const mockReset = jest.fn() beforeEach(() => { wrapper = shallow( @@ -58,4 +60,16 @@ describe('Form', () => { wrapper.find(FieldContext.Provider).props().value ).toMatchObject({ initialValues: {} }) }) + + it('correctly updates initial values', () => { + const form = mount( + + + + ) + + form.setProps({ initialValues: mockInitialValues }) + + expect(mockReset).toHaveBeenCalledWith(mockInitialValues) + }) }) diff --git a/src/components/Form/FormInput/index.tsx b/src/components/Form/FormInput/index.tsx index 2c8ab90a..01a67727 100644 --- a/src/components/Form/FormInput/index.tsx +++ b/src/components/Form/FormInput/index.tsx @@ -6,6 +6,7 @@ import React, { FC, useContext } from 'react' interface BaseFieldProps { label?: string + labelSkeletonWidth?: number name: string required?: boolean rules?: ValidationRules @@ -17,6 +18,7 @@ export interface FormInputProps const FormInput: FC = ({ label, + labelSkeletonWidth, name, required, rules = {}, @@ -40,6 +42,7 @@ const FormInput: FC = ({ label={label} loading={loading} required={required} + skeletonWidth={labelSkeletonWidth} /> )} Date: Wed, 26 Aug 2020 21:20:52 -0700 Subject: [PATCH 12/12] feat #65 - Write tests for custom skeleton dimension props --- src/components/Button/Button.test.tsx | 26 +++++++++++++++++-- .../Form/FieldLabel/FieldLabel.test.tsx | 16 +++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/components/Button/Button.test.tsx b/src/components/Button/Button.test.tsx index 76d6c6bf..b36b190e 100644 --- a/src/components/Button/Button.test.tsx +++ b/src/components/Button/Button.test.tsx @@ -37,15 +37,37 @@ describe('Button', () => { }) describe('Loading Button', () => { - it('renders a skeleton', () => { + beforeEach(() => { wrapper = shallow( ) - + }) + it('renders a skeleton', () => { expect(wrapper.find(Skeleton)).toHaveLength(1) }) + + it('renders a skeleton with a default width of 75 and height of 32', () => { + expect(wrapper.find(Skeleton).props().height).toBe(32) + expect(wrapper.find(Skeleton).props().width).toBe(75) + }) + + it('renders a skeleton with the correct custom dimensions', () => { + wrapper = shallow( + + ) + + expect(wrapper.find(Skeleton).props().height).toBe(50) + expect(wrapper.find(Skeleton).props().width).toBe(200) + }) }) describe('Disabled Button', () => { diff --git a/src/components/Form/FieldLabel/FieldLabel.test.tsx b/src/components/Form/FieldLabel/FieldLabel.test.tsx index 171caa7d..91074ded 100644 --- a/src/components/Form/FieldLabel/FieldLabel.test.tsx +++ b/src/components/Form/FieldLabel/FieldLabel.test.tsx @@ -33,10 +33,24 @@ describe('Field Label', () => { }) describe('loading', () => { - it('renders a skeleton if loading prop is passed as true', () => { + beforeEach(() => { wrapper = shallow() + }) + it('renders a skeleton if loading prop is passed as true', () => { expect(wrapper.find(Skeleton)).toHaveLength(1) }) + + it('passes a default skeleton width of 100', () => { + expect(wrapper.find(Skeleton).props().width).toBe(100) + }) + + it('passes the correct width to Skeleton if one is provided', () => { + wrapper = shallow( + + ) + + expect(wrapper.find(Skeleton).props().width).toBe(300) + }) }) })