Fakultas Ilmu Komputer UI

Commit 8586a16d authored by Lia Yuliana's avatar Lia Yuliana
Browse files

[CHORES] add getToken function in authSlice.js

parents 5c74cf39 e44e73c2
......@@ -4,7 +4,8 @@
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:testing-library/recommended"
"plugin:testing-library/recommended",
"plugin:cypress/recommended"
],
"env": {
"browser": true,
......
......@@ -68,6 +68,23 @@ build:
paths:
- node_modules/
integration:
image: cypress/browsers:node12.16.2-chrome81-ff75
stage: test-build
script:
- apt-get install xvfb
- npm run cypress:install
- npm run start:gitlab
- npm run cypress:gitlab
except:
changes:
- "README.md"
cache:
key: dependency
policy: pull
paths:
- node_modules/
SonarScanner Dev:
image:
name: sonarsource/sonar-scanner-cli:4.6
......
{
"baseUrl": "http://localhost:3000",
"video": false,
"defaultCommandTimeout": 6000
}
describe('Submit to Backend Validate Email (Success Case)', () => {
beforeEach(() => {
cy.intercept(
{
method: 'POST',
url: '**/api/v1/auth/validate-email/',
},
{
statusCode: 200,
},
).as('postValidateEmailSuccess')
cy.visit('/login')
cy.contains('Email').type('a@gmail.com')
cy.get('[data-testid=next-button]').click()
})
it('should have correct body response', () => {
cy.wait('@postValidateEmailSuccess').should(({request}) => {
expect(request.body).to.have.property('email')
})
})
it('should directed to next page if email is validated', () => {
cy.wait('@postValidateEmailSuccess')
cy.url().should('include', '/login')
cy.get('[data-testid=password-input]').should('be.visible')
})
})
describe('Submit to Backend Validate Email (Error Case)', () => {
beforeEach(() => {
cy.intercept(
{
method: 'POST',
url: '**/api/v1/auth/validate-email/',
},
{
statusCode: 400,
body: {non_field_errors: 'Email is not registered yet.'},
},
).as('postValidateEmailError')
cy.visit('/login')
cy.contains('Email').type('a@gmail.com')
cy.get('[data-testid=next-button]').click()
})
it('should show toast error if email is not registered', () => {
cy.wait('@postValidateEmailError')
cy.get('[data-testid=toast-error-login]').should('be.visible')
})
})
describe('Submit to Backend Login (Success Case)', () => {
beforeEach(() => {
cy.intercept(
{
method: 'POST',
url: '**/api/v1/auth/validate-email/',
},
{
statusCode: 200,
},
).as('postValidateEmailSuccess')
cy.visit('/login')
cy.contains('Email').type('a@gmail.com')
cy.get('[data-testid=next-button]').click()
cy.contains('Password').type('abcdefgh1')
cy.intercept(
{
method: 'POST',
url: '**/api/v1/auth/login/',
},
{
statusCode: 200,
},
).as('postLoginSuccess')
cy.contains('Masuk').click()
})
it('should have correct body response', () => {
cy.wait('@postLoginSuccess').should(({request}) => {
expect(request.body).to.have.property('email')
expect(request.body).to.have.property('password')
})
})
it('should directed to / and show success toast after success login', () => {
cy.wait('@postLoginSuccess')
cy.url().should('include', '/')
cy.get('[data-testid=toast-homepage]').should('be.visible')
})
})
describe('Submit to Backend Login (Error Case)', () => {
beforeEach(() => {
cy.visit('/login')
cy.intercept(
{
method: 'POST',
url: '**/api/v1/auth/validate-email/',
},
{
statusCode: 200,
},
).as('postValidateEmailSuccess')
cy.contains('Email').type('a@gmail.com')
cy.get('[data-testid=next-button]').click()
cy.contains('Password').type('abcdefgh1')
cy.intercept(
{
method: 'POST',
url: '**/api/v1/auth/login/',
},
{
statusCode: 404,
body: {non_field_errors: 'Password is wrong'},
headers: {'access-control-allow-origin': '*'},
delayMs: 500,
},
).as('postLoginError')
cy.contains('Masuk').click()
})
it('shows non field error toast after post error in Login', () => {
cy.wait('@postLoginError')
cy.contains('Password is wrong').scrollIntoView().should('be.visible')
})
})
describe('Submit to Backend Register (Success Case)', () => {
beforeEach(() => {
cy.visit('/register')
cy.contains('Email').type('a@gmail.com')
cy.contains('No Handphone Aktif').type('812345678')
cy.contains('Password').type('abcdefgh1')
cy.contains('Konfirmasi Password').type('abcdefgh1')
cy.intercept(
{
method: 'POST',
url: '**/api/v1/auth/register/',
},
{
statusCode: 200,
},
).as('postRegisterSuccess')
cy.get('[data-testid=daftar]').click()
})
it('should have correct body response', () => {
cy.wait('@postRegisterSuccess').should(({request}) => {
expect(request.body).to.have.property('email')
expect(request.body).to.have.property('phone_number')
expect(request.body).to.have.property('password')
})
})
it('should directed to / and show success toast after success register', () => {
cy.wait('@postRegisterSuccess')
cy.url().should('include', '/')
cy.get('[data-testid=toast-homepage]').should('be.visible')
})
})
describe('Submit to Backend Register (Error Case)', () => {
beforeEach(() => {
cy.visit('/register')
cy.contains('Email').type('a@gmail.com')
cy.contains('No Handphone Aktif').type('812345678')
cy.contains('Password').type('abcdefgh1')
cy.contains('Konfirmasi Password').type('abcdefgh1')
})
it('shows email error toast after post error in email', () => {
cy.intercept(
{
method: 'POST',
url: '**/api/v1/auth/register/',
},
{
statusCode: 404,
body: {email: 'Email already exists'},
headers: {'access-control-allow-origin': '*'},
delayMs: 500,
},
).as('postRegisterEmailError')
cy.get('[data-testid=daftar]').click()
cy.wait('@postRegisterEmailError')
cy.contains('Email already exists').scrollIntoView().should('be.visible')
})
it('shows non field error toast after post error in non field', () => {
cy.intercept(
{
method: 'POST',
url: '**/api/v1/auth/register/',
},
{
statusCode: 404,
body: {non_field_errors: 'Password is too common'},
headers: {'access-control-allow-origin': '*'},
delayMs: 500,
},
).as('postRegisterNonFieldError')
cy.get('[data-testid=daftar]').click()
cy.wait('@postRegisterNonFieldError')
cy.contains('Password is too common').scrollIntoView().should('be.visible')
})
})
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// https://github.com/cypress-io/cypress/issues/349
// add --disable-dev-shm-usage chrome flag
on('before:browser:launch', (browser, launchOptions) => {
if (browser.family === 'chromium') {
console.log('Adding Chrome flag: --disable-dev-shm-usage')
launchOptions.args.push('--disable-dev-shm-usage')
}
return launchOptions
})
return config
}
This diff is collapsed.
......@@ -7,7 +7,6 @@
"@reduxjs/toolkit": "^1.5.1",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^12.8.1",
"autoprefixer": "^10.2.5",
"axios": "^0.21.1",
"msw": "^0.28.0",
......@@ -18,10 +17,13 @@
"react-hot-toast": "^1.0.2",
"react-redux": "^7.2.3",
"react-scripts": "4.0.3",
"react-toast": "^1.0.1",
"redux": "^4.0.5",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"router": "^1.3.5",
"tailwindcss": "^2.0.3",
"wait-on": "^5.3.0",
"web-vitals": "^1.1.0"
},
"scripts": {
......@@ -35,7 +37,12 @@
"test:coverage": "CI=true npm run test --env=jsdom -- --coverage",
"lint": "eslint .",
"lint:fix": "eslint --fix .",
"format": "prettier --write \"**/*.+(js|jsx|json|css)\""
"format": "prettier --write \"**/*.+(js|jsx|json|css)\"",
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"cypress:install": "cypress install",
"cypress:gitlab": "CYPRESS_BASE_URL=http://172.17.0.3:3000/ DEBUG=cypress:* xvfb-run cypress run --headed --browser chrome",
"start:gitlab": "npm start & wait-on http://172.17.0.3:3000/"
},
"eslintConfig": {
"extends": [
......@@ -57,17 +64,23 @@
},
"jest": {
"coveragePathIgnorePatterns": [
"index.js"
"index.js",
"src/api/*"
]
},
"devDependencies": {
"@testing-library/user-event": "^12.8.3",
"cypress": "^6.8.0",
"eslint": "^7.22.0",
"eslint-plugin-cypress": "^2.11.2",
"eslint-plugin-jest": "^24.3.2",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-testing-library": "^3.10.1",
"husky": "^5.1.3",
"jest-environment-jsdom-sixteen": "^1.0.3",
"lint-staged": "^10.5.4",
"mutationobserver-shim": "^0.3.7",
"prettier": "^2.2.1"
},
"husky": {
......
import axios from 'axios'
import BASE_API from '../constant'
const API = `${BASE_API.API_BASE_URL}/api/v1/auth`
export default {
login: (data) => axios.post(`${API}/login/`, data),
register: (data) => axios.post(`${API}/register/`, data),
}
......@@ -15,7 +15,10 @@ import {useSelector} from 'react-redux'
import {isLoggedIn} from '../../store/auth/authSlice'
const Login = () => {
const {register, handleSubmit, watch, errors} = useForm()
const {register, handleSubmit, watch, errors} = useForm({
mode: 'onChange',
reValidateMode: 'onChange',
})
const dispatch = useDispatch()
const [maskPassword, setMaskPassword] = useState(true)
const [submitPhase, setSubmitPhase] = useState(1)
......@@ -29,13 +32,23 @@ const Login = () => {
if (loggedIn) {
navigate('/')
}
// eslint-disable-next-line
// eslint-disable-next-line
}, [])
const onSubmit = (input) => {
if (submitPhase == 1) {
setData({...data, ...input})
setSubmitPhase(2)
axios
.post(`${BASE_URL.API_BASE_URL}/api/v1/auth/validate-email/`, {
...data,
...input,
})
.then(() => {
setSubmitPhase(2)
})
.catch((error) => {
handleError(error.response.data.non_field_errors)
})
} else if (submitPhase == 2) {
setData({...data, ...input})
axios
......@@ -51,13 +64,21 @@ const Login = () => {
})
})
.catch((error) => {
if (error.response.data.non_field_errors) {
toast.error(error.response.data.non_field_errors)
}
handleError(error.response?.data?.non_field_errors)
})
}
}
const handleError = (error) => {
if(error) {
toast.error(
<span data-testid="toast-error-login">
{error}
</span>,
)
}
}
const changeMaskPassword = () => {
if (maskPassword) {
setMaskPassword(false)
......@@ -68,7 +89,7 @@ const Login = () => {
return (
<div
className="bg-lightgrey pt-32 lg:pt-24 xl:pt-24 2xl:pt-24 pb-52 lg:pb-44 xl:pb-44 2xl:pb-44"
className="bg-lightgrey pt-32 lg:pt-24 xl:pt-24 2xl:pt-24 h-screen"
data-testid="login-page"
>
{submitPhase == 1 ? (
......@@ -86,7 +107,7 @@ const Login = () => {
<span className="text-popblue">Daftar</span>
</Link>
</p>
<div className="pt-12">
<div className="pt-12 w-80">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col">
<label className="text-charchoal heading-3" htmlFor="email">
......@@ -121,7 +142,7 @@ const Login = () => {
<div className="flex flex-row">
<div className="pt-4">
<button
className="bg-poporange text-white heading-3 px-36 py-2.5 shadow rounded-lg submit"
className="bg-poporange text-white heading-3 py-2.5 shadow rounded-lg submit w-80 text-center"
data-testid="next-button"
>
Selanjutnya
......@@ -138,7 +159,7 @@ const Login = () => {
<p className="pt-2 text-charchoal heading-3">{data.email}</p>
<div className="pt-12">
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col">
<div className="flex flex-col w-80">
<label className="text-charchoal heading-3" htmlFor="password">
Password
</label>
......@@ -150,7 +171,7 @@ const Login = () => {
id="password"
required
name="password"
placeholder="Buat password"
placeholder="Masukkan password"
ref={register({
required: 'Wajib diisi',
minLength: {
......@@ -191,7 +212,7 @@ const Login = () => {
<div className="flex flex-col">
<div className="pt-4">
<button className="bg-poporange text-white heading-3 px-40 py-2.5 shadow rounded-lg submit">
<button className="bg-poporange text-white heading-3 py-2.5 shadow rounded-lg submit w-80 text-center">
Masuk
</button>
</div>
......
import '@testing-library/jest-dom'
import {rest} from 'msw'
import {setupServer} from 'msw/node'
import {fireEvent, screen} from '@testing-library/react'
import Login from './Login'
import {testRender, makeTestStore} from '../../testUtils'
import BASE_URL from '../../api/config'
const server = setupServer(
rest.post(
`${BASE_URL.API_BASE_URL}/api/v1/auth/validate-email/`,
(req, res, ctx) => {
return res(ctx.status(200))
},
),
)
beforeEach(() => server.listen())
afterEach(() => server.resetHandlers())
afterEach(() => server.close())
test('renders login page', () => {
const store = makeTestStore()
......@@ -10,6 +26,61 @@ test('renders login page', () => {
expect(screen.getByTestId('daftar-url-link')).toBeInTheDocument()
})
test('email is valid and registered', async () => {
const store = makeTestStore()
testRender(<Login />, {store})
const emailInput = screen.getByLabelText(/Email/i)
fireEvent.change(emailInput, {
target: {value: 'sigendud@email.com'},
})
const selanjutnyaButton = screen.getByText(/Selanjutnya/i)
fireEvent.click(selanjutnyaButton)
const checkPasswordElement = await screen.findByLabelText(/Password/i)
expect(checkPasswordElement).toBeVisible()
const passwordElement = screen.getByLabelText(/Password/i)
fireEvent.change(passwordElement, {
target: {value: 'sebuahPassYangValid22'},
})
const masukButton = screen.getByText(/Masuk/i)
fireEvent.click(masukButton)
})
test('login error if email is not registered', async () => {
server.resetHandlers(
rest.post(
`${BASE_URL.API_BASE_URL}/api/v1/auth/validate-email/`,
(req, res, ctx) => {
return res(
ctx.status(400),
ctx.json({non_field_errors: 'Email is not registered yet.'}),
)
},
),
)
const store = makeTestStore()
testRender(<Login />, {
store,
})
const emailInput = screen.getByLabelText(/Email/i)
fireEvent.change(emailInput, {
target: {value: 'sibambi@email.com'},
})
const selanjutnyaButton = screen.getByText(/Selanjutnya/i)
fireEvent.click(selanjutnyaButton)
const toastError = await screen.findByTestId('toast-error-login')
expect(toastError).toBeInTheDocument()
})
test('login error if email format not valid', async () => {
const store = makeTestStore()
......@@ -88,6 +159,80 @@ test('login error if password not contain number', async () => {
expect(passwordOneNumber).toBeVisible()
})
test('login is successful', async () => {
server.use(
rest.post(
`${BASE_URL.API_BASE_URL}/api/v1/auth/login/`,
(req, res, ctx) => {
return res(ctx.status(200))
},
),
)
const store = makeTestStore()
testRender(<Login />, {
store,
})
const emailInput = screen.getByLabelText(/Email/i)
fireEvent.change(emailInput, {
target: {value: 'sigendud@email.com'},
})
const selanjutnyaButton = screen.getByText(/Selanjutnya/i)
fireEvent.click(selanjutnyaButton)
const checkPasswordElement = await screen.findByLabelText(/Password/i)
expect(checkPasswordElement).toBeVisible()
const passwordElement = screen.getByLabelText(/Password/i)
fireEvent.change(passwordElement, {
target: {value: 'sebuahPassYangValid22'},
})
const masukButton = screen.getByText(/Masuk/i)
fireEvent.click(masukButton)
})
test('login error if password is wrong', async () => {
server.use(
rest.post(