musora-content-services 2.13.0 → 2.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/.yarnrc.yml +1 -0
- package/CHANGELOG.md +17 -0
- package/babel.config.cjs +0 -0
- package/docs/ContentOrganization.html +2 -2
- package/docs/Gamification.html +2 -2
- package/docs/UserManagementSystem.html +2 -2
- package/docs/api_types.js.html +2 -2
- package/docs/config.js.html +2 -2
- package/docs/content-org_content-org.js.html +2 -2
- package/docs/content-org_playlists-types.js.html +4 -2
- package/docs/content-org_playlists.js.html +25 -3
- package/docs/content.js.html +3 -3
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/gamification_awards.js.html +2 -2
- package/docs/gamification_gamification.js.html +2 -2
- package/docs/gamification_types.js.html +2 -2
- package/docs/global.html +51 -3
- package/docs/index.html +2 -3
- package/docs/module-Awards.html +2 -2
- package/docs/module-Config.html +2 -2
- package/docs/module-Content-Services-V2.html +2 -2
- package/docs/module-Interests.html +2 -2
- package/docs/module-Permissions.html +2 -2
- package/docs/module-Playlists.html +198 -31
- package/docs/module-Railcontent-Services.html +168 -17
- package/docs/module-Sanity-Services.html +25 -21
- package/docs/module-Sessions.html +2 -2
- package/docs/module-UserActivity.html +15 -15
- package/docs/module-UserChat.html +410 -0
- package/docs/module-UserManagement.html +2 -2
- package/docs/module-UserNotifications.html +838 -19
- package/docs/module-UserProfile.html +2 -2
- package/docs/railcontent.js.html +15 -3
- package/docs/sanity.js.html +93 -33
- package/docs/scripts/collapse.js +0 -0
- package/docs/scripts/commonNav.js +0 -0
- package/docs/scripts/linenumber.js +0 -0
- package/docs/scripts/nav.js +0 -0
- package/docs/scripts/polyfill.js +0 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
- package/docs/scripts/prettify/lang-css.js +0 -0
- package/docs/scripts/prettify/prettify.js +0 -0
- package/docs/scripts/search.js +0 -0
- package/docs/styles/jsdoc.css +0 -0
- package/docs/styles/prettify.css +0 -0
- package/docs/userActivity.js.html +27 -17
- package/docs/user_chat.js.html +98 -0
- package/docs/user_interests.js.html +2 -2
- package/docs/user_management.js.html +2 -2
- package/docs/user_notifications.js.html +125 -5
- package/docs/user_permissions.js.html +2 -2
- package/docs/user_profile.js.html +2 -2
- package/docs/user_sessions.js.html +2 -2
- package/docs/user_types.js.html +2 -2
- package/docs/user_user-management-system.js.html +2 -2
- package/jest.config.js +0 -0
- package/link_mcs.sh +0 -0
- package/package.json +1 -1
- package/src/index.d.ts +14 -1
- package/src/index.js +14 -1
- package/src/services/content-org/guided-courses.ts +26 -0
- package/src/services/contentProgress.js +2 -2
- package/src/services/sanity.js +1 -1
- package/src/services/user/notifications.js +97 -0
- package/test/dataContext.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/notifications.test.js +178 -0
package/src/index.d.ts
CHANGED
|
@@ -5,6 +5,12 @@ import {
|
|
|
5
5
|
initializeService
|
|
6
6
|
} from './services/config.js';
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
enrollUserInGuidedCourse,
|
|
10
|
+
fetchEnrollmentPageMetadata,
|
|
11
|
+
unEnrollUserInGuidedCourse
|
|
12
|
+
} from './services/content-org/guided-courses.js';
|
|
13
|
+
|
|
8
14
|
import {
|
|
9
15
|
addItemToPlaylist,
|
|
10
16
|
createPlaylist,
|
|
@@ -237,11 +243,13 @@ import {
|
|
|
237
243
|
|
|
238
244
|
import {
|
|
239
245
|
deleteNotification,
|
|
246
|
+
fetchNotificationSettings,
|
|
240
247
|
fetchNotifications,
|
|
241
248
|
fetchUnreadCount,
|
|
242
249
|
markAllNotificationsAsRead,
|
|
243
250
|
markNotificationAsRead,
|
|
244
|
-
markNotificationAsUnread
|
|
251
|
+
markNotificationAsUnread,
|
|
252
|
+
updateNotificationSetting
|
|
245
253
|
} from './services/user/notifications.js';
|
|
246
254
|
|
|
247
255
|
import {
|
|
@@ -309,6 +317,7 @@ declare module 'musora-content-services' {
|
|
|
309
317
|
deleteUserActivity,
|
|
310
318
|
duplicatePlaylist,
|
|
311
319
|
editComment,
|
|
320
|
+
enrollUserInGuidedCourse,
|
|
312
321
|
extractSanityUrl,
|
|
313
322
|
fetchAll,
|
|
314
323
|
fetchAllCompletedStates,
|
|
@@ -338,6 +347,7 @@ declare module 'musora-content-services' {
|
|
|
338
347
|
fetchContentInProgress,
|
|
339
348
|
fetchContentPageUserData,
|
|
340
349
|
fetchContentProgress,
|
|
350
|
+
fetchEnrollmentPageMetadata,
|
|
341
351
|
fetchFoundation,
|
|
342
352
|
fetchGenreLessons,
|
|
343
353
|
fetchHandler,
|
|
@@ -357,6 +367,7 @@ declare module 'musora-content-services' {
|
|
|
357
367
|
fetchNewReleases,
|
|
358
368
|
fetchNextContentDataForParent,
|
|
359
369
|
fetchNextPreviousLesson,
|
|
370
|
+
fetchNotificationSettings,
|
|
360
371
|
fetchNotifications,
|
|
361
372
|
fetchOtherSongVersions,
|
|
362
373
|
fetchOwnedChallenges,
|
|
@@ -481,6 +492,7 @@ declare module 'musora-content-services' {
|
|
|
481
492
|
restoreUserPractice,
|
|
482
493
|
setStudentViewForUser,
|
|
483
494
|
togglePlaylistPrivate,
|
|
495
|
+
unEnrollUserInGuidedCourse,
|
|
484
496
|
unassignModeratorToComment,
|
|
485
497
|
unblockUser,
|
|
486
498
|
undeletePlaylist,
|
|
@@ -488,6 +500,7 @@ declare module 'musora-content-services' {
|
|
|
488
500
|
unlikeContent,
|
|
489
501
|
unlikePlaylist,
|
|
490
502
|
unpinProgressRow,
|
|
503
|
+
updateNotificationSetting,
|
|
491
504
|
updatePlaylist,
|
|
492
505
|
updatePracticeNotes,
|
|
493
506
|
updateUserPractice,
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,12 @@ import {
|
|
|
5
5
|
initializeService
|
|
6
6
|
} from './services/config.js';
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
enrollUserInGuidedCourse,
|
|
10
|
+
fetchEnrollmentPageMetadata,
|
|
11
|
+
unEnrollUserInGuidedCourse
|
|
12
|
+
} from './services/content-org/guided-courses.js';
|
|
13
|
+
|
|
8
14
|
import {
|
|
9
15
|
addItemToPlaylist,
|
|
10
16
|
createPlaylist,
|
|
@@ -237,11 +243,13 @@ import {
|
|
|
237
243
|
|
|
238
244
|
import {
|
|
239
245
|
deleteNotification,
|
|
246
|
+
fetchNotificationSettings,
|
|
240
247
|
fetchNotifications,
|
|
241
248
|
fetchUnreadCount,
|
|
242
249
|
markAllNotificationsAsRead,
|
|
243
250
|
markNotificationAsRead,
|
|
244
|
-
markNotificationAsUnread
|
|
251
|
+
markNotificationAsUnread,
|
|
252
|
+
updateNotificationSetting
|
|
245
253
|
} from './services/user/notifications.js';
|
|
246
254
|
|
|
247
255
|
import {
|
|
@@ -308,6 +316,7 @@ export {
|
|
|
308
316
|
deleteUserActivity,
|
|
309
317
|
duplicatePlaylist,
|
|
310
318
|
editComment,
|
|
319
|
+
enrollUserInGuidedCourse,
|
|
311
320
|
extractSanityUrl,
|
|
312
321
|
fetchAll,
|
|
313
322
|
fetchAllCompletedStates,
|
|
@@ -337,6 +346,7 @@ export {
|
|
|
337
346
|
fetchContentInProgress,
|
|
338
347
|
fetchContentPageUserData,
|
|
339
348
|
fetchContentProgress,
|
|
349
|
+
fetchEnrollmentPageMetadata,
|
|
340
350
|
fetchFoundation,
|
|
341
351
|
fetchGenreLessons,
|
|
342
352
|
fetchHandler,
|
|
@@ -356,6 +366,7 @@ export {
|
|
|
356
366
|
fetchNewReleases,
|
|
357
367
|
fetchNextContentDataForParent,
|
|
358
368
|
fetchNextPreviousLesson,
|
|
369
|
+
fetchNotificationSettings,
|
|
359
370
|
fetchNotifications,
|
|
360
371
|
fetchOtherSongVersions,
|
|
361
372
|
fetchOwnedChallenges,
|
|
@@ -480,6 +491,7 @@ export {
|
|
|
480
491
|
restoreUserPractice,
|
|
481
492
|
setStudentViewForUser,
|
|
482
493
|
togglePlaylistPrivate,
|
|
494
|
+
unEnrollUserInGuidedCourse,
|
|
483
495
|
unassignModeratorToComment,
|
|
484
496
|
unblockUser,
|
|
485
497
|
undeletePlaylist,
|
|
@@ -487,6 +499,7 @@ export {
|
|
|
487
499
|
unlikeContent,
|
|
488
500
|
unlikePlaylist,
|
|
489
501
|
unpinProgressRow,
|
|
502
|
+
updateNotificationSetting,
|
|
490
503
|
updatePlaylist,
|
|
491
504
|
updatePracticeNotes,
|
|
492
505
|
updateUserPractice,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module GuidedCourses
|
|
3
|
+
*/
|
|
4
|
+
import { globalConfig } from '../config.js'
|
|
5
|
+
import { fetchHandler } from '../railcontent.js'
|
|
6
|
+
import './playlists-types.js'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const excludeFromGeneratedIndex: string[] = []
|
|
10
|
+
|
|
11
|
+
const BASE_PATH: string = `/api/content-org`
|
|
12
|
+
|
|
13
|
+
export async function enrollUserInGuidedCourse(guidedCourse) {
|
|
14
|
+
const url: string = `${BASE_PATH}/v1/user/guided-courses/enroll-user/${guidedCourse}`
|
|
15
|
+
return await fetchHandler(url, 'POST')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function unEnrollUserInGuidedCourse(guidedCourse) {
|
|
19
|
+
const url: string = `${BASE_PATH}/v1/user/guided-courses/un-enroll-user/${guidedCourse}`
|
|
20
|
+
return await fetchHandler(url, 'POST')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function fetchEnrollmentPageMetadata(guidedCourse) {
|
|
24
|
+
const url: string = `${BASE_PATH}/v1/user/guided-courses/enrollment/${guidedCourse}`
|
|
25
|
+
return await fetchHandler(url, 'GET')
|
|
26
|
+
}
|
|
@@ -198,7 +198,7 @@ export async function assignmentStatusCompleted(assignmentId, parentContentId) {
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
export async function contentStatusCompleted(contentId) {
|
|
201
|
-
await dataContext.update(
|
|
201
|
+
return await dataContext.update(
|
|
202
202
|
async function (localContext) {
|
|
203
203
|
let hierarchy = await fetchHierarchy(contentId)
|
|
204
204
|
completeStatusInLocalContext(localContext, contentId, hierarchy)
|
|
@@ -361,7 +361,7 @@ function getMediaTypeId(mediaType, mediaCategory) {
|
|
|
361
361
|
case 'practice_play-alongs':
|
|
362
362
|
return 4
|
|
363
363
|
case 'video_soundslice':
|
|
364
|
-
return 6
|
|
364
|
+
return 6
|
|
365
365
|
default:
|
|
366
366
|
return 5
|
|
367
367
|
}
|
package/src/services/sanity.js
CHANGED
|
@@ -1438,7 +1438,7 @@ export async function fetchRelatedLessons(railContentId, brand) {
|
|
|
1438
1438
|
pullFutureContent: true,
|
|
1439
1439
|
}).buildFilter()
|
|
1440
1440
|
const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
|
|
1441
|
-
const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds,
|
|
1441
|
+
const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type, "genre": genre[]->name`
|
|
1442
1442
|
const queryFieldsWithSort = queryFields + ', sort'
|
|
1443
1443
|
const query = `*[railcontent_id == ${railContentId} && brand == "${brand}" && (!defined(permission) || references(*[_type=='permission']._id))]{
|
|
1444
1444
|
_type, parent_type, 'parent_id': parent_content_data[0].id, railcontent_id,
|
|
@@ -6,6 +6,12 @@ import './types.js'
|
|
|
6
6
|
|
|
7
7
|
const baseUrl = `/api/notifications`
|
|
8
8
|
|
|
9
|
+
const NotificationChannels = {
|
|
10
|
+
EMAIL: 'email',
|
|
11
|
+
PUSH: 'push',
|
|
12
|
+
BELL: 'bell',
|
|
13
|
+
};
|
|
14
|
+
|
|
9
15
|
/**
|
|
10
16
|
* Fetches notifications for a given brand with optional filters for unread status and limit.
|
|
11
17
|
*
|
|
@@ -141,3 +147,94 @@ export async function fetchUnreadCount({ brand = null} = {}) {
|
|
|
141
147
|
const url = `${baseUrl}/v1/unread-count?brand=${brand}`
|
|
142
148
|
return fetchHandler(url, 'get')
|
|
143
149
|
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Fetches the notification settings for the current user grouped by brand.
|
|
153
|
+
*
|
|
154
|
+
* @returns {Promise<Object>} A promise that resolves to an object where keys are brands and values are arrays of settings objects.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* fetchNotificationSetting()
|
|
158
|
+
* .then(settings => {
|
|
159
|
+
* console.log(settings);
|
|
160
|
+
* })
|
|
161
|
+
* .catch(error => {
|
|
162
|
+
* console.error(error);
|
|
163
|
+
* });
|
|
164
|
+
*/
|
|
165
|
+
export async function fetchNotificationSettings() {
|
|
166
|
+
const url = `/api/notification-settings/v1`;
|
|
167
|
+
const settings = await fetchHandler(url, 'get');
|
|
168
|
+
|
|
169
|
+
if (!settings || typeof settings !== 'object') return {};
|
|
170
|
+
|
|
171
|
+
const result = {};
|
|
172
|
+
|
|
173
|
+
for (const [brand, brandSettings] of Object.entries(settings)) {
|
|
174
|
+
result[brand] = Object.entries(brandSettings).map(([name, value]) => ({
|
|
175
|
+
name,
|
|
176
|
+
...value,
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Updates notification settings for specified channels within a given brand.
|
|
185
|
+
*
|
|
186
|
+
* @param {Object} options - Options to update notification settings.
|
|
187
|
+
* @param {string} options.brand - The brand context for the notification settings.
|
|
188
|
+
* @param {string} options.settingName - The name of the notification setting to update (required).
|
|
189
|
+
* @param {boolean} [options.email] - Whether email notifications are enabled or disabled.
|
|
190
|
+
* @param {boolean} [options.push] - Whether push notifications are enabled or disabled.
|
|
191
|
+
* @param {boolean} [options.bell] - Whether bell notifications are enabled or disabled.
|
|
192
|
+
* @returns {Promise<Object>} - A promise that resolves to the server response after updating settings.
|
|
193
|
+
*
|
|
194
|
+
* @throws {Error} Throws an error if `settingName` is not provided or if no channels are specified.
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* updateNotificationSetting({
|
|
198
|
+
* brand: 'drumeo',
|
|
199
|
+
* settingName: 'new_lesson',
|
|
200
|
+
* email: true,
|
|
201
|
+
* push: false,
|
|
202
|
+
* bell: true
|
|
203
|
+
* })
|
|
204
|
+
* .then(response => console.log(response))
|
|
205
|
+
* .catch(error => console.error(error));
|
|
206
|
+
*/
|
|
207
|
+
export async function updateNotificationSetting({ brand, settingName, email, push, bell } = {}) {
|
|
208
|
+
if (!settingName) {
|
|
209
|
+
throw new Error('The "settingName" parameter is required.');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const channelValues = {
|
|
213
|
+
[NotificationChannels.EMAIL]: email,
|
|
214
|
+
[NotificationChannels.PUSH]: push,
|
|
215
|
+
[NotificationChannels.BELL]: bell,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const settings = Object.entries(channelValues)
|
|
219
|
+
.filter(([, value]) => value !== undefined)
|
|
220
|
+
.map(([channel, value]) => ({
|
|
221
|
+
name: settingName,
|
|
222
|
+
channel,
|
|
223
|
+
value,
|
|
224
|
+
brand,
|
|
225
|
+
}));
|
|
226
|
+
|
|
227
|
+
if (settings.length === 0) {
|
|
228
|
+
throw new Error('At least one channel (email, push, or bell) must be provided.');
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const payload = { settings };
|
|
232
|
+
const url = '/api/notification-settings/v1';
|
|
233
|
+
|
|
234
|
+
return fetchHandler(url, 'PUT', null, payload);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
package/test/dataContext.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/localStorageMock.js
CHANGED
|
File without changes
|
package/test/log.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { initializeTestService } from './initializeTests.js'
|
|
2
|
+
import * as UserNotifications from "../src/services/user/notifications.js";
|
|
3
|
+
import { fetchHandler } from '../src/services/railcontent.js'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
jest.mock('../src/services/railcontent.js', () => ({
|
|
7
|
+
fetchUserPermissionsData: jest.fn(() => ({ permissions: [78, 91, 92], isAdmin: false })),
|
|
8
|
+
fetchHandler: jest.fn(),
|
|
9
|
+
}))
|
|
10
|
+
|
|
11
|
+
const baseUrl = `/api/notifications`
|
|
12
|
+
|
|
13
|
+
describe('UserNotifications module', function () {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
initializeTestService()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
describe('fetchNotifications', () => {
|
|
19
|
+
it('throws if brand not provided', async () => {
|
|
20
|
+
await expect(UserNotifications.fetchNotifications()).rejects.toThrow('brand is required')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('calls fetchHandler with correct url and method', async () => {
|
|
24
|
+
fetchHandler.mockResolvedValueOnce([{ id: 1 }])
|
|
25
|
+
|
|
26
|
+
const result = await UserNotifications.fetchNotifications({
|
|
27
|
+
brand: 'drumeo',
|
|
28
|
+
limit: 5,
|
|
29
|
+
onlyUnread: true,
|
|
30
|
+
page: 2,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
expect(fetchHandler).toHaveBeenCalledWith(
|
|
34
|
+
`${baseUrl}/v1?brand=drumeo&unread=1&limit=5&page=2`,
|
|
35
|
+
'get'
|
|
36
|
+
)
|
|
37
|
+
expect(result).toEqual([{ id: 1 }])
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe('markNotificationAsRead', () => {
|
|
42
|
+
it('throws if notificationId not provided', async () => {
|
|
43
|
+
await expect(UserNotifications.markNotificationAsRead()).rejects.toThrow('notificationId is required')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('calls fetchHandler with correct url and method', async () => {
|
|
47
|
+
fetchHandler.mockResolvedValueOnce({ success: true })
|
|
48
|
+
|
|
49
|
+
const result = await UserNotifications.markNotificationAsRead(123)
|
|
50
|
+
expect(fetchHandler).toHaveBeenCalledWith(`${baseUrl}/v1/read?id=123`, 'put')
|
|
51
|
+
expect(result).toEqual({ success: true })
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
describe('markAllNotificationsAsRead', () => {
|
|
56
|
+
it('calls fetchHandler with correct url and method', async () => {
|
|
57
|
+
fetchHandler.mockResolvedValueOnce({ success: true })
|
|
58
|
+
|
|
59
|
+
const result = await UserNotifications.markAllNotificationsAsRead('drumeo')
|
|
60
|
+
expect(fetchHandler).toHaveBeenCalledWith(`${baseUrl}/v1/read?brand=drumeo`, 'put')
|
|
61
|
+
expect(result).toEqual({ success: true })
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('markNotificationAsUnread', () => {
|
|
66
|
+
it('throws if notificationId not provided', async () => {
|
|
67
|
+
await expect(UserNotifications.markNotificationAsUnread()).rejects.toThrow('notificationId is required')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('calls fetchHandler with correct url and method', async () => {
|
|
71
|
+
fetchHandler.mockResolvedValueOnce({ success: true })
|
|
72
|
+
|
|
73
|
+
const result = await UserNotifications.markNotificationAsUnread(456)
|
|
74
|
+
expect(fetchHandler).toHaveBeenCalledWith(`${baseUrl}/v1/unread?id=456`, 'put')
|
|
75
|
+
expect(result).toEqual({ success: true })
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('deleteNotification', () => {
|
|
80
|
+
it('throws if notificationId not provided', async () => {
|
|
81
|
+
await expect(UserNotifications.deleteNotification()).rejects.toThrow('notificationId is required')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('calls fetchHandler with correct url and method', async () => {
|
|
85
|
+
fetchHandler.mockResolvedValueOnce({ success: true })
|
|
86
|
+
|
|
87
|
+
const result = await UserNotifications.deleteNotification(789)
|
|
88
|
+
expect(fetchHandler).toHaveBeenCalledWith(`${baseUrl}/v1/789`, 'delete')
|
|
89
|
+
expect(result).toEqual({ success: true })
|
|
90
|
+
})
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
describe('fetchUnreadCount', () => {
|
|
94
|
+
it('throws if brand not provided', async () => {
|
|
95
|
+
await expect(UserNotifications.fetchUnreadCount()).rejects.toThrow('brand is required')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('calls fetchHandler with correct url and method', async () => {
|
|
99
|
+
fetchHandler.mockResolvedValueOnce({ unread_count: 42 })
|
|
100
|
+
|
|
101
|
+
const result = await UserNotifications.fetchUnreadCount({ brand: 'drumeo' })
|
|
102
|
+
expect(fetchHandler).toHaveBeenCalledWith(`${baseUrl}/v1/unread-count?brand=drumeo`, 'get')
|
|
103
|
+
expect(result).toEqual({ unread_count: 42 })
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
describe('fetchNotificationSettings', () => {
|
|
108
|
+
it('returns empty object if settings is falsy or not object', async () => {
|
|
109
|
+
fetchHandler.mockResolvedValueOnce(null)
|
|
110
|
+
expect(await UserNotifications.fetchNotificationSettings()).toEqual({})
|
|
111
|
+
|
|
112
|
+
fetchHandler.mockResolvedValueOnce('string')
|
|
113
|
+
expect(await UserNotifications.fetchNotificationSettings()).toEqual({})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('returns transformed settings grouped by brand', async () => {
|
|
117
|
+
fetchHandler.mockResolvedValueOnce({
|
|
118
|
+
drumeo: {
|
|
119
|
+
new_lessons_and_features: { channel: 'email', value: true, brand: 'drumeo' },
|
|
120
|
+
membership_perks_promotions: { channel: 'push', value: false, brand: 'drumeo' },
|
|
121
|
+
},
|
|
122
|
+
pianote: {
|
|
123
|
+
membership_perks_promotions: { channel: 'email', value: true, brand: 'pianote' },
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const result = await UserNotifications.fetchNotificationSettings()
|
|
128
|
+
|
|
129
|
+
expect(result).toEqual({
|
|
130
|
+
drumeo: [
|
|
131
|
+
{ name: 'new_lessons_and_features', channel: 'email', value: true, brand: 'drumeo' },
|
|
132
|
+
{ name: 'membership_perks_promotions', channel: 'push', value: false, brand: 'drumeo' },
|
|
133
|
+
],
|
|
134
|
+
pianote: [{ name: 'membership_perks_promotions', channel: 'email', value: true, brand: 'pianote' }],
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
describe('updateNotificationSetting', () => {
|
|
140
|
+
it('throws if settingName not provided', async () => {
|
|
141
|
+
await expect(UserNotifications.updateNotificationSetting({ brand: 'drumeo' })).rejects.toThrow(
|
|
142
|
+
'The "settingName" parameter is required.'
|
|
143
|
+
)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('throws if no channels provided', async () => {
|
|
147
|
+
await expect(
|
|
148
|
+
UserNotifications.updateNotificationSetting({ brand: 'drumeo', settingName: 'new_lesson' })
|
|
149
|
+
).rejects.toThrow('At least one channel (email, push, or bell) must be provided.')
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('calls fetchHandler with correct payload and url', async () => {
|
|
153
|
+
fetchHandler.mockResolvedValueOnce({ success: true })
|
|
154
|
+
|
|
155
|
+
const result = await UserNotifications.updateNotificationSetting({
|
|
156
|
+
brand: 'drumeo',
|
|
157
|
+
settingName: 'membership_perks_promotions',
|
|
158
|
+
email: true,
|
|
159
|
+
push: false,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
expect(fetchHandler).toHaveBeenCalledWith(
|
|
163
|
+
'/api/notification-settings/v1',
|
|
164
|
+
'PUT',
|
|
165
|
+
null,
|
|
166
|
+
{
|
|
167
|
+
settings: [
|
|
168
|
+
{ name: 'membership_perks_promotions', channel: 'email', value: true, brand: 'drumeo' },
|
|
169
|
+
{ name: 'membership_perks_promotions', channel: 'push', value: false, brand: 'drumeo' },
|
|
170
|
+
],
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
expect(result).toEqual({ success: true })
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
})
|