From 6efa31092d116615aca867022ce04fab0b017e44 Mon Sep 17 00:00:00 2001 From: SundasNoreen Date: Thu, 15 Jun 2023 13:57:23 +0500 Subject: [PATCH] test: added redux, selector and api cases --- package-lock.json | 57 +++++++++++++++++++----- package.json | 6 ++- src/Notifications/data/api.js | 44 +++++++++--------- src/Notifications/data/redux.test.js | 33 +++++++------- src/Notifications/data/selector.test.jsx | 5 ++- src/Notifications/data/thunks.js | 4 +- 6 files changed, 97 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index a85b517..930d299 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@fortawesome/free-solid-svg-icons": "6.3.0", "@fortawesome/react-fontawesome": "^0.2.0", "@reduxjs/toolkit": "1.9.5", + "axios-mock-adapter": "1.21.4", "babel-polyfill": "6.26.0", "classnames": "2.3.2", "lodash": "4.17.21", @@ -23,6 +24,7 @@ "react-responsive": "8.2.0", "react-router-dom": "5.3.4", "react-transition-group": "4.4.5", + "rosie": "2.1.0", "timeago.js": "4.0.2" }, "devDependencies": { @@ -7709,8 +7711,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -7799,7 +7800,6 @@ "version": "0.27.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dev": true, "dependencies": { "follow-redirects": "^1.14.9", "form-data": "^4.0.0" @@ -7819,6 +7819,40 @@ "url": "https://github.com/ArthurFiorette/axios-cache-interceptor?sponsor=1" } }, + "node_modules/axios-mock-adapter": { + "version": "1.21.4", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-1.21.4.tgz", + "integrity": "sha512-ztnENm28ONAKeRXC/6SUW6pcsaXbThKq93MRDRAA47LYTzrGSDoO/DCr1NHz7jApEl95DrBoGPvZ0r9xtSbjqw==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + }, + "peerDependencies": { + "axios": ">= 0.17.0" + } + }, + "node_modules/axios-mock-adapter/node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, "node_modules/axobject-query": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", @@ -9166,7 +9200,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -9896,7 +9929,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -11774,8 +11806,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-defer": { "version": "1.1.7", @@ -12052,7 +12083,6 @@ "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true, "funding": [ { "type": "individual", @@ -12283,7 +12313,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -19203,7 +19232,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -19212,7 +19240,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -22628,6 +22655,14 @@ "rimraf": "bin.js" } }, + "node_modules/rosie": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/rosie/-/rosie-2.1.0.tgz", + "integrity": "sha512-Dbzdc+prLXZuB/suRptDnBUY29SdGvND3bLg6cll8n7PNqzuyCxSlRfrkn8PqjS9n4QVsiM7RCvxCkKAkTQRjA==", + "engines": { + "node": ">=10" + } + }, "node_modules/rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", diff --git a/package.json b/package.json index cf5ec01..8533d00 100644 --- a/package.json +++ b/package.json @@ -62,14 +62,16 @@ "@fortawesome/free-solid-svg-icons": "6.3.0", "@fortawesome/react-fontawesome": "^0.2.0", "@reduxjs/toolkit": "1.9.5", + "axios-mock-adapter": "1.21.4", "babel-polyfill": "6.26.0", "classnames": "2.3.2", "lodash": "4.17.21", "react-redux": "7.2.9", "react-responsive": "8.2.0", + "react-router-dom": "5.3.4", "react-transition-group": "4.4.5", - "timeago.js": "4.0.2", - "react-router-dom": "5.3.4" + "rosie": "2.1.0", + "timeago.js": "4.0.2" }, "peerDependencies": { "@edx/frontend-platform": "^4.0.0", diff --git a/src/Notifications/data/api.js b/src/Notifications/data/api.js index 8e7c117..adb7438 100644 --- a/src/Notifications/data/api.js +++ b/src/Notifications/data/api.js @@ -1,8 +1,15 @@ -import { camelCaseObject } from '@edx/frontend-platform'; -import notificationsList from './notifications.json'; +import { camelCaseObject, 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 async function getNotifications(appName, page, pageSize) { - const { data } = notificationsList; + const params = snakeCaseObject({ page, pageSize }); + const { data } = await getAuthenticatedHttpClient().get(getNotificationsApiUrl(), { params }); + const startIndex = (page - 1) * pageSize; const endIndex = startIndex + pageSize; @@ -11,30 +18,25 @@ export async function getNotifications(appName, page, pageSize) { } export async function getNotificationCounts() { - const data = { - count: 45, - count_by_app_name: { - reminders: 10, - discussions: 20, - grades: 10, - authoring: 5, - }, - show_notification_tray: false, - }; + const { data } = await getAuthenticatedHttpClient().get(getNotificationsCountApiUrl()); + return camelCaseObject(data); } -export async function markNotificationSeen() { - const data = []; +export async function markNotificationSeen(appName) { + const { data } = await getAuthenticatedHttpClient().put(`${markNotificationsSeenApiUrl(appName)}`); + return camelCaseObject(data); } -export async function markAllNotificationRead() { - const { data } = camelCaseObject(notificationsList); - return data; +export async function markAllNotificationRead(appName) { + const { data } = await getAuthenticatedHttpClient().put(`${markAllNotificationsAsReadpiUrl(appName)}`); + + return camelCaseObject(data); } -export async function markNotificationRead(notificationId) { - const { data } = camelCaseObject(notificationsList); - return { data, id: notificationId }; +export async function markNotificationRead(appName, notificationId) { + const { data } = await getAuthenticatedHttpClient().put(`${markAllNotificationsAsReadpiUrl(appName, notificationId)}`); + + return camelCaseObject({ data, id: notificationId }); } diff --git a/src/Notifications/data/redux.test.js b/src/Notifications/data/redux.test.js index 4810458..34b137a 100644 --- a/src/Notifications/data/redux.test.js +++ b/src/Notifications/data/redux.test.js @@ -40,7 +40,10 @@ describe('Notification Redux', () => { store = initializeStore(); axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount'))); - axiosMock.onGet(notificationsApiUrl).reply(200, (Factory.buildList('notification', 2, null))); + axiosMock.onGet(notificationsApiUrl).reply( + 200, + (Factory.buildList('notification', 2, null, { createdDate: new Date().toISOString() })), + ); await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState); await executeThunk(fetchNotificationList({ page: 1, pageSize: 10 }), store.dispatch, store.getState); }); @@ -68,9 +71,9 @@ describe('Notification Redux', () => { }); it('successfully loaded notifications list in the redux.', async () => { - const state = store.getState(); + const { notifications: { notifications } } = store.getState(); - expect(Object.keys(state.notifications.notifications)).toHaveLength(2); + expect(Object.keys(notifications)).toHaveLength(2); }); it('successfully loaded notification counts in the redux.', async () => { @@ -84,9 +87,9 @@ describe('Notification Redux', () => { }); it('successfully loaded showNotificationTray status in the redux based on api.', async () => { - const state = store.getState(); + const { notifications: { showNotificationTray } } = store.getState(); - expect(state.notifications.showNotificationTray).toEqual(true); + expect(showNotificationTray).toEqual(true); }); it('successfully store the count, numPages, currentPage, and nextPage data in redux.', async () => { @@ -98,15 +101,15 @@ describe('Notification Redux', () => { }); it('successfully updated the selected app name in redux.', async () => { - const state = store.getState(); + const { notifications: { appName } } = store.getState(); - expect(state.notifications.appName).toEqual('discussions'); + expect(appName).toEqual('discussions'); }); it('successfully store notification ids in the selected app in apps.', async () => { - const state = store.getState(); + const { notifications: { apps } } = store.getState(); - expect(state.notifications.apps.discussions).toHaveLength(2); + expect(apps.discussions).toHaveLength(2); }); it('successfully marked all notifications as seen for selected app.', async () => { @@ -120,10 +123,10 @@ describe('Notification Redux', () => { axiosMock.onPut(markedAllNotificationsAsReadApiUrl).reply(200); await executeThunk(markAllNotificationsAsRead('discussions'), store.dispatch, store.getState); - const { notifications } = store.getState(); - const firstNotification = Object.values(notifications.notifications)[0]; + const { notifications: { notificationStatus, notifications } } = store.getState(); + const firstNotification = Object.values(notifications)[0]; - expect(notifications.notificationStatus).toEqual('successful'); + expect(notificationStatus).toEqual('successful'); expect(firstNotification.lastRead).not.toBeNull(); }); @@ -131,10 +134,10 @@ describe('Notification Redux', () => { axiosMock.onPut(markedNotificationAsReadApiUrl).reply(200); await executeThunk(markNotificationsAsRead('discussions', 1), store.dispatch, store.getState); - const { notifications } = store.getState(); - const firstNotification = Object.values(notifications.notifications)[0]; + const { notifications: { notificationStatus, notifications } } = store.getState(); + const firstNotification = Object.values(notifications)[0]; - expect(notifications.notificationStatus).toEqual('successful'); + expect(notificationStatus).toEqual('successful'); expect(firstNotification.lastRead).not.toBeNull(); }); }); diff --git a/src/Notifications/data/selector.test.jsx b/src/Notifications/data/selector.test.jsx index 90f51f8..d09c368 100644 --- a/src/Notifications/data/selector.test.jsx +++ b/src/Notifications/data/selector.test.jsx @@ -43,7 +43,10 @@ describe('Notification Selectors', () => { store = initializeStore(); axiosMock.onGet(notificationCountsApiUrl).reply(200, (Factory.build('notificationsCount'))); - axiosMock.onGet(notificationsApiUrl).reply(200, (Factory.buildList('notification', 2, null))); + axiosMock.onGet(notificationsApiUrl).reply( + 200, + (Factory.buildList('notification', 2, null, { createdDate: new Date().toISOString() })), + ); await executeThunk(fetchAppsNotificationCount(), store.dispatch, store.getState); await executeThunk(fetchNotificationList({ page: 1, pageSize: 10 }), store.dispatch, store.getState); }); diff --git a/src/Notifications/data/thunks.js b/src/Notifications/data/thunks.js index 1e702b2..df71063 100644 --- a/src/Notifications/data/thunks.js +++ b/src/Notifications/data/thunks.js @@ -97,11 +97,11 @@ export const markAllNotificationsAsRead = (appName) => ( } ); -export const markNotificationsAsRead = (notificationId) => ( +export const markNotificationsAsRead = (appName, notificationId) => ( async (dispatch) => { try { dispatch(markNotificationsAsReadRequest({ notificationId })); - const data = await markNotificationRead(notificationId); + const data = await markNotificationRead(appName, notificationId); dispatch(markNotificationsAsReadSuccess(data)); } catch (error) { if (getHttpErrorStatus(error) === 403) {