| @@ -13,7 +13,7 @@ | |||
| "@mui/icons-material": "^5.0.5", | |||
| "@mui/material": "^5.0.6", | |||
| "@mui/x-data-grid": "^5.0.1", | |||
| "@mui/x-date-pickers": "^5.0.9", | |||
| "@mui/x-date-pickers": "^5.0.10", | |||
| "@reduxjs/toolkit": "^1.5.1", | |||
| "@testing-library/jest-dom": "^5.13.0", | |||
| "@testing-library/user-event": "^12.8.3", | |||
| @@ -3054,9 +3054,9 @@ | |||
| } | |||
| }, | |||
| "node_modules/@mui/x-date-pickers": { | |||
| "version": "5.0.9", | |||
| "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.9.tgz", | |||
| "integrity": "sha512-PM3RU8MiwDVi+dSDGJ7ylI0hCe79wSCDfrjghS8ApGGFn/n87S8pUZxsZ5czw3mVRN6VfS2C19peo4nM1Tx+nA==", | |||
| "version": "5.0.10", | |||
| "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.10.tgz", | |||
| "integrity": "sha512-k+SKBqlpYsY49JVs7PORDvnfoXw9nJELfImR/3jTgHqP8hQQ6loFjB9ZFvc5NjV4Jf2NIJFmdcp6g8cO31Wmbg==", | |||
| "dependencies": { | |||
| "@babel/runtime": "^7.18.9", | |||
| "@date-io/core": "^2.15.0", | |||
| @@ -26924,9 +26924,9 @@ | |||
| } | |||
| }, | |||
| "@mui/x-date-pickers": { | |||
| "version": "5.0.9", | |||
| "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.9.tgz", | |||
| "integrity": "sha512-PM3RU8MiwDVi+dSDGJ7ylI0hCe79wSCDfrjghS8ApGGFn/n87S8pUZxsZ5czw3mVRN6VfS2C19peo4nM1Tx+nA==", | |||
| "version": "5.0.10", | |||
| "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.10.tgz", | |||
| "integrity": "sha512-k+SKBqlpYsY49JVs7PORDvnfoXw9nJELfImR/3jTgHqP8hQQ6loFjB9ZFvc5NjV4Jf2NIJFmdcp6g8cO31Wmbg==", | |||
| "requires": { | |||
| "@babel/runtime": "^7.18.9", | |||
| "@date-io/core": "^2.15.0", | |||
| @@ -8,7 +8,7 @@ | |||
| "@mui/icons-material": "^5.0.5", | |||
| "@mui/material": "^5.0.6", | |||
| "@mui/x-data-grid": "^5.0.1", | |||
| "@mui/x-date-pickers": "^5.0.9", | |||
| "@mui/x-date-pickers": "^5.0.10", | |||
| "@reduxjs/toolkit": "^1.5.1", | |||
| "@testing-library/jest-dom": "^5.13.0", | |||
| "@testing-library/user-event": "^12.8.3", | |||
| @@ -0,0 +1,91 @@ | |||
| import * as redux from "react-redux"; | |||
| import store from "../../store"; | |||
| import { Router } from "react-router-dom"; | |||
| import history from "../../store/utils/history"; | |||
| import { mockState } from "../../mockState"; | |||
| import { render } from "@testing-library/react"; | |||
| import CandidatesPage from "../../pages/CandidatesPage/CandidatesPage"; | |||
| import * as api from "../../request/technologiesRequest"; | |||
| import { runSaga } from "redux-saga"; | |||
| import { FETCH_TECHNOLOGIES_REQ } from "../../store/actions/technologies/technologiesActionConstants"; | |||
| import { getTechnologies } from "../../store/saga/technologiesSaga"; | |||
| import { | |||
| setTechnologies, | |||
| setTechnologiesError, | |||
| } from "../../store/actions/technologies/technologiesActions"; | |||
| describe("CandidatesPage render tests", () => { | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <CandidatesPage /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| let spyOnUseDispatch; | |||
| let mockDispatch; | |||
| beforeEach(() => { | |||
| // Mock useSelector hook | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector.mockReturnValueOnce(mockState.technologies.technologies); | |||
| // Mock useDispatch hook | |||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||
| // Mock dispatch function returned from useDispatch | |||
| mockDispatch = jest.fn(); | |||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should dispatch get technologies request when rendered", () => { | |||
| render(cont); | |||
| expect(mockDispatch).toHaveBeenCalledWith({ | |||
| type: FETCH_TECHNOLOGIES_REQ, | |||
| }); | |||
| }); | |||
| it("should load and handle techonologies in case of success", async () => { | |||
| const dispatchedActions = []; | |||
| const mockedCall = { data: mockState.technologies.technologies }; | |||
| api.getAllTechnologies = jest.fn(() => Promise.resolve(mockedCall)); | |||
| const fakeStore = { | |||
| getState: () => mockState.technologies.technologies, | |||
| dispatch: (action) => dispatchedActions.push(action), | |||
| }; | |||
| await runSaga(fakeStore, getTechnologies).done; | |||
| expect(api.getAllTechnologies.mock.calls.length).toBe(1); | |||
| expect(dispatchedActions).toContainEqual(setTechnologies(mockedCall.data)); | |||
| }); | |||
| // it("should handle technologies load errors in case of failure", async () => { | |||
| // const dispatchedActions = []; | |||
| // const error = { | |||
| // response: { | |||
| // data: { message: mockState.technologies.fetchTecnologiesErrorMessage }, | |||
| // }, | |||
| // }; | |||
| // api.getAllTechnologies = jest.fn(() => Promise.reject(error)); | |||
| // const fakeStore = { | |||
| // getState: () => mockState.technologies.technologies, | |||
| // dispatch: (action) => dispatchedActions.push(action), | |||
| // }; | |||
| // await runSaga(fakeStore, getTechnologies).done; | |||
| // expect(api.getAllTechnologies.mock.calls.length).toBe(1); | |||
| // expect(dispatchedActions).toContainEqual( | |||
| // setTechnologiesError(error.response.data.message) | |||
| // ); | |||
| // }); | |||
| }); | |||
| @@ -0,0 +1,69 @@ | |||
| import { render, screen, fireEvent } from "@testing-library/react"; | |||
| import * as redux from "react-redux"; | |||
| import CandidatesPage from "../../pages/CandidatesPage/CandidatesPage"; | |||
| import store from "../../store"; | |||
| import { mockState } from "../../mockState"; | |||
| import { Router } from "react-router-dom"; | |||
| import history from "../../store/utils/history"; | |||
| describe("CandidatesPage render tests", () => { | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <CandidatesPage /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| beforeEach(() => { | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector.mockReturnValueOnce(mockState.technologies.technologies); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should render", () => { | |||
| render(cont); | |||
| expect(screen.getByTestId("candidates-page")).toBeDefined(); | |||
| }); | |||
| it("Should render button responsible for showing different components inside page", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("candidate-btn")[0]).toBeDefined(); | |||
| }); | |||
| it("should be rendered button which is used for showing input responsible for searching by name", () => { | |||
| const { container } = render(cont); | |||
| expect( | |||
| container.getElementsByClassName("candidate-btn")[1] | |||
| ).toBeDefined(); | |||
| }); | |||
| it("Should render filter button", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("candidate-btn")[2]).toBeDefined(); | |||
| }); | |||
| it("input for searching by name should not be shown when component is initialy rendered", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("proba")[0].style.visibility).toBe( | |||
| "hidden" | |||
| ); | |||
| }); | |||
| // it("input for searching by name should be shown after clicking button for first time",() => { | |||
| // const { container } = render(cont); | |||
| // fireEvent.click(container.getElementsByClassName("candidate-btn")[1]) | |||
| // expect(container.getElementsByClassName("proba")[0].style.visibility).toBe('vissible'); | |||
| // }) | |||
| // it("should be rendered AdsCandidatesPage component when button for switching to another view is clicked for the first time",() => { | |||
| // const { container } = render(cont); | |||
| // fireEvent.click(container.getElementsByClassName("candidate-btn")[0]) | |||
| // expect(container.getElementsByClassName("ads-candidates-container")[0]).toBeDefined(); | |||
| // }) | |||
| }); | |||
| @@ -219,7 +219,7 @@ | |||
| background-color: white; | |||
| position: absolute; | |||
| top: 112px; | |||
| left: 800px; | |||
| right: 70px; | |||
| z-index: 1000; | |||
| } | |||
| @@ -229,6 +229,22 @@ | |||
| font-style: italic; | |||
| } | |||
| .cls1 .slick-track{ | |||
| margin-left: 30px !important; | |||
| } | |||
| .cls2 .slick-track{ | |||
| margin-left: 20 !important; | |||
| } | |||
| .cls3 .slick-track{ | |||
| margin-left: 20 !important; | |||
| } | |||
| .cls4 .slick-track{ | |||
| margin-left: 45px !important; | |||
| } | |||
| @media only screen and (max-width: 600px) { | |||
| .ads-candidates-title { | |||
| font-size: 18px; | |||
| @@ -1,20 +1,20 @@ | |||
| import React, { useRef } from 'react'; | |||
| import PropType from 'prop-types'; | |||
| import React, { useRef } from "react"; | |||
| import PropType from "prop-types"; | |||
| const IconButton = ({ children, onClick, className }) => { | |||
| const buttonRef = useRef(null); | |||
| function handleClick(e) { | |||
| e.stopPropagation() | |||
| e.stopPropagation(); | |||
| buttonRef.current.blur(); | |||
| if (typeof onClick === 'function') { | |||
| if (typeof onClick === "function") { | |||
| onClick(); | |||
| } | |||
| } | |||
| return ( | |||
| <button | |||
| data-testid="btn-testid" | |||
| data-testid="btn-testid" | |||
| type="button" | |||
| ref={buttonRef} | |||
| onClick={handleClick} | |||
| @@ -1,4 +1,102 @@ | |||
| export const mockState = { | |||
| technologies: { | |||
| technologies: [ | |||
| { | |||
| technologyId: 1, | |||
| technologyType: "Backend", | |||
| name: ".NET", | |||
| isChecked:false | |||
| }, | |||
| { | |||
| technologyId: 2, | |||
| technologyType: "Other", | |||
| name: "Git", | |||
| isChecked:false | |||
| }, | |||
| { | |||
| technologyId: 3, | |||
| technologyType: "Frontend", | |||
| name: "HTML/CSS", | |||
| isChecked:false | |||
| }, | |||
| ], | |||
| fetchTecnologiesErrorMessage: "Server error", | |||
| }, | |||
| candidates: { | |||
| total: 2, | |||
| items: [ | |||
| { | |||
| applicantId: 1, | |||
| firstName: "Dzenis", | |||
| lastName: "Hadzifejzovic", | |||
| position: ".NET Developer", | |||
| dateOfApplication: "2022-02-11T00:00:00", | |||
| cv: "link", | |||
| email: "dzenis@gmail.com", | |||
| phoneNumber: "774567", | |||
| linkedlnLink: "link1", | |||
| githubLink: "link2", | |||
| bitBucketLink: null, | |||
| experience: 1, | |||
| applicationChannel: null, | |||
| typeOfEmployment: "Posao", | |||
| technologyApplicants: [ | |||
| { | |||
| technology: { | |||
| technologyId: 1, | |||
| technologyType: "Backend", | |||
| name: ".NET", | |||
| }, | |||
| }, | |||
| ], | |||
| comments: [], | |||
| ads: [ | |||
| { | |||
| id: 10, | |||
| title: ".NET Intern", | |||
| minimumExperience: 2, | |||
| createdAt: "2022-11-14T08:23:00.772", | |||
| expiredAt: "2024-12-06T09:53:42.439572", | |||
| keyResponsibilities: "KR|KR|KR|KR", | |||
| requirements: "RQ|RQ|RQ|RQ", | |||
| offer: "OF|OF|OF|OF", | |||
| technologies: [], | |||
| workHour: "FullTime", | |||
| employmentType: "Intership", | |||
| }, | |||
| ], | |||
| selectionProcesses: [], | |||
| }, | |||
| { | |||
| applicantId: 2, | |||
| firstName: "Ermin", | |||
| lastName: "Bronja", | |||
| position: ".NET Developer", | |||
| dateOfApplication: "2022-02-11T00:00:00", | |||
| cv: "link", | |||
| email: "ermin@gmail.com", | |||
| phoneNumber: "342424", | |||
| linkedlnLink: "link1", | |||
| githubLink: "link2", | |||
| bitBucketLink: null, | |||
| experience: 3, | |||
| applicationChannel: null, | |||
| typeOfEmployment: "Posao", | |||
| technologyApplicants: [ | |||
| { | |||
| technology: { | |||
| technologyId: 1, | |||
| technologyType: "Backend", | |||
| name: ".NET", | |||
| }, | |||
| }, | |||
| ], | |||
| comments: [], | |||
| ads: [], | |||
| selectionProcesses: [], | |||
| }, | |||
| ], | |||
| }, | |||
| users: { | |||
| users: [ | |||
| { | |||
| @@ -28,7 +126,7 @@ export const mockState = { | |||
| toggleEnableErrorMessage: "", | |||
| }, | |||
| selections: { | |||
| process:{doneProcess: false}, | |||
| process: { doneProcess: false }, | |||
| processes: [ | |||
| { | |||
| id: 1, | |||
| @@ -103,7 +201,7 @@ export const mockState = { | |||
| firstName: "Meris", | |||
| lastName: "Ahmatovic", | |||
| }, | |||
| } | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -143,14 +241,15 @@ export const mockState = { | |||
| }, | |||
| }, | |||
| ], | |||
| } | |||
| }, | |||
| ], | |||
| selected: {}, | |||
| fetchSelectionsErrorMessage: "Server error", | |||
| statuses:[ | |||
| {"isChecked":false,"name":"Zakazan"}, | |||
| {"isChecked":false,"name":"Odrađen"}, | |||
| {"isChecked":false,"name":"Čeka na zakazivanje"}, | |||
| {"isChecked":false,"name":"Čeka se odgovor"}] | |||
| } | |||
| statuses: [ | |||
| { isChecked: false, name: "Zakazan" }, | |||
| { isChecked: false, name: "Odrađen" }, | |||
| { isChecked: false, name: "Čeka na zakazivanje" }, | |||
| { isChecked: false, name: "Čeka se odgovor" }, | |||
| ], | |||
| }, | |||
| }; | |||
| @@ -65,7 +65,7 @@ const AdsCandidatesPage = ({ history, search }) => { | |||
| settings: { | |||
| slidesToShow: 1, | |||
| slidesToScroll: 1, | |||
| initialSlide:0 | |||
| initialSlide: 0, | |||
| }, | |||
| }, | |||
| ], | |||
| @@ -91,6 +91,14 @@ const AdsCandidatesPage = ({ history, search }) => { | |||
| getRef(index.toString()).current.slickNext(); | |||
| }; | |||
| const filterByName = (adCandidates) => { | |||
| return adCandidates.applicants.filter((candidate) => | |||
| (candidate.firstName + " " + candidate.lastName) | |||
| .toLowerCase() | |||
| .includes(search.toLowerCase()) | |||
| ); | |||
| }; | |||
| return ( | |||
| <div className="ads-candidates-container top-cnd"> | |||
| {adsCandidates.map((adCandidates, index) => ( | |||
| @@ -102,11 +110,12 @@ const AdsCandidatesPage = ({ history, search }) => { | |||
| | {adCandidates.nubmerOfApplicants} prijavljenih | |||
| </p> | |||
| </div> | |||
| <div | |||
| className="ads-candidates-slider" | |||
| > | |||
| {adCandidates.applicants.length > 3 && ( | |||
| <div className="active-ads-ads-arrows" style={matches ? {marginLeft:36} : {marginLeft:0}}> | |||
| <div className="ads-candidates-slider"> | |||
| {filterByName(adCandidates).length > 3 && ( | |||
| <div | |||
| className="active-ads-ads-arrows" | |||
| style={matches ? { marginLeft: 36 } : { marginLeft: 0 }} | |||
| > | |||
| <button onClick={() => activeAdsArrowLeftHandler(index)}> | |||
| <img src={arrow_left} alt="arrow-left" /> | |||
| </button> | |||
| @@ -118,23 +127,24 @@ const AdsCandidatesPage = ({ history, search }) => { | |||
| <Slider | |||
| {...settings} | |||
| ref={setRef(index.toString())} | |||
| style={{ width: "100%",marginLeft:adCandidates.applicants.length > 3 ? (matches ? 30 : 20) : (matches ? 30 : 66) }} | |||
| className='left-move-candidateAd-page' | |||
| className={`left-move-candidateAd-page ${ | |||
| matches | |||
| ? filterByName(adCandidates).length > 3 | |||
| ? "cls1" | |||
| : "cls2" | |||
| : filterByName(adCandidates).length > 3 | |||
| ? "cls3" | |||
| : "cls4" | |||
| }`} | |||
| > | |||
| {adCandidates.applicants | |||
| .filter((candidate) => | |||
| (candidate.firstName + " " + candidate.lastName) | |||
| .toLowerCase() | |||
| .includes(search.toLowerCase()) | |||
| ) | |||
| .map((candidate, index) => ( | |||
| <CandidateCard | |||
| key={index} | |||
| candidate={candidate} | |||
| history={history} | |||
| /> | |||
| ))} | |||
| {adCandidates.applicants.length <= 4 && | |||
| {filterByName(adCandidates).map((candidate, index) => ( | |||
| <CandidateCard | |||
| key={index} | |||
| candidate={candidate} | |||
| history={history} | |||
| /> | |||
| ))} | |||
| {filterByName(adCandidates).length <= 4 && | |||
| getDummyCandidates(adCandidates.applicants.length)} | |||
| </Slider> | |||
| </div> | |||
| @@ -55,7 +55,7 @@ const CandidatesPage = ({ history }) => { | |||
| onChange={(e) => setSearch(e.target.value)} | |||
| className="candidate-search-field" | |||
| onClick={stopPropagation} | |||
| style={{ zIndex: 1000 }} | |||
| role="input" | |||
| /> | |||
| </div> | |||
| ); | |||
| @@ -64,6 +64,7 @@ const CandidatesPage = ({ history }) => { | |||
| <div | |||
| className="main-candidates-container" | |||
| onClick={() => setIsSearchFieldVisible(false)} | |||
| data-testid="candidates-page" | |||
| > | |||
| <CandidateFilters | |||
| open={toggleFiltersDrawer} | |||
| @@ -84,7 +85,7 @@ const CandidatesPage = ({ history }) => { | |||
| </p> | |||
| )} | |||
| <div style={{ postion: "relative" }}> | |||
| <Fade in={isSearchFieldVisible} timeout={500}> | |||
| <Fade in={isSearchFieldVisible} timeout={500} className="proba"> | |||
| {input} | |||
| </Fade> | |||
| <Fade in={isSearchFieldVisible} timeout={500}> | |||
| @@ -92,7 +93,7 @@ const CandidatesPage = ({ history }) => { | |||
| style={{ | |||
| position: "absolute", | |||
| zIndex: 10000, | |||
| marginLeft: 300, | |||
| right: 95, | |||
| marginTop: 15, | |||
| }} | |||
| > | |||
| @@ -2,4 +2,14 @@ | |||
| // allows you to do things like: | |||
| // expect(element).toHaveTextContent(/react/i) | |||
| // learn more: https://github.com/testing-library/jest-dom | |||
| import '@testing-library/jest-dom'; | |||
| import "@testing-library/jest-dom"; | |||
| window.matchMedia = | |||
| window.matchMedia || | |||
| function () { | |||
| return { | |||
| matches: false, | |||
| addListener: function () {}, | |||
| removeListener: function () {}, | |||
| }; | |||
| }; | |||
| @@ -1760,10 +1760,10 @@ | |||
| "prop-types" "^15.8.1" | |||
| "reselect" "^4.1.6" | |||
| "@mui/x-date-pickers@^5.0.9": | |||
| "integrity" "sha512-PM3RU8MiwDVi+dSDGJ7ylI0hCe79wSCDfrjghS8ApGGFn/n87S8pUZxsZ5czw3mVRN6VfS2C19peo4nM1Tx+nA==" | |||
| "resolved" "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.9.tgz" | |||
| "version" "5.0.9" | |||
| "@mui/x-date-pickers@^5.0.10": | |||
| "integrity" "sha512-k+SKBqlpYsY49JVs7PORDvnfoXw9nJELfImR/3jTgHqP8hQQ6loFjB9ZFvc5NjV4Jf2NIJFmdcp6g8cO31Wmbg==" | |||
| "resolved" "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-5.0.10.tgz" | |||
| "version" "5.0.10" | |||
| dependencies: | |||
| "@babel/runtime" "^7.18.9" | |||
| "@date-io/core" "^2.15.0" | |||