| @@ -0,0 +1,173 @@ | |||
| import * as redux from "react-redux"; | |||
| import store from "../../store"; | |||
| import { Router } from "react-router-dom"; | |||
| import { mockState } from "../../mockState"; | |||
| import { fireEvent, render, screen, waitFor } from "@testing-library/react"; | |||
| import history from "../../store/utils/history"; | |||
| import LoginPage from "../../pages/LoginPage/LoginPageMUI"; | |||
| // class LocalStorageMock { | |||
| // constructor() { | |||
| // this.store = {}; | |||
| // } | |||
| // clear() { | |||
| // this.store = {}; | |||
| // } | |||
| // getItem(key) { | |||
| // return undefined; | |||
| // } | |||
| // setItem(key, value) { | |||
| // this.store[key] = String(value); | |||
| // } | |||
| // removeItem(key) { | |||
| // delete this.store[key]; | |||
| // } | |||
| // } | |||
| // global.localStorage = new LocalStorageMock; | |||
| describe("LoginPage render tests", () => { | |||
| var props = { | |||
| history: { | |||
| replace: jest.fn(), | |||
| push: jest.fn(), | |||
| location: { | |||
| pathname: "/", | |||
| }, | |||
| }, | |||
| }; | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <LoginPage {...props} /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| let spyOnUseDispatch; | |||
| let mockDispatch; | |||
| beforeEach(() => { | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector.mockReturnValue("some error"); | |||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||
| mockDispatch = jest.fn(); | |||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should render", () => { | |||
| const { container } = render(cont); | |||
| expect( | |||
| container.getElementsByClassName("c-login-container")[0] | |||
| ).toBeDefined(); | |||
| }); | |||
| it("Should render login-logo", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("login-logo")[0]).toBeDefined(); | |||
| }); | |||
| it("Should render welcome message", () => { | |||
| const { container } = render(cont); | |||
| expect( | |||
| container | |||
| .getElementsByClassName("c-login-container")[0] | |||
| .querySelector("h") | |||
| ).toBeDefined(); | |||
| }); | |||
| it("Should render error message because we mocked error message", () => { | |||
| render(cont); | |||
| expect(screen.getByTestId("error-message")).toBeDefined(); | |||
| }); | |||
| it("Should render username input", () => { | |||
| render(cont); | |||
| expect(screen.getByTestId("username-input")).toBeDefined(); | |||
| }); | |||
| it("Should render password input", () => { | |||
| render(cont); | |||
| expect(screen.getByTestId("password-input")).toBeDefined(); | |||
| }); | |||
| it("Should render forgot paswword link", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("text-end")[0]).toBeDefined(); | |||
| }); | |||
| it("Should render submit button", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("c-btn")[0]).toBeDefined(); | |||
| }); | |||
| it("Should render separator container", () => { | |||
| render(cont); | |||
| expect(screen.getByTestId("separator")).toBeDefined(); | |||
| }); | |||
| it("Should dilig logo", () => { | |||
| render(cont); | |||
| expect(screen.getByTestId("dilig-logo")).toBeDefined(); | |||
| }); | |||
| it("Should not dispatch functions after clicking submit button because username and password inputs are emtpy", async () => { | |||
| const { container } = render(cont); | |||
| fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(0)); | |||
| }); | |||
| it("Should not dispatch functions after clicking submit button because password input is emtpy", async () => { | |||
| const { container } = render(cont); | |||
| fireEvent.change(screen.getByTestId("username-input"), { | |||
| target: { value: "some username" }, | |||
| }); | |||
| fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(0)); | |||
| }); | |||
| it("Should not dispatch functions after clicking submit button because username input is emtpy", async () => { | |||
| const { container } = render(cont); | |||
| fireEvent.change(screen.getByTestId("password-input"), { | |||
| target: { value: "some password" }, | |||
| }); | |||
| fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(0)); | |||
| }); | |||
| it("Should dispatch two functions after clicking submit button", async () => { | |||
| const { container } = render(cont); | |||
| fireEvent.change(screen.getByTestId("username-input"), { | |||
| target: { value: "some username" }, | |||
| }); | |||
| fireEvent.change(screen.getByTestId("password-input"), { | |||
| target: { value: "some password" }, | |||
| }); | |||
| fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(2)); | |||
| }); | |||
| // it("After clicking submit button we should go to ads page", async () => { | |||
| // const { container } = render(cont); | |||
| // fireEvent.change(screen.getByTestId("username-input"), { | |||
| // target: { value: "some username" }, | |||
| // }); | |||
| // fireEvent.change(screen.getByTestId("password-input"), { | |||
| // target: { value: "some password" }, | |||
| // }); | |||
| // fireEvent.click(container.getElementsByClassName("c-btn")[0]); | |||
| // const arg = { pathname: "/ads" }; | |||
| // expect(props.history.push).toHaveBeenCalledWith(arg); | |||
| // }); | |||
| }); | |||
| @@ -6,7 +6,6 @@ import { Router } from "react-router-dom"; | |||
| import history from "../../store/utils/history"; | |||
| import TableViewPage from "../../pages/CandidatesPage/TableViewPage"; | |||
| import { PAGE_SIZE_CANDIDATES } from "../../constants/keyCodeConstants"; | |||
| import * as requests from "../../request/candidatesRequest"; | |||
| describe("TableViewPage render tests", () => { | |||
| var props = { | |||
| @@ -17,6 +16,7 @@ describe("TableViewPage render tests", () => { | |||
| pathname: "/candidates", | |||
| }, | |||
| }, | |||
| setPage: jest.fn(), | |||
| }; | |||
| const cont = ( | |||
| @@ -28,12 +28,18 @@ describe("TableViewPage render tests", () => { | |||
| ); | |||
| let spyOnUseSelector; | |||
| let spyOnUseDispatch; | |||
| let mockDispatch; | |||
| beforeEach(() => { | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector | |||
| .mockReturnValueOnce(mockState.candidates.candidates) | |||
| .mockReturnValueOnce(mockState.candidates.pagination); | |||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||
| mockDispatch = jest.fn(); | |||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||
| }); | |||
| afterEach(() => { | |||
| @@ -47,6 +53,16 @@ describe("TableViewPage render tests", () => { | |||
| ).toBeDefined(); | |||
| }); | |||
| it("Should dispatch function when component is rendered", async () => { | |||
| render(cont); | |||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(1)); | |||
| }); | |||
| it("Should render table", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("usersTable")[0]).toBeDefined(); | |||
| }); | |||
| it("Should render pagination component", () => { | |||
| const { container } = render(cont); | |||
| expect( | |||
| @@ -91,6 +107,16 @@ describe("TableViewPage render tests", () => { | |||
| ).toBe("0"); | |||
| }); | |||
| it("When user change table page function for fetching users should be called", async () => { | |||
| const { container } = render(cont); | |||
| const pag = container | |||
| .getElementsByClassName("MuiPagination-ul")[0] | |||
| .getElementsByTagName("li")[1] | |||
| .querySelector("button"); | |||
| fireEvent.click(pag); | |||
| await waitFor(() => expect(mockDispatch).toBeCalledTimes(2)); | |||
| }); | |||
| // How to mock getCV() function ? | |||
| // it("Should render CV of candidate after clicking on CV name", async () => { | |||
| // const mockedCall = { data: mockState.candidates.adsCandidates }; | |||
| @@ -1,24 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import SectionLoader from '../Loader/SectionLoader'; | |||
| const AuthCard = ({ children, title, subtitle, isLoading }) => { | |||
| return ( | |||
| <div className="c-auth-card"> | |||
| <SectionLoader isLoading={isLoading}> | |||
| <h1 className="c-auth-card__title">{title}</h1> | |||
| <h2 className="c-auth-card__subtitle">{subtitle}</h2> | |||
| {children} | |||
| </SectionLoader> | |||
| </div> | |||
| ); | |||
| }; | |||
| AuthCard.propTypes = { | |||
| children: PropTypes.node, | |||
| title: PropTypes.string, | |||
| subtitle: PropTypes.string, | |||
| isLoading: PropTypes.bool, | |||
| }; | |||
| export default AuthCard; | |||
| @@ -1,187 +0,0 @@ | |||
| import React, { useEffect, useState, useRef } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import { ErrorMessage } from 'formik'; | |||
| import IconButton from '../IconButton/IconButton'; | |||
| import { ReactComponent as Search } from '../../assets/images/svg/search.svg'; | |||
| import { ReactComponent as EyeOn } from '../../assets/images/svg/eye-on.svg'; | |||
| import { ReactComponent as EyeOff } from '../../assets/images/svg/eye-off.svg'; | |||
| import { ReactComponent as CapsLock } from '../../assets/images/svg/caps-lock.svg'; | |||
| const BaseInputField = ({ | |||
| type, | |||
| label, | |||
| field, | |||
| form, | |||
| placeholder, | |||
| clearPlaceholderOnFocus = true, | |||
| isSearch, | |||
| className, | |||
| disabled, | |||
| centerText, | |||
| link, | |||
| errorMessage, | |||
| autoFocus, | |||
| isCapsLockOn, | |||
| ...props | |||
| }) => { | |||
| const [inputPlaceholder, setPlaceholder] = useState(placeholder); | |||
| const inputField = useRef(null); | |||
| useEffect(() => { | |||
| if (autoFocus) { | |||
| inputField.current.focus(); | |||
| } | |||
| }, [autoFocus, inputField]); | |||
| useEffect(() => { | |||
| if (errorMessage) { | |||
| form.setFieldError(field.name, errorMessage); | |||
| } | |||
| }, [errorMessage]); // eslint-disable-line | |||
| useEffect(() => { | |||
| setPlaceholder(placeholder); | |||
| }, [placeholder]); | |||
| const [inputType, setInputType] = useState('password'); | |||
| const passwordInput = type === 'password' ? ' c-input--password' : ''; | |||
| const showPassword = () => { | |||
| if (inputType === 'password') { | |||
| setInputType('text'); | |||
| } else { | |||
| setInputType('password'); | |||
| } | |||
| }; | |||
| // Nester Formik Field Names get bugged because of Undefined values, so i had to fix it like this | |||
| // If you ask why 0 and 1? I dont see a need for forms to be nested more then 2 levels? | |||
| const fieldName = field.name.split('.'); | |||
| const formError = | |||
| fieldName[0] && fieldName[1] | |||
| ? form.errors[fieldName[0]] && form.errors[fieldName[0]][fieldName[1]] | |||
| : form.errors[fieldName[0]]; | |||
| const formTouched = | |||
| fieldName[0] && fieldName[1] | |||
| ? form.touched[fieldName[0]] && form.touched[fieldName[0]][fieldName[1]] | |||
| : form.touched[fieldName[0]]; | |||
| function styles() { | |||
| let style = 'c-input'; | |||
| if (formError && formTouched) { | |||
| style += ` c-input--error`; | |||
| } | |||
| if (type === 'password') { | |||
| style += ` c-input--password`; | |||
| } | |||
| if (isSearch) { | |||
| style += ` c-input--search`; | |||
| } | |||
| if (centerText) { | |||
| style += ` c-input--center-text`; | |||
| } | |||
| if (type === 'number') { | |||
| style += ` c-input--demi-bold`; | |||
| } | |||
| if (className) { | |||
| style += ` ${className}`; | |||
| } | |||
| return style; | |||
| } | |||
| const additionalActions = () => { | |||
| if (!clearPlaceholderOnFocus) { | |||
| return null; | |||
| } | |||
| return { | |||
| onFocus: () => { | |||
| setPlaceholder(''); | |||
| }, | |||
| onBlur: (e) => { | |||
| setPlaceholder(placeholder); | |||
| field.onBlur(e); | |||
| }, | |||
| }; | |||
| }; | |||
| return ( | |||
| <div className={styles()}> | |||
| {!!label && ( | |||
| <label className="c-input__label" htmlFor={field.name}> | |||
| {label} | |||
| </label> | |||
| )} | |||
| {link && <div className="c-input__link">{link}</div>} | |||
| <div className="c-input__field-wrap"> | |||
| <input | |||
| ref={inputField} | |||
| type={type === 'password' ? inputType : type} | |||
| placeholder={inputPlaceholder} | |||
| disabled={disabled} | |||
| {...field} | |||
| {...props} | |||
| {...additionalActions()} | |||
| className="c-input__field" | |||
| /> | |||
| {!!isSearch && <Search className="c-input__icon" />} | |||
| {!!passwordInput && ( | |||
| <> | |||
| {isCapsLockOn && <CapsLock className="c-input__caps-lock" />} | |||
| <IconButton | |||
| onClick={() => { | |||
| showPassword(); | |||
| }} | |||
| className="c-input__icon" | |||
| > | |||
| {inputType === 'password' ? <EyeOff /> : <EyeOn />} | |||
| </IconButton> | |||
| </> | |||
| )} | |||
| </div> | |||
| <ErrorMessage name={field.name}> | |||
| {(errorMessage) => ( | |||
| <span className="c-input__error">{errorMessage}</span> | |||
| )} | |||
| </ErrorMessage> | |||
| </div> | |||
| ); | |||
| }; | |||
| BaseInputField.propTypes = { | |||
| type: PropTypes.string, | |||
| field: PropTypes.shape({ | |||
| name: PropTypes.string, | |||
| onFocus: PropTypes.func, | |||
| onBlur: PropTypes.func, | |||
| }), | |||
| form: PropTypes.shape({ | |||
| errors: PropTypes.shape({}), | |||
| setFieldError: PropTypes.func, | |||
| touched: PropTypes.shape({}), | |||
| }), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), | |||
| disabled: PropTypes.bool, | |||
| isSearch: PropTypes.bool, | |||
| className: PropTypes.string, | |||
| link: PropTypes.node, | |||
| errorMessage: PropTypes.string, | |||
| centerText: PropTypes.bool, | |||
| clearPlaceholderOnFocus: PropTypes.bool, | |||
| demiBold: PropTypes.bool, | |||
| touched: PropTypes.bool, | |||
| autoFocus: PropTypes.bool, | |||
| isCapsLockOn: PropTypes.bool, | |||
| }; | |||
| export default BaseInputField; | |||
| @@ -1,74 +0,0 @@ | |||
| import React, { useState } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import BaseInputField from './BaseInputField'; | |||
| import PasswordStrength from './PasswordStrength'; | |||
| const PasswordField = ({ | |||
| field, | |||
| form, | |||
| label, | |||
| placeholder, | |||
| disabled, | |||
| shouldTestPasswordStrength, | |||
| autoFocus, | |||
| ...props | |||
| }) => { | |||
| const [passwordValue, setPasswordValue] = useState(''); | |||
| const [isCapsLockOn, setIsCapsLockOn] = useState(false); | |||
| const onChange = (e) => { | |||
| if (shouldTestPasswordStrength) { | |||
| const { value } = e.target; | |||
| setPasswordValue(value); | |||
| } | |||
| field.onChange(e); | |||
| }; | |||
| const onKeyDown = (keyEvent) => { | |||
| if (keyEvent.getModifierState('CapsLock')) { | |||
| setIsCapsLockOn(true); | |||
| } else { | |||
| setIsCapsLockOn(false); | |||
| } | |||
| }; | |||
| return ( | |||
| <div className="c-password"> | |||
| <BaseInputField | |||
| type="password" | |||
| label={label} | |||
| placeholder={placeholder} | |||
| disabled={disabled} | |||
| form={form} | |||
| field={field} | |||
| {...props} | |||
| onChange={onChange} | |||
| autoFocus={autoFocus} | |||
| onKeyDown={onKeyDown} | |||
| isCapsLockOn={isCapsLockOn} | |||
| /> | |||
| {shouldTestPasswordStrength && ( | |||
| <PasswordStrength | |||
| passwordValue={passwordValue} | |||
| shouldTestPasswordStrength | |||
| /> | |||
| )} | |||
| </div> | |||
| ); | |||
| }; | |||
| PasswordField.propTypes = { | |||
| field: PropTypes.shape({ | |||
| onChange: PropTypes.func, | |||
| }), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.oneOfType([PropTypes.string, PropTypes.shape({})]), | |||
| placeholder: PropTypes.string, | |||
| disabled: PropTypes.bool, | |||
| shouldTestPasswordStrength: PropTypes.bool, | |||
| autoFocus: PropTypes.bool, | |||
| }; | |||
| export default PasswordField; | |||
| @@ -1,130 +0,0 @@ | |||
| import React, { useEffect, useRef, useState } from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import owasp from 'owasp-password-strength-test'; | |||
| import i18next from 'i18next'; | |||
| owasp.config({ | |||
| minOptionalTestsToPass: 3, | |||
| }); | |||
| const passwordStrengthOptions = [ | |||
| { | |||
| strength: 'weak', | |||
| color: '#FF5028', | |||
| }, | |||
| { | |||
| strength: 'average', | |||
| color: '#FDB942', | |||
| }, | |||
| { | |||
| strength: 'good', | |||
| color: '#06BEE7', | |||
| }, | |||
| { | |||
| strength: 'strong', | |||
| color: '#00876A', | |||
| }, | |||
| ]; | |||
| /** | |||
| * User must pass a required test and at least 3 optional. | |||
| * @param result - owasp result | |||
| * @returns {number} - index of password strength 0-3 | |||
| */ | |||
| function getPasswordStrengthIndex(result) { | |||
| // requirement for strong password is required test passed and at least 3 optional tests | |||
| if (result.strong) { | |||
| return 3; | |||
| } | |||
| if (!result.strong && result.optionalTestsPassed >= 3) { | |||
| return 2; | |||
| } | |||
| if (result.optionalTestsPassed <= 0) { | |||
| return 0; | |||
| } | |||
| return result.optionalTestsPassed - 1; | |||
| } | |||
| const PasswordStrength = ({ | |||
| shouldTestPasswordStrength, | |||
| passwordValue, | |||
| passwordStrengthTestsRequired, | |||
| }) => { | |||
| const strengthContainer = useRef(null); | |||
| const [passwordStrength, setPasswordStrength] = useState({ | |||
| width: 0, | |||
| color: 'red', | |||
| }); | |||
| const [error, setError] = useState(''); | |||
| useEffect(() => { | |||
| if (shouldTestPasswordStrength && passwordValue) { | |||
| const bBox = strengthContainer.current.getBoundingClientRect(); | |||
| const result = owasp.test(passwordValue); | |||
| const passwordStrengthIndex = getPasswordStrengthIndex(result); | |||
| const passwordOption = passwordStrengthOptions[passwordStrengthIndex]; | |||
| const width = !passwordValue | |||
| ? 0 | |||
| : (bBox.width * (passwordStrengthIndex + 1)) / | |||
| passwordStrengthTestsRequired; | |||
| setPasswordStrength({ width, color: passwordOption.color }); | |||
| const strength = i18next.t(`password.${passwordOption.strength}`); | |||
| setError(i18next.t('login.passwordStrength', { strength })); | |||
| } | |||
| }, [ | |||
| passwordValue, | |||
| shouldTestPasswordStrength, | |||
| passwordStrengthTestsRequired, | |||
| ]); | |||
| if (!shouldTestPasswordStrength || !passwordValue) { | |||
| return null; | |||
| } | |||
| const renderError = () => { | |||
| if (!error) { | |||
| return null; | |||
| } | |||
| return ( | |||
| <div | |||
| className="c-input--error" | |||
| style={{ | |||
| color: passwordStrength.color, | |||
| }} | |||
| > | |||
| {error} | |||
| </div> | |||
| ); | |||
| }; | |||
| return ( | |||
| <div ref={strengthContainer} className="c-password-strength__container"> | |||
| <div className="c-password-strength__line--wrapper"> | |||
| <div | |||
| className="c-password-strength__line" | |||
| style={{ | |||
| backgroundColor: passwordStrength.color, | |||
| width: passwordStrength.width, | |||
| }} | |||
| /> | |||
| </div> | |||
| {renderError()} | |||
| </div> | |||
| ); | |||
| }; | |||
| PasswordStrength.propTypes = { | |||
| shouldTestPasswordStrength: PropTypes.bool, | |||
| passwordValue: PropTypes.string, | |||
| passwordStrengthTestsRequired: PropTypes.number, | |||
| }; | |||
| PasswordStrength.defaultProps = { | |||
| passwordStrengthTestsRequired: 4, | |||
| }; | |||
| export default PasswordStrength; | |||
| @@ -1,72 +0,0 @@ | |||
| import React from 'react'; | |||
| import PropTypes from 'prop-types'; | |||
| import BaseInputField from './BaseInputField'; | |||
| import { | |||
| BACKSPACE_KEYCODE, | |||
| TAB_KEYCODE, | |||
| RIGHT_ARROW_KEYCODE, | |||
| LEFT_ARROW_KEYCODE, | |||
| } from '../../constants/keyCodeConstants'; | |||
| const TextField = ({ | |||
| field, | |||
| form, | |||
| label, | |||
| placeholder, | |||
| disabled, | |||
| centerText, | |||
| autoFocus, | |||
| preventAllExceptNumbers, | |||
| ...props | |||
| }) => { | |||
| const onKeydownHandler = (event) => { | |||
| if (preventAllExceptNumbers) { | |||
| if ( | |||
| event.keyCode === BACKSPACE_KEYCODE || | |||
| event.keyCode === TAB_KEYCODE || | |||
| event.keyCode === RIGHT_ARROW_KEYCODE || | |||
| event.keyCode === LEFT_ARROW_KEYCODE | |||
| ) { | |||
| return; | |||
| } | |||
| if ( | |||
| (event.keyCode < 58 && event.keyCode > 47) || | |||
| (event.keyCode < 106 && event.keyCode > 95) | |||
| ) { | |||
| return; | |||
| } | |||
| event.preventDefault(); | |||
| } | |||
| }; | |||
| return ( | |||
| <BaseInputField | |||
| autoFocus={autoFocus} | |||
| type="text" | |||
| label={label} | |||
| placeholder={placeholder} | |||
| disabled={disabled} | |||
| form={form} | |||
| field={field} | |||
| centerText={centerText} | |||
| {...props} | |||
| onKeyDown={(event) => onKeydownHandler(event)} | |||
| /> | |||
| ); | |||
| }; | |||
| TextField.propTypes = { | |||
| field: PropTypes.shape({}), | |||
| form: PropTypes.shape({}), | |||
| label: PropTypes.string, | |||
| placeholder: PropTypes.string, | |||
| disabled: PropTypes.bool, | |||
| centerText: PropTypes.bool, | |||
| autoFocus: PropTypes.bool, | |||
| preventAllExceptNumbers: PropTypes.bool, | |||
| }; | |||
| export default TextField; | |||
| @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | |||
| import { Typography } from '@mui/material'; | |||
| const ErrorMessageComponent = ({ error }) => ( | |||
| <Typography variant="body1" color="error" my={2}> | |||
| <Typography variant="body1" color="error" my={2} data-testid="error-message"> | |||
| {error} | |||
| </Typography> | |||
| ); | |||
| @@ -20,9 +20,6 @@ const TableViewPage = ({ | |||
| setPage, | |||
| page, | |||
| search, | |||
| // setIsCVDisplayed, | |||
| // isCVDisplayed, | |||
| // setLinkToCV, | |||
| }) => { | |||
| const dispatch = useDispatch(); | |||
| const candidates = useSelector(selectCandidates); | |||
| @@ -212,9 +209,6 @@ TableViewPage.propTypes = { | |||
| setPage: PropTypes.func, | |||
| page: PropTypes.number, | |||
| search: PropTypes.string, | |||
| // setIsCVDisplayed: PropTypes.func, | |||
| // isCVDisplayed: PropTypes.bool, | |||
| // setLinkToCV: PropTypes.func, | |||
| }; | |||
| export default TableViewPage; | |||
| @@ -32,7 +32,10 @@ import { Visibility, VisibilityOff } from "@mui/icons-material"; | |||
| import Backdrop from "../../components/MUI/BackdropComponent"; | |||
| import ErrorMessage from "../../components/MUI/ErrorMessageComponent"; | |||
| import { selectIsLoadingByActionType } from "../../store/selectors/loadingSelectors"; | |||
| import { LOGIN_USER_LOADING, LOGIN_GOOGLE_USER_LOADING } from "../../store/actions/login/loginActionConstants"; | |||
| import { | |||
| LOGIN_USER_LOADING, | |||
| LOGIN_GOOGLE_USER_LOADING, | |||
| } from "../../store/actions/login/loginActionConstants"; | |||
| import { authScopeStringGetHelper } from "../../util/helpers/authScopeHelpers"; | |||
| import { JWT_TOKEN } from "../../constants/localStorage"; | |||
| const LoginPage = ({ history }) => { | |||
| @@ -47,7 +50,7 @@ const LoginPage = ({ history }) => { | |||
| useEffect(() => { | |||
| function redirectClient() { | |||
| let token = authScopeStringGetHelper(JWT_TOKEN); | |||
| let token = authScopeStringGetHelper(JWT_TOKEN); | |||
| if (!token) { | |||
| return; | |||
| } | |||
| @@ -73,7 +76,7 @@ const LoginPage = ({ history }) => { | |||
| const isLoading = useSelector( | |||
| selectIsLoadingByActionType(LOGIN_USER_LOADING) | |||
| ); | |||
| const isGoogleLoading = useSelector( | |||
| selectIsLoadingByActionType(LOGIN_GOOGLE_USER_LOADING) | |||
| ); | |||
| @@ -104,21 +107,20 @@ const LoginPage = ({ history }) => { | |||
| setIsInit(false); | |||
| return; | |||
| } | |||
| try{ | |||
| google.accounts.id.initialize({ | |||
| client_id: | |||
| "734219382849-nvnulsu7ibfl4bk3n164bgb7c1h5dgca.apps.googleusercontent.com", | |||
| callback: handleCallbackResponse, | |||
| }); | |||
| google.accounts.id.renderButton(document.getElementById("signInDiv"), { | |||
| theme: "outline", | |||
| size: "large", | |||
| width: "250", | |||
| }); | |||
| } | |||
| catch{ | |||
| console.log('Google login is not initialized'); | |||
| } | |||
| try { | |||
| google.accounts.id.initialize({ | |||
| client_id: | |||
| "734219382849-nvnulsu7ibfl4bk3n164bgb7c1h5dgca.apps.googleusercontent.com", | |||
| callback: handleCallbackResponse, | |||
| }); | |||
| google.accounts.id.renderButton(document.getElementById("signInDiv"), { | |||
| theme: "outline", | |||
| size: "large", | |||
| width: "250", | |||
| }); | |||
| } catch { | |||
| console.log("Google login is not initialized"); | |||
| } | |||
| }, [isInit]); | |||
| // const handleGoogleSubmit = (values) => { | |||
| @@ -178,7 +180,10 @@ const LoginPage = ({ history }) => { | |||
| onSubmit={formik.handleSubmit} | |||
| sx={{ position: "relative" }} | |||
| > | |||
| <Backdrop position="absolute" isLoading={isLoading || isGoogleLoading} /> | |||
| <Backdrop | |||
| position="absolute" | |||
| isLoading={isLoading || isGoogleLoading} | |||
| /> | |||
| <TextField | |||
| name="username" | |||
| label={t("common.labelUsername")} | |||
| @@ -189,6 +194,7 @@ const LoginPage = ({ history }) => { | |||
| helperText={formik.touched.username && formik.errors.username} | |||
| autoFocus | |||
| fullWidth | |||
| inputProps={{ "data-testid": "username-input" }} | |||
| /> | |||
| <TextField | |||
| className="rounded-input" | |||
| @@ -201,6 +207,7 @@ const LoginPage = ({ history }) => { | |||
| error={formik.touched.password && Boolean(formik.errors.password)} | |||
| helperText={formik.touched.password && formik.errors.password} | |||
| fullWidth | |||
| inputProps={{"data-testid":"password-input"}} | |||
| InputProps={{ | |||
| endAdornment: ( | |||
| <InputAdornment position="end"> | |||
| @@ -237,7 +244,7 @@ const LoginPage = ({ history }) => { | |||
| > | |||
| {t("login.logIn")} | |||
| </Button> | |||
| <div className="flex-center"> | |||
| <div className="flex-center" data-testid="separator"> | |||
| <div className="hr hr-s"></div> | |||
| <div className="hr-mid">{t("common.or")}</div> | |||
| <div className="hr hr-e"></div> | |||
| @@ -255,7 +262,7 @@ const LoginPage = ({ history }) => { | |||
| </Button> */} | |||
| <div id="signInDiv"></div> | |||
| </div> | |||
| <div className="flex-center"> | |||
| <div className="flex-center" data-testid="dilig-logo"> | |||
| <img src={DiligLogo} style={{ margin: "70px auto 0px auto" }} /> | |||
| </div> | |||
| </Box> | |||
| @@ -1,5 +0,0 @@ | |||
| export const LOAD_DATA = 'LOAD_DATA'; | |||
| export const UPDATE_PAGE = 'UPDATE_PAGE'; | |||
| export const UPDATE_ITEMS_PER_PAGE = 'UPDATE_ITEMS_PER_PAGE'; | |||
| export const UPDATE_FILTER = 'UPDATE_FILTER'; | |||
| export const UPDATE_SORT = 'UPDATE_SORT'; | |||
| @@ -1,32 +0,0 @@ | |||
| import { | |||
| LOAD_DATA, | |||
| UPDATE_PAGE, | |||
| UPDATE_ITEMS_PER_PAGE, | |||
| UPDATE_FILTER, | |||
| UPDATE_SORT | |||
| } from './randomDataActionConstants'; | |||
| export const loadData = (payload) => ({ | |||
| type: LOAD_DATA, | |||
| payload, | |||
| }); | |||
| export const updatePage = (payload) => ({ | |||
| type: UPDATE_PAGE, | |||
| payload, | |||
| }); | |||
| export const updateItemsPerPage = (payload) => ({ | |||
| type: UPDATE_ITEMS_PER_PAGE, | |||
| payload, | |||
| }); | |||
| export const updateFilter = (payload) => ({ | |||
| type: UPDATE_FILTER, | |||
| payload, | |||
| }) | |||
| export const updateSort = (payload) => ({ | |||
| type: UPDATE_SORT, | |||
| payload, | |||
| }) | |||
| @@ -2,7 +2,6 @@ import { combineReducers } from "redux"; | |||
| import loginReducer from "./login/loginReducer"; | |||
| import loadingReducer from "./loading/loadingReducer"; | |||
| import userReducer from "./user/userReducer"; | |||
| import randomDataReducer from "./randomData/randomDataReducer"; | |||
| import usersReducer from "./user/usersReducer"; | |||
| import candidateReducer from "./candidate/candidateReducer"; | |||
| import adsReducer from "./ad/adsReducer"; | |||
| @@ -39,7 +38,6 @@ export default combineReducers({ | |||
| login: loginReducer, | |||
| user: userReducer, | |||
| loading: loadingReducer, | |||
| randomData: randomDataReducer, | |||
| users: usersReducer, | |||
| candidate: candidateReducer, | |||
| ads: adsReducer, | |||
| @@ -1,103 +0,0 @@ | |||
| import createReducer from '../../utils/createReducer'; | |||
| import { | |||
| LOAD_DATA, | |||
| UPDATE_PAGE, | |||
| UPDATE_ITEMS_PER_PAGE, | |||
| UPDATE_FILTER, | |||
| UPDATE_SORT, | |||
| } from '../../actions/randomData/randomDataActionConstants.js'; | |||
| import generate from '../../../util/helpers/randomData'; | |||
| const initialState = { | |||
| items: [], | |||
| filteredItems: [], | |||
| count: 0, | |||
| page: 0, | |||
| itemsPerPage: 12, | |||
| filter: '', | |||
| sort: '', | |||
| }; | |||
| export default createReducer( | |||
| { | |||
| [LOAD_DATA]: loadRandomData, | |||
| [UPDATE_PAGE]: updatePage, | |||
| [UPDATE_ITEMS_PER_PAGE]: updateItemsPerPage, | |||
| [UPDATE_FILTER]: updateFilter, | |||
| [UPDATE_SORT]: updateSort, | |||
| }, | |||
| initialState | |||
| ); | |||
| function loadRandomData(state, action) { | |||
| const count = action.payload; | |||
| const items = generate(count); | |||
| return { | |||
| ...state, | |||
| items, | |||
| filteredItems: items, | |||
| count: items.length, | |||
| }; | |||
| } | |||
| function updatePage(state, action) { | |||
| const page = action.payload; | |||
| return { | |||
| ...state, | |||
| page, | |||
| }; | |||
| } | |||
| function updateItemsPerPage(state, action) { | |||
| const itemsPerPage = action.payload; | |||
| return { | |||
| ...state, | |||
| itemsPerPage, | |||
| }; | |||
| } | |||
| function updateFilter(state, action) { | |||
| const filter = action.payload; | |||
| const filteredItems = filter | |||
| ? state.items.filter((item) => item.name.toLowerCase().includes(filter.toLowerCase())) : state.items; | |||
| return { | |||
| ...state, | |||
| filter, | |||
| filteredItems, | |||
| count: filteredItems.length, | |||
| }; | |||
| } | |||
| function updateSort(state, action) { | |||
| const sort = action.payload; | |||
| const [field, direction] = sort.split('-'); | |||
| const sortDirection = direction === 'asc' ? 1 : -1; | |||
| const dataItems = state.filteredItems.length | |||
| ? state.filteredItems | |||
| : state.items; | |||
| const sorted = dataItems.sort((a, b) => { | |||
| if (a[field] > b[field]) { | |||
| return sortDirection; | |||
| } | |||
| if (b[field] > a[field]) { | |||
| return sortDirection * -1; | |||
| } | |||
| return 0; | |||
| }); | |||
| const filteredItems = state.filteredItems.length | |||
| ? sorted | |||
| : state.filteredItems; | |||
| return { | |||
| ...state, | |||
| sort, | |||
| filteredItems, | |||
| }; | |||
| } | |||