diff --git a/src/Notifications/data/api.js b/src/Notifications/data/api.js index adb7438..2aa2c05 100644 --- a/src/Notifications/data/api.js +++ b/src/Notifications/data/api.js @@ -1,10 +1,10 @@ -import { camelCaseObject, getConfig, snakeCaseObject } from '@edx/frontend-platform'; +import { getConfig, snakeCaseObject } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; export const getNotificationsCountApiUrl = () => `${getConfig().LMS_BASE_URL}/api/notifications/count/`; export const getNotificationsApiUrl = () => `${getConfig().LMS_BASE_URL}/api/notifications/`; export const markNotificationsSeenApiUrl = (appName) => `${getConfig().LMS_BASE_URL}/api/notifications/mark-notifications-unseen/${appName}/`; -export const markAllNotificationsAsReadpiUrl = (appName, id) => `${getConfig().LMS_BASE_URL}/api/notifications/mark-notifications-read/${appName}/${id}`; +export const markAllNotificationsAsReadpiUrl = () => `${getConfig().LMS_BASE_URL}/api/notifications/read/`; export async function getNotifications(appName, page, pageSize) { const params = snakeCaseObject({ page, pageSize }); @@ -14,29 +14,31 @@ export async function getNotifications(appName, page, pageSize) { const endIndex = startIndex + pageSize; const notifications = data.slice(startIndex, endIndex); - return { notifications: camelCaseObject(notifications), numPages: 2, currentPage: page }; + return { notifications, numPages: 2, currentPage: page }; } export async function getNotificationCounts() { const { data } = await getAuthenticatedHttpClient().get(getNotificationsCountApiUrl()); - return camelCaseObject(data); + return data; } export async function markNotificationSeen(appName) { const { data } = await getAuthenticatedHttpClient().put(`${markNotificationsSeenApiUrl(appName)}`); - return camelCaseObject(data); + return data; } export async function markAllNotificationRead(appName) { - const { data } = await getAuthenticatedHttpClient().put(`${markAllNotificationsAsReadpiUrl(appName)}`); + const params = snakeCaseObject({ appName }); + const { data } = await getAuthenticatedHttpClient().put(markAllNotificationsAsReadpiUrl(), { params }); - return camelCaseObject(data); + return data; } -export async function markNotificationRead(appName, notificationId) { - const { data } = await getAuthenticatedHttpClient().put(`${markAllNotificationsAsReadpiUrl(appName, notificationId)}`); +export async function markNotificationRead(notificationId) { + const params = snakeCaseObject({ notificationId }); + const { data } = await getAuthenticatedHttpClient().put(markAllNotificationsAsReadpiUrl(), { params }); - return camelCaseObject({ data, id: notificationId }); + return { data, id: notificationId }; } diff --git a/src/Notifications/data/api.test.js b/src/Notifications/data/api.test.js index 58f8963..488aeab 100644 --- a/src/Notifications/data/api.test.js +++ b/src/Notifications/data/api.test.js @@ -4,30 +4,19 @@ import { Factory } from 'rosie'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { initializeMockApp } from '@edx/frontend-platform/testing'; -import { initializeStore } from '../../store'; -import executeThunk from '../../test-utils'; import { getNotificationsApiUrl, getNotificationsCountApiUrl, markAllNotificationsAsReadpiUrl, markNotificationsSeenApiUrl, getNotificationCounts, getNotifications, markNotificationSeen, markAllNotificationRead, markNotificationRead, } from './api'; -import { - fetchAppsNotificationCount, - fetchNotificationList, - markAllNotificationsAsRead, - markNotificationsAsRead, - markNotificationsAsSeen, -} from './thunks'; import './__factories__'; const notificationCountsApiUrl = getNotificationsCountApiUrl(); const notificationsApiUrl = getNotificationsApiUrl(); const markedAllNotificationsAsSeenApiUrl = markNotificationsSeenApiUrl('discussions'); -const markedAllNotificationsAsReadApiUrl = markAllNotificationsAsReadpiUrl('discussions'); -const markedNotificationAsReadApiUrl = markAllNotificationsAsReadpiUrl('discussions', 1); +const markedAllNotificationsAsReadApiUrl = markAllNotificationsAsReadpiUrl(); let axiosMock = null; -let store; describe('Notifications API', () => { beforeEach(async () => { @@ -41,14 +30,13 @@ describe('Notifications API', () => { }); axiosMock = new MockAdapter(getAuthenticatedHttpClient()); Factory.resetAll(); - store = initializeStore(); }); afterEach(() => { axiosMock.reset(); }); - it('successfully get notification counts for different tabs.', async () => { + it('Successfully get notification counts for different tabs.', async () => { axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount'))); const { count, countByAppName } = await getNotificationCounts(); @@ -60,21 +48,20 @@ describe('Notifications API', () => { expect(countByAppName.authoring).toEqual(5); }); - it('failed to get notification counts.', async () => { - axiosMock.onGet(notificationCountsApiUrl).reply(404); - await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState); - - expect(store.getState().notifications.notificationStatus).toEqual('failed'); + it.each([ + { statusCode: 404, message: 'Failed to get notification counts.' }, + { statusCode: 403, message: 'Denied to get notification counts.' }, + ])('%s for notification counts API.', async ({ statusCode, message }) => { + axiosMock.onGet(notificationCountsApiUrl).reply(statusCode, { message }); + try { + await getNotificationCounts(); + } catch (error) { + expect(error.response.status).toEqual(statusCode); + expect(error.response.data.message).toEqual(message); + } }); - it('denied to get notification counts.', async () => { - axiosMock.onGet(notificationCountsApiUrl).reply(403, {}); - await executeThunk(fetchAppsNotificationCount(), store.dispatch); - - expect(store.getState().notifications.notificationStatus).toEqual('denied'); - }); - - it('successfully get notifications.', async () => { + it('Successfully get notifications.', async () => { axiosMock.onGet(notificationsApiUrl).reply( 200, (Factory.buildList('notification', 2, null, { createdDate: new Date().toISOString() })), @@ -85,21 +72,20 @@ describe('Notifications API', () => { expect(notifications).toHaveLength(2); }); - it('failed to get notifications.', async () => { - axiosMock.onGet(notificationsApiUrl).reply(404); - await executeThunk(fetchNotificationList({ page: 1, pageSize: 10 }), store.dispatch, store.getState); - - expect(store.getState().notifications.notificationStatus).toEqual('failed'); + it.each([ + { statusCode: 404, message: 'Failed to get notifications.' }, + { statusCode: 403, message: 'Denied to get notifications.' }, + ])('%s for notification API.', async ({ statusCode, message }) => { + axiosMock.onGet(notificationsApiUrl).reply(statusCode, { message }); + try { + await getNotifications({ page: 1, pageSize: 10 }); + } catch (error) { + expect(error.response.status).toEqual(statusCode); + expect(error.response.data.message).toEqual(message); + } }); - it('denied to get notifications.', async () => { - axiosMock.onGet(notificationsApiUrl).reply(403, {}); - await executeThunk(fetchNotificationList({ page: 1, pageSize: 10 }), store.dispatch); - - expect(store.getState().notifications.notificationStatus).toEqual('denied'); - }); - - it('successfully marked all notifications as seen for selected app.', async () => { + it('Successfully marked all notifications as seen for selected app.', async () => { axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(200, { message: 'Notifications marked seen.' }); const { message } = await markNotificationSeen('discussions'); @@ -107,21 +93,20 @@ describe('Notifications API', () => { expect(message).toEqual('Notifications marked seen.'); }); - it('failed to mark all notifications as seen for selected app.', async () => { - axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(404); - await executeThunk(markNotificationsAsSeen('discussions'), store.dispatch, store.getState); - - expect(store.getState().notifications.notificationStatus).toEqual('failed'); + it.each([ + { statusCode: 404, message: 'Failed to mark all notifications as seen for selected app.' }, + { statusCode: 403, message: 'Denied to mark all notifications as seen for selected app.' }, + ])('%s for notification mark as seen API.', async ({ statusCode, message }) => { + axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(statusCode, { message }); + try { + await markNotificationSeen('discussions'); + } catch (error) { + expect(error.response.status).toEqual(statusCode); + expect(error.response.data.message).toEqual(message); + } }); - it('denied to mark all notifications as seen for selected app.', async () => { - axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(403, {}); - await executeThunk(markNotificationsAsSeen('discussions'), store.dispatch); - - expect(store.getState().notifications.notificationStatus).toEqual('denied'); - }); - - it('successfully marked all notifications as read for selected app.', async () => { + it('Successfully marked all notifications as read for selected app.', async () => { axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notifications marked read.' }); const { message } = await markAllNotificationRead('discussions'); @@ -129,39 +114,37 @@ describe('Notifications API', () => { expect(message).toEqual('Notifications marked read.'); }); - it('failed to mark all notifications as read for selected app.', async () => { - axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(404); - await executeThunk(markAllNotificationsAsRead('discussions'), store.dispatch, store.getState); - - expect(store.getState().notifications.notificationStatus).toEqual('failed'); + it.each([ + { statusCode: 404, message: 'Failed to mark all notifications as read for selected app.' }, + { statusCode: 403, message: 'Denied to mark all notifications as read for selected app.' }, + ])('%s for notification mark all as read API.', async ({ statusCode, message }) => { + axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(statusCode, { message }); + try { + await markAllNotificationRead('discussions'); + } catch (error) { + expect(error.response.status).toEqual(statusCode); + expect(error.response.data.message).toEqual(message); + } }); - it('denied to mark all notifications as read for selected app.', async () => { - axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(403, {}); - await executeThunk(markAllNotificationsAsRead('discussions'), store.dispatch); + it('Successfully marked notification as read.', async () => { + axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notification marked read.' }); - expect(store.getState().notifications.notificationStatus).toEqual('denied'); - }); - - it('successfully marked notification as read.', async () => { - axiosMock.onPut(markedNotificationAsReadApiUrl).reply(200, { message: 'Notification marked read.' }); - - const { data } = await markNotificationRead('discussions', 1); + const { data } = await markNotificationRead(1); expect(data.message).toEqual('Notification marked read.'); }); - it('failed to mark notification as read .', async () => { - axiosMock.onPut(markedNotificationAsReadApiUrl).reply(404); - await executeThunk(markNotificationsAsRead('discussions', 1), store.dispatch, store.getState); - - expect(store.getState().notifications.notificationStatus).toEqual('failed'); - }); - - it('denied to mark notification as read.', async () => { - axiosMock.onPut(markedNotificationAsReadApiUrl).reply(403, {}); - await executeThunk(markNotificationsAsRead('discussions', 1), store.dispatch); - - expect(store.getState().notifications.notificationStatus).toEqual('denied'); + it.each([ + { statusCode: 404, message: 'Failed to mark notification as read.' }, + { statusCode: 403, message: 'Denied to mark notification as read.' }, + ])('%s for notification mark as read API.', async ({ statusCode, message }) => { + axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(statusCode, { message }); + try { + await markAllNotificationRead(1); + } catch (error) { + expect(error.response.status).toEqual(statusCode); + expect(error.response.data.message).toEqual(message); + } }); }); diff --git a/src/Notifications/data/redux.test.js b/src/Notifications/data/redux.test.js index 34b137a..998ce8f 100644 --- a/src/Notifications/data/redux.test.js +++ b/src/Notifications/data/redux.test.js @@ -18,8 +18,7 @@ import './__factories__'; const notificationCountsApiUrl = getNotificationsCountApiUrl(); const notificationsApiUrl = getNotificationsApiUrl(); -const markedNotificationAsReadApiUrl = markAllNotificationsAsReadpiUrl('discussions', 1); -const markedAllNotificationsAsReadApiUrl = markAllNotificationsAsReadpiUrl('discussions'); +const markedAllNotificationsAsReadApiUrl = markAllNotificationsAsReadpiUrl(); const markedAllNotificationsAsSeenApiUrl = markNotificationsSeenApiUrl('discussions'); let axiosMock; @@ -52,7 +51,7 @@ describe('Notification Redux', () => { axiosMock.reset(); }); - it('successfully loaded initial notification states in the redux.', async () => { + it('Successfully loaded initial notification states in the redux.', async () => { executeThunk(resetNotificationState(), store.dispatch, store.getState); const { notifications } = store.getState(); @@ -70,13 +69,13 @@ describe('Notification Redux', () => { expect(notifications.pagination.nextPage).toBeNull(); }); - it('successfully loaded notifications list in the redux.', async () => { + it('Successfully loaded notifications list in the redux.', async () => { const { notifications: { notifications } } = store.getState(); expect(Object.keys(notifications)).toHaveLength(2); }); - it('successfully loaded notification counts in the redux.', async () => { + it('Successfully loaded notification counts in the redux.', async () => { const { notifications: { tabsCount } } = store.getState(); expect(tabsCount.count).toEqual(25); @@ -86,40 +85,14 @@ describe('Notification Redux', () => { expect(tabsCount.authoring).toEqual(5); }); - it('successfully loaded showNotificationTray status in the redux based on api.', async () => { - const { notifications: { showNotificationTray } } = store.getState(); - - expect(showNotificationTray).toEqual(true); - }); - - it('successfully store the count, numPages, currentPage, and nextPage data in redux.', async () => { - const { notifications: { pagination } } = store.getState(); - - expect(pagination.count).toEqual(10); - expect(pagination.currentPage).toEqual(1); - expect(pagination.numPages).toEqual(2); - }); - - it('successfully updated the selected app name in redux.', async () => { - const { notifications: { appName } } = store.getState(); - - expect(appName).toEqual('discussions'); - }); - - it('successfully store notification ids in the selected app in apps.', async () => { - const { notifications: { apps } } = store.getState(); - - expect(apps.discussions).toHaveLength(2); - }); - - it('successfully marked all notifications as seen for selected app.', async () => { + it('Successfully marked all notifications as seen for selected app.', async () => { axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(200); await executeThunk(markNotificationsAsSeen('discussions'), store.dispatch, store.getState); expect(store.getState().notifications.notificationStatus).toEqual('successful'); }); - it('successfully marked all notifications as read for selected app in the redux.', async () => { + it('Successfully marked all notifications as read for selected app in the redux.', async () => { axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(200); await executeThunk(markAllNotificationsAsRead('discussions'), store.dispatch, store.getState); @@ -130,9 +103,9 @@ describe('Notification Redux', () => { expect(firstNotification.lastRead).not.toBeNull(); }); - it('successfully marked notification as read in the redux.', async () => { - axiosMock.onPut(markedNotificationAsReadApiUrl).reply(200); - await executeThunk(markNotificationsAsRead('discussions', 1), store.dispatch, store.getState); + it('Successfully marked notification as read in the redux.', async () => { + axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(200); + await executeThunk(markNotificationsAsRead(1), store.dispatch, store.getState); const { notifications: { notificationStatus, notifications } } = store.getState(); const firstNotification = Object.values(notifications)[0]; diff --git a/src/Notifications/data/selector.test.jsx b/src/Notifications/data/selector.test.jsx index d09c368..bc73a3f 100644 --- a/src/Notifications/data/selector.test.jsx +++ b/src/Notifications/data/selector.test.jsx @@ -55,14 +55,14 @@ describe('Notification Selectors', () => { axiosMock.reset(); }); - it('should return notification status.', async () => { + it('Should return notification status.', async () => { const state = store.getState(); const status = selectNotificationStatus()(state); expect(status).toEqual('successful'); }); - it('should return notification tabs count.', async () => { + it('Should return notification tabs count.', async () => { const state = store.getState(); const tabsCount = selectNotificationTabsCount()(state); @@ -73,49 +73,49 @@ describe('Notification Selectors', () => { expect(tabsCount.authoring).toEqual(5); }); - it('should return notification tabs.', async () => { + it('Should return notification tabs.', async () => { const state = store.getState(); const tabs = selectNotificationTabs()(state); expect(tabs).toHaveLength(4); }); - it('should return selected app notification ids.', async () => { + it('Should return selected app notification ids.', async () => { const state = store.getState(); const notificationIds = selectSelectedAppNotificationIds('discussions')(state); expect(notificationIds).toHaveLength(2); }); - it('should return show notification tray status.', async () => { + it('Should return show notification tray status.', async () => { const state = store.getState(); const showNotificationTrayStatus = selectShowNotificationTray()(state); expect(showNotificationTrayStatus).toEqual(true); }); - it('should return notifications.', async () => { + it('Should return notifications.', async () => { const state = store.getState(); const notifications = selectNotifications()(state); expect(Object.keys(notifications)).toHaveLength(2); }); - it('should return notifications from Ids.', async () => { + it('Should return notifications from Ids.', async () => { const state = store.getState(); const notifications = selectNotificationsByIds('discussions')(state); expect(notifications).toHaveLength(2); }); - it('should return selected app name.', async () => { + it('Should return selected app name.', async () => { const state = store.getState(); const appName = selectSelectedAppName()(state); expect(appName).toEqual('discussions'); }); - it('should return pagination data.', async () => { + it('Should return pagination data.', async () => { const state = store.getState(); const paginationData = selectPaginationData()(state); diff --git a/src/Notifications/data/thunks.js b/src/Notifications/data/thunks.js index df71063..3ffbb82 100644 --- a/src/Notifications/data/thunks.js +++ b/src/Notifications/data/thunks.js @@ -1,3 +1,4 @@ +import { camelCaseObject } from '@edx/frontend-platform'; import { fetchNotificationSuccess, fetchNotificationRequest, @@ -47,7 +48,7 @@ export const fetchNotificationList = ({ appName, page, pageSize }) => ( try { dispatch(fetchNotificationRequest({ appName })); const data = await getNotifications(appName, page, pageSize); - const normalisedData = normalizeNotifications((data)); + const normalisedData = normalizeNotifications((camelCaseObject(data))); dispatch(fetchNotificationSuccess({ ...normalisedData, numPages: data.numPages, currentPage: data.currentPage })); } catch (error) { if (getHttpErrorStatus(error) === 403) { @@ -64,7 +65,7 @@ export const fetchAppsNotificationCount = () => ( try { dispatch(fetchNotificationsCountRequest()); const data = await getNotificationCounts(); - const normalisedData = normalizeNotificationCounts((data)); + const normalisedData = normalizeNotificationCounts((camelCaseObject(data))); dispatch(fetchNotificationsCountSuccess({ ...normalisedData, countByAppName: data.countByAppName, @@ -86,7 +87,7 @@ export const markAllNotificationsAsRead = (appName) => ( try { dispatch(markAllNotificationsAsReadRequest({ appName })); const data = await markAllNotificationRead(appName); - dispatch(markAllNotificationsAsReadSuccess(data)); + dispatch(markAllNotificationsAsReadSuccess(camelCaseObject(data))); } catch (error) { if (getHttpErrorStatus(error) === 403) { dispatch(markAllNotificationsAsReadDenied()); @@ -97,12 +98,12 @@ export const markAllNotificationsAsRead = (appName) => ( } ); -export const markNotificationsAsRead = (appName, notificationId) => ( +export const markNotificationsAsRead = (notificationId) => ( async (dispatch) => { try { dispatch(markNotificationsAsReadRequest({ notificationId })); - const data = await markNotificationRead(appName, notificationId); - dispatch(markNotificationsAsReadSuccess(data)); + const data = await markNotificationRead(notificationId); + dispatch(markNotificationsAsReadSuccess(camelCaseObject(data))); } catch (error) { if (getHttpErrorStatus(error) === 403) { dispatch(markNotificationsAsReadDenied()); @@ -118,7 +119,7 @@ export const markNotificationsAsSeen = (appName) => ( try { dispatch(markNotificationsAsSeenRequest({ appName })); const data = await markNotificationSeen(appName); - dispatch(markNotificationsAsSeenSuccess(data)); + dispatch(markNotificationsAsSeenSuccess(camelCaseObject(data))); } catch (error) { if (getHttpErrorStatus(error) === 403) { dispatch(markNotificationsAsSeenDenied());