ソースを参照

Merge branch 'feature/unit_tests_FE' of Neca/HRCenter into FE_dev

pull/80/head
safet.purkovic 3年前
コミット
6b10c54945

+ 168
- 0
src/__tests__/ReduxTests/processesReducer.test.js ファイルの表示

@@ -0,0 +1,168 @@
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));
});
});








+ 61
- 0
src/__tests__/UITests/selectionProcessesPageUI.test.js ファイルの表示

@@ -0,0 +1,61 @@
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}'
},
})
});
})

+ 6
- 6
src/components/Selection/Selection.js ファイルの表示

@@ -24,15 +24,15 @@ const Selection = (props) => {
const errorMessage = useSelector(selectDoneProcessError);
const dispatch = useDispatch();
const user = useSelector(selectAuthUser);
const dropItem = (e, selId) => {
var data = e.dataTransfer.getData("text/plain");
const selectionProcess = JSON.parse(data);
if (selectionProcess.selectionLevelId < selId) {
dispatch(setDoneProcessReq({
id: selectionProcess.id,
name: "radnom name",
name: "Some random name",
applicantId: selectionProcess.applicant.applicantId,
schedulerId: user.id
}));
@@ -69,7 +69,7 @@ const Selection = (props) => {
);

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)}
onDrop={e => dropItem(e, props.selection.id)}
>
@@ -78,8 +78,8 @@ const Selection = (props) => {
</div>
<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">
<p>Nema kandidata u selekciji</p>
</div>

+ 156
- 0
src/mockState.js ファイルの表示

@@ -0,0 +1,156 @@
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"}]
}
};

+ 3
- 3
src/pages/SelectionProcessPage/SelectionProcessPage.js ファイルの表示

@@ -53,13 +53,13 @@ const SelectionProcessPage = ({history}) => {
};

const renderList = processes.map((item, index) => {
const renderList = processes?.map((item, index) => {
return <Selection selection={item} key={index} history={history}/>
}
);

return (
<>
<div data-testid="selections-page">
<div className="l-t-rectangle"></div>
<div className="r-b-rectangle"></div>
<SelectionFilter />
@@ -99,7 +99,7 @@ const SelectionProcessPage = ({history}) => {
</div>
</div>
</div>
</>
</div>
);
};


+ 17
- 5
src/store/saga/processSaga.js ファイルの表示

@@ -7,6 +7,7 @@ import { JWT_TOKEN } from "../../constants/localStorage";
import { setDoneProcess, setDoneProcessError } from "../actions/processes/processAction";
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 { rejectErrorCodeHelper } from "../../util/helpers/rejectErrorCodeHelper";

export function* getProcesses() {
try {
@@ -15,7 +16,10 @@ export function* getProcesses() {
const result = yield call(getAllLevels);
yield put(setProcesses(result.data));
} catch (error) {
yield put(setProcessesError(error));
if (error.response && error.response.data) {
const errorMessage = yield call(rejectErrorCodeHelper, error);
yield put(setProcessesError(errorMessage));
}
}
}

@@ -27,7 +31,10 @@ export function* getFilteredProcesses(payload) {
const result = yield call(getAllFilteredProcessesReq, payload.payload);
yield put(setProcesses(result.data));
} catch (error) {
yield put(setProcessesError(error));
if (error.response && error.response.data) {
const errorMessage = yield call(rejectErrorCodeHelper, error);
yield put(setProcessesError(errorMessage));
}
}
}

@@ -38,8 +45,10 @@ export function* finishProcess(payload) {
const model = payload.payload;
const result = yield call(doneProcess,model);
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));
}
}
}

@@ -51,7 +60,10 @@ export function* getApplicantProcesses(payload) {
const {data} = yield call(getProcessesOfApplicant,id);
yield put(setApplicant(data));
} catch (error) {
yield put(setApplicantError(error));
if (error.response && error.response.data) {
const errorMessage = yield call(rejectErrorCodeHelper, error);
yield put(setApplicantError(errorMessage));
}
}
}


読み込み中…
キャンセル
保存