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 @@ ...@@ -4,7 +4,8 @@
"eslint:recommended", "eslint:recommended",
"plugin:react/recommended", "plugin:react/recommended",
"plugin:react-hooks/recommended", "plugin:react-hooks/recommended",
"plugin:testing-library/recommended" "plugin:testing-library/recommended",
"plugin:cypress/recommended"
], ],
"env": { "env": {
"browser": true, "browser": true,
......
...@@ -68,6 +68,23 @@ build: ...@@ -68,6 +68,23 @@ build:
paths: paths:
- node_modules/ - 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: SonarScanner Dev:
image: image:
name: sonarsource/sonar-scanner-cli:4.6 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 @@ ...@@ -7,7 +7,6 @@
"@reduxjs/toolkit": "^1.5.1", "@reduxjs/toolkit": "^1.5.1",
"@testing-library/jest-dom": "^5.11.9", "@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^11.2.5", "@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^12.8.1",
"autoprefixer": "^10.2.5", "autoprefixer": "^10.2.5",
"axios": "^0.21.1", "axios": "^0.21.1",
"msw": "^0.28.0", "msw": "^0.28.0",
...@@ -18,10 +17,13 @@ ...@@ -18,10 +17,13 @@
"react-hot-toast": "^1.0.2", "react-hot-toast": "^1.0.2",
"react-redux": "^7.2.3", "react-redux": "^7.2.3",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"react-toast": "^1.0.1",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"router": "^1.3.5",
"tailwindcss": "^2.0.3", "tailwindcss": "^2.0.3",
"wait-on": "^5.3.0",
"web-vitals": "^1.1.0" "web-vitals": "^1.1.0"
}, },
"scripts": { "scripts": {
...@@ -35,7 +37,12 @@ ...@@ -35,7 +37,12 @@
"test:coverage": "CI=true npm run test --env=jsdom -- --coverage", "test:coverage": "CI=true npm run test --env=jsdom -- --coverage",
"lint": "eslint .", "lint": "eslint .",
"lint:fix": "eslint --fix .", "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": { "eslintConfig": {
"extends": [ "extends": [
...@@ -57,17 +64,23 @@ ...@@ -57,17 +64,23 @@
}, },
"jest": { "jest": {
"coveragePathIgnorePatterns": [ "coveragePathIgnorePatterns": [
"index.js" "index.js",
"src/api/*"
] ]
}, },
"devDependencies": { "devDependencies": {
"@testing-library/user-event": "^12.8.3",
"cypress": "^6.8.0",
"eslint": "^7.22.0", "eslint": "^7.22.0",
"eslint-plugin-cypress": "^2.11.2",
"eslint-plugin-jest": "^24.3.2", "eslint-plugin-jest": "^24.3.2",
"eslint-plugin-react": "^7.22.0", "eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-testing-library": "^3.10.1", "eslint-plugin-testing-library": "^3.10.1",
"husky": "^5.1.3", "husky": "^5.1.3",
"jest-environment-jsdom-sixteen": "^1.0.3",
"lint-staged": "^10.5.4", "lint-staged": "^10.5.4",
"mutationobserver-shim": "^0.3.7",
"prettier": "^2.2.1" "prettier": "^2.2.1"
}, },
"husky": { "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' ...@@ -15,7 +15,10 @@ import {useSelector} from 'react-redux'
import {isLoggedIn} from '../../store/auth/authSlice' import {isLoggedIn} from '../../store/auth/authSlice'
const Login = () => { const Login = () => {
const {register, handleSubmit, watch, errors} = useForm() const {register, handleSubmit, watch, errors} = useForm({
mode: 'onChange',
reValidateMode: 'onChange',
})
const dispatch = useDispatch() const dispatch = useDispatch()
const [maskPassword, setMaskPassword] = useState(true) const [maskPassword, setMaskPassword] = useState(true)
const [submitPhase, setSubmitPhase] = useState(1) const [submitPhase, setSubmitPhase] = useState(1)
...@@ -29,13 +32,23 @@ const Login = () => { ...@@ -29,13 +32,23 @@ const Login = () => {
if (loggedIn) { if (loggedIn) {
navigate('/') navigate('/')
} }
// eslint-disable-next-line // eslint-disable-next-line
}, []) }, [])
const onSubmit = (input) => { const onSubmit = (input) => {
if (submitPhase == 1) { if (submitPhase == 1) {
setData({...data, ...input}) 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) { } else if (submitPhase == 2) {
setData({...data, ...input}) setData({...data, ...input})
axios axios
...@@ -51,13 +64,21 @@ const Login = () => { ...@@ -51,13 +64,21 @@ const Login = () => {
}) })
}) })
.catch((error) => { .catch((error) => {
if (error.response.data.non_field_errors) { handleError(error.response?.data?.non_field_errors)
toast.error(error.response.data.non_field_errors)
}
}) })
} }
} }
const handleError = (error) => {
if(error) {
toast.error(
<span data-testid="toast-error-login">
{error}
</span>,
)
}
}
const changeMaskPassword = () => { const changeMaskPassword = () => {
if (maskPassword) { if (maskPassword) {
setMaskPassword(false) setMaskPassword(false)
...@@ -68,7 +89,7 @@ const Login = () => { ...@@ -68,7 +89,7 @@ const Login = () => {
return ( return (
<div <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" data-testid="login-page"
> >
{submitPhase == 1 ? ( {submitPhase == 1 ? (
...@@ -86,7 +107,7 @@ const Login = () => { ...@@ -86,7 +107,7 @@ const Login = () => {
<span className="text-popblue">Daftar</span> <span className="text-popblue">Daftar</span>
</Link> </Link>
</p> </p>
<div className="pt-12"> <div className="pt-12 w-80">
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col"> <div className="flex flex-col">
<label className="text-charchoal heading-3" htmlFor="email"> <label className="text-charchoal heading-3" htmlFor="email">
...@@ -121,7 +142,7 @@ const Login = () => { ...@@ -121,7 +142,7 @@ const Login = () => {
<div className="flex flex-row"> <div className="flex flex-row">
<div className="pt-4"> <div className="pt-4">
<button <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" data-testid="next-button"
> >
Selanjutnya Selanjutnya
...@@ -138,7 +159,7 @@ const Login = () => { ...@@ -138,7 +159,7 @@ const Login = () => {
<p className="pt-2 text-charchoal heading-3">{data.email}</p> <p className="pt-2 text-charchoal heading-3">{data.email}</p>
<div className="pt-12"> <div className="pt-12">
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col"> <div className="flex flex-col w-80">
<label className="text-charchoal heading-3" htmlFor="password"> <label className="text-charchoal heading-3" htmlFor="password">
Password Password
</label> </label>
...@@ -150,7 +171,7 @@ const Login = () => { ...@@ -150,7 +171,7 @@ const Login = () => {
id="password" id="password"
required required
name="password" name="password"
placeholder="Buat password" placeholder="Masukkan password"
ref={register({ ref={register({
required: 'Wajib diisi', required: 'Wajib diisi',
minLength: { minLength: {
...@@ -191,7 +212,7 @@ const Login = () => { ...@@ -191,7 +212,7 @@ const Login = () => {
<div className="flex flex-col"> <div className="flex flex-col">
<div className="pt-4"> <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 Masuk
</button> </button>
</div> </div>
......
import '@testing-library/jest-dom' import '@testing-library/jest-dom'
import {rest} from 'msw'
import {setupServer} from 'msw/node'
import {fireEvent, screen} from '@testing-library/react' import {fireEvent, screen} from '@testing-library/react'
import Login from './Login' import Login from './Login'
import {testRender, makeTestStore} from '../../testUtils' 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', () => { test('renders login page', () => {
const store = makeTestStore() const store = makeTestStore()
...@@ -10,6 +26,61 @@ test('renders login page', () => { ...@@ -10,6 +26,61 @@ test('renders login page', () => {
expect(screen.getByTestId('daftar-url-link')).toBeInTheDocument() expect(screen.getByTestId('daftar-url-link')).toBeInTheDocument()
}) })