diff --git a/jest.config.js b/jest.config.js index 75118f59..ca9c1263 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,6 +5,7 @@ module.exports = { "/packages/app/server/**/*.js", "/packages/app/src/**/*.js", "!/packages/app/src/svgs/**/*.js", + "!/packages/app/src/store.js", ], coverageDirectory: "/coverage", projects: ["/packages/*/jest.config.js"], diff --git a/packages/app/src/actions/types.js b/packages/app/src/actions/types.js index 90242fec..42ed5632 100644 --- a/packages/app/src/actions/types.js +++ b/packages/app/src/actions/types.js @@ -40,9 +40,9 @@ export default { // Multiple comments: COMMENTS_DELETE: "COMMENTS_DELETE", COMMENTS_DELETE_FAILURE: "COMMENTS_DELETE_FAILURE", - COMMENTS_LOAD: "COMMENT_LOAD", - COMMENTS_LOAD_FAILURE: "COMMENT_LOAD_FAILURE", - COMMENTS_LOAD_SUCCESS: "COMMENT_LOAD_SUCCESS", + COMMENTS_LOAD: "COMMENTS_LOAD", + COMMENTS_LOAD_FAILURE: "COMMENTS_LOAD_FAILURE", + COMMENTS_LOAD_SUCCESS: "COMMENTS_LOAD_SUCCESS", // One comment: COMMENT_CREATE_ONE: "COMMENT_CREATE_ONE", diff --git a/packages/app/src/reducers/agreements.js b/packages/app/src/reducers/agreements.js index 879b79e0..5e6701b3 100644 --- a/packages/app/src/reducers/agreements.js +++ b/packages/app/src/reducers/agreements.js @@ -1,6 +1,6 @@ import { actionTypes } from "../actions/index"; -const initialState = { +export const initialState = { checked: [], data: null, error: null, diff --git a/packages/app/src/reducers/answers.js b/packages/app/src/reducers/answers.js index 1afffbe7..6b7bc2f3 100644 --- a/packages/app/src/reducers/answers.js +++ b/packages/app/src/reducers/answers.js @@ -1,6 +1,6 @@ import { actionTypes } from "../actions/index"; -const initialState = { +export const initialState = { checked: [], data: null, error: null, diff --git a/packages/app/src/reducers/comments.js b/packages/app/src/reducers/comments.js index 0bea4b64..c3add24e 100644 --- a/packages/app/src/reducers/comments.js +++ b/packages/app/src/reducers/comments.js @@ -1,6 +1,6 @@ import { actionTypes } from "../actions/index"; -const initialState = { +export const initialState = { answerId: null, currentIsLoading: false, currentIsPrivate: false, diff --git a/packages/app/src/reducers/legal-references.js b/packages/app/src/reducers/legal-references.js index 2770e424..716c2e89 100644 --- a/packages/app/src/reducers/legal-references.js +++ b/packages/app/src/reducers/legal-references.js @@ -1,6 +1,6 @@ import { actionTypes } from "../actions/index"; -const initialState = { +export const initialState = { category: null, data: null, error: null, diff --git a/packages/app/src/reducers/logs.js b/packages/app/src/reducers/logs.js index f34ce157..0be1457a 100644 --- a/packages/app/src/reducers/logs.js +++ b/packages/app/src/reducers/logs.js @@ -1,6 +1,6 @@ import { actionTypes } from "../actions/index"; -const initialState = { +export const initialState = { checked: [], error: null, isLoading: true, diff --git a/packages/app/src/reducers/questions.js b/packages/app/src/reducers/questions.js index cd88d12c..14c98568 100644 --- a/packages/app/src/reducers/questions.js +++ b/packages/app/src/reducers/questions.js @@ -1,6 +1,6 @@ import { actionTypes } from "../actions/index"; -const initialState = { +export const initialState = { checked: [], data: null, error: null, diff --git a/packages/app/src/sagas/agreements/__tests__/load.test.js b/packages/app/src/sagas/agreements/__tests__/load.test.js new file mode 100644 index 00000000..5749c479 --- /dev/null +++ b/packages/app/src/sagas/agreements/__tests__/load.test.js @@ -0,0 +1,126 @@ +import axios from "axios"; +import { runSaga } from "redux-saga"; + +import { initialState } from "../../../reducers/agreements"; +import load from "../load"; + +jest.mock("axios"); +const mockedAxios = /** @type {jest.Mocked} */ (axios); +mockedAxios.create.mockReturnValue(mockedAxios); + +describe(`sagas/agreements/load()`, () => { + let DISPATCHED; + + beforeEach(() => { + DISPATCHED = []; + }); + + describe(`with pageIndex=0`, () => { + describe(`with query=""`, () => { + it(`should behave as expected`, async () => { + const data = [{ name: "An Agreement" }]; + + mockedAxios.get.mockResolvedValueOnce({ + data, + headers: { + "content-range": "0-9/15", + }, + }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pageIndex: 0, query: "" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + "/agreements?select=*&order=idcc.asc&limit=10&offset=0", + { + headers: { Prefer: "count=exact" }, + }, + ); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: data, pageIndex: 0, pagesLength: 2 }, + type: "AGREEMENTS_LOAD_SUCCESS", + }); + }); + }); + + describe(`with query="A Query"`, () => { + it(`should behave as expected`, async () => { + const data = [{ name: "An Agreement" }]; + + mockedAxios.get.mockResolvedValueOnce({ + data, + headers: { + "content-range": "0-9/15", + }, + }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pageIndex: 0, query: "A Query" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + "/agreements?select=*&name=ilike.*A Query*&order=idcc.asc&limit=10&offset=0", + { + headers: { Prefer: "count=exact" }, + }, + ); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: data, pageIndex: 0, pagesLength: 2 }, + type: "AGREEMENTS_LOAD_SUCCESS", + }); + }); + }); + }); + + describe(`with pageIndex=-1`, () => { + describe(`with query=""`, () => { + it(`should behave as expected`, async () => { + const data = [{ name: "An Agreement" }]; + + mockedAxios.get.mockResolvedValueOnce({ + data, + headers: { + "content-range": "0-14/15", + }, + }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pageIndex: -1, query: "" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith("/agreements?select=*&order=idcc.asc", { + headers: { Prefer: "count=exact" }, + }); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: data, pageIndex: -1, pagesLength: -1 }, + type: "AGREEMENTS_LOAD_SUCCESS", + }); + }); + }); + }); +}); diff --git a/packages/app/src/sagas/agreements/load.js b/packages/app/src/sagas/agreements/load.js index ffd0f288..66400127 100644 --- a/packages/app/src/sagas/agreements/load.js +++ b/packages/app/src/sagas/agreements/load.js @@ -15,7 +15,7 @@ export default function* load({ meta: { pageIndex, query } }) { } if (query.length > 0) { - request.or.ilike("name", query); + request.ilike("name", query); } request.orderBy("idcc"); @@ -30,7 +30,7 @@ export default function* load({ meta: { pageIndex, query } }) { pagesLength, }), ); - } catch (err) { + } catch (err) /* istanbul ignore next */ { if (err.response !== undefined && err.response.status === 416) { const pageIndex = Math.floor(Number(err.response.headers["content-range"].substr(2)) / 10); diff --git a/packages/app/src/sagas/answers/__tests__/load.test.js b/packages/app/src/sagas/answers/__tests__/load.test.js new file mode 100644 index 00000000..cdb9d4b2 --- /dev/null +++ b/packages/app/src/sagas/answers/__tests__/load.test.js @@ -0,0 +1,166 @@ +import axios from "axios"; +import { runSaga } from "redux-saga"; + +import getCurrentUser from "../../../libs/getCurrentUser"; +import { initialState } from "../../../reducers/answers"; +import { getAnswersFilters } from "../../../selectors"; +import load from "../load"; + +jest.mock("axios"); +const mockedAxios = /** @type {jest.Mocked} */ (axios); +mockedAxios.create.mockReturnValue(mockedAxios); + +jest.mock("../../../libs/getCurrentUser"); +jest.mock("../../../selectors"); + +describe(`sagas/answers/load()`, () => { + const DATA = { + answers: [ + { agreement_name: "An Agreement", id: "00000000-0000-4000-8000-000000000001" }, + { agreement_name: null, id: "00000000-0000-4000-8000-000000000002" }, + ], + answersReferences: [], + }; + let DISPATCHED; + let FILTERS; + + beforeEach(() => { + DISPATCHED = []; + + getAnswersFilters.mockReturnValueOnce(FILTERS); + }); + + describe(`when the user is a contributor`, () => { + beforeAll(() => { + FILTERS = initialState.filters; + + getCurrentUser.mockReturnValue({ + agreements: [ + "00000000-0000-4000-8000-000000000003", + "00000000-0000-4000-8000-000000000004", + ], + id: "00000000-0000-4000-8000-000000000005", + role: "contributor", + }); + }); + + describe(`when the filter {state}=["todo", "draft]`, () => { + it(`should behave as expected`, async () => { + mockedAxios.get.mockResolvedValueOnce({ + data: DATA.answers, + headers: { + "content-range": "0-9/15", + }, + }); + mockedAxios.get.mockResolvedValueOnce({ + data: DATA.answersReferences, + headers: { + "content-range": "0-14/15", + }, + }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pagesIndex: 0 } }, + ).toPromise(); + + expect(getAnswersFilters).toHaveBeenCalledTimes(1); + + expect(mockedAxios.get).toHaveBeenCalledTimes(2); + expect(mockedAxios.get).toHaveBeenNthCalledWith( + 1, + `/full_answers?select=*&state=in.(todo,draft,pending_review,under_review,validated)&agreement_id=in.("00000000-0000-4000-8000-000000000003","00000000-0000-4000-8000-000000000004")&order=question_index.asc,agreement_idcc.asc&limit=10&offset=0`, + { + headers: { Prefer: "count=exact" }, + }, + ); + expect(mockedAxios.get).toHaveBeenNthCalledWith( + 2, + `/answers_references?select=*&answer_id=in.(00000000-0000-4000-8000-000000000001,00000000-0000-4000-8000-000000000002)&order=category.asc,value.asc`, + {}, + ); + + const expectedList = [ + { + agreement_name: "An Agreement", + id: "00000000-0000-4000-8000-000000000001", + references: [], + }, + { agreement_name: null, id: "00000000-0000-4000-8000-000000000002", references: [] }, + ]; + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { length: 15, list: expectedList, pagesIndex: 0, pagesLength: 2 }, + type: "ANSWERS_LOAD_SUCCESS", + }); + }); + }); + + describe(`when the filter {state}=["draft]`, () => { + FILTERS = { + ...initialState.filters, + states: ["draft"], + }; + + it(`should behave as expected`, async () => { + mockedAxios.get.mockResolvedValueOnce({ + data: DATA.answers, + headers: { + "content-range": "0-9/15", + }, + }); + mockedAxios.get.mockResolvedValueOnce({ + data: DATA.answersReferences, + headers: { + "content-range": "0-14/15", + }, + }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pagesIndex: 0 } }, + ).toPromise(); + + expect(getAnswersFilters).toHaveBeenCalledTimes(1); + + expect(mockedAxios.get).toHaveBeenCalledTimes(2); + expect(mockedAxios.get).toHaveBeenNthCalledWith( + 1, + `/full_answers?select=*&state=in.(todo,draft,pending_review,under_review,validated)&agreement_id=in.("00000000-0000-4000-8000-000000000003","00000000-0000-4000-8000-000000000004")&order=question_index.asc,agreement_idcc.asc&limit=10&offset=0`, + { + headers: { Prefer: "count=exact" }, + }, + ); + expect(mockedAxios.get).toHaveBeenNthCalledWith( + 2, + `/answers_references?select=*&answer_id=in.(00000000-0000-4000-8000-000000000001,00000000-0000-4000-8000-000000000002)&order=category.asc,value.asc`, + {}, + ); + + const expectedList = [ + { + agreement_name: "An Agreement", + id: "00000000-0000-4000-8000-000000000001", + references: [], + }, + { agreement_name: null, id: "00000000-0000-4000-8000-000000000002", references: [] }, + ]; + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { length: 15, list: expectedList, pagesIndex: 0, pagesLength: 2 }, + type: "ANSWERS_LOAD_SUCCESS", + }); + }); + }); + }); +}); diff --git a/packages/app/src/sagas/answers/addReferences.js b/packages/app/src/sagas/answers/addReferences.js index add2f648..08607fa9 100644 --- a/packages/app/src/sagas/answers/addReferences.js +++ b/packages/app/src/sagas/answers/addReferences.js @@ -11,7 +11,7 @@ export default function* addReferences({ meta: { data }, next }) { yield customPostgrester().post(API_PATH, data); next(); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.addReferencesFailure({ message: null })); } diff --git a/packages/app/src/sagas/answers/cancel.js b/packages/app/src/sagas/answers/cancel.js index 9b595c2c..488c492c 100644 --- a/packages/app/src/sagas/answers/cancel.js +++ b/packages/app/src/sagas/answers/cancel.js @@ -31,7 +31,7 @@ export default function* cancel({ meta: { ids, next } }) { ); next(); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.cancelFailure({ message: null })); } diff --git a/packages/app/src/sagas/answers/load.js b/packages/app/src/sagas/answers/load.js index 2ae71c1e..5649cbd5 100644 --- a/packages/app/src/sagas/answers/load.js +++ b/packages/app/src/sagas/answers/load.js @@ -4,7 +4,7 @@ import React from "react"; import { put, select } from "redux-saga/effects"; import * as actions from "../../actions"; -import { ANSWER_STATE, ANSWER_STATES, USER_ROLE } from "../../constants"; +import { ANSWER_STATES, USER_ROLE } from "../../constants"; import shortenAgreementName from "../../helpers/shortenAgreementName"; import customPostgrester from "../../libs/customPostgrester"; import getCurrentUser from "../../libs/getCurrentUser"; @@ -13,7 +13,7 @@ import { getAnswersFilters } from "../../selectors"; export default function* load({ meta: { pagesIndex } }) { try { - const { agreements: userAgreements, id: userId, role: userRole } = getCurrentUser(); + const { agreements: userAgreementIds, role: userRole } = getCurrentUser(); const filters = yield select(getAnswersFilters); const request = customPostgrester(); @@ -24,17 +24,23 @@ export default function* load({ meta: { pagesIndex } }) { filters.states.length > 0 ? filters.states.map(({ value }) => value) : ANSWER_STATES; request.in("state", states); - if (userRole === USER_ROLE.CONTRIBUTOR) { - request.in("agreement_id", userAgreements, true); - - if (!states.includes(ANSWER_STATE.TO_DO)) { - request.eq("user_id", userId); - } + if (filters.agreements.length > 0) { + const selectedAgreementIds = filters.agreements.map(({ value }) => value); + const allowedSelectedAgreementIds = + userRole === USER_ROLE.ADMINISTRATOR + ? selectedAgreementIds + : selectedAgreementIds.filter(id => userAgreementIds.includes(id)); + + request.in("agreement_id", allowedSelectedAgreementIds, true); + } else if (userRole === USER_ROLE.CONTRIBUTOR) { + request.in("agreement_id", userAgreementIds, true); } - if (filters.agreements.length > 0) { - const agreementIds = filters.agreements.map(({ value }) => value); - request.in("agreement_id", agreementIds, true); + if (userRole === USER_ROLE.ADMINISTRATOR) { + if (filters.agreements.length > 0) { + const agreementIds = filters.agreements.map(({ value }) => value); + request.in("agreement_id", agreementIds, true); + } } if (filters.questions.length > 0) { @@ -87,7 +93,7 @@ export default function* load({ meta: { pagesIndex } }) { pagesLength, }), ); - } catch (err) { + } catch (err) /* istanbul ignore next */ { if (err.response !== undefined && err.response.status === 416) { toast.error( diff --git a/packages/app/src/sagas/answers/loadOne.js b/packages/app/src/sagas/answers/loadOne.js index 6e2e785b..d23d9285 100644 --- a/packages/app/src/sagas/answers/loadOne.js +++ b/packages/app/src/sagas/answers/loadOne.js @@ -33,7 +33,7 @@ export default function* loadOne({ meta: { id } }) { answer.references = references; yield put(answers.loadOneSuccess(answer)); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.loadOneFailure({ message: null })); } diff --git a/packages/app/src/sagas/answers/removeReferences.js b/packages/app/src/sagas/answers/removeReferences.js index 199a9e82..14541608 100644 --- a/packages/app/src/sagas/answers/removeReferences.js +++ b/packages/app/src/sagas/answers/removeReferences.js @@ -13,7 +13,7 @@ export default function* removeReferences({ meta: { ids }, next }) { yield request.delete(API_PATH); next(); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.removeReferencesFailure({ message: null })); } diff --git a/packages/app/src/sagas/answers/setFilters.js b/packages/app/src/sagas/answers/setFilters.js index 68fd1645..b63d9a35 100644 --- a/packages/app/src/sagas/answers/setFilters.js +++ b/packages/app/src/sagas/answers/setFilters.js @@ -28,7 +28,7 @@ export default function* setFilter({ meta: { filters } }) { }); yield put(answers.load(0)); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.setFiltersFailure({ message: null })); } diff --git a/packages/app/src/sagas/answers/toggleCheck.js b/packages/app/src/sagas/answers/toggleCheck.js index 517a5c6e..d72860e5 100644 --- a/packages/app/src/sagas/answers/toggleCheck.js +++ b/packages/app/src/sagas/answers/toggleCheck.js @@ -14,7 +14,7 @@ export default function* toggleCheck({ meta: { ids } }) { payload: { checked: newChecked }, type: actionTypes.ANSWERS_TOGGLE_CHECK_SUCESS, }); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.toggleCheckFailure({ message: null })); } diff --git a/packages/app/src/sagas/answers/updateGenericReference.js b/packages/app/src/sagas/answers/updateGenericReference.js index 526d02ea..a380a000 100644 --- a/packages/app/src/sagas/answers/updateGenericReference.js +++ b/packages/app/src/sagas/answers/updateGenericReference.js @@ -38,7 +38,7 @@ export default function* updateGenericReference({ meta: { genericReference, ids, ); next(); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.updateGenericReferenceFailure({ message: null })); } diff --git a/packages/app/src/sagas/answers/updateIsPublished.js b/packages/app/src/sagas/answers/updateIsPublished.js index 651448b0..acb339d3 100644 --- a/packages/app/src/sagas/answers/updateIsPublished.js +++ b/packages/app/src/sagas/answers/updateIsPublished.js @@ -22,7 +22,7 @@ export default function* updateIsPublished({ meta: { ids, is, next } }) { ); next(); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.updateIsPublishedFailure({ message: null })); } diff --git a/packages/app/src/sagas/answers/updateReferences.js b/packages/app/src/sagas/answers/updateReferences.js index baaa3de7..412862d5 100644 --- a/packages/app/src/sagas/answers/updateReferences.js +++ b/packages/app/src/sagas/answers/updateReferences.js @@ -14,7 +14,7 @@ export default function* updateReferences({ meta: { data }, next }) { }); next(); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.updateReferencesFailure({ message: null })); } diff --git a/packages/app/src/sagas/answers/updateState.js b/packages/app/src/sagas/answers/updateState.js index bb5fa37f..c4e2177c 100644 --- a/packages/app/src/sagas/answers/updateState.js +++ b/packages/app/src/sagas/answers/updateState.js @@ -49,7 +49,7 @@ export default function* updateState({ meta: { ids, next, state } }) { ); next(); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(answers.updateStateFailure({ message: null })); } diff --git a/packages/app/src/sagas/comments/__tests__/createOne.test.js b/packages/app/src/sagas/comments/__tests__/createOne.test.js new file mode 100644 index 00000000..4c772ea4 --- /dev/null +++ b/packages/app/src/sagas/comments/__tests__/createOne.test.js @@ -0,0 +1,59 @@ +import axios from "axios"; +import { runSaga } from "redux-saga"; + +import getCurrentUser from "../../../libs/getCurrentUser"; +import { initialState } from "../../../reducers/comments"; +import createOne from "../createOne"; + +jest.mock("axios"); +const mockedAxios = /** @type {jest.Mocked} */ (axios); +mockedAxios.create.mockReturnValue(mockedAxios); + +jest.mock("../../../libs/getCurrentUser"); + +describe(`sagas/comments/createOne()`, () => { + let DISPATCHED; + + beforeAll(() => { + mockedAxios.post.mockResolvedValue(); + getCurrentUser.mockReturnValue({ id: "00000000-0000-4000-8000-000000000001" }); + }); + + beforeEach(() => { + DISPATCHED = []; + }); + + it(`should behave as expected`, async () => { + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + createOne, + { + meta: { + answerId: "00000000-0000-4000-8000-000000000002", + isPrivate: true, + value: `A value`, + }, + }, + ).toPromise(); + + expect(mockedAxios.post).toHaveBeenCalledTimes(1); + expect(mockedAxios.post).toHaveBeenCalledWith("/answers_comments", { + answer_id: "00000000-0000-4000-8000-000000000002", + is_private: true, + user_id: "00000000-0000-4000-8000-000000000001", + value: `A value`, + }); + + expect(DISPATCHED).toHaveLength(2); + expect(DISPATCHED[0]).toEqual({ + type: "COMMENT_CREATE_ONE_SUCCESS", + }); + expect(DISPATCHED[1]).toEqual({ + meta: { answerId: "00000000-0000-4000-8000-000000000002" }, + type: "COMMENTS_LOAD", + }); + }); +}); diff --git a/packages/app/src/sagas/comments/__tests__/delete.test.js b/packages/app/src/sagas/comments/__tests__/delete.test.js new file mode 100644 index 00000000..65cb6ce8 --- /dev/null +++ b/packages/app/src/sagas/comments/__tests__/delete.test.js @@ -0,0 +1,51 @@ +import axios from "axios"; +import { runSaga } from "redux-saga"; + +import { initialState } from "../../../reducers/comments"; +import _delete from "../delete"; + +jest.mock("axios"); +const mockedAxios = /** @type {jest.Mocked} */ (axios); +mockedAxios.create.mockReturnValue(mockedAxios); + +describe(`sagas/comments/delete()`, () => { + let DISPATCHED; + + beforeEach(() => { + DISPATCHED = []; + }); + + it(`should behave as expected`, async () => { + const data = []; + + mockedAxios.get.mockResolvedValueOnce({ + data, + headers: {}, + }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + _delete, + { + meta: { + answerId: "00000000-0000-4000-8000-000000000001", + ids: ["00000000-0000-4000-8000-000000000002", "00000000-0000-4000-8000-000000000003"], + }, + }, + ).toPromise(); + + expect(mockedAxios.delete).toHaveBeenCalledTimes(1); + expect(mockedAxios.delete).toHaveBeenCalledWith( + "/answers_comments?id=in.(00000000-0000-4000-8000-000000000002,00000000-0000-4000-8000-000000000003)", + ); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + meta: { answerId: "00000000-0000-4000-8000-000000000001" }, + type: "COMMENTS_LOAD", + }); + }); +}); diff --git a/packages/app/src/sagas/comments/__tests__/load.test.js b/packages/app/src/sagas/comments/__tests__/load.test.js new file mode 100644 index 00000000..8324e8e8 --- /dev/null +++ b/packages/app/src/sagas/comments/__tests__/load.test.js @@ -0,0 +1,48 @@ +import axios from "axios"; +import { runSaga } from "redux-saga"; + +import { initialState } from "../../../reducers/comments"; +import load from "../load"; + +jest.mock("axios"); +const mockedAxios = /** @type {jest.Mocked} */ (axios); +mockedAxios.create.mockReturnValue(mockedAxios); + +describe(`sagas/comments/load()`, () => { + const DATA = []; + let DISPATCHED; + + beforeAll(() => { + mockedAxios.get.mockResolvedValue({ + data: DATA, + headers: {}, + }); + }); + + beforeEach(() => { + DISPATCHED = []; + }); + + it(`should behave as expected`, async () => { + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { answerId: "00000000-0000-4000-8000-000000000001" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + "/answers_comments?select=*&answer_id=eq.00000000-0000-4000-8000-000000000001&order=created_at.asc", + {}, + ); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: DATA }, + type: "COMMENTS_LOAD_SUCCESS", + }); + }); +}); diff --git a/packages/app/src/sagas/comments/createOne.js b/packages/app/src/sagas/comments/createOne.js index c6fc7eff..e5f6f681 100644 --- a/packages/app/src/sagas/comments/createOne.js +++ b/packages/app/src/sagas/comments/createOne.js @@ -22,7 +22,7 @@ export default function* createOne({ meta: { answerId, isPrivate, value } }) { yield request.post(API_PATH, data); yield put({ type: actionTypes.COMMENT_CREATE_ONE_SUCCESS }); yield put(comments.load(answerId)); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(comments.addOneFailure({ message: null })); } diff --git a/packages/app/src/sagas/comments/delete.js b/packages/app/src/sagas/comments/delete.js index 3f22766f..fe9ffe62 100644 --- a/packages/app/src/sagas/comments/delete.js +++ b/packages/app/src/sagas/comments/delete.js @@ -12,7 +12,7 @@ export default function* _delete({ meta: { answerId, ids } }) { yield request.delete(API_PATH); yield put(comments.load(answerId)); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(comments.deleteFailure({ message: null })); } diff --git a/packages/app/src/sagas/comments/load.js b/packages/app/src/sagas/comments/load.js index e164beda..9d2ec7a2 100644 --- a/packages/app/src/sagas/comments/load.js +++ b/packages/app/src/sagas/comments/load.js @@ -13,7 +13,7 @@ export default function* load({ meta: { answerId } }) { const { data: list } = yield request.get(API_PATH); yield put(comments.loadSuccess({ list })); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(comments.loadFailure({ message: null })); } diff --git a/packages/app/src/sagas/legal-references/__tests__/load.test.js b/packages/app/src/sagas/legal-references/__tests__/load.test.js new file mode 100644 index 00000000..4c2f271e --- /dev/null +++ b/packages/app/src/sagas/legal-references/__tests__/load.test.js @@ -0,0 +1,105 @@ +import { runSaga } from "redux-saga"; + +import api from "../../../libs/api"; +import { initialState } from "../../../reducers/legal-references"; +import load from "../load"; + +jest.mock("../../../libs/api"); + +describe(`sagas/legal-references/load()`, () => { + const DATA = [ + { id: "00000000-0000-4000-8000-000000000001", index: "1.2.3", title: "A Title" }, + { id: "00000000-0000-4000-8000-000000000002", index: null, title: "Another Title" }, + ]; + let DISPATCHED; + + beforeAll(() => { + api.get.mockResolvedValue(DATA); + }); + + beforeEach(() => { + DISPATCHED = []; + }); + describe(`with query="A Query"`, () => { + describe(`with category="agreement"`, () => { + it(`should behave as expected`, async () => { + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { category: "agreement", idcc: "1234", query: "A Query" } }, + ).toPromise(); + + expect(api.get).toHaveBeenCalledTimes(1); + expect(api.get).toHaveBeenCalledWith( + "/legal-references?category=agreement&idcc=1234&query=A Query", + ); + + const expectedList = [ + { id: "00000000-0000-4000-8000-000000000001", name: "[Article 1.2.3] A Title" }, + { id: "00000000-0000-4000-8000-000000000002", name: "Another Title" }, + ]; + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { category: "agreement", list: expectedList }, + type: "LEGAL_REFERENCES_LOAD_SUCCESS", + }); + }); + }); + + describe(`with category="labor_code"`, () => { + it(`should behave as expected`, async () => { + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { category: "labor_code", idcc: "1234", query: "A Query" } }, + ).toPromise(); + + expect(api.get).toHaveBeenCalledTimes(1); + expect(api.get).toHaveBeenCalledWith( + "/legal-references?category=labor_code&idcc=1234&query=A Query", + ); + + const expectedList = [ + { id: "00000000-0000-4000-8000-000000000001", name: "1.2.3" }, + { id: "00000000-0000-4000-8000-000000000002", name: null }, + ]; + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { category: "labor_code", list: expectedList }, + type: "LEGAL_REFERENCES_LOAD_SUCCESS", + }); + }); + }); + }); + + describe(`with query=""`, () => { + describe(`with category="agreement"`, () => { + it(`should behave as expected`, async () => { + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { category: "agreement", idcc: "1234", query: "" } }, + ).toPromise(); + + expect(api.get).not.toHaveBeenCalled(); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { category: "agreement", list: [] }, + type: "LEGAL_REFERENCES_LOAD_SUCCESS", + }); + }); + }); + }); +}); diff --git a/packages/app/src/sagas/legal-references/load.js b/packages/app/src/sagas/legal-references/load.js index de8ed622..bd1c4679 100644 --- a/packages/app/src/sagas/legal-references/load.js +++ b/packages/app/src/sagas/legal-references/load.js @@ -10,7 +10,7 @@ import toast from "../../libs/toast"; export default function* load({ meta: { category, idcc, query } }) { try { if (query.length === 0) { - yield put(legalReferences.loadSuccess({ data: [], query })); + yield put(legalReferences.loadSuccess({ category, list: [] })); return; } @@ -29,7 +29,7 @@ export default function* load({ meta: { category, idcc, query } }) { })); yield put(legalReferences.loadSuccess({ category, list })); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(legalReferences.loadFailure({ message: null })); } diff --git a/packages/app/src/sagas/logs/__tests__/deleteOlderThanOneWeek.test.js b/packages/app/src/sagas/logs/__tests__/deleteOlderThanOneWeek.test.js new file mode 100644 index 00000000..b98638f9 --- /dev/null +++ b/packages/app/src/sagas/logs/__tests__/deleteOlderThanOneWeek.test.js @@ -0,0 +1,97 @@ +import axios from "axios"; +import { toast } from "react-toastify"; +import { runSaga } from "redux-saga"; + +import { initialState } from "../../../reducers/logs"; +import deleteOlderThanOneWeek from "../deleteOlderThanOneWeek"; + +jest.mock("axios"); +jest.mock("react-toastify"); +const mockedAxios = /** @type {jest.Mocked} */ (axios); +const mockedToast = /** @type {jest.Mocked} */ (toast); +mockedAxios.create.mockReturnValue(mockedAxios); + +// class MockDate { +// constructor() { +// return new Date(0); +// } + +// static now() { +// return 0; +// } +// } + +describe(`sagas/logs/deleteOlderThanOneWeek()`, () => { + let DISPATCHED; + + beforeEach(() => { + DISPATCHED = []; + + // global.Date = MockDate; + }); + + describe(`when there are logs older than one week`, () => { + const data = [{ id: "00000000-0000-4000-8000-000000000001" }]; + + it(`should behave as expected`, async () => { + mockedAxios.get.mockResolvedValueOnce({ data }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + deleteOlderThanOneWeek, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + expect.stringMatching( + /^\/logs\?select=\*&created_at=lte\.\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, + ), + {}, + ); + + expect(mockedAxios.post).toHaveBeenCalledTimes(1); + expect(mockedAxios.post).toHaveBeenCalledWith("/rpc/purge_logs", undefined); + + expect(mockedToast.success).toHaveBeenCalledTimes(1); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + meta: { pageIndex: -1, query: "" }, + type: "LOGS_LOAD", + }); + }); + }); + + describe(`when there is no log older than one week`, () => { + const data = []; + + it(`should behave as expected`, async () => { + mockedAxios.get.mockResolvedValueOnce({ data }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + deleteOlderThanOneWeek, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + expect.stringMatching( + /^\/logs\?select=\*&created_at=lte\.\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/, + ), + {}, + ); + + expect(mockedToast.warn).toHaveBeenCalledTimes(1); + + expect(mockedAxios.post).not.toHaveBeenCalled(); + expect(mockedToast.success).not.toHaveBeenCalled(); + expect(DISPATCHED).toHaveLength(0); + }); + }); +}); diff --git a/packages/app/src/sagas/logs/__tests__/load.test.js b/packages/app/src/sagas/logs/__tests__/load.test.js new file mode 100644 index 00000000..8c120fe8 --- /dev/null +++ b/packages/app/src/sagas/logs/__tests__/load.test.js @@ -0,0 +1,112 @@ +import axios from "axios"; +import { runSaga } from "redux-saga"; + +import { initialState } from "../../../reducers/logs"; +import load from "../load"; + +jest.mock("axios"); +const mockedAxios = /** @type {jest.Mocked} */ (axios); +mockedAxios.create.mockReturnValue(mockedAxios); + +describe(`sagas/logs/load()`, () => { + const DATA = []; + let DISPATCHED; + + beforeAll(() => { + mockedAxios.get.mockResolvedValue({ + data: DATA, + headers: { + "content-range": "0-9/10", + }, + }); + }); + + beforeEach(() => { + DISPATCHED = []; + }); + + describe(`with pageIndex=0`, () => { + describe(`with query=""`, () => { + it(`should behave as expected`, async () => { + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pageIndex: 0, query: "" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + "/logs?select=*,user(*)&order=created_at.desc&limit=10&offset=0", + { + headers: { Prefer: "count=exact" }, + }, + ); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: DATA, pageIndex: 0, pagesLength: 1 }, + type: "LOGS_LOAD_SUCCESS", + }); + }); + }); + + describe(`with query="A Query"`, () => { + it(`should behave as expected`, async () => { + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pageIndex: 0, query: "A Query" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + "/logs?select=*,user(*)&url=ilike.*A Query*&order=created_at.desc&limit=10&offset=0", + { + headers: { Prefer: "count=exact" }, + }, + ); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: DATA, pageIndex: 0, pagesLength: 1 }, + type: "LOGS_LOAD_SUCCESS", + }); + }); + }); + }); + + describe(`with pageIndex=-1`, () => { + describe(`with query=""`, () => { + it(`should behave as expected`, async () => { + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pageIndex: -1, query: "" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + "/logs?select=*,user(*)&order=created_at.desc", + { + headers: { Prefer: "count=exact" }, + }, + ); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: DATA, pageIndex: -1, pagesLength: -1 }, + type: "LOGS_LOAD_SUCCESS", + }); + }); + }); + }); +}); diff --git a/packages/app/src/sagas/logs/deleteOlderThanOneWeek.js b/packages/app/src/sagas/logs/deleteOlderThanOneWeek.js index 14f907c0..f655d13e 100644 --- a/packages/app/src/sagas/logs/deleteOlderThanOneWeek.js +++ b/packages/app/src/sagas/logs/deleteOlderThanOneWeek.js @@ -24,7 +24,7 @@ export default function* deleteOlderThanOneWeek() { yield deleteRequest.post(API_DELETE_PATH); toast.success(`${customNumeral(data.length).format("0,0")} logs ont été purgés.`); yield put(logs.load({ pageIndex: -1 })); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); yield put(logs.deleteOlderThanOneWeekFailure({ message: null })); } diff --git a/packages/app/src/sagas/logs/load.js b/packages/app/src/sagas/logs/load.js index 6f734b94..a5e6b2ed 100644 --- a/packages/app/src/sagas/logs/load.js +++ b/packages/app/src/sagas/logs/load.js @@ -14,7 +14,7 @@ export default function* load({ meta: { pageIndex, query } }) { } if (query.length > 0) { - request.or.ilike("url", query); + request.ilike("url", query); } const { data: list, pagesLength } = yield request.get("/logs", true); @@ -26,7 +26,7 @@ export default function* load({ meta: { pageIndex, query } }) { pagesLength, }), ); - } catch (err) { + } catch (err) /* istanbul ignore next */ { if (err.response !== undefined && err.response.status === 416) { const pageIndex = Math.floor(Number(err.response.headers["content-range"].substr(2)) / 10); diff --git a/packages/app/src/sagas/modal.js b/packages/app/src/sagas/modal/index.js similarity index 80% rename from packages/app/src/sagas/modal.js rename to packages/app/src/sagas/modal/index.js index 4b28ef8c..21971960 100644 --- a/packages/app/src/sagas/modal.js +++ b/packages/app/src/sagas/modal/index.js @@ -1,7 +1,7 @@ import { put, takeLatest } from "redux-saga/effects"; -import { actionTypes } from "../actions"; -import toast from "../libs/toast"; +import { actionTypes } from "../../actions"; +import toast from "../../libs/toast"; let ACTION = resetAction(); @@ -18,7 +18,7 @@ function open({ meta: { action } }) { function* submit() { try { yield put(ACTION()); - } catch (err) { + } catch (err) /* istanbul ignore next */ { toast.error(err.message); } diff --git a/packages/app/src/sagas/questions/__tests__/load.test.js b/packages/app/src/sagas/questions/__tests__/load.test.js new file mode 100644 index 00000000..755a9e0c --- /dev/null +++ b/packages/app/src/sagas/questions/__tests__/load.test.js @@ -0,0 +1,126 @@ +import axios from "axios"; +import { runSaga } from "redux-saga"; + +import { initialState } from "../../../reducers/questions"; +import load from "../load"; + +jest.mock("axios"); +const mockedAxios = /** @type {jest.Mocked} */ (axios); +mockedAxios.create.mockReturnValue(mockedAxios); + +describe(`sagas/questions/load()`, () => { + let DISPATCHED; + + beforeEach(() => { + DISPATCHED = []; + }); + + describe(`with pageIndex=0`, () => { + describe(`with query=""`, () => { + it(`should behave as expected`, async () => { + const data = []; + + mockedAxios.get.mockResolvedValueOnce({ + data, + headers: { + "content-range": "0-9/15", + }, + }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pageIndex: 0, query: "" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + "/questions?select=*&order=index.asc&limit=10&offset=0", + { + headers: { Prefer: "count=exact" }, + }, + ); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: data, pageIndex: 0, pagesLength: 2 }, + type: "QUESTIONS_LOAD_SUCCESS", + }); + }); + }); + + describe(`with query="A Query"`, () => { + it(`should behave as expected`, async () => { + const data = []; + + mockedAxios.get.mockResolvedValueOnce({ + data, + headers: { + "content-range": "0-9/15", + }, + }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pageIndex: 0, query: "A Query" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith( + "/questions?select=*&value=ilike.*A Query*&order=index.asc&limit=10&offset=0", + { + headers: { Prefer: "count=exact" }, + }, + ); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: data, pageIndex: 0, pagesLength: 2 }, + type: "QUESTIONS_LOAD_SUCCESS", + }); + }); + }); + }); + + describe(`with pageIndex=-1`, () => { + describe(`with query=""`, () => { + it(`should behave as expected`, async () => { + const data = []; + + mockedAxios.get.mockResolvedValueOnce({ + data, + headers: { + "content-range": "0-14/15", + }, + }); + + await runSaga( + { + dispatch: action => DISPATCHED.push(action), + getState: () => initialState, + }, + load, + { meta: { pageIndex: -1, query: "" } }, + ).toPromise(); + + expect(mockedAxios.get).toHaveBeenCalledTimes(1); + expect(mockedAxios.get).toHaveBeenCalledWith("/questions?select=*&order=index.asc", { + headers: { Prefer: "count=exact" }, + }); + + expect(DISPATCHED).toHaveLength(1); + expect(DISPATCHED[0]).toEqual({ + payload: { list: data, pageIndex: -1, pagesLength: -1 }, + type: "QUESTIONS_LOAD_SUCCESS", + }); + }); + }); + }); +}); diff --git a/packages/app/src/sagas/questions/load.js b/packages/app/src/sagas/questions/load.js index 8f458473..fb258c73 100644 --- a/packages/app/src/sagas/questions/load.js +++ b/packages/app/src/sagas/questions/load.js @@ -14,7 +14,7 @@ export default function* load({ meta: { pageIndex, query } }) { } if (query.length > 0) { - request.or.ilike("value", query); + request.ilike("value", query); } request.orderBy("index"); @@ -28,7 +28,7 @@ export default function* load({ meta: { pageIndex, query } }) { pagesLength, }), ); - } catch (err) { + } catch (err) /* istanbul ignore next */ { if (err.response !== undefined && err.response.status === 416) { const pageIndex = Math.floor(Number(err.response.headers["content-range"].substr(2)) / 10);