| @@ -0,0 +1,133 @@ | |||
| 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 * as api from "../../request/candidatesRequest"; | |||
| import { runSaga } from "redux-saga"; | |||
| import { CANDIDATE_FETCH } from "../../store/actions/candidate/candidateActionConstants"; | |||
| import { FETCH_USERS_REQ } from "../../store/actions/users/usersActionConstants"; | |||
| import { getSingleCandidate } from "../../store/saga/candidatesSaga"; | |||
| import { | |||
| fetchCandidateSuccess, | |||
| fetchCandidateError, | |||
| } from "../../store/actions/candidate/candidateActions"; | |||
| import * as helper from "../../util/helpers/rejectErrorCodeHelper"; | |||
| import CandidateDetailsPage from "../../pages/CandidatesPage/CandidateDetailsPage"; | |||
| const mockHistoryPush = jest.fn(); | |||
| // mock param which we send as part of URL | |||
| jest.mock("react-router-dom", () => ({ | |||
| ...jest.requireActual("react-router-dom"), | |||
| useHistory: () => ({ | |||
| push: mockHistoryPush, | |||
| }), | |||
| useParams: () => ({ | |||
| id: 1, | |||
| }), | |||
| })); | |||
| describe("CandidateDetailsPage render tests", () => { | |||
| var props = { | |||
| history: { | |||
| replace: jest.fn(), | |||
| push: jest.fn(), | |||
| location: { | |||
| pathname: "", | |||
| }, | |||
| }, | |||
| }; | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <CandidateDetailsPage {...props} /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| let spyOnUseDispatch; | |||
| let mockDispatch; | |||
| beforeEach(() => { | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector | |||
| .mockReturnValueOnce(mockState.users.users) | |||
| .mockReturnValueOnce(mockState.users.user) | |||
| .mockReturnValueOnce(mockState.candidate.candidate); | |||
| spyOnUseDispatch = jest.spyOn(redux, "useDispatch"); | |||
| // Mock dispatch function returned from useDispatch | |||
| mockDispatch = jest.fn(); | |||
| spyOnUseDispatch.mockReturnValue(mockDispatch); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should dispatch fetch candidate request when rendered", () => { | |||
| render(cont); | |||
| expect(mockDispatch).toHaveBeenCalledWith({ | |||
| payload: { id: 1 }, | |||
| type: CANDIDATE_FETCH, | |||
| }); | |||
| }); | |||
| it("Should dispatch fetch users request when rendered", () => { | |||
| render(cont); | |||
| expect(mockDispatch).toHaveBeenCalledWith({ | |||
| type: FETCH_USERS_REQ, | |||
| }); | |||
| }); | |||
| it("should load and handle candidate in case of success", async () => { | |||
| const dispatchedActions = []; | |||
| helper.rejectErrorCodeHelper = jest.fn(() => "Server error"); | |||
| const mockedCall = { data: mockState.candidate.candidate }; | |||
| api.getCandidate = jest.fn(() => Promise.resolve(mockedCall)); | |||
| const fakeStore = { | |||
| getState: () => mockState.candidate.candidate, | |||
| dispatch: (action) => dispatchedActions.push(action), | |||
| }; | |||
| await runSaga(fakeStore, getSingleCandidate, { payload: { id: 1 } }).done; | |||
| expect(api.getCandidate.mock.calls.length).toBe(1); | |||
| expect(dispatchedActions).toContainEqual( | |||
| fetchCandidateSuccess(mockedCall.data) | |||
| ); | |||
| }); | |||
| it("should handle candidate load errors in case of failure", async () => { | |||
| const dispatchedActions = []; | |||
| helper.rejectErrorCodeHelper = jest.fn( | |||
| () => mockState.candidate.fetchCandidateErrorMessage | |||
| ); | |||
| const error = { | |||
| response: { | |||
| data: { message: mockState.candidate.fetchCandidateErrorMessage }, | |||
| }, | |||
| }; | |||
| api.getCandidate = jest.fn(() => Promise.reject(error)); | |||
| const fakeStore = { | |||
| getState: () => mockState.candidate.candidate, | |||
| dispatch: (action) => dispatchedActions.push(action), | |||
| }; | |||
| await runSaga(fakeStore, getSingleCandidate, { payload: { id: 1 } }).done; | |||
| expect(api.getCandidate.mock.calls.length).toBe(1); | |||
| expect(dispatchedActions).toContainEqual( | |||
| fetchCandidateError(error.response.data.message) | |||
| ); | |||
| }); | |||
| }); | |||
| @@ -29,13 +29,10 @@ describe("CandidatesPage render tests", () => { | |||
| 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); | |||
| }); | |||
| @@ -15,6 +15,7 @@ import { | |||
| filterCandidatesError, | |||
| } from "../../store/actions/candidates/candidatesActions"; | |||
| import { PAGE_SIZE_CANDIDATES } from "../../constants/keyCodeConstants"; | |||
| import * as helper from "../../util/helpers/rejectErrorCodeHelper"; | |||
| describe("TableViewPage render tests", () => { | |||
| const cont = ( | |||
| @@ -80,26 +81,30 @@ describe("TableViewPage render tests", () => { | |||
| ); | |||
| }); | |||
| // it("should handle candidates load errors in case of failure", async () => { | |||
| // const dispatchedActions = []; | |||
| it("should handle technologies load errors in case of failure", async () => { | |||
| const dispatchedActions = []; | |||
| helper.rejectErrorCodeHelper = jest.fn( | |||
| () => mockState.candidates.fetchCandidatesErrorMessage | |||
| ); | |||
| // const error = { | |||
| // response: { | |||
| // data: { message: mockState.candidates.fetchCandidatesErrorMessage }, | |||
| // }, | |||
| // }; | |||
| // api.getFilteredCandidates = jest.fn(() => Promise.reject(error)); | |||
| const error = { | |||
| response: { | |||
| data: { message: mockState.candidates.fetchCandidatesErrorMessage }, | |||
| }, | |||
| }; | |||
| api.getFilteredCandidates = jest.fn(() => Promise.reject(error)); | |||
| // const fakeStore = { | |||
| // getState: () => mockState.candidates.items, | |||
| // dispatch: (action) => dispatchedActions.push(action), | |||
| // }; | |||
| const fakeStore = { | |||
| getState: () => mockState.candidates.candidates, | |||
| dispatch: (action) => dispatchedActions.push(action), | |||
| }; | |||
| // await runSaga(fakeStore, fc.filterCandidates, {}).done; | |||
| await runSaga(fakeStore, fc.filterCandidates, {}).done; | |||
| // expect(api.getFilteredCandidates.mock.calls.length).toBe(1); | |||
| // expect(dispatchedActions).toContainEqual( | |||
| // filterCandidatesError(error.response.data.message) | |||
| // ); | |||
| // }); | |||
| expect(api.getFilteredCandidates.mock.calls.length).toBe(1); | |||
| expect(dispatchedActions).toContainEqual( | |||
| filterCandidatesError(error.response.data.message) | |||
| ); | |||
| }); | |||
| }); | |||
| @@ -1,4 +1,4 @@ | |||
| import { render } from "@testing-library/react"; | |||
| import { render, fireEvent } from "@testing-library/react"; | |||
| import * as redux from "react-redux"; | |||
| import store from "../../store"; | |||
| import { mockState } from "../../mockState"; | |||
| @@ -7,10 +7,20 @@ import history from "../../store/utils/history"; | |||
| import AdsCandidatesPage from "../../pages/CandidatesPage/AdsCandidatesPage"; | |||
| describe("TableViewPage render tests", () => { | |||
| var props = { | |||
| history: { | |||
| replace: jest.fn(), | |||
| push: jest.fn(), | |||
| location: { | |||
| pathname: "/candidates", | |||
| }, | |||
| }, | |||
| }; | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <AdsCandidatesPage search="" /> | |||
| <AdsCandidatesPage search="" {...props} /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| @@ -33,7 +43,7 @@ describe("TableViewPage render tests", () => { | |||
| ).toBeDefined(); | |||
| }); | |||
| it("Number of sliders should be equal to length of our array adsCandidates", () => { | |||
| it("Number of sliders should be equal to length of our adsCandidates array", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("ads-candidates").length).toBe( | |||
| mockState.candidates.adsCandidates.length | |||
| @@ -46,4 +56,13 @@ describe("TableViewPage render tests", () => { | |||
| container.getElementsByClassName("active-ads-ads-arrows").length | |||
| ).toBe(0); | |||
| }); | |||
| it("Should render candidate details page", () => { | |||
| const { container } = render(cont); | |||
| fireEvent.click( | |||
| container.getElementsByClassName("candidate-card-container")[0] | |||
| ); | |||
| const arg = { pathname: "/candidates/1" }; | |||
| expect(props.history.push).toHaveBeenCalledWith(arg); | |||
| }); | |||
| }); | |||
| @@ -0,0 +1,97 @@ | |||
| import { render, fireEvent, screen } from "@testing-library/react"; | |||
| import * as redux from "react-redux"; | |||
| import store from "../../store"; | |||
| import { mockState } from "../../mockState"; | |||
| import { Router } from "react-router-dom"; | |||
| import history from "../../store/utils/history"; | |||
| import CandidateDetailsPage from "../../pages/CandidatesPage/CandidateDetailsPage"; | |||
| describe("CandidateDetailsPage render tests", () => { | |||
| var props = { | |||
| history: { | |||
| replace: jest.fn(), | |||
| push: jest.fn(), | |||
| location: { | |||
| pathname: "/candidates/1", | |||
| }, | |||
| }, | |||
| }; | |||
| const cont = ( | |||
| <redux.Provider store={store}> | |||
| <Router history={history}> | |||
| <CandidateDetailsPage {...props} /> | |||
| </Router> | |||
| </redux.Provider> | |||
| ); | |||
| let spyOnUseSelector; | |||
| beforeEach(() => { | |||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||
| spyOnUseSelector | |||
| .mockReturnValueOnce(mockState.users.users) | |||
| .mockReturnValueOnce(mockState.users.user) | |||
| .mockReturnValueOnce(mockState.candidate.candidate) | |||
| .mockReturnValueOnce(mockState.users.users) | |||
| .mockReturnValueOnce(mockState.users.user) | |||
| .mockReturnValueOnce(mockState.candidate.candidate); | |||
| }); | |||
| afterEach(() => { | |||
| jest.restoreAllMocks(); | |||
| }); | |||
| it("Should render", () => { | |||
| const { container } = render(cont); | |||
| expect( | |||
| container.getElementsByClassName("main-candidate-container")[0] | |||
| ).toBeDefined(); | |||
| }); | |||
| it("Should render button for deleting candidate", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("candidate-btn")[0]).toBeDefined(); | |||
| }); | |||
| it("Should render dialog after clicking button for deleting candidate", () => { | |||
| const { container } = render(cont); | |||
| fireEvent.click(container.getElementsByClassName("candidate-btn")[0]); | |||
| expect(screen.getByTestId("alert-container")).toBeDefined(); | |||
| }); | |||
| it("Should render div for sending comment", () => { | |||
| const { container } = render(cont); | |||
| expect( | |||
| container.getElementsByClassName("comment-container")[0] | |||
| ).toBeDefined(); | |||
| }); | |||
| it("Should render button for downloading CV", () => { | |||
| const { container } = render(cont); | |||
| expect( | |||
| container.getElementsByClassName("applicant-cv-button")[0] | |||
| ).toBeDefined(); | |||
| }); | |||
| it("Should render two ads for candidate", () => { | |||
| const { container } = render(cont); | |||
| expect(container.getElementsByClassName("applicant-add").length).toBe(2); | |||
| }); | |||
| it("Should render three comments", () => { | |||
| const { container } = render(cont); | |||
| expect( | |||
| container.getElementsByClassName("comment-sub-container").length | |||
| ).toBe(3); | |||
| }); | |||
| it("Should render page with all candidates", () => { | |||
| const { container } = render(cont); | |||
| fireEvent.click( | |||
| container.getElementsByClassName("applicant-ads-back-button")[0] | |||
| ); | |||
| const arg = { pathname: "/candidates" }; | |||
| expect(props.history.push).toHaveBeenCalledWith(arg); | |||
| }); | |||
| }); | |||
| @@ -392,6 +392,7 @@ | |||
| line-height: 20px; | |||
| text-decoration-line: underline; | |||
| color: #226cb0; | |||
| cursor: pointer; | |||
| } | |||
| .tagStyle { | |||
| @@ -7,9 +7,6 @@ const CandidateCard = ({ candidate, className, history }) => { | |||
| const navigateToDetailsPage = () => { | |||
| history.push({ | |||
| pathname: CANDIDATES_PAGE + "/" + candidate.applicantId, | |||
| state: { | |||
| from: history.location.pathname, | |||
| }, | |||
| }); | |||
| }; | |||
| @@ -1,6 +1,6 @@ | |||
| import React from "react"; | |||
| import { useEffect, useState, useRef } from "react"; | |||
| import { Link, useParams } from "react-router-dom"; | |||
| import { useParams } from "react-router-dom"; | |||
| import deleteImage from "../../../src/assets/images/delete.png"; | |||
| import planeImage from "../../../src/assets/images/planeVectorBlue.png"; | |||
| import IconButton from "../../components/IconButton/IconButton"; | |||
| @@ -204,6 +204,12 @@ const CandidateDetailsPage = ({ history }) => { | |||
| } | |||
| }; | |||
| const goToPageWithAllCandidates = () => { | |||
| history.push({ | |||
| pathname: "/candidates", | |||
| }); | |||
| }; | |||
| return (candidate && Object.keys(candidate).length === 0) || | |||
| user === undefined ? ( | |||
| <p>Loading...</p> | |||
| @@ -267,7 +273,7 @@ const CandidateDetailsPage = ({ history }) => { | |||
| > | |||
| <p className="candidate-property-value">{candidate.email}</p> | |||
| <p className="candidate-property-value"> | |||
| {candidate.phoneNumber} | |||
| {candidate.phoneNumber === "" ? "/" : candidate.phoneNumber} | |||
| </p> | |||
| </div> | |||
| </div> | |||
| @@ -283,13 +289,15 @@ const CandidateDetailsPage = ({ history }) => { | |||
| className="candidate-property-container" | |||
| > | |||
| <p className="candidate-property-value"> | |||
| {candidate.linkedlnLink ?? "/"} | |||
| {candidate.linkedlnLink === "" ? "/" : candidate.linkedlnLink} | |||
| </p> | |||
| <p className="candidate-property-value"> | |||
| {candidate.gitHubLink ?? "/"} | |||
| {candidate.githubLink === "" ? "/" : candidate.githubLink} | |||
| </p> | |||
| <p className="candidate-property-value"> | |||
| {candidate.bitBucketLink ?? "/"} | |||
| {candidate.bitBucketLink === "" | |||
| ? "/" | |||
| : candidate.bitBucketLink} | |||
| </p> | |||
| </div> | |||
| </div> | |||
| @@ -435,9 +443,13 @@ const CandidateDetailsPage = ({ history }) => { | |||
| </div> | |||
| </div> | |||
| <div className="applicant-ads-buttons-container"> | |||
| <Link to="/candidates" className="applicant-ads-back-button"> | |||
| <p | |||
| to="/candidates" | |||
| className="applicant-ads-back-button" | |||
| onClick={goToPageWithAllCandidates} | |||
| > | |||
| Nazad na sve kandidate | |||
| </Link> | |||
| </p> | |||
| <a | |||
| className="applicant-cv-button" | |||
| download={candidate.firstName + candidate.lastName + ".pdf"} | |||