diff --git a/src/export-page/data/thunks.js b/src/export-page/data/thunks.js index 0bfdd410b..f5fdcae07 100644 --- a/src/export-page/data/thunks.js +++ b/src/export-page/data/thunks.js @@ -22,6 +22,21 @@ import { updateSavingStatus, } from './slice'; +function setExportDate({ + date, exportStatus, exportOutput, dispatch, +}) { + // If there is no cookie for the last export date, set it now. + const cookies = new Cookies(); + const cookieData = cookies.get(LAST_EXPORT_COOKIE_NAME); + if (!cookieData?.completed) { + setExportCookie(date, exportStatus === EXPORT_STAGES.SUCCESS); + } + // If we don't have export date set yet via cookie, set success date to current date. + if (exportOutput && !cookieData?.completed) { + dispatch(updateSuccessDate(date)); + } +} + export function startExportingCourse(courseId) { return async (dispatch) => { dispatch(updateSavingStatus({ status: RequestStatus.PENDING })); @@ -45,29 +60,30 @@ export function fetchExportStatus(courseId) { return async (dispatch) => { dispatch(updateLoadingStatus({ status: RequestStatus.IN_PROGRESS })); try { - const { exportStatus, exportOutput, exportError } = await getExportStatus(courseId); + const { + exportStatus, exportOutput, exportError, + } = await getExportStatus(courseId); dispatch(updateCurrentStage(Math.abs(exportStatus))); + const date = moment().valueOf(); + + setExportDate({ + date, exportStatus, exportOutput, dispatch, + }); + + if (exportError) { + const errorMessage = exportError.rawErrorMsg || exportError; + const errorUnitUrl = exportError.editUnitUrl || null; + dispatch(updateError({ msg: errorMessage, unitUrl: errorUnitUrl })); + dispatch(updateIsErrorModalOpen(true)); + } + if (exportOutput) { if (exportOutput.startsWith('/')) { dispatch(updateDownloadPath(`${getConfig().STUDIO_BASE_URL}${exportOutput}`)); } else { dispatch(updateDownloadPath(exportOutput)); } - dispatch(updateSuccessDate(moment().valueOf())); - } - - const cookies = new Cookies(); - const cookieData = cookies.get(LAST_EXPORT_COOKIE_NAME); - if (!cookieData?.completed) { - setExportCookie(moment().valueOf(), exportStatus === EXPORT_STAGES.SUCCESS); - } - - if (exportError) { - const errorMessage = exportError.rawErrorMsg || exportError; - const errorUnitUrl = exportError.editUnitUrl || null; - dispatch(updateError({ msg: errorMessage, unitUrl: errorUnitUrl })); - dispatch(updateIsErrorModalOpen(true)); } dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL })); diff --git a/src/export-page/data/thunks.test.js b/src/export-page/data/thunks.test.js new file mode 100644 index 000000000..e8dd9762f --- /dev/null +++ b/src/export-page/data/thunks.test.js @@ -0,0 +1,146 @@ +import Cookies from 'universal-cookie'; +import { fetchExportStatus } from './thunks'; +import * as api from './api'; +import { EXPORT_STAGES } from './constants'; + +jest.mock('universal-cookie', () => jest.fn().mockImplementation(() => ({ + get: jest.fn().mockImplementation(() => ({ completed: false })), +}))); + +jest.mock('../utils', () => ({ + setExportCookie: jest.fn(), +})); + +describe('fetchExportStatus thunk', () => { + const dispatch = jest.fn(); + const getState = jest.fn(); + const courseId = 'course-123'; + const exportStatus = EXPORT_STAGES.COMPRESSING; + const exportOutput = 'export output'; + const exportError = 'export error'; + let mockGetExportStatus; + + beforeEach(() => { + jest.clearAllMocks(); + + mockGetExportStatus = jest.spyOn(api, 'getExportStatus').mockResolvedValue({ + exportStatus, + exportOutput, + exportError, + }); + }); + + it('should dispatch updateCurrentStage with export status', async () => { + mockGetExportStatus.mockResolvedValue({ + exportStatus, + exportOutput, + exportError, + }); + + await fetchExportStatus(courseId)(dispatch, getState); + + expect(dispatch).toHaveBeenCalledWith({ + payload: exportStatus, + type: 'exportPage/updateCurrentStage', + }); + }); + + it('should dispatch updateError on export error', async () => { + mockGetExportStatus.mockResolvedValue({ + exportStatus, + exportOutput, + exportError, + }); + + await fetchExportStatus(courseId)(dispatch, getState); + + expect(dispatch).toHaveBeenCalledWith({ + payload: { + msg: exportError, + unitUrl: null, + }, + type: 'exportPage/updateError', + }); + }); + + it('should dispatch updateIsErrorModalOpen with true if export error', async () => { + mockGetExportStatus.mockResolvedValue({ + exportStatus, + exportOutput, + exportError, + }); + + await fetchExportStatus(courseId)(dispatch, getState); + + expect(dispatch).toHaveBeenCalledWith({ + payload: true, + type: 'exportPage/updateIsErrorModalOpen', + }); + }); + + it('should not dispatch updateIsErrorModalOpen if no export error', async () => { + mockGetExportStatus.mockResolvedValue({ + exportStatus, + exportOutput, + exportError: null, + }); + + await fetchExportStatus(courseId)(dispatch, getState); + + expect(dispatch).not.toHaveBeenCalledWith({ + payload: false, + type: 'exportPage/updateIsErrorModalOpen', + }); + }); + + it("should dispatch updateDownloadPath if there's export output", async () => { + mockGetExportStatus.mockResolvedValue({ + exportStatus, + exportOutput, + exportError, + }); + + await fetchExportStatus(courseId)(dispatch, getState); + + expect(dispatch).toHaveBeenCalledWith({ + payload: exportOutput, + type: 'exportPage/updateDownloadPath', + }); + }); + + it('should dispatch updateSuccessDate with current date if export status is success', async () => { + mockGetExportStatus.mockResolvedValue({ + exportStatus: + EXPORT_STAGES.SUCCESS, + exportOutput, + exportError, + }); + + await fetchExportStatus(courseId)(dispatch, getState); + + expect(dispatch).toHaveBeenCalledWith({ + payload: expect.any(Number), + type: 'exportPage/updateSuccessDate', + }); + }); + + it('should not dispatch updateSuccessDate with current date if last-export cookie is already set', async () => { + mockGetExportStatus.mockResolvedValue({ + exportStatus: + EXPORT_STAGES.SUCCESS, + exportOutput, + exportError, + }); + + Cookies.mockImplementation(() => ({ + get: jest.fn().mockReturnValueOnce({ completed: true }), + })); + + await fetchExportStatus(courseId)(dispatch, getState); + + expect(dispatch).not.toHaveBeenCalledWith({ + payload: expect.any, + type: 'exportPage/updateSuccessDate', + }); + }); +});