Last active
August 1, 2020 23:24
-
-
Save thiagobutignon/fc3946c214e2571a355dedacd30b9e4e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | |
}) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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