Преглед изворни кода

additional unit tests on frontend

pull/173/head
Dzenis Hadzifejzovic пре 3 година
родитељ
комит
89d3bf28fa

+ 173
- 0
src/__tests__/UITests/loginPageUI.test.js Прегледај датотеку

@@ -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);
// });
});

+ 27
- 1
src/__tests__/UITests/tableViewPageUI.test.js Прегледај датотеку

@@ -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 };

+ 0
- 24
src/components/AuthCards/AuthCard.js Прегледај датотеку

@@ -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;

+ 0
- 187
src/components/InputFields/BaseInputField.js Прегледај датотеку

@@ -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;

+ 0
- 74
src/components/InputFields/PasswordField.js Прегледај датотеку

@@ -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;

+ 0
- 130
src/components/InputFields/PasswordStrength.js Прегледај датотеку

@@ -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;

+ 0
- 72
src/components/InputFields/TextField.js Прегледај датотеку

@@ -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;

+ 1
- 1
src/components/MUI/ErrorMessageComponent.js Прегледај датотеку

@@ -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>
);

+ 0
- 6
src/pages/CandidatesPage/TableViewPage.js Прегледај датотеку

@@ -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;

+ 28
- 21
src/pages/LoginPage/LoginPageMUI.js Прегледај датотеку

@@ -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>

+ 0
- 5
src/store/actions/randomData/randomDataActionConstants.js Прегледај датотеку

@@ -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';

+ 0
- 32
src/store/actions/randomData/randomDataActions.js Прегледај датотеку

@@ -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,
})

+ 0
- 2
src/store/reducers/index.js Прегледај датотеку

@@ -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,

+ 0
- 103
src/store/reducers/randomData/randomDataReducer.js Прегледај датотеку

@@ -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,
};
}

Loading…
Откажи
Сачувај