From bccb2dbdcd327004194998d861658fdee23af382 Mon Sep 17 00:00:00 2001 From: ayeshoali Date: Mon, 3 Jul 2023 15:46:48 +0500 Subject: [PATCH 1/5] feat: integrated notifications tray with backend apis --- src/Notifications/NotificationSections.jsx | 18 ++++++++---------- src/Notifications/NotificationTabs.jsx | 2 +- src/Notifications/data/api.js | 21 ++++++++------------- src/Notifications/data/redux.test.js | 10 +++++----- src/Notifications/data/selector.test.jsx | 2 +- src/Notifications/data/slice.js | 10 +++++----- src/Notifications/data/thunks.js | 14 ++++++++------ src/index.scss | 2 +- 8 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/Notifications/NotificationSections.jsx b/src/Notifications/NotificationSections.jsx index f2fefc7a7..0678059df 100644 --- a/src/Notifications/NotificationSections.jsx +++ b/src/Notifications/NotificationSections.jsx @@ -6,16 +6,19 @@ import isEmpty from 'lodash/isEmpty'; import messages from './messages'; import NotificationRowItem from './NotificationRowItem'; import { markAllNotificationsAsRead } from './data/thunks'; -import { selectNotificationsByIds, selectPaginationData, selectSelectedAppName } from './data/selectors'; +import { + selectNotificationsByIds, selectPaginationData, selectSelectedAppName, selectNotificationStatus, +} from './data/selectors'; import { splitNotificationsByTime } from './utils'; -import { updatePaginationRequest } from './data/slice'; +import { updatePaginationRequest, RequestStatus } from './data/slice'; const NotificationSections = () => { const intl = useIntl(); const dispatch = useDispatch(); const selectedAppName = useSelector(selectSelectedAppName()); + const notificationRequestStatus = useSelector(selectNotificationStatus()); const notifications = useSelector(selectNotificationsByIds(selectedAppName)); - const { currentPage, numPages } = useSelector(selectPaginationData()); + const { nextPage } = useSelector(selectPaginationData()); const { today = [], earlier = [] } = useMemo( () => splitNotificationsByTime(notifications), [notifications], @@ -70,13 +73,8 @@ const NotificationSections = () => {
{renderNotificationSection('today', today)} {renderNotificationSection('earlier', earlier)} - {currentPage < numPages && ( - )} diff --git a/src/Notifications/NotificationTabs.jsx b/src/Notifications/NotificationTabs.jsx index 79d361195..83ecdea4f 100644 --- a/src/Notifications/NotificationTabs.jsx +++ b/src/Notifications/NotificationTabs.jsx @@ -17,7 +17,7 @@ const NotificationTabs = () => { const { currentPage } = useSelector(selectPaginationData()); useEffect(() => { - dispatch(fetchNotificationList({ appName: selectedAppName, page: currentPage, pageSize: 10 })); + dispatch(fetchNotificationList({ appName: selectedAppName, page: currentPage })); if (selectedAppName) { dispatch(markNotificationsAsSeen(selectedAppName)); } }, [currentPage, selectedAppName]); diff --git a/src/Notifications/data/api.js b/src/Notifications/data/api.js index 020ef8046..5d18f6b97 100644 --- a/src/Notifications/data/api.js +++ b/src/Notifications/data/api.js @@ -2,19 +2,14 @@ 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 getNotificationsListApiUrl = () => `${getConfig().LMS_BASE_URL}/api/notifications/`; +export const markNotificationsSeenApiUrl = (appName) => `${getConfig().LMS_BASE_URL}/api/notifications/mark-seen/${appName}/`; export const markNotificationAsReadApiUrl = () => `${getConfig().LMS_BASE_URL}/api/notifications/read/`; -export async function getNotifications(appName, page, pageSize) { - const params = snakeCaseObject({ page, pageSize }); - const { data } = await getAuthenticatedHttpClient().get(getNotificationsApiUrl(), { params }); - - const startIndex = (page - 1) * pageSize; - const endIndex = startIndex + pageSize; - - const notifications = data.slice(startIndex, endIndex); - return { notifications, numPages: 2, currentPage: page }; +export async function getNotificationsList(appName, page) { + const params = snakeCaseObject({ appName, page }); + const { data } = await getAuthenticatedHttpClient().get(getNotificationsListApiUrl(), { params }); + return data; } export async function getNotificationCounts() { @@ -31,14 +26,14 @@ export async function markNotificationSeen(appName) { export async function markAllNotificationRead(appName) { const params = snakeCaseObject({ appName }); - const { data } = await getAuthenticatedHttpClient().put(markNotificationAsReadApiUrl(), { params }); + const { data } = await getAuthenticatedHttpClient().patch(markNotificationAsReadApiUrl(), params); return data; } export async function markNotificationRead(notificationId) { const params = snakeCaseObject({ notificationId }); - const { data } = await getAuthenticatedHttpClient().put(markNotificationAsReadApiUrl(), { params }); + const { data } = await getAuthenticatedHttpClient().patch(markNotificationAsReadApiUrl(), params); return { data, id: notificationId }; } diff --git a/src/Notifications/data/redux.test.js b/src/Notifications/data/redux.test.js index fd19943ba..83c7be7ab 100644 --- a/src/Notifications/data/redux.test.js +++ b/src/Notifications/data/redux.test.js @@ -7,7 +7,7 @@ import { initializeMockApp } from '@edx/frontend-platform/testing'; import { initializeStore } from '../../store'; import executeThunk from '../../test-utils'; import { - getNotificationsApiUrl, getNotificationsCountApiUrl, markNotificationAsReadApiUrl, markNotificationsSeenApiUrl, + getNotificationsListApiUrl, getNotificationsCountApiUrl, markNotificationAsReadApiUrl, markNotificationsSeenApiUrl, } from './api'; import { fetchAppsNotificationCount, fetchNotificationList, markNotificationsAsRead, markAllNotificationsAsRead, @@ -17,7 +17,7 @@ import { import './__factories__'; const notificationCountsApiUrl = getNotificationsCountApiUrl(); -const notificationsApiUrl = getNotificationsApiUrl(); +const notificationsListApiUrl = getNotificationsListApiUrl(); const markedAllNotificationsAsReadApiUrl = markNotificationAsReadApiUrl(); const markedAllNotificationsAsSeenApiUrl = markNotificationsSeenApiUrl('discussions'); @@ -39,7 +39,7 @@ describe('Notification Redux', () => { store = initializeStore(); axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount'))); - axiosMock.onGet(notificationsApiUrl).reply( + axiosMock.onGet(notificationsListApiUrl).reply( 200, (Factory.buildList('notification', 2, null, { createdDate: new Date().toISOString() })), ); @@ -64,7 +64,7 @@ describe('Notification Redux', () => { expect(notifications.tabsCount).toEqual({}); expect(notifications.showNotificationsTray).toEqual(false); expect(notifications.pagination.count).toEqual(10); - expect(notifications.pagination.numPages).toEqual(1); + expect(notifications.pagination.totalPages).toEqual(1); expect(notifications.pagination.currentPage).toEqual(1); expect(notifications.pagination.nextPage).toBeNull(); }); @@ -79,7 +79,7 @@ describe('Notification Redux', () => { { statusCode: 404, status: 'failed' }, { statusCode: 403, status: 'denied' }, ])('%s to load notifications list in the redux.', async ({ statusCode, status }) => { - axiosMock.onGet(notificationsApiUrl).reply(statusCode); + axiosMock.onGet(notificationsListApiUrl).reply(statusCode); await executeThunk(fetchNotificationList({ page: 1, pageSize: 10 }), store.dispatch, store.getState); const { notifications: { notificationStatus } } = store.getState(); diff --git a/src/Notifications/data/selector.test.jsx b/src/Notifications/data/selector.test.jsx index bc73a3f93..2d5860276 100644 --- a/src/Notifications/data/selector.test.jsx +++ b/src/Notifications/data/selector.test.jsx @@ -121,6 +121,6 @@ describe('Notification Selectors', () => { expect(paginationData.count).toEqual(10); expect(paginationData.currentPage).toEqual(1); - expect(paginationData.numPages).toEqual(2); + expect(paginationData.totalPages).toEqual(2); }); }); diff --git a/src/Notifications/data/slice.js b/src/Notifications/data/slice.js index 412cc4d2b..b25e21945 100644 --- a/src/Notifications/data/slice.js +++ b/src/Notifications/data/slice.js @@ -11,15 +11,14 @@ export const RequestStatus = { const initialState = { notificationStatus: 'idle', - appName: 'discussions', + appName: 'discussion', appsId: [], apps: {}, notifications: {}, tabsCount: {}, showNotificationsTray: false, pagination: { - count: 10, - numPages: 1, + totalPages: 1, currentPage: 1, nextPage: null, }, @@ -39,7 +38,7 @@ const slice = createSlice({ }, fetchNotificationSuccess: (state, { payload }) => { const { - newNotificationIds, notificationsKeyValuePair, numPages, currentPage, + newNotificationIds, notificationsKeyValuePair, totalPages, currentPage, nextPage, } = payload; const existingNotificationIds = state.apps[state.appName]; @@ -48,8 +47,9 @@ const slice = createSlice({ state.tabsCount.count -= state.tabsCount[state.appName]; state.tabsCount[state.appName] = 0; state.notificationStatus = RequestStatus.LOADED; - state.pagination.numPages = numPages; + state.pagination.totalPages = totalPages; state.pagination.currentPage = currentPage; + state.pagination.nextPage = nextPage; }, fetchNotificationsCountDenied: (state) => { state.notificationStatus = RequestStatus.DENIED; diff --git a/src/Notifications/data/thunks.js b/src/Notifications/data/thunks.js index f87e2e1c2..b016a859b 100644 --- a/src/Notifications/data/thunks.js +++ b/src/Notifications/data/thunks.js @@ -23,7 +23,7 @@ import { markNotificationsAsReadFailure, } from './slice'; import { - getNotifications, getNotificationCounts, markNotificationSeen, markAllNotificationRead, markNotificationRead, + getNotificationsList, getNotificationCounts, markNotificationSeen, markAllNotificationRead, markNotificationRead, } from './api'; import { getHttpErrorStatus } from '../utils'; @@ -35,7 +35,7 @@ const normalizeNotificationCounts = ({ countByAppName, count, showNotificationsT }; }; -const normalizeNotifications = ({ notifications }) => { +const normalizeNotifications = (notifications) => { const newNotificationIds = notifications.map(notification => notification.id.toString()); const notificationsKeyValuePair = notifications.reduce((acc, obj) => { acc[obj.id] = obj; return acc; }, {}); return { @@ -43,13 +43,15 @@ const normalizeNotifications = ({ notifications }) => { }; }; -export const fetchNotificationList = ({ appName, page, pageSize }) => ( +export const fetchNotificationList = ({ appName, page }) => ( async (dispatch) => { try { dispatch(fetchNotificationRequest({ appName })); - const data = await getNotifications(appName, page, pageSize); - const normalisedData = normalizeNotifications((camelCaseObject(data))); - dispatch(fetchNotificationSuccess({ ...normalisedData, numPages: data.numPages, currentPage: data.currentPage })); + const data = await getNotificationsList(appName, page); + const normalisedData = normalizeNotifications((camelCaseObject(data.results))); + dispatch(fetchNotificationSuccess({ + ...normalisedData, totalPages: data.num_pages, currentPage: data.current_page, nextPage: data.next, + })); } catch (error) { if (getHttpErrorStatus(error) === 403) { dispatch(fetchNotificationDenied(appName)); diff --git a/src/index.scss b/src/index.scss index 1e1eecab2..46ed12df7 100644 --- a/src/index.scss +++ b/src/index.scss @@ -123,7 +123,7 @@ $white: #fff; } .content { - b { + strong { color: #00262B !important; font-weight: 500 !important; } From 39adc34376462e04f39c0b32e5646e08aa101caa Mon Sep 17 00:00:00 2001 From: ayeshoali Date: Tue, 4 Jul 2023 14:33:26 +0500 Subject: [PATCH 2/5] test: updates test cases --- src/Notifications/NotificationSections.jsx | 9 ++++- .../__factories__/notifications.factory.js | 15 ++++++-- src/Notifications/data/api.test.js | 37 +++++++++---------- src/Notifications/data/redux.test.js | 29 ++++++--------- src/Notifications/data/selector.test.jsx | 23 +++++------- src/Notifications/data/slice.js | 27 +++++++------- .../notificationRowItem.test.jsx | 2 +- .../notificationSections.test.jsx | 20 +++++++--- src/Notifications/notificationTabs.test.jsx | 2 +- src/Notifications/test-utils.js | 21 ++++++----- .../AuthenticatedUserDropdown.jsx | 2 +- 11 files changed, 99 insertions(+), 88 deletions(-) diff --git a/src/Notifications/NotificationSections.jsx b/src/Notifications/NotificationSections.jsx index 0678059df..2352db584 100644 --- a/src/Notifications/NotificationSections.jsx +++ b/src/Notifications/NotificationSections.jsx @@ -73,8 +73,13 @@ const NotificationSections = () => {
{renderNotificationSection('today', today)} {renderNotificationSection('earlier', earlier)} - {nextPage && notificationRequestStatus === RequestStatus.LOADED && ( - )} diff --git a/src/Notifications/data/__factories__/notifications.factory.js b/src/Notifications/data/__factories__/notifications.factory.js index 29645bf3d..7abf45bc7 100644 --- a/src/Notifications/data/__factories__/notifications.factory.js +++ b/src/Notifications/data/__factories__/notifications.factory.js @@ -4,7 +4,7 @@ Factory.define('notificationsCount') .attr('count', 45) .attr('countByAppName', { reminders: 10, - discussions: 20, + discussion: 20, grades: 10, authoring: 5, }) @@ -13,10 +13,19 @@ Factory.define('notificationsCount') Factory.define('notification') .sequence('id') .attr('type', 'post') - .sequence('content', ['id'], (idx, notificationId) => `

User ${idx} posts Hello and welcome to SC0x - ${notificationId}!

`) + .sequence('content', ['id'], (idx, notificationId) => `

User ${idx} posts Hello and welcome to SC0x + ${notificationId}!

`) .attr('course_name', 'Supply Chain Analytics') .sequence('content_url', (idx) => `https://example.com/${idx}`) .attr('last_read', null) .attr('last_seen', null) .sequence('created_at', ['createdDate'], (idx, date) => date); + +Factory.define('notificationsList') + .attr('next', null) + .attr('previous', null) + .attr('count', null, 2) + .attr('num_pages', null, 1) + .attr('current_page', null, 1) + .attr('start', null, 0) + .attr('results', ['results'], (results) => results || Factory.buildList('notification', 2, null, { createdDate: new Date().toISOString() })); diff --git a/src/Notifications/data/api.test.js b/src/Notifications/data/api.test.js index 9c0d82a48..3ef2ce8b3 100644 --- a/src/Notifications/data/api.test.js +++ b/src/Notifications/data/api.test.js @@ -5,15 +5,15 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { initializeMockApp } from '@edx/frontend-platform/testing'; import { - getNotificationsApiUrl, getNotificationsCountApiUrl, markNotificationAsReadApiUrl, markNotificationsSeenApiUrl, - getNotificationCounts, getNotifications, markNotificationSeen, markAllNotificationRead, markNotificationRead, + getNotificationsListApiUrl, getNotificationsCountApiUrl, markNotificationAsReadApiUrl, markNotificationsSeenApiUrl, + getNotificationCounts, getNotificationsList, markNotificationSeen, markAllNotificationRead, markNotificationRead, } from './api'; import './__factories__'; const notificationCountsApiUrl = getNotificationsCountApiUrl(); -const notificationsApiUrl = getNotificationsApiUrl(); -const markedAllNotificationsAsSeenApiUrl = markNotificationsSeenApiUrl('discussions'); +const notificationsApiUrl = getNotificationsListApiUrl(); +const markedAllNotificationsAsSeenApiUrl = markNotificationsSeenApiUrl('discussion'); const markedAllNotificationsAsReadApiUrl = markNotificationAsReadApiUrl(); let axiosMock = null; @@ -43,7 +43,7 @@ describe('Notifications API', () => { expect(count).toEqual(45); expect(countByAppName.reminders).toEqual(10); - expect(countByAppName.discussions).toEqual(20); + expect(countByAppName.discussion).toEqual(20); expect(countByAppName.grades).toEqual(10); expect(countByAppName.authoring).toEqual(5); }); @@ -62,14 +62,11 @@ describe('Notifications API', () => { }); it('Successfully get notifications.', async () => { - axiosMock.onGet(notificationsApiUrl).reply( - 200, - (Factory.buildList('notification', 2, null, { createdDate: new Date().toISOString() })), - ); + axiosMock.onGet(notificationsApiUrl).reply(200, (Factory.build('notificationsList'))); - const { notifications } = await getNotifications('discussions', 1, 10); + const notifications = await getNotificationsList('discussion', 1); - expect(notifications).toHaveLength(2); + expect(Object.keys(notifications.results)).toHaveLength(2); }); it.each([ @@ -78,7 +75,7 @@ describe('Notifications API', () => { ])('%s for notification API.', async ({ statusCode, message }) => { axiosMock.onGet(notificationsApiUrl).reply(statusCode, { message }); try { - await getNotifications({ page: 1, pageSize: 10 }); + await getNotificationsList('discussion', 1); } catch (error) { expect(error.response.status).toEqual(statusCode); expect(error.response.data.message).toEqual(message); @@ -88,7 +85,7 @@ describe('Notifications API', () => { 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'); + const { message } = await markNotificationSeen('discussion'); expect(message).toEqual('Notifications marked seen.'); }); @@ -99,7 +96,7 @@ describe('Notifications API', () => { ])('%s for notification mark as seen API.', async ({ statusCode, message }) => { axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(statusCode, { message }); try { - await markNotificationSeen('discussions'); + await markNotificationSeen('discussion'); } catch (error) { expect(error.response.status).toEqual(statusCode); expect(error.response.data.message).toEqual(message); @@ -107,9 +104,9 @@ describe('Notifications API', () => { }); it('Successfully marked all notifications as read for selected app.', async () => { - axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notifications marked read.' }); + axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notifications marked read.' }); - const { message } = await markAllNotificationRead('discussions'); + const { message } = await markAllNotificationRead('discussion'); expect(message).toEqual('Notifications marked read.'); }); @@ -118,9 +115,9 @@ describe('Notifications API', () => { { 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 }); + axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(statusCode, { message }); try { - await markAllNotificationRead('discussions'); + await markAllNotificationRead('discussion'); } catch (error) { expect(error.response.status).toEqual(statusCode); expect(error.response.data.message).toEqual(message); @@ -128,7 +125,7 @@ describe('Notifications API', () => { }); it('Successfully marked notification as read.', async () => { - axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notification marked read.' }); + axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notification marked read.' }); const { data } = await markNotificationRead(1); @@ -139,7 +136,7 @@ describe('Notifications API', () => { { 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 }); + axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(statusCode, { message }); try { await markAllNotificationRead(1); } catch (error) { diff --git a/src/Notifications/data/redux.test.js b/src/Notifications/data/redux.test.js index 83c7be7ab..77743f5db 100644 --- a/src/Notifications/data/redux.test.js +++ b/src/Notifications/data/redux.test.js @@ -19,7 +19,7 @@ import './__factories__'; const notificationCountsApiUrl = getNotificationsCountApiUrl(); const notificationsListApiUrl = getNotificationsListApiUrl(); const markedAllNotificationsAsReadApiUrl = markNotificationAsReadApiUrl(); -const markedAllNotificationsAsSeenApiUrl = markNotificationsSeenApiUrl('discussions'); +const markedAllNotificationsAsSeenApiUrl = markNotificationsSeenApiUrl('discussion'); let axiosMock; let store; @@ -39,12 +39,9 @@ describe('Notification Redux', () => { store = initializeStore(); axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount'))); - axiosMock.onGet(notificationsListApiUrl).reply( - 200, - (Factory.buildList('notification', 2, null, { createdDate: new Date().toISOString() })), - ); + axiosMock.onGet(notificationsListApiUrl).reply(200, (Factory.build('notificationsList'))); await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState); - await executeThunk(fetchNotificationList({ page: 1, pageSize: 10 }), store.dispatch, store.getState); + await executeThunk(fetchNotificationList({ appName: 'discussion', page: 1 }), store.dispatch, store.getState); }); afterEach(() => { @@ -57,13 +54,12 @@ describe('Notification Redux', () => { const { notifications } = store.getState(); expect(notifications.notificationStatus).toEqual('idle'); - expect(notifications.appName).toEqual('discussions'); + expect(notifications.appName).toEqual('discussion'); expect(notifications.appsId).toHaveLength(0); expect(notifications.apps).toEqual({}); expect(notifications.notifications).toEqual({}); expect(notifications.tabsCount).toEqual({}); expect(notifications.showNotificationsTray).toEqual(false); - expect(notifications.pagination.count).toEqual(10); expect(notifications.pagination.totalPages).toEqual(1); expect(notifications.pagination.currentPage).toEqual(1); expect(notifications.pagination.nextPage).toBeNull(); @@ -71,7 +67,6 @@ describe('Notification Redux', () => { it('Successfully loaded notifications list in the redux.', async () => { const { notifications: { notifications } } = store.getState(); - expect(Object.keys(notifications)).toHaveLength(2); }); @@ -80,7 +75,7 @@ describe('Notification Redux', () => { { statusCode: 403, status: 'denied' }, ])('%s to load notifications list in the redux.', async ({ statusCode, status }) => { axiosMock.onGet(notificationsListApiUrl).reply(statusCode); - await executeThunk(fetchNotificationList({ page: 1, pageSize: 10 }), store.dispatch, store.getState); + await executeThunk(fetchNotificationList({ page: 1 }), store.dispatch, store.getState); const { notifications: { notificationStatus } } = store.getState(); @@ -92,7 +87,7 @@ describe('Notification Redux', () => { expect(tabsCount.count).toEqual(25); expect(tabsCount.reminders).toEqual(10); - expect(tabsCount.discussions).toEqual(0); + expect(tabsCount.discussion).toEqual(0); expect(tabsCount.grades).toEqual(10); expect(tabsCount.authoring).toEqual(5); }); @@ -111,7 +106,7 @@ describe('Notification Redux', () => { it('Successfully marked all notifications as seen for selected app.', async () => { axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(200); - await executeThunk(markNotificationsAsSeen('discussions'), store.dispatch, store.getState); + await executeThunk(markNotificationsAsSeen('discussion'), store.dispatch, store.getState); expect(store.getState().notifications.notificationStatus).toEqual('successful'); }); @@ -121,7 +116,7 @@ describe('Notification Redux', () => { { statusCode: 403, status: 'denied' }, ])('%s to mark all notifications as seen for selected app.', async ({ statusCode, status }) => { axiosMock.onPut(markedAllNotificationsAsSeenApiUrl).reply(statusCode); - await executeThunk(markNotificationsAsSeen('discussions'), store.dispatch, store.getState); + await executeThunk(markNotificationsAsSeen('discussion'), store.dispatch, store.getState); const { notifications: { notificationStatus } } = store.getState(); @@ -129,8 +124,8 @@ describe('Notification Redux', () => { }); 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); + axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200); + await executeThunk(markAllNotificationsAsRead('discussion'), store.dispatch, store.getState); const { notifications: { notificationStatus, notifications } } = store.getState(); const firstNotification = Object.values(notifications)[0]; @@ -140,7 +135,7 @@ describe('Notification Redux', () => { }); it('Successfully marked notification as read in the redux.', async () => { - axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(200); + axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200); await executeThunk(markNotificationsAsRead(1), store.dispatch, store.getState); const { notifications: { notificationStatus, notifications } } = store.getState(); @@ -154,7 +149,7 @@ describe('Notification Redux', () => { { statusCode: 404, status: 'failed' }, { statusCode: 403, status: 'denied' }, ])('%s to marked notification as read in the redux.', async ({ statusCode, status }) => { - axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(statusCode); + axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(statusCode); await executeThunk(markNotificationsAsRead(1), store.dispatch, store.getState); const { notifications: { notificationStatus } } = store.getState(); diff --git a/src/Notifications/data/selector.test.jsx b/src/Notifications/data/selector.test.jsx index 2d5860276..ff589ae51 100644 --- a/src/Notifications/data/selector.test.jsx +++ b/src/Notifications/data/selector.test.jsx @@ -6,7 +6,7 @@ import { initializeMockApp } from '@edx/frontend-platform/testing'; import { initializeStore } from '../../store'; import executeThunk from '../../test-utils'; -import { getNotificationsApiUrl, getNotificationsCountApiUrl } from './api'; +import { getNotificationsListApiUrl, getNotificationsCountApiUrl } from './api'; import { selectNotifications, selectNotificationsByIds, @@ -23,7 +23,7 @@ import { fetchAppsNotificationCount, fetchNotificationList } from './thunks'; import './__factories__'; const notificationCountsApiUrl = getNotificationsCountApiUrl(); -const notificationsApiUrl = getNotificationsApiUrl(); +const notificationsApiUrl = getNotificationsListApiUrl(); let axiosMock; let store; @@ -43,12 +43,9 @@ describe('Notification Selectors', () => { store = initializeStore(); axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount'))); - axiosMock.onGet(notificationsApiUrl).reply( - 200, - (Factory.buildList('notification', 2, null, { createdDate: new Date().toISOString() })), - ); + axiosMock.onGet(notificationsApiUrl).reply(200, (Factory.build('notificationsList'))); await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState); - await executeThunk(fetchNotificationList({ page: 1, pageSize: 10 }), store.dispatch, store.getState); + await executeThunk(fetchNotificationList({ appName: 'discussion', page: 1 }), store.dispatch, store.getState); }); afterEach(() => { @@ -68,7 +65,7 @@ describe('Notification Selectors', () => { expect(tabsCount.count).toEqual(25); expect(tabsCount.reminders).toEqual(10); - expect(tabsCount.discussions).toEqual(0); + expect(tabsCount.discussion).toEqual(0); expect(tabsCount.grades).toEqual(10); expect(tabsCount.authoring).toEqual(5); }); @@ -82,7 +79,7 @@ describe('Notification Selectors', () => { it('Should return selected app notification ids.', async () => { const state = store.getState(); - const notificationIds = selectSelectedAppNotificationIds('discussions')(state); + const notificationIds = selectSelectedAppNotificationIds('discussion')(state); expect(notificationIds).toHaveLength(2); }); @@ -103,7 +100,7 @@ describe('Notification Selectors', () => { it('Should return notifications from Ids.', async () => { const state = store.getState(); - const notifications = selectNotificationsByIds('discussions')(state); + const notifications = selectNotificationsByIds('discussion')(state); expect(notifications).toHaveLength(2); }); @@ -112,15 +109,15 @@ describe('Notification Selectors', () => { const state = store.getState(); const appName = selectSelectedAppName()(state); - expect(appName).toEqual('discussions'); + expect(appName).toEqual('discussion'); }); it('Should return pagination data.', async () => { const state = store.getState(); const paginationData = selectPaginationData()(state); - expect(paginationData.count).toEqual(10); expect(paginationData.currentPage).toEqual(1); - expect(paginationData.totalPages).toEqual(2); + expect(paginationData.totalPages).toEqual(1); + expect(paginationData.nextPage).toEqual(null); }); }); diff --git a/src/Notifications/data/slice.js b/src/Notifications/data/slice.js index b25e21945..c2255bd74 100644 --- a/src/Notifications/data/slice.js +++ b/src/Notifications/data/slice.js @@ -3,14 +3,14 @@ import { createSlice } from '@reduxjs/toolkit'; export const RequestStatus = { IDLE: 'idle', - LOADING: 'in-progress', - LOADED: 'successful', + IN_PROGRESS: 'in-progress', + SUCCESSFUL: 'successful', FAILED: 'failed', DENIED: 'denied', }; const initialState = { - notificationStatus: 'idle', + notificationStatus: RequestStatus.IDLE, appName: 'discussion', appsId: [], apps: {}, @@ -34,19 +34,18 @@ const slice = createSlice({ state.notificationStatus = RequestStatus.FAILED; }, fetchNotificationRequest: (state) => { - state.notificationStatus = RequestStatus.LOADING; + state.notificationStatus = RequestStatus.IN_PROGRESS; }, fetchNotificationSuccess: (state, { payload }) => { const { newNotificationIds, notificationsKeyValuePair, totalPages, currentPage, nextPage, } = payload; const existingNotificationIds = state.apps[state.appName]; - state.apps[state.appName] = Array.from(new Set([...existingNotificationIds, ...newNotificationIds])); state.notifications = { ...state.notifications, ...notificationsKeyValuePair }; state.tabsCount.count -= state.tabsCount[state.appName]; state.tabsCount[state.appName] = 0; - state.notificationStatus = RequestStatus.LOADED; + state.notificationStatus = RequestStatus.SUCCESSFUL; state.pagination.totalPages = totalPages; state.pagination.currentPage = currentPage; state.pagination.nextPage = nextPage; @@ -58,7 +57,7 @@ const slice = createSlice({ state.notificationStatus = RequestStatus.FAILED; }, fetchNotificationsCountRequest: (state) => { - state.notificationStatus = RequestStatus.LOADING; + state.notificationStatus = RequestStatus.IN_PROGRESS; }, fetchNotificationsCountSuccess: (state, { payload }) => { const { @@ -68,13 +67,13 @@ const slice = createSlice({ state.appsId = appIds; state.apps = apps; state.showNotificationsTray = showNotificationsTray; - state.notificationStatus = RequestStatus.LOADED; + state.notificationStatus = RequestStatus.SUCCESSFUL; }, markNotificationsAsSeenRequest: (state) => { - state.notificationStatus = RequestStatus.LOADING; + state.notificationStatus = RequestStatus.IN_PROGRESS; }, markNotificationsAsSeenSuccess: (state) => { - state.notificationStatus = RequestStatus.LOADED; + state.notificationStatus = RequestStatus.SUCCESSFUL; }, markNotificationsAsSeenDenied: (state) => { state.notificationStatus = RequestStatus.DENIED; @@ -83,7 +82,7 @@ const slice = createSlice({ state.notificationStatus = RequestStatus.FAILED; }, markAllNotificationsAsReadRequest: (state) => { - state.notificationStatus = RequestStatus.LOADING; + state.notificationStatus = RequestStatus.IN_PROGRESS; }, markAllNotificationsAsReadSuccess: (state) => { const updatedNotifications = Object.fromEntries( @@ -92,7 +91,7 @@ const slice = createSlice({ ]), ); state.notifications = updatedNotifications; - state.notificationStatus = RequestStatus.LOADED; + state.notificationStatus = RequestStatus.SUCCESSFUL; }, markAllNotificationsAsReadDenied: (state) => { state.notificationStatus = RequestStatus.DENIED; @@ -101,12 +100,12 @@ const slice = createSlice({ state.notificationStatus = RequestStatus.FAILED; }, markNotificationsAsReadRequest: (state) => { - state.notificationStatus = RequestStatus.LOADING; + state.notificationStatus = RequestStatus.IN_PROGRESS; }, markNotificationsAsReadSuccess: (state, { payload }) => { const date = new Date().toISOString(); state.notifications[payload.id] = { ...state.notifications[payload.id], lastRead: date }; - state.notificationStatus = RequestStatus.LOADED; + state.notificationStatus = RequestStatus.SUCCESSFUL; }, markNotificationsAsReadDenied: (state) => { state.notificationStatus = RequestStatus.DENIED; diff --git a/src/Notifications/notificationRowItem.test.jsx b/src/Notifications/notificationRowItem.test.jsx index 5443ab455..332a026de 100644 --- a/src/Notifications/notificationRowItem.test.jsx +++ b/src/Notifications/notificationRowItem.test.jsx @@ -73,7 +73,7 @@ describe('Notification row item test cases.', () => { ); it('Successfully marked notification as read.', async () => { - axiosMock.onPut(markedNotificationAsReadApiUrl).reply(200, { message: 'Notification marked read.' }); + axiosMock.onPatch(markedNotificationAsReadApiUrl).reply(200, { message: 'Notification marked read.' }); renderComponent(); const bellIcon = screen.queryByTestId('notification-bell-icon'); diff --git a/src/Notifications/notificationSections.test.jsx b/src/Notifications/notificationSections.test.jsx index 2130325e0..1495fac31 100644 --- a/src/Notifications/notificationSections.test.jsx +++ b/src/Notifications/notificationSections.test.jsx @@ -14,9 +14,10 @@ import { AppContext, AppProvider } from '@edx/frontend-platform/react'; import AuthenticatedUserDropdown from '../learning-header/AuthenticatedUserDropdown'; import { initializeStore } from '../store'; -import { markNotificationAsReadApiUrl } from './data/api'; +import { markNotificationAsReadApiUrl, markNotificationsSeenApiUrl, getNotificationsListApiUrl } from './data/api'; import mockNotificationsResponse from './test-utils'; - +import { markNotificationsAsSeen, fetchNotificationList } from './data/thunks'; +import executeThunk from '../test-utils'; import './data/__factories__'; const markedAllNotificationsAsReadApiUrl = markNotificationAsReadApiUrl(); @@ -69,7 +70,7 @@ describe('Notification sections test cases.', () => { }); it('Successfully marked all notifications as read, removing the unread status.', async () => { - axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notifications marked read.' }); + axiosMock.onPatch(markedAllNotificationsAsReadApiUrl).reply(200, { message: 'Notifications marked read.' }); renderComponent(); const bellIcon = screen.queryByTestId('notification-bell-icon'); @@ -83,16 +84,23 @@ describe('Notification sections test cases.', () => { }); it('Successfully load more notifications by clicking on load more notification button.', async () => { + axiosMock.onPut(markNotificationsSeenApiUrl('discussion')).reply(200); + await executeThunk(markNotificationsAsSeen('discussions'), store.dispatch, store.getState); renderComponent(); const bellIcon = screen.queryByTestId('notification-bell-icon'); await act(async () => { fireEvent.click(bellIcon); }); + expect(screen.queryAllByTestId('notification-contents')).toHaveLength(10); const loadMoreButton = screen.queryByTestId('load-more-notifications'); - expect(screen.queryAllByTestId('notification-contents')).toHaveLength(10); - await act(async () => { fireEvent.click(loadMoreButton); }); + axiosMock.onGet(getNotificationsListApiUrl()).reply( + 200, + (Factory.build('notificationsList', { num_pages: 2, current_page: 2 })), + ); + await executeThunk(fetchNotificationList({ appName: 'discussion', page: 2 }), store.dispatch, store.getState); - expect(screen.queryAllByTestId('notification-contents')).toHaveLength(16); + await act(async () => { fireEvent.click(loadMoreButton); }); + expect(screen.queryAllByTestId('notification-contents')).toHaveLength(12); }); }); diff --git a/src/Notifications/notificationTabs.test.jsx b/src/Notifications/notificationTabs.test.jsx index 8bec8b850..50e1e6331 100644 --- a/src/Notifications/notificationTabs.test.jsx +++ b/src/Notifications/notificationTabs.test.jsx @@ -59,7 +59,7 @@ describe('Notification Tabs test cases.', () => { const selectedTab = tabs.find(tab => tab.getAttribute('aria-selected') === 'true'); expect(tabs.length).toEqual(5); - expect(within(selectedTab).queryByText('discussions')).toBeInTheDocument(); + expect(within(selectedTab).queryByText('discussion')).toBeInTheDocument(); expect(within(selectedTab).queryByRole('status')).not.toBeInTheDocument(); }); diff --git a/src/Notifications/test-utils.js b/src/Notifications/test-utils.js index 85489ce5d..c0dcd5873 100644 --- a/src/Notifications/test-utils.js +++ b/src/Notifications/test-utils.js @@ -5,27 +5,28 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { initializeStore } from '../store'; import executeThunk from '../test-utils'; -import { getNotificationsApiUrl, getNotificationsCountApiUrl } from './data/api'; +import { getNotificationsListApiUrl, getNotificationsCountApiUrl } from './data/api'; import { fetchAppsNotificationCount, fetchNotificationList } from './data/thunks'; import './data/__factories__'; const notificationCountsApiUrl = getNotificationsCountApiUrl(); -const notificationsApiUrl = getNotificationsApiUrl(); +const notificationsApiUrl = getNotificationsListApiUrl(); export default async function mockNotificationsResponse() { const store = initializeStore(); const axiosMock = new MockAdapter(getAuthenticatedHttpClient()); - + const notifications = (Factory.buildList('notification', 8, null, { createdDate: new Date().toISOString() }).concat( + Factory.buildList('notification', 2, null, { createdDate: '2023-06-01T00:46:11.979531Z' }), + )); axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount'))); - axiosMock.onGet(notificationsApiUrl).reply( - 200, - (Factory.buildList('notification', 8, null, { createdDate: new Date().toISOString() }).concat( - Factory.buildList('notification', 8, null, { createdDate: '2023-06-01T00:46:11.979531Z' }), - )), - ); + axiosMock.onGet(notificationsApiUrl).reply(200, (Factory.build('notificationsList', { + results: notifications, + num_pages: 2, + next: `${notificationsApiUrl}?app_name=discussion&page=2`, + }))); await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState); - await executeThunk(fetchNotificationList({ page: 1, pageSize: 10 }), store.dispatch, store.getState); + await executeThunk(fetchNotificationList({ appName: 'discussion', page: 1 }), store.dispatch, store.getState); return { store, axiosMock }; } diff --git a/src/learning-header/AuthenticatedUserDropdown.jsx b/src/learning-header/AuthenticatedUserDropdown.jsx index 0060c5082..889ff3f67 100644 --- a/src/learning-header/AuthenticatedUserDropdown.jsx +++ b/src/learning-header/AuthenticatedUserDropdown.jsx @@ -24,7 +24,7 @@ const AuthenticatedUserDropdown = ({ intl, username }) => { dispatch(fetchAppsNotificationCount()); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [notificationStatus]); + }, []); const dashboardMenuItem = ( From eb5dc0831c02b39b6998cb601a0a50a7d163063d Mon Sep 17 00:00:00 2001 From: ayeshoali Date: Thu, 6 Jul 2023 13:50:03 +0500 Subject: [PATCH 3/5] refactor: loader added and resolves minor nits --- src/Notifications/NotificationSections.jsx | 28 +++++++++++++--------- src/Notifications/data/slice.js | 12 +++------- src/Notifications/data/thunks.js | 9 ++++--- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/src/Notifications/NotificationSections.jsx b/src/Notifications/NotificationSections.jsx index 2352db584..37d081eb8 100644 --- a/src/Notifications/NotificationSections.jsx +++ b/src/Notifications/NotificationSections.jsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo } from 'react'; -import { Button } from '@edx/paragon'; +import { Button, Spinner } from '@edx/paragon'; import { useDispatch, useSelector } from 'react-redux'; import { useIntl } from '@edx/frontend-platform/i18n'; import isEmpty from 'lodash/isEmpty'; @@ -18,7 +18,7 @@ const NotificationSections = () => { const selectedAppName = useSelector(selectSelectedAppName()); const notificationRequestStatus = useSelector(selectNotificationStatus()); const notifications = useSelector(selectNotificationsByIds(selectedAppName)); - const { nextPage } = useSelector(selectPaginationData()); + const { hasMorePages } = useSelector(selectPaginationData()); const { today = [], earlier = [] } = useMemo( () => splitNotificationsByTime(notifications), [notifications], @@ -73,15 +73,21 @@ const NotificationSections = () => {
{renderNotificationSection('today', today)} {renderNotificationSection('earlier', earlier)} - {nextPage && notificationRequestStatus === RequestStatus.SUCCESSFUL && ( - + {hasMorePages && notificationRequestStatus === RequestStatus.IN_PROGRESS ? ( +
+ +
+ ) : (hasMorePages && notificationRequestStatus === RequestStatus.SUCCESSFUL + && ( + + ) )}
); diff --git a/src/Notifications/data/slice.js b/src/Notifications/data/slice.js index c2255bd74..6ee417823 100644 --- a/src/Notifications/data/slice.js +++ b/src/Notifications/data/slice.js @@ -17,11 +17,7 @@ const initialState = { notifications: {}, tabsCount: {}, showNotificationsTray: false, - pagination: { - totalPages: 1, - currentPage: 1, - nextPage: null, - }, + pagination: {}, }; const slice = createSlice({ name: 'notifications', @@ -38,7 +34,7 @@ const slice = createSlice({ }, fetchNotificationSuccess: (state, { payload }) => { const { - newNotificationIds, notificationsKeyValuePair, totalPages, currentPage, nextPage, + newNotificationIds, notificationsKeyValuePair, pagination, } = payload; const existingNotificationIds = state.apps[state.appName]; state.apps[state.appName] = Array.from(new Set([...existingNotificationIds, ...newNotificationIds])); @@ -46,9 +42,7 @@ const slice = createSlice({ state.tabsCount.count -= state.tabsCount[state.appName]; state.tabsCount[state.appName] = 0; state.notificationStatus = RequestStatus.SUCCESSFUL; - state.pagination.totalPages = totalPages; - state.pagination.currentPage = currentPage; - state.pagination.nextPage = nextPage; + state.pagination = pagination; }, fetchNotificationsCountDenied: (state) => { state.notificationStatus = RequestStatus.DENIED; diff --git a/src/Notifications/data/thunks.js b/src/Notifications/data/thunks.js index b016a859b..7688ae222 100644 --- a/src/Notifications/data/thunks.js +++ b/src/Notifications/data/thunks.js @@ -49,9 +49,12 @@ export const fetchNotificationList = ({ appName, page }) => ( dispatch(fetchNotificationRequest({ appName })); const data = await getNotificationsList(appName, page); const normalisedData = normalizeNotifications((camelCaseObject(data.results))); - dispatch(fetchNotificationSuccess({ - ...normalisedData, totalPages: data.num_pages, currentPage: data.current_page, nextPage: data.next, - })); + const pagination = { + numPages: data.num_pages, + currentPage: data.current_page, + hasMorePages: !!data.next, + }; + dispatch(fetchNotificationSuccess({ ...normalisedData, pagination })); } catch (error) { if (getHttpErrorStatus(error) === 403) { dispatch(fetchNotificationDenied(appName)); From e44e18c15aabb7746339d830810c45271a8d71b1 Mon Sep 17 00:00:00 2001 From: ayeshoali Date: Thu, 6 Jul 2023 14:31:46 +0500 Subject: [PATCH 4/5] test: fixes test cases related to pagination --- src/Notifications/data/api.test.js | 2 +- src/Notifications/data/redux.test.js | 12 ++++-------- src/Notifications/data/selector.test.jsx | 22 +++++++--------------- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/src/Notifications/data/api.test.js b/src/Notifications/data/api.test.js index 3ef2ce8b3..a905f6c2f 100644 --- a/src/Notifications/data/api.test.js +++ b/src/Notifications/data/api.test.js @@ -66,7 +66,7 @@ describe('Notifications API', () => { const notifications = await getNotificationsList('discussion', 1); - expect(Object.keys(notifications.results)).toHaveLength(2); + expect(notifications.results).toHaveLength(2); }); it.each([ diff --git a/src/Notifications/data/redux.test.js b/src/Notifications/data/redux.test.js index 77743f5db..3ceb0bf19 100644 --- a/src/Notifications/data/redux.test.js +++ b/src/Notifications/data/redux.test.js @@ -6,6 +6,7 @@ import { initializeMockApp } from '@edx/frontend-platform/testing'; import { initializeStore } from '../../store'; import executeThunk from '../../test-utils'; +import mockNotificationsResponse from '../test-utils'; import { getNotificationsListApiUrl, getNotificationsCountApiUrl, markNotificationAsReadApiUrl, markNotificationsSeenApiUrl, } from './api'; @@ -38,10 +39,7 @@ describe('Notification Redux', () => { Factory.resetAll(); store = initializeStore(); - axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount'))); - axiosMock.onGet(notificationsListApiUrl).reply(200, (Factory.build('notificationsList'))); - await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState); - await executeThunk(fetchNotificationList({ appName: 'discussion', page: 1 }), store.dispatch, store.getState); + ({ store, axiosMock } = await mockNotificationsResponse()); }); afterEach(() => { @@ -60,14 +58,12 @@ describe('Notification Redux', () => { expect(notifications.notifications).toEqual({}); expect(notifications.tabsCount).toEqual({}); expect(notifications.showNotificationsTray).toEqual(false); - expect(notifications.pagination.totalPages).toEqual(1); - expect(notifications.pagination.currentPage).toEqual(1); - expect(notifications.pagination.nextPage).toBeNull(); + expect(notifications.pagination).toEqual({}); }); it('Successfully loaded notifications list in the redux.', async () => { const { notifications: { notifications } } = store.getState(); - expect(Object.keys(notifications)).toHaveLength(2); + expect(Object.keys(notifications)).toHaveLength(10); }); it.each([ diff --git a/src/Notifications/data/selector.test.jsx b/src/Notifications/data/selector.test.jsx index ff589ae51..31f2c800a 100644 --- a/src/Notifications/data/selector.test.jsx +++ b/src/Notifications/data/selector.test.jsx @@ -5,8 +5,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { initializeMockApp } from '@edx/frontend-platform/testing'; import { initializeStore } from '../../store'; -import executeThunk from '../../test-utils'; -import { getNotificationsListApiUrl, getNotificationsCountApiUrl } from './api'; +import mockNotificationsResponse from '../test-utils'; import { selectNotifications, selectNotificationsByIds, @@ -18,13 +17,9 @@ import { selectSelectedAppNotificationIds, selectShowNotificationTray, } from './selectors'; -import { fetchAppsNotificationCount, fetchNotificationList } from './thunks'; import './__factories__'; -const notificationCountsApiUrl = getNotificationsCountApiUrl(); -const notificationsApiUrl = getNotificationsListApiUrl(); - let axiosMock; let store; @@ -42,10 +37,7 @@ describe('Notification Selectors', () => { Factory.resetAll(); store = initializeStore(); - axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount'))); - axiosMock.onGet(notificationsApiUrl).reply(200, (Factory.build('notificationsList'))); - await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState); - await executeThunk(fetchNotificationList({ appName: 'discussion', page: 1 }), store.dispatch, store.getState); + ({ store, axiosMock } = await mockNotificationsResponse()); }); afterEach(() => { @@ -81,7 +73,7 @@ describe('Notification Selectors', () => { const state = store.getState(); const notificationIds = selectSelectedAppNotificationIds('discussion')(state); - expect(notificationIds).toHaveLength(2); + expect(notificationIds).toHaveLength(10); }); it('Should return show notification tray status.', async () => { @@ -95,14 +87,14 @@ describe('Notification Selectors', () => { const state = store.getState(); const notifications = selectNotifications()(state); - expect(Object.keys(notifications)).toHaveLength(2); + expect(Object.keys(notifications)).toHaveLength(10); }); it('Should return notifications from Ids.', async () => { const state = store.getState(); const notifications = selectNotificationsByIds('discussion')(state); - expect(notifications).toHaveLength(2); + expect(notifications).toHaveLength(10); }); it('Should return selected app name.', async () => { @@ -117,7 +109,7 @@ describe('Notification Selectors', () => { const paginationData = selectPaginationData()(state); expect(paginationData.currentPage).toEqual(1); - expect(paginationData.totalPages).toEqual(1); - expect(paginationData.nextPage).toEqual(null); + expect(paginationData.numPages).toEqual(2); + expect(paginationData.hasMorePages).toEqual(true); }); }); From a0aa6e79f8ea5764bacc9a3779b11c08b8a5cf25 Mon Sep 17 00:00:00 2001 From: ayeshoali Date: Fri, 7 Jul 2023 17:22:39 +0500 Subject: [PATCH 5/5] refactor: moved pagination to normalised data --- src/Notifications/data/thunks.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Notifications/data/thunks.js b/src/Notifications/data/thunks.js index 7688ae222..585ce0eaa 100644 --- a/src/Notifications/data/thunks.js +++ b/src/Notifications/data/thunks.js @@ -35,11 +35,16 @@ const normalizeNotificationCounts = ({ countByAppName, count, showNotificationsT }; }; -const normalizeNotifications = (notifications) => { - const newNotificationIds = notifications.map(notification => notification.id.toString()); - const notificationsKeyValuePair = notifications.reduce((acc, obj) => { acc[obj.id] = obj; return acc; }, {}); +const normalizeNotifications = (data) => { + const newNotificationIds = data.results.map(notification => notification.id.toString()); + const notificationsKeyValuePair = data.results.reduce((acc, obj) => { acc[obj.id] = obj; return acc; }, {}); + const pagination = { + numPages: data.numPages, + currentPage: data.currentPage, + hasMorePages: !!data.next, + }; return { - newNotificationIds, notificationsKeyValuePair, + newNotificationIds, notificationsKeyValuePair, pagination, }; }; @@ -48,13 +53,8 @@ export const fetchNotificationList = ({ appName, page }) => ( try { dispatch(fetchNotificationRequest({ appName })); const data = await getNotificationsList(appName, page); - const normalisedData = normalizeNotifications((camelCaseObject(data.results))); - const pagination = { - numPages: data.num_pages, - currentPage: data.current_page, - hasMorePages: !!data.next, - }; - dispatch(fetchNotificationSuccess({ ...normalisedData, pagination })); + const normalisedData = normalizeNotifications((camelCaseObject(data))); + dispatch(fetchNotificationSuccess({ ...normalisedData })); } catch (error) { if (getHttpErrorStatus(error) === 403) { dispatch(fetchNotificationDenied(appName));