| import { runSaga } from 'redux-saga'; | |||||
| import * as api from '../../request/processesReguest'; | |||||
| import { render } from "@testing-library/react"; | |||||
| import * as redux from "react-redux"; | |||||
| import SelectionProcessPage from '../../pages/selectionProcessPage/selectionProcessPage'; "../../pages/SelectionProcessPage/SelectionProcessPage"; | |||||
| import store from "../../store"; | |||||
| import "../../i18n"; | |||||
| import { mockState } from "../../mockState"; | |||||
| import { | |||||
| FETCH_PROCESSES_REQ | |||||
| } from "../../store/actions/processes/processesActionConstants"; | |||||
| import { Router } from "react-router-dom"; | |||||
| import history from "../../store/utils/history"; | |||||
| import { getProcesses, getFilteredProcesses } from '../../store/saga/processSaga'; | |||||
| import { setProcesses, setProcessesError } from '../../store/actions/processes/processesAction'; | |||||
| describe("SelectionProcessPage render tests", () => { | |||||
| const cont = ( | |||||
| <redux.Provider store={store}> | |||||
| <Router history={history}> | |||||
| <SelectionProcessPage /> | |||||
| </Router> | |||||
| </redux.Provider> | |||||
| ); | |||||
| let spyOnUseSelector; | |||||
| let spyOnUseDispatch; | |||||
| let mockDispatch; | |||||
| beforeEach(() => { | |||||
| // Mock useSelector hook | |||||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||||
| spyOnUseSelector.mockReturnValueOnce(mockState.selections).mockReturnValueOnce(mockState.selections.processes).mockReturnValueOnce(mockState.selections.statuses); | |||||
| // 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 processes request when rendered", () => { | |||||
| render(cont); | |||||
| expect(mockDispatch).toHaveBeenCalledWith({ | |||||
| type: FETCH_PROCESSES_REQ, | |||||
| }); | |||||
| }); | |||||
| it('should load and handle levels with processes in case of success', async () => { | |||||
| // we push all dispatched actions to make assertions easier | |||||
| // and our tests less brittle | |||||
| const dispatchedActions = []; | |||||
| // we don't want to perform an actual api call in our tests | |||||
| // so we will mock the getAllUsers api with jest | |||||
| // this will mutate the dependency which we may reset if other tests | |||||
| // are dependent on it | |||||
| const mockedCall = { data: mockState.selections.processes }; | |||||
| api.getAllLevels = jest.fn(() => Promise.resolve(mockedCall)); | |||||
| const fakeStore = { | |||||
| getState: () => (mockState.selections.processes), | |||||
| dispatch: action => dispatchedActions.push(action), | |||||
| }; | |||||
| // wait for saga to complete | |||||
| await runSaga(fakeStore, getProcesses).done; | |||||
| expect(api.getAllLevels.mock.calls.length).toBe(1); | |||||
| expect(dispatchedActions).toContainEqual(setProcesses(mockedCall.data)); | |||||
| }); | |||||
| it('should handle processes load errors in case of failure', async () => { | |||||
| const dispatchedActions = []; | |||||
| // we simulate an error by rejecting the promise | |||||
| // then we assert if our saga dispatched the action(s) correctly | |||||
| const error = { response: { data: { message: mockState.selections.fetchSelectionsErrorMessage } } }; | |||||
| api.getAllLevels = jest.fn(() => Promise.reject(error)); | |||||
| const fakeStore = { | |||||
| getState: () => (mockState.users.users), | |||||
| dispatch: action => dispatchedActions.push(action), | |||||
| }; | |||||
| await runSaga(fakeStore, getProcesses).done; | |||||
| expect(api.getAllLevels.mock.calls.length).toBe(1); | |||||
| expect(dispatchedActions).toContainEqual(setProcessesError(error.response.data.message)); | |||||
| }); | |||||
| it('should load and handle levels with filtered processes in case of success', async () => { | |||||
| // we push all dispatched actions to make assertions easier | |||||
| // and our tests less brittle | |||||
| const dispatchedActions = []; | |||||
| const filter = { | |||||
| statuses: ["Zakazan","Odrađen"], | |||||
| dateStart: new Date(2023,0,5), | |||||
| dateEnd: new Date(2024,1,1) | |||||
| }; | |||||
| const filteredData = []; | |||||
| mockState.selections.processes.forEach(level => { | |||||
| const filteredLevel = level; | |||||
| filteredLevel.selectionProcesses = level.selectionProcesses.filter(v => v.date >= filter.dateStart && v.date <= filter.dateEnd && filter.statuses.includes(v.status)); | |||||
| filteredData.push(filteredLevel); | |||||
| }); | |||||
| // we don't want to perform an actual api call in our tests | |||||
| // so we will mock the getAllUsers api with jest | |||||
| // this will mutate the dependency which we may reset if other tests | |||||
| // are dependent on it | |||||
| const mockedCall = { data: filteredData }; | |||||
| api.getAllFilteredProcessesReq = jest.fn(() => Promise.resolve(mockedCall)); | |||||
| const fakeStore = { | |||||
| getState: () => (mockState.selections.processes), | |||||
| dispatch: action => dispatchedActions.push(action), | |||||
| }; | |||||
| // wait for saga to complete | |||||
| await runSaga(fakeStore, getFilteredProcesses,filter).done; | |||||
| expect(api.getAllFilteredProcessesReq.mock.calls.length).toBe(1); | |||||
| expect(dispatchedActions).toContainEqual(setProcesses(filteredData)); | |||||
| }); | |||||
| it('should handle process to set it done in case of success', async () => { | |||||
| // we push all dispatched actions to make assertions easier | |||||
| // and our tests less brittle | |||||
| const dispatchedActions = []; | |||||
| const filter = { | |||||
| statuses: ["Zakazan","Odrađen"], | |||||
| dateStart: new Date(2023,0,5), | |||||
| dateEnd: new Date(2024,1,1) | |||||
| }; | |||||
| const filteredData = []; | |||||
| mockState.selections.processes.forEach(level => { | |||||
| const filteredLevel = level; | |||||
| filteredLevel.selectionProcesses = level.selectionProcesses.filter(v => v.date >= filter.dateStart && v.date <= filter.dateEnd && filter.statuses.includes(v.status)); | |||||
| filteredData.push(filteredLevel); | |||||
| }); | |||||
| // we don't want to perform an actual api call in our tests | |||||
| // so we will mock the getAllUsers api with jest | |||||
| // this will mutate the dependency which we may reset if other tests | |||||
| // are dependent on it | |||||
| const mockedCall = { data: filteredData }; | |||||
| api.getAllFilteredProcessesReq = jest.fn(() => Promise.resolve(mockedCall)); | |||||
| const fakeStore = { | |||||
| getState: () => (mockState.selections.processes), | |||||
| dispatch: action => dispatchedActions.push(action), | |||||
| }; | |||||
| // wait for saga to complete | |||||
| await runSaga(fakeStore, getFilteredProcesses,filter).done; | |||||
| expect(api.getAllFilteredProcessesReq.mock.calls.length).toBe(1); | |||||
| expect(dispatchedActions).toContainEqual(setProcesses(filteredData)); | |||||
| }); | |||||
| }); | |||||
| import { fireEvent, render, screen } from "@testing-library/react"; | |||||
| import * as redux from "react-redux"; | |||||
| import SelectionProcessPage from "../../pages/selectionProcessPage/selectionProcessPage" | |||||
| import store from "../../store"; | |||||
| import "../../i18n"; | |||||
| import { mockState } from "../../mockState"; | |||||
| import { Router } from "react-router-dom"; | |||||
| import history from "../../store/utils/history"; | |||||
| describe("SelectionProcessPage render tests", () => { | |||||
| const cont = ( | |||||
| <redux.Provider store={store}> | |||||
| <Router history={history}> | |||||
| <SelectionProcessPage /> | |||||
| </Router> | |||||
| </redux.Provider> | |||||
| ); | |||||
| let spyOnUseSelector; | |||||
| beforeEach(() => { | |||||
| // Mock useSelector hook | |||||
| spyOnUseSelector = jest.spyOn(redux, "useSelector"); | |||||
| spyOnUseSelector.mockReturnValueOnce(mockState.selections).mockReturnValueOnce(mockState.selections.processes).mockReturnValueOnce(mockState.selections.statuses); | |||||
| // spyOnUseSelector.mockReturnValue(mockState.selections); | |||||
| }); | |||||
| afterEach(() => { | |||||
| jest.restoreAllMocks(); | |||||
| }); | |||||
| it("Should render", () => { | |||||
| render(cont); | |||||
| expect(screen.getByTestId("selections-page")).toBeDefined(); | |||||
| }); | |||||
| it("Should render a card foreach mocked level", () => { | |||||
| const { container } = render(cont); | |||||
| expect(container.getElementsByClassName("selection-card").length).toBe(4); | |||||
| }); | |||||
| it("Should render a button with specific class for an enabled user", () => { | |||||
| const { container } = render(cont); | |||||
| expect(container.getElementsByClassName("td-btn").length).toBe(0); | |||||
| }); | |||||
| it("Should render filter buttonn", () => { | |||||
| const { container } = render(cont); | |||||
| expect(container.getElementsByClassName("userPageBtn").length).toBe(1); | |||||
| }); | |||||
| it("Drag and drop", () => { | |||||
| const { container } = render(cont); | |||||
| fireEvent.drop(container.getElementsByClassName("selection-card")[0], { | |||||
| dataTransfer: { | |||||
| getData: (type) => '{"id":32,"name":"random","status":"Kandidat primljen","date":null,"link":"link","applicant":{"applicantId":6,"firstName":"Safet","lastName":"Purkovic","position":"React Developer","dateOfApplication":"2021-05-05T00:00:00","cv":"dasdas","email":"safet@gmail.com","phoneNumber":"2313123","linkedlnLink":"sda","githubLink":null,"bitBucketLink":null,"experience":2,"applicationChannel":null,"typeOfEmployment":"Posao","technologyApplicants":[],"comments":[],"ads":[],"selectionProcesses":[{"status":"Kandidat primljen","date":null,"link":"link","scheduler":{"id":7,"firstName":"Safet","lastName":"Purkovic","email":"safet.purkovic@dilig.net","isEnabled":true},"selectionLevel":{"id":4,"name":"Konacna odluka"}}]},"selectionLevelId":4}' | |||||
| }, | |||||
| }) | |||||
| }); | |||||
| }) |
| const errorMessage = useSelector(selectDoneProcessError); | const errorMessage = useSelector(selectDoneProcessError); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const user = useSelector(selectAuthUser); | const user = useSelector(selectAuthUser); | ||||
| const dropItem = (e, selId) => { | const dropItem = (e, selId) => { | ||||
| var data = e.dataTransfer.getData("text/plain"); | var data = e.dataTransfer.getData("text/plain"); | ||||
| const selectionProcess = JSON.parse(data); | const selectionProcess = JSON.parse(data); | ||||
| if (selectionProcess.selectionLevelId < selId) { | if (selectionProcess.selectionLevelId < selId) { | ||||
| dispatch(setDoneProcessReq({ | dispatch(setDoneProcessReq({ | ||||
| id: selectionProcess.id, | id: selectionProcess.id, | ||||
| name: "radnom name", | |||||
| name: "Some random name", | |||||
| applicantId: selectionProcess.applicant.applicantId, | applicantId: selectionProcess.applicant.applicantId, | ||||
| schedulerId: user.id | schedulerId: user.id | ||||
| })); | })); | ||||
| ); | ); | ||||
| return ( | return ( | ||||
| <div dropppable="true" id={props.selection.id} className="selection-card" | |||||
| <div data-testid="selection-level" dropppable="true" id={props.selection.id} className="selection-card" | |||||
| onDragOver={e => dragOver(e)} | onDragOver={e => dragOver(e)} | ||||
| onDrop={e => dropItem(e, props.selection.id)} | onDrop={e => dropItem(e, props.selection.id)} | ||||
| > | > | ||||
| </div> | </div> | ||||
| <Backdrop position="absolute" isLoading={isLoading} /> | <Backdrop position="absolute" isLoading={isLoading} /> | ||||
| {applicants.length > 0 && renderList} | |||||
| {applicants.length === 0 && <div className="sel-item-no-data"> | |||||
| {applicants && applicants !== null && applicants?.length > 0 && renderList} | |||||
| {applicants && applicants !== null && applicants?.length === 0 && <div className="sel-item-no-data"> | |||||
| <div className="date"> | <div className="date"> | ||||
| <p>Nema kandidata u selekciji</p> | <p>Nema kandidata u selekciji</p> | ||||
| </div> | </div> |
| export const mockState = { | |||||
| users: { | |||||
| users: [ | |||||
| { | |||||
| id: 1, | |||||
| firstName: "First", | |||||
| lastName: "User", | |||||
| email: "first@gmail.com", | |||||
| isEnabled: false, | |||||
| }, | |||||
| { | |||||
| id: 2, | |||||
| firstName: "Second", | |||||
| lastName: "User", | |||||
| email: "second@gmail.com", | |||||
| isEnabled: true, | |||||
| }, | |||||
| { | |||||
| id: 3, | |||||
| firstName: "Third", | |||||
| lastName: "User", | |||||
| email: "third@gmail.com", | |||||
| isEnabled: false, | |||||
| }, | |||||
| ], | |||||
| selected: {}, | |||||
| fetchUsersErrorMessage: "Server error", | |||||
| toggleEnableErrorMessage: "", | |||||
| }, | |||||
| selections: { | |||||
| process:{doneProcess: false}, | |||||
| processes: [ | |||||
| { | |||||
| id: 1, | |||||
| name: "HR intervju", | |||||
| selectionProcesses: [ | |||||
| { | |||||
| id: 1, | |||||
| name: "sel1", | |||||
| status: "Odrađen", | |||||
| date: new Date(2023, 1, 1, 12, 0), | |||||
| link: "http://google.com", | |||||
| selectionLevelId: 1, | |||||
| applicant: { | |||||
| applicantId: 1, | |||||
| firstName: "Dzenis", | |||||
| lastName: "Hadzifejzovic", | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 2, | |||||
| name: "sel2", | |||||
| status: "Odrađen", | |||||
| date: new Date(2023, 1, 1, 13, 0), | |||||
| link: "http://google.com", | |||||
| selectionLevelId: 1, | |||||
| applicant: { | |||||
| applicantId: 2, | |||||
| firstName: "Meris", | |||||
| lastName: "Ahmatovic", | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 3, | |||||
| name: "sel3", | |||||
| status: "Zakazan", | |||||
| date: new Date(2023, 1, 10, 12, 0), | |||||
| link: "http://google.com", | |||||
| selectionLevelId: 1, | |||||
| applicant: { | |||||
| applicantId: 3, | |||||
| firstName: "Ermin", | |||||
| lastName: "Bronja", | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| id: 2, | |||||
| name: "Screening test", | |||||
| selectionProcesses: [ | |||||
| { | |||||
| id: 4, | |||||
| name: "sel4", | |||||
| status: "Čeka na zakazivanje", | |||||
| date: new Date(2023, 1, 10, 12, 0), | |||||
| selectionLevelId: 2, | |||||
| applicant: { | |||||
| applicantId: 1, | |||||
| firstName: "Dzenis", | |||||
| lastName: "Hadzifejzovic", | |||||
| }, | |||||
| }, | |||||
| { | |||||
| id: 5, | |||||
| name: "sel5", | |||||
| status: "Odrađen", | |||||
| date: new Date(2023, 1, 3, 12, 0), | |||||
| link: "http://google.com", | |||||
| selectionLevelId: 1, | |||||
| applicant: { | |||||
| applicantId: 2, | |||||
| firstName: "Meris", | |||||
| lastName: "Ahmatovic", | |||||
| }, | |||||
| } | |||||
| ], | |||||
| }, | |||||
| { | |||||
| id: 3, | |||||
| name: "Tehnicki intervju", | |||||
| selectionProcesses: [ | |||||
| { | |||||
| id: 6, | |||||
| name: "sel6", | |||||
| status: "Zakazan", | |||||
| date: new Date(2023, 1, 11, 10, 0), | |||||
| link: "http://google.com", | |||||
| selectionLevelId: 3, | |||||
| applicant: { | |||||
| applicantId: 2, | |||||
| firstName: "Meris", | |||||
| lastName: "Ahmatovic", | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| { | |||||
| id: 4, | |||||
| name: "Konacna odluka", | |||||
| selectionProcesses: [ | |||||
| { | |||||
| id: 6, | |||||
| name: "sel6", | |||||
| status: "Zakazan", | |||||
| date: new Date(2023, 1, 11, 10, 0), | |||||
| link: "http://google.com", | |||||
| selectionLevelId: 3, | |||||
| applicant: { | |||||
| applicantId: 2, | |||||
| firstName: "Meris", | |||||
| lastName: "Ahmatovic", | |||||
| }, | |||||
| }, | |||||
| ], | |||||
| } | |||||
| ], | |||||
| 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"}] | |||||
| } | |||||
| }; |
| }; | }; | ||||
| const renderList = processes.map((item, index) => { | |||||
| const renderList = processes?.map((item, index) => { | |||||
| return <Selection selection={item} key={index} history={history}/> | return <Selection selection={item} key={index} history={history}/> | ||||
| } | } | ||||
| ); | ); | ||||
| return ( | return ( | ||||
| <> | |||||
| <div data-testid="selections-page"> | |||||
| <div className="l-t-rectangle"></div> | <div className="l-t-rectangle"></div> | ||||
| <div className="r-b-rectangle"></div> | <div className="r-b-rectangle"></div> | ||||
| <SelectionFilter /> | <SelectionFilter /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </> | |||||
| </div> | |||||
| ); | ); | ||||
| }; | }; | ||||
| import { setDoneProcess, setDoneProcessError } from "../actions/processes/processAction"; | import { setDoneProcess, setDoneProcessError } from "../actions/processes/processAction"; | ||||
| import { setApplicant, setApplicantError } from "../actions/processes/applicantAction"; | import { setApplicant, setApplicantError } from "../actions/processes/applicantAction"; | ||||
| import { FETCH_PROCESSES_REQ, FETCH_FILTERED_PROCESSES_REQ ,PUT_PROCESS_REQ, FETCH_APPLICANT_PROCESSES_REQ } from "../actions/processes/processesActionConstants"; | import { FETCH_PROCESSES_REQ, FETCH_FILTERED_PROCESSES_REQ ,PUT_PROCESS_REQ, FETCH_APPLICANT_PROCESSES_REQ } from "../actions/processes/processesActionConstants"; | ||||
| import { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper"; | |||||
| export function* getProcesses() { | export function* getProcesses() { | ||||
| try { | try { | ||||
| const result = yield call(getAllLevels); | const result = yield call(getAllLevels); | ||||
| yield put(setProcesses(result.data)); | yield put(setProcesses(result.data)); | ||||
| } catch (error) { | } catch (error) { | ||||
| yield put(setProcessesError(error)); | |||||
| if (error.response && error.response.data) { | |||||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | |||||
| yield put(setProcessesError(errorMessage)); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| const result = yield call(getAllFilteredProcessesReq, payload.payload); | const result = yield call(getAllFilteredProcessesReq, payload.payload); | ||||
| yield put(setProcesses(result.data)); | yield put(setProcesses(result.data)); | ||||
| } catch (error) { | } catch (error) { | ||||
| yield put(setProcessesError(error)); | |||||
| if (error.response && error.response.data) { | |||||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | |||||
| yield put(setProcessesError(errorMessage)); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| const model = payload.payload; | const model = payload.payload; | ||||
| const result = yield call(doneProcess,model); | const result = yield call(doneProcess,model); | ||||
| yield put(setDoneProcess(result.data)); | yield put(setDoneProcess(result.data)); | ||||
| } catch (error) { | |||||
| yield put(setDoneProcessError(error)); | |||||
| } catch (error) { if (error.response && error.response.data) { | |||||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | |||||
| yield put(setDoneProcessError(errorMessage)); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| const {data} = yield call(getProcessesOfApplicant,id); | const {data} = yield call(getProcessesOfApplicant,id); | ||||
| yield put(setApplicant(data)); | yield put(setApplicant(data)); | ||||
| } catch (error) { | } catch (error) { | ||||
| yield put(setApplicantError(error)); | |||||
| if (error.response && error.response.data) { | |||||
| const errorMessage = yield call(rejectErrorCodeHelper, error); | |||||
| yield put(setApplicantError(errorMessage)); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||