diff --git a/frontend/src/actions/projectActions.js b/frontend/src/actions/projectActions.js index 8e366075a..6de076bdd 100644 --- a/frontend/src/actions/projectActions.js +++ b/frontend/src/actions/projectActions.js @@ -29,7 +29,7 @@ const fetchProjectError = (error) => { const fetchProject = (projectId, params) => { return (dispatch) => { dispatch(fetchProjectRequested()) - axios + return axios .get(api.API_URL + `/projects/fetch/${projectId}`, { params }) .then((project) => { if (project.data) { @@ -40,11 +40,11 @@ const fetchProject = (projectId, params) => { }) .catch((e) => { dispatch(addNotification('actions.task.fetch.other.error', { severity: 'error' })) - dispatch(fetchProjectError(e)) // eslint-disable-next-line no-console console.log('not possible to fetch issue') // eslint-disable-next-line no-console console.log(e) + return dispatch(fetchProjectError(e)) }) } } @@ -68,7 +68,7 @@ const listProjectsError = (error) => { const listProjects = () => { return (dispatch) => { dispatch(listProjectsRequested()) - axios + return axios .get(api.API_URL + '/projects/list') .then((projects) => { if (projects.data) { @@ -79,11 +79,11 @@ const listProjects = () => { }) .catch((e) => { dispatch(addNotification('actions.task.fetch.other.error', { severity: 'error' })) - dispatch(listProjectsError(e)) // eslint-disable-next-line no-console console.log('not possible to fetch issue') // eslint-disable-next-line no-console console.log(e) + return dispatch(listProjectsError(e)) }) } } diff --git a/frontend/tests/actions/projectActions.test.js b/frontend/tests/actions/projectActions.test.js new file mode 100644 index 000000000..c9d8d42b5 --- /dev/null +++ b/frontend/tests/actions/projectActions.test.js @@ -0,0 +1,71 @@ +import axios from 'axios' +import { applyMiddleware, combineReducers, createStore } from 'redux' +import { thunk } from 'redux-thunk' +import { fetchProject, listProjects } from '../../src/actions/projectActions' +import { project, projects } from '../../src/reducers/projectReducer' + +jest.mock('axios') + +describe.each([ + [ + 'fetchProject', + () => fetchProject(7, { status: 'open' }), + 'project', + { id: 7 }, + 'FETCH_PROJECT' + ], + ['listProjects', () => listProjects(), 'projects', [{ id: 7 }], 'LIST_PROJECTS'] +])('%s completion', (name, action, stateKey, responseData, prefix) => { + let store + + beforeEach(() => { + jest.clearAllMocks() + store = createStore( + combineReducers({ project, projects, intl: () => ({ messages: {} }) }), + applyMiddleware(thunk) + ) + jest.spyOn(console, 'log').mockImplementation(() => {}) + }) + + afterEach(() => jest.restoreAllMocks()) + + it('waits for the HTTP response and the reducer update before resolving', async () => { + let resolveRequest + axios.get.mockReturnValue( + new Promise((resolve) => { + resolveRequest = resolve + }) + ) + + const completion = store.dispatch(action()) + expect(completion).toEqual(expect.any(Promise)) + let settled = false + completion.then(() => { + settled = true + }) + await Promise.resolve() + expect(settled).toBe(false) + expect(store.getState()[stateKey].completed).toBe(false) + + resolveRequest({ data: responseData }) + await expect(completion).resolves.toMatchObject({ type: `${prefix}_SUCCESS` }) + expect(store.getState()[stateKey]).toMatchObject({ completed: true, data: responseData }) + }) + + it('resolves to the error action after a network failure is reduced', async () => { + const error = new Error('Network Error') + axios.get.mockRejectedValue(error) + await expect(store.dispatch(action())).resolves.toMatchObject({ + type: `${prefix}_ERROR`, + error + }) + expect(store.getState()[stateKey]).toMatchObject({ completed: true, error }) + }) + + it('resolves to the error action when the response has no project data', async () => { + axios.get.mockResolvedValue({ data: null }) + await expect(store.dispatch(action())).resolves.toMatchObject({ type: `${prefix}_ERROR` }) + expect(store.getState()[stateKey].completed).toBe(true) + expect(store.getState()[stateKey].error).toEqual({ message: 'actions.task.fetch.unavailable' }) + }) +})