diff --git a/test/CreateForm.test.tsx b/test/CreateForm.test.tsx new file mode 100644 index 00000000..ebb33ad9 --- /dev/null +++ b/test/CreateForm.test.tsx @@ -0,0 +1,331 @@ +import React from 'react' +import each from 'jest-each' +import faker from 'faker' +import { createForm } from './../src/CreateForm' +import { CreateFormArgs } from '../src/Types' +import { renderHook } from '@testing-library/react-hooks' +import { waitFor, render, fireEvent } from '@testing-library/react' + +function makeSut(args: CreateFormArgs = {}, mode = 'onChange' as any) { + const state = {} + + const spy = jest.fn() + const useForm = createForm(args) + + const { result: sut } = renderHook(() => + useForm({ mode, onChange: spy, onBlur: spy, onSubmit: spy }) + ) + function Component() { + const form = sut.current + Object.assign(state, form) + + return ( +
+ + + + +
+ ) + } + + const element = render() + + return { + element, + spy, + sut + } +} + +function makeMockedValues() { + return { + name: faker.name.firstName(), + email: faker.internet.email(), + password: faker.internet.password() + } +} + +describe('CreateForm', () => { + each(['onChange', 'debounce']).it( + 'Should init the hook with the initial properties - [%s] mode', + mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + expect(form.sut.current.state.values).toEqual(initialValues) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run setFieldValue - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const newValue = faker.name.findName() + form.sut.current.setFieldValue('name', newValue) + + await waitFor(() => { + expect(form.sut.current.state.values.name).toEqual(newValue) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run setFieldError - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const newError = faker.name.findName() + form.sut.current.setFieldError('name', newError) + + await waitFor(() => { + expect(form.sut.current.state.errors.name).toEqual(newError) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run setFieldTouched - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + form.sut.current.setFieldTouched('name', true) + + await waitFor(() => { + expect(form.sut.current.state.touched.name).toEqual(true) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run setFieldsValue - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const newValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + form.sut.current.setFieldsValue(newValues) + + await waitFor(() => { + expect(form.sut.current.state.values).toEqual(newValues) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run setFieldsError - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const newErrors = makeMockedValues() + form.sut.current.setFieldsError(newErrors) + + await waitFor(() => { + expect(form.sut.current.state.errors).toEqual(newErrors) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run setFieldsTouched - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const newTouched = { + name: true, + email: true, + password: true + } + form.sut.current.setFieldsTouched(newTouched) + + await waitFor(() => { + expect(form.sut.current.state.touched).toEqual(newTouched) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run resetErrors - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const newErrors = makeMockedValues() + form.sut.current.setFieldsError(newErrors) + + await waitFor(() => { + expect(form.sut.current.state.errors).toEqual(newErrors) + }) + form.sut.current.resetErrors() + await waitFor(() => { + expect(form.sut.current.state.errors).toEqual({}) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run resetValues - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const newValues = makeMockedValues() + form.sut.current.setFieldsValue(newValues) + + await waitFor(() => { + expect(form.sut.current.state.values).toEqual(newValues) + }) + form.sut.current.resetValues() + await waitFor(() => { + expect(form.sut.current.state.values).toEqual(initialValues) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run resetTouched - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const newTouched = { + name: true, + email: true, + password: true + } + form.sut.current.setFieldsTouched(newTouched) + await waitFor(() => { + expect(form.sut.current.state.touched).toEqual(newTouched) + }) + + form.sut.current.resetTouched() + + await waitFor(() => { + expect(form.sut.current.state.touched).toEqual({}) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update the hook when run reset - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const newValues = makeMockedValues() + await waitFor(() => { + form.sut.current.setFieldsValue(newValues) + }) + form.sut.current.reset() + + await waitFor(() => { + expect(form.sut.current.state.values).toEqual(initialValues) + expect(form.sut.current.state.touched).toEqual({}) + expect(form.sut.current.state.errors).toEqual({}) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'SHould call handleSubmit function when run onSubmit - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const submitButton = form.element.getByTestId('submit') + + fireEvent.click(submitButton) + // since we aren't passing any validation we assume the form is valid + const submittedValues = [initialValues, true] + + await waitFor(() => { + expect(form.spy).toHaveBeenCalledWith(...submittedValues) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should call handleSubmit function when run onSubmit with errors - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const initialErrors = { + name: faker.name.findName() + } + + const form = makeSut({ initialValues, initialErrors }, mode) + const submitButton = form.element.getByTestId('submit') + + fireEvent.click(submitButton) + // since we are passing an error validation we assume the form is invalid + const submittedValues = [initialValues, false] + + await waitFor(() => { + expect(form.spy).toHaveBeenCalledWith(...submittedValues) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should call onChange function when any change event happens - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const input = form.element.getByTestId('name') + const nextValue = faker.name.findName() + const nextValues = { + ...initialValues, + name: nextValue + } + + fireEvent.input(input, { target: { value: nextValue } }) + + await waitFor(() => { + expect(form.spy).toHaveBeenCalledWith(nextValues) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should call onBlur function when any blur event happens - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const input = form.element.getByTestId('name') + fireEvent.blur(input) + + await waitFor(() => { + expect(form.spy).toHaveBeenCalledWith(initialValues) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update hook state when a change event happens - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const input = form.element.getByTestId('name') + const nextValue = faker.name.findName() + const nextValues = { + ...initialValues, + name: nextValue + } + + fireEvent.input(input, { target: { value: nextValue } }) + + await waitFor(() => { + expect(form.sut.current.state.values).toEqual(nextValues) + }) + } + ) + + each(['onChange', 'debounce']).it( + 'Should update hook state when a blur event happens - [%s] mode', + async mode => { + const initialValues = makeMockedValues() + const form = makeSut({ initialValues }, mode) + const input = form.element.getByTestId('name') + fireEvent.blur(input) + + await waitFor(() => { + expect(form.sut.current.state.touched).toEqual({ name: true }) + }) + } + ) +}) diff --git a/test/ObjectUtils.test.ts b/test/ObjectUtils.test.ts new file mode 100644 index 00000000..ee1efbb4 --- /dev/null +++ b/test/ObjectUtils.test.ts @@ -0,0 +1,61 @@ +import * as Dot from './../src/ObjectUtils' + +describe('Dot set', () => { + it('Should set a value', () => { + const obj = (value: string) => ({ foo: value }) + const newValue = 'baz' + const newObj = Dot.set(obj('bar'), 'foo', newValue) + expect(newObj).toEqual(obj(newValue)) + }) + + it('Should set a value in an array', () => { + const obj = (value: string) => ({ foo: [value] }) + const newValue = 'bar' + const newObj = Dot.set(obj('bar'), 'foo[0]', newValue) + expect(newObj).toEqual(obj(newValue)) + }) + + it('Should set a value in an array with a number', () => { + const newValue = 'baz' + const newObj = Dot.set({ foo: [] }, 'foo.1', newValue) + expect(newObj).toEqual({ foo: [undefined, newValue] }) + }) + + it('Should set a value in a nested object', () => { + const newValue = 'baz' + const newObj = Dot.set({ foo: { bar: 'bar' } }, 'foo.bar', newValue) + expect(newObj).toEqual({ foo: { bar: newValue } }) + }) +}) + +describe('Dot get', () => { + it('Should get a value', () => { + const obj = { foo: 'bar' } + expect(Dot.get(obj, 'foo')).toEqual('bar') + }) + + it('Should get a value in an array', () => { + const obj = { foo: ['bar'] } + expect(Dot.get(obj, 'foo[0]')).toEqual('bar') + }) + + it('Should get a value in an array with a number', () => { + const obj = { foo: [undefined, 'bar'] } + expect(Dot.get(obj, 'foo.1')).toEqual('bar') + }) + + it('Should get a value in a nested object', () => { + const obj = { foo: { bar: 'bar' } } + expect(Dot.get(obj, 'foo.bar')).toEqual('bar') + }) + + it('Should return undefined when property does not exist', () => { + const obj = { foo: 'bar' } + expect(Dot.get(obj, 'bar')).toEqual(undefined) + }) + + it('Should return undefined when property does not exist in an array', () => { + const obj = { foo: ['bar'] } + expect(Dot.get(obj, 'foo[1]')).toEqual(undefined) + }) +}) diff --git a/test/Store.test.ts b/test/Store.test.ts new file mode 100644 index 00000000..4268a03d --- /dev/null +++ b/test/Store.test.ts @@ -0,0 +1,66 @@ +import { createStore } from '../src/Store' + +function makeSut(state = {}) { + const spy = jest.fn() + const sut = createStore(state) + + return { + sut, + spy + } +} + +describe('Store', () => { + it('Should set initial state when createStore is called', () => { + const initialState = { + foo: 'bar' + } + const { sut } = makeSut(initialState) + expect(sut.get()).toEqual(initialState) + }) + + it('Should set a new state when set is called', () => { + const { sut } = makeSut() + const newState = { + foo: 'bar' + } + sut.set(newState) + expect(sut.get()).toEqual(newState) + }) + + it('Should call subscribers when set is called', () => { + const { sut, spy } = makeSut() + sut.subscribe(spy) + sut.set({ foo: 'bar' }) + expect(spy).toHaveBeenCalledWith({ foo: 'bar' }) + }) + + it('Should patch a state when patch is called', () => { + const { sut } = makeSut() + sut.patch('foo', 'bar') + expect(sut.get()).toEqual({ foo: 'bar' }) + }) + + it('Should call subscribers when patch is called', () => { + const { sut, spy } = makeSut() + sut.subscribe(spy) + sut.patch('foo', 'bar') + expect(spy).toHaveBeenCalledWith({ foo: 'bar' }) + }) + + it('Should get a property value when getPropertyValue is called', () => { + const { sut } = makeSut() + sut.patch('foo', 'bar') + expect(sut.getPropertyValue('foo')).toEqual('bar') + }) + + it('Should get an initial property value when getInitialPropertyValue is called', () => { + const { sut } = makeSut() + expect(sut.getInitialPropertyValue('foo')).toEqual(undefined) + }) + + it('Should get an initial state when getInitialState is called', () => { + const { sut } = makeSut() + expect(sut.getInitialState()).toEqual({}) + }) +}) diff --git a/test/Wrapper.test.tsx b/test/Wrapper.test.tsx new file mode 100644 index 00000000..9ecd4327 --- /dev/null +++ b/test/Wrapper.test.tsx @@ -0,0 +1,63 @@ +import React from 'react' +import { render, fireEvent, waitFor } from '@testing-library/react' +import faker from 'faker' +import { Wrapper } from '../src/Wrapper' +import { createForm } from '../src' +import { renderHook } from '@testing-library/react-hooks' + +function makeSut() { + const sut = render() + return { + sut + } +} + +function Component(props: any) { + function handleOnChange(e: React.ChangeEvent) { + props.onChange(e.target.value) + } + + return ( +
+ +
+ ) +} + +const value = faker.random.word() +const useForm = createForm({ + initialValues: { + name: value + } +}) + +function Setup() { + const form = useForm() + + return +} + +describe('Wrapper', () => { + it('Should render children with the value', () => { + const { sut } = makeSut() + const input = sut.getByTestId('name') as HTMLInputElement + expect(input.value).toBe(value) + }) + + it('Should set the custom input value', async () => { + const form = renderHook(() => useForm({ mode: 'onChange' })) + const { sut } = makeSut() + const input = sut.getByTestId('name') as HTMLInputElement + const nextValue = faker.random.word() + fireEvent.change(input, { target: { value: nextValue } }) + + await waitFor(() => { + expect(form.result.current.state.values.name).toBe(nextValue) + }) + }) +})