Skip to content

Instantly share code, notes, and snippets.

@thiagobutignon
Last active August 1, 2020 23:24
Show Gist options
  • Save thiagobutignon/fc3946c214e2571a355dedacd30b9e4e to your computer and use it in GitHub Desktop.
Save thiagobutignon/fc3946c214e2571a355dedacd30b9e4e to your computer and use it in GitHub Desktop.
import faker from 'faker'
import { AddAccount } from '@/domain/usecases'
import { mockAccountResultModel } from '@/domain/test'
export const mockAddAccountParams = (): AddAccount.Params => {
const password = faker.internet.password()
return {
fullName: faker.name.findName(),
username: faker.internet.userName(),
email: faker.internet.email(),
birthday: faker.date.past(10).toString(),
rg: faker.random.number(8).toString(),
cpf: faker.random.number(11).toString(),
address: {
zipcode: faker.address.zipCode(),
street: faker.address.streetName(),
number: faker.random.number().toString(),
complemento: faker.address.secondaryAddress(),
neighborhood: faker.address.streetName(),
city: faker.address.city(),
estado: faker.address.state()
},
password,
passwordConfirmation: password
}
}
export const mockAddAccountResultModel = (): AddAccount.ResultModel =>
mockAccountResultModel()
export class AddAccountSpy implements AddAccount {
account = mockAddAccountResultModel()
params: AddAccount.Params
callsCount = 0
async add(params: AddAccount.Params): Promise<AddAccount.ResultModel> {
this.params = params
this.callsCount++
return Promise.resolve(this.account)
}
}
import React from 'react'
import { createMemoryHistory } from 'history'
import faker from 'faker'
import { screen, render, fireEvent, waitFor } from '@testing-library/react'
import { ApiContext } from '@/presentation/contexts'
import SignUp from './signup'
import { Helper, ValidationStub } from '@/presentation/test'
import { EmailInUseError } from '@/domain/errors'
import { Router } from 'react-router-dom'
import { AddAccount } from '@/domain/usecases'
import { AddAccountSpy } from '@/domain/test'
type SutTypes = {
addAccountSpy: AddAccountSpy
setCurrentAccountResultModelMock: (account: AddAccount.ResultModel) => void
}
type SutParams = {
validationError: string
}
const history = createMemoryHistory({ initialEntries: ['/signup'] })
const makeSut = (params?: SutParams): SutTypes => {
const validationStub = new ValidationStub()
validationStub.errorMessage = params?.validationError
const addAccountSpy = new AddAccountSpy()
const setCurrentAccountResultModelMock = jest.fn()
render(
<ApiContext.Provider
value={{ setCurrentAccountResultModel: setCurrentAccountResultModelMock }}
>
<Router history={history}>
<SignUp validation={validationStub} addAccount={addAccountSpy} />
</Router>
</ApiContext.Provider>
)
return {
addAccountSpy,
setCurrentAccountResultModelMock
}
}
const simulateValidSubmit = async (
fullName = faker.name.findName(),
username = faker.internet.userName(),
birthday = faker.date.past(10).toString(),
rg = faker.random.number(8).toString(),
cpf = faker.random.number(11).toString(),
email = faker.internet.email(),
address = {
zipcode: faker.address.zipCode(),
street: faker.address.streetName(),
number: faker.random.number().toString(),
complemento: faker.address.secondaryAddress(),
neighborhood: faker.address.streetName(),
city: faker.address.city(),
estado: faker.address.state()
},
password = faker.internet.password()
): Promise<void> => {
Helper.populateField('fullName', fullName)
Helper.populateField('username', username)
Helper.populateField('birthday', birthday)
Helper.populateField('rg', rg)
Helper.populateField('cpf', cpf)
Helper.populateField('email', email)
Helper.populateField('zipcode', address.zipcode)
Helper.populateField('street', address.street)
Helper.populateField('number', address.number)
Helper.populateField('complemento', address.complemento)
Helper.populateField('neighborhood', address.neighborhood)
Helper.populateField('city', address.city)
Helper.populateField('estado', address.estado)
Helper.populateField('password', password)
Helper.populateField('passwordConfirmation', password)
const form = screen.getByTestId('form')
fireEvent.submit(form)
await waitFor(() => form)
}
const makeValidationFails = (field: string): void => {
const validationError = faker.random.words()
makeSut({ validationError })
Helper.populateField(field)
Helper.testStatusForField(field, validationError)
}
const makeValidationSucceds = (field: string): void => {
makeSut()
Helper.populateField(field)
Helper.testStatusForField(field)
}
describe('SignUp Component', () => {
test('Should start with initial state', () => {
const validationError = faker.random.words()
makeSut({ validationError })
expect(screen.getByTestId('error-wrap').children).toHaveLength(0)
expect(screen.getByTestId('submit')).toBeDisabled()
Helper.testStatusForField('fullName', validationError)
Helper.testStatusForField('username', validationError)
Helper.testStatusForField('birthday', validationError)
Helper.testStatusForField('rg', validationError)
Helper.testStatusForField('cpf', validationError)
Helper.testStatusForField('email', validationError)
Helper.testStatusForField('password', validationError)
Helper.testStatusForField('passwordConfirmation', validationError)
Helper.testStatusForField('zipcode', validationError)
Helper.testStatusForField('street', validationError)
Helper.testStatusForField('number', validationError)
Helper.testStatusForField('complemento', validationError)
Helper.testStatusForField('neighborhood', validationError)
Helper.testStatusForField('city', validationError)
Helper.testStatusForField('estado', validationError)
})
test('Should show name error if Validation fails', () => {
makeValidationFails('fullName')
})
test('Should show username error if Validation fails', () => {
makeValidationFails('username')
})
test('Should show birthday error if Validation fails', () => {
makeValidationFails('birthday')
})
test('Should show rg error if Validation fails', () => {
makeValidationFails('rg')
})
test('Should show cpf error if Validation fails', () => {
makeValidationFails('cpf')
})
test('Should show email error if Validation fails', () => {
makeValidationFails('email')
})
test('Should show password error if Validation fails', () => {
makeValidationFails('password')
})
test('Should show passwordConfirmation error if Validation fails', () => {
makeValidationFails('passwordConfirmation')
})
test('Should show zipcode error if Validation fails', () => {
makeValidationFails('zipcode')
})
test('Should show street error if Validation fails', () => {
makeValidationFails('street')
})
test('Should show complemento error if Validation fails', () => {
makeValidationFails('complemento')
})
test('Should show neighborhood error if Validation fails', () => {
makeValidationFails('neighborhood')
})
test('Should show city error if Validation fails', () => {
makeValidationFails('city')
})
test('Should show estado error if Validation fails', () => {
makeValidationFails('estado')
})
test('Should show valid fullName state if Validation succeeds', () => {
makeValidationSucceds('fullName')
})
test('Should show valid username state if Validation succeeds', () => {
makeValidationSucceds('username')
})
test('Should show valid birthday state if Validation succeeds', () => {
makeValidationSucceds('birthday')
})
test('Should show valid rg state if Validation succeeds', () => {
makeValidationSucceds('rg')
})
test('Should show valid cpf state if Validation succeeds', () => {
makeValidationSucceds('cpf')
})
test('Should show valid email state if Validation succeeds', () => {
makeValidationSucceds('email')
})
test('Should show valid password state if Validation succeeds', () => {
makeValidationSucceds('password')
})
test('Should show valid zipcode state if Validation succeeds', () => {
makeValidationSucceds('zipcode')
})
test('Should show valid street state if Validation succeeds', () => {
makeValidationSucceds('street')
})
test('Should show valid number state if Validation succeeds', () => {
makeValidationSucceds('number')
})
test('Should show valid complemento state if Validation succeeds', () => {
makeValidationSucceds('complemento')
})
test('Should show valid neighborhood state if Validation succeeds', () => {
makeValidationSucceds('neighborhood')
})
test('Should show valid city state if Validation succeeds', () => {
makeValidationSucceds('city')
})
test('Should show valid estado state if Validation succeeds', () => {
makeValidationSucceds('estado')
})
test('Should enable submit button if form is valid', () => {
makeSut()
Helper.populateField('fullName')
Helper.populateField('username')
Helper.populateField('birthday')
Helper.populateField('rg')
Helper.populateField('cpf')
Helper.populateField('email')
Helper.populateField('password')
Helper.populateField('passwordConfirmation')
Helper.populateField('zipcode')
Helper.populateField('street')
Helper.populateField('number')
Helper.populateField('complemento')
Helper.populateField('neighborhood')
Helper.populateField('city')
Helper.populateField('estado')
expect(screen.getByTestId('submit')).toBeEnabled()
})
test('Should show spinner on submit', async () => {
makeSut()
await simulateValidSubmit()
expect(screen.queryByTestId('spinner')).toBeInTheDocument()
})
test('Should call AddAccount with correct values', async () => {
const { addAccountSpy } = makeSut()
const fullName = faker.name.findName()
const username = faker.internet.userName()
const birthday = faker.date.past(10).toString()
const rg = faker.random.number(8).toString()
const cpf = faker.random.number(11).toString()
const email = faker.internet.email()
const address = {
zipcode: faker.address.zipCode(),
street: faker.address.streetName(),
number: faker.random.number().toString(),
complemento: faker.address.secondaryAddress(),
neighborhood: faker.address.streetName(),
city: faker.address.city(),
estado: faker.address.state()
}
const password = faker.internet.password()
await simulateValidSubmit(
fullName,
username,
birthday,
rg,
cpf,
email,
address,
password
)
expect(addAccountSpy.params).toEqual({
fullName,
username,
birthday,
rg,
cpf,
email,
address,
password,
passwordConfirmation: password
})
})
test('Should call AddAccount only once', async () => {
const { addAccountSpy } = makeSut()
await simulateValidSubmit()
await simulateValidSubmit()
expect(addAccountSpy.callsCount).toBe(1)
})
test('Should not call AddAccount if form is invalid', async () => {
const validationError = faker.random.words()
const { addAccountSpy } = makeSut({ validationError })
await simulateValidSubmit()
expect(addAccountSpy.callsCount).toBe(0)
})
test('Should present error if AddAccount fails', async () => {
const { addAccountSpy } = makeSut()
const error = new EmailInUseError()
jest.spyOn(addAccountSpy, 'add').mockRejectedValueOnce(error)
await simulateValidSubmit()
expect(screen.getByTestId('main-error')).toHaveTextContent(error.message)
expect(screen.getByTestId('error-wrap').children).toHaveLength(1)
})
test('Should call UpdateCurrentAccountResultModel on success', async () => {
const { addAccountSpy, setCurrentAccountResultModelMock } = makeSut()
await simulateValidSubmit()
expect(setCurrentAccountResultModelMock).toHaveBeenCalledWith(
addAccountSpy.account
)
expect(history.length).toBe(1)
expect(history.location.pathname).toBe('/')
})
test('Should go to signup page', () => {
makeSut()
const loginLink = screen.getByTestId('login-link')
fireEvent.click(loginLink)
expect(history.length).toBe(1)
expect(history.location.pathname).toBe('/login')
})
})
import React, { useState, useEffect, useContext } from 'react'
import { useHistory, Link } from 'react-router-dom'
import Styles from './signup-styles.scss'
import {
Footer,
Input,
LoginHeader,
FormStatus,
SubmitButton
} from '@/presentation/components'
import { FormContext, ApiContext } from '@/presentation/contexts'
import { Validation } from '@/presentation/protocols/validation'
import { AddAccount } from '@/domain/usecases'
type Props = {
validation: Validation
addAccount: AddAccount
}
type StateProps = {
isLoading: boolean
isFormInvalid: boolean
fullName: string
username: string
birthday: string
rg: string
cpf: string
email: string
address: {
zipcode: string
street: string
number: string
complemento: string
neighborhood: string
city: string
estado: string
}
password: string
passwordConfirmation: string
fullNameError: string
usernameError: string
birthdayError: string
rgError: string
cpfError: string
emailError: string
addressError: string
zipcodeError: string
streetError: string
numberError: string
complementoError: string
neighborhoodError: string
cityError: string
estadoError: string
passwordError: string
passwordConfirmationError: string
mainError: string
}
const SignUp: React.FC<Props> = ({ validation, addAccount }: Props) => {
const { setCurrentAccountResultModel } = useContext(ApiContext)
const history = useHistory()
const [state, setState] = useState<StateProps>({
isLoading: false,
isFormInvalid: true,
fullName: '',
username: '',
birthday: '',
rg: '',
cpf: '',
email: '',
address: {
zipcode: '',
street: '',
number: '',
complemento: '',
neighborhood: '',
city: '',
estado: ''
},
password: '',
passwordConfirmation: '',
fullNameError: '',
usernameError: '',
birthdayError: '',
rgError: '',
cpfError: '',
emailError: '',
addressError: '',
zipcodeError: '',
streetError: '',
numberError: '',
complementoError: '',
neighborhoodError: '',
cityError: '',
estadoError: '',
passwordError: '',
passwordConfirmationError: '',
mainError: ''
})
useEffect(() => {
validate('fullName')
}, [state.fullName])
useEffect(() => {
validate('username')
}, [state.username])
useEffect(() => {
validate('birthday')
}, [state.birthday])
useEffect(() => {
validate('rg')
}, [state.rg])
useEffect(() => {
validate('cpf')
}, [state.cpf])
useEffect(() => {
validate('email')
}, [state.email])
useEffect(() => {
validate('zipcode')
}, [state.address.zipcode])
useEffect(() => {
validate('street')
}, [state.address.street])
useEffect(() => {
validate('number')
}, [state.address.number])
useEffect(() => {
validate('complemento')
}, [state.address.complemento])
useEffect(() => {
validate('neighborhood')
}, [state.address.neighborhood])
useEffect(() => {
validate('city')
}, [state.address.city])
useEffect(() => {
validate('estado')
}, [state.address.estado])
useEffect(() => {
validate('password')
}, [state.password])
useEffect(() => {
validate('passwordConfirmation')
}, [state.passwordConfirmation])
const validate = (field: string): void => {
const {
fullName,
username,
birthday,
rg,
cpf,
email,
address: {
zipcode,
street,
number,
complemento,
neighborhood,
city,
estado
},
password,
passwordConfirmation
} = state
const formData = {
fullName,
username,
birthday,
rg,
cpf,
email,
address: {
zipcode,
street,
number,
complemento,
neighborhood,
city,
estado
},
password,
passwordConfirmation
}
setState((old) => ({
...old,
[`${field}Error`]: validation.validate(field, formData)
}))
setState((old) => ({
...old,
isFormInvalid:
!!old.fullNameError ||
!!old.usernameError ||
!!old.birthdayError ||
!!old.rgError ||
!!old.cpfError ||
!!old.emailError ||
!!old.addressError ||
!!old.zipcodeError ||
!!old.streetError ||
!!old.numberError ||
!!old.complementoError ||
!!old.neighborhoodError ||
!!old.cityError ||
!!old.estadoError ||
!!old.passwordError ||
!!old.passwordConfirmationError
}))
}
const handleSubmit = async (
event: React.FormEvent<HTMLFormElement>
): Promise<void> => {
event.preventDefault()
try {
if (state.isLoading || state.isFormInvalid) {
return
}
setState({ ...state, isLoading: true })
const account = await addAccount.add({
fullName: state.fullName,
username: state.username,
birthday: state.birthday,
rg: state.rg,
cpf: state.cpf,
email: state.email,
address: {
zipcode: state.address.zipcode,
street: state.address.street,
number: state.address.number,
complemento: state.address.complemento,
neighborhood: state.address.neighborhood,
city: state.address.city,
estado: state.address.estado
},
password: state.password,
passwordConfirmation: state.passwordConfirmation
})
setCurrentAccountResultModel(account)
history.replace('/')
} catch (error) {
setState({
...state,
isLoading: false,
mainError: error.message
})
}
}
return (
<div className={Styles.signupWrap}>
<LoginHeader />
<FormContext.Provider value={{ state, setState }}>
<form
data-testid="form"
className={Styles.form}
onSubmit={handleSubmit}
>
<h2>Criar conta</h2>
<hr></hr>
<h3>Dados pessoais</h3>
<Input
type="text"
name="fullName"
placeholder="Digite o seu nome completo"
/>
<Input
type="text"
name="username"
placeholder="Digite um nome de usuário"
/>
<Input
type="text"
name="birthday"
placeholder="Digite a data de nascimento"
/>
<Input type="text" name="rg" placeholder="Digite o seu RG" />
<Input type="text" name="cpf" placeholder="Digite o seu CPF" />
<Input type="text" name="email" placeholder="Digite o seu email" />
<Input
type="password"
name="password"
placeholder="Digite sua senha"
/>
<Input
type="password"
name="passwordConfirmation"
placeholder="Confirme sua senha"
/>
<h3 className={Styles.addressTitle}>Endereço</h3>
<Input type="text" name="zipcode" placeholder="Digite o seu CEP" />
<Input
type="text"
name="street"
placeholder="Digite o nome sua rua"
/>
<Input
type="text"
name="number"
placeholder="Digite o número da sua casa"
/>
<Input
type="text"
name="complemento"
placeholder="Digite o seu complemento"
/>
<Input
type="text"
name="neighborhood"
placeholder="Digite o seu bairro"
/>
<Input
type="text"
name="city"
placeholder="Digite o nome da sua cidade"
/>
<Input
type="text"
name="estado"
placeholder="Digite o nome do seu estado"
/>
<SubmitButton text="Criar conta" />
<Link
data-testid="login-link"
replace
to="/login"
className={Styles.link}
>
Voltar para Login
</Link>
<FormStatus />
</form>
</FormContext.Provider>
<Footer />
</div>
)
}
export default SignUp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment