musora-content-services 2.0.7 → 2.1.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.
Files changed (52) hide show
  1. package/.github/workflows/node.js.yml +0 -0
  2. package/.prettierignore +0 -0
  3. package/.prettierrc +0 -0
  4. package/CHANGELOG.md +9 -0
  5. package/README.md +0 -0
  6. package/babel.config.cjs +0 -0
  7. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  8. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  9. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  10. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  11. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  12. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  13. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  14. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  15. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  16. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  17. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  18. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  19. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  20. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  21. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  22. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  23. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  24. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  25. package/docs/scripts/collapse.js +0 -0
  26. package/docs/scripts/commonNav.js +0 -0
  27. package/docs/scripts/linenumber.js +0 -0
  28. package/docs/scripts/nav.js +0 -0
  29. package/docs/scripts/polyfill.js +0 -0
  30. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  31. package/docs/scripts/prettify/lang-css.js +0 -0
  32. package/docs/scripts/prettify/prettify.js +0 -0
  33. package/docs/scripts/search.js +0 -0
  34. package/docs/styles/jsdoc.css +0 -0
  35. package/docs/styles/prettify.css +0 -0
  36. package/jest.config.js +0 -0
  37. package/package.json +1 -1
  38. package/src/contentTypeConfig.js +6 -0
  39. package/src/index.d.ts +12 -7
  40. package/src/index.js +11 -7
  41. package/src/services/contentLikes.js +0 -0
  42. package/src/services/contentProgress.js +3 -3
  43. package/src/services/railcontent.js +11 -23
  44. package/src/services/recommendations.js +2 -2
  45. package/src/services/sanity.js +76 -3
  46. package/test/contentProgress.test.js +3 -3
  47. package/test/initializeTests.js +2 -2
  48. package/test/live/contentProgressLive.test.js +0 -0
  49. package/test/live/railcontentLive.test.js +0 -0
  50. package/test/localStorageMock.js +0 -0
  51. package/test/log.js +0 -0
  52. package/test/sanityQueryService.test.js +142 -102
File without changes
package/.prettierignore CHANGED
File without changes
package/.prettierrc CHANGED
File without changes
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.1.0](https://github.com/railroadmedia/musora-content-services/compare/v2.0.8...v2.1.0) (2025-03-26)
6
+
7
+
8
+ ### Features
9
+
10
+ * **BEH-326:** update routing for top/all comment endpoints ([#203](https://github.com/railroadmedia/musora-content-services/issues/203)) ([1f4807f](https://github.com/railroadmedia/musora-content-services/commit/1f4807fcef3cb7ce0b03202519b4ccb4fbd6232f))
11
+
12
+ ### [2.0.8](https://github.com/railroadmedia/musora-content-services/compare/v2.0.7...v2.0.8) (2025-03-25)
13
+
5
14
  ### [2.0.7](https://github.com/railroadmedia/musora-content-services/compare/v2.0.6...v2.0.7) (2025-03-21)
6
15
 
7
16
 
package/README.md CHANGED
File without changes
package/babel.config.cjs CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/jest.config.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.0.7",
3
+ "version": "2.1.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -3,6 +3,12 @@ import {Tabs} from "./contentMetaData.js";
3
3
 
4
4
  export const AWSUrl = 'https://s3.us-east-1.amazonaws.com/musora-web-platform'
5
5
  export const CloudFrontURl = 'https://d3fzm1tzeyr5n3.cloudfront.net'
6
+
7
+ export const SONG_TYPES = ['song', 'play-along', 'jam-track', 'song-tutorial-children']
8
+ // Challenges are excluded for the moment as they're still in design flex
9
+ // Single hierarchy refers to only one element in the hierarchy has video lessons, not that they have a single parent
10
+ export const SINGLE_PARENT_TYPES = ['course-part', 'pack-bundle-lesson', 'song-tutorial-children']
11
+
6
12
  export const DEFAULT_FIELDS = [
7
13
  "'sanity_id' : _id",
8
14
  "'id': railcontent_id",
package/src/index.d.ts CHANGED
@@ -96,7 +96,6 @@ import {
96
96
  pinPlaylist,
97
97
  playback,
98
98
  postChallengesCommunityNotification,
99
- postChallengesCompleteLesson,
100
99
  postChallengesEnroll,
101
100
  postChallengesEnrollmentNotification,
102
101
  postChallengesHideCompletedBanner,
@@ -104,7 +103,7 @@ import {
104
103
  postChallengesSetStartDate,
105
104
  postChallengesSoloNotification,
106
105
  postChallengesUnlock,
107
- postContentCompleted,
106
+ postContentComplete,
108
107
  postContentLiked,
109
108
  postContentReset,
110
109
  postContentUnliked,
@@ -120,10 +119,10 @@ import {
120
119
  } from './services/railcontent.js';
121
120
 
122
121
  import {
122
+ fetchSimilarItems,
123
123
  rankCategories,
124
124
  rankItems,
125
- recommendations,
126
- similarItems
125
+ recommendations
127
126
  } from './services/recommendations.js';
128
127
 
129
128
  import {
@@ -144,6 +143,7 @@ import {
144
143
  fetchHierarchy,
145
144
  fetchLeaving,
146
145
  fetchLessonContent,
146
+ fetchLessonsFeaturingThisContent,
147
147
  fetchLiveEvent,
148
148
  fetchMetadata,
149
149
  fetchMethod,
@@ -152,12 +152,14 @@ import {
152
152
  fetchMethodPreviousNextLesson,
153
153
  fetchNewReleases,
154
154
  fetchNextPreviousLesson,
155
+ fetchOtherSongVersions,
155
156
  fetchPackAll,
156
157
  fetchPackData,
157
158
  fetchParentForDownload,
158
159
  fetchPlayAlongsCount,
159
160
  fetchRecent,
160
161
  fetchRelatedLessons,
162
+ fetchRelatedRecommendedContent,
161
163
  fetchRelatedSongs,
162
164
  fetchReturning,
163
165
  fetchSanity,
@@ -237,6 +239,7 @@ declare module 'musora-content-services' {
237
239
  fetchHierarchy,
238
240
  fetchLeaving,
239
241
  fetchLessonContent,
242
+ fetchLessonsFeaturingThisContent,
240
243
  fetchLiveEvent,
241
244
  fetchMetadata,
242
245
  fetchMethod,
@@ -246,6 +249,7 @@ declare module 'musora-content-services' {
246
249
  fetchNewReleases,
247
250
  fetchNextContentDataForParent,
248
251
  fetchNextPreviousLesson,
252
+ fetchOtherSongVersions,
249
253
  fetchOwnedChallenges,
250
254
  fetchPackAll,
251
255
  fetchPackData,
@@ -257,12 +261,14 @@ declare module 'musora-content-services' {
257
261
  fetchPlaylistItems,
258
262
  fetchRecent,
259
263
  fetchRelatedLessons,
264
+ fetchRelatedRecommendedContent,
260
265
  fetchRelatedSongs,
261
266
  fetchReturning,
262
267
  fetchSanity,
263
268
  fetchScheduledAndNewReleases,
264
269
  fetchScheduledReleases,
265
270
  fetchShowsData,
271
+ fetchSimilarItems,
266
272
  fetchSongArtistCount,
267
273
  fetchSongById,
268
274
  fetchSongsInProgress,
@@ -308,7 +314,7 @@ declare module 'musora-content-services' {
308
314
  pinPlaylist,
309
315
  playback,
310
316
  postChallengesCommunityNotification,
311
- postChallengesCompleteLesson,
317
+
312
318
  postChallengesEnroll,
313
319
  postChallengesEnrollmentNotification,
314
320
  postChallengesHideCompletedBanner,
@@ -316,7 +322,7 @@ declare module 'musora-content-services' {
316
322
  postChallengesSetStartDate,
317
323
  postChallengesSoloNotification,
318
324
  postChallengesUnlock,
319
- postContentCompleted,
325
+ postContentComplete,
320
326
  postContentLiked,
321
327
  postContentReset,
322
328
  postContentUnliked,
@@ -329,7 +335,6 @@ declare module 'musora-content-services' {
329
335
  reportPlaylist,
330
336
  reset,
331
337
  setStudentViewForUser,
332
- similarItems,
333
338
  unassignModeratorToComment,
334
339
  unlikeComment,
335
340
  unlikeContent,
package/src/index.js CHANGED
@@ -96,7 +96,6 @@ import {
96
96
  pinPlaylist,
97
97
  playback,
98
98
  postChallengesCommunityNotification,
99
- postChallengesCompleteLesson,
100
99
  postChallengesEnroll,
101
100
  postChallengesEnrollmentNotification,
102
101
  postChallengesHideCompletedBanner,
@@ -104,7 +103,7 @@ import {
104
103
  postChallengesSetStartDate,
105
104
  postChallengesSoloNotification,
106
105
  postChallengesUnlock,
107
- postContentCompleted,
106
+ postContentComplete,
108
107
  postContentLiked,
109
108
  postContentReset,
110
109
  postContentUnliked,
@@ -120,10 +119,10 @@ import {
120
119
  } from './services/railcontent.js';
121
120
 
122
121
  import {
122
+ fetchSimilarItems,
123
123
  rankCategories,
124
124
  rankItems,
125
- recommendations,
126
- similarItems
125
+ recommendations
127
126
  } from './services/recommendations.js';
128
127
 
129
128
  import {
@@ -144,6 +143,7 @@ import {
144
143
  fetchHierarchy,
145
144
  fetchLeaving,
146
145
  fetchLessonContent,
146
+ fetchLessonsFeaturingThisContent,
147
147
  fetchLiveEvent,
148
148
  fetchMetadata,
149
149
  fetchMethod,
@@ -152,12 +152,14 @@ import {
152
152
  fetchMethodPreviousNextLesson,
153
153
  fetchNewReleases,
154
154
  fetchNextPreviousLesson,
155
+ fetchOtherSongVersions,
155
156
  fetchPackAll,
156
157
  fetchPackData,
157
158
  fetchParentForDownload,
158
159
  fetchPlayAlongsCount,
159
160
  fetchRecent,
160
161
  fetchRelatedLessons,
162
+ fetchRelatedRecommendedContent,
161
163
  fetchRelatedSongs,
162
164
  fetchReturning,
163
165
  fetchSanity,
@@ -236,6 +238,7 @@ export {
236
238
  fetchHierarchy,
237
239
  fetchLeaving,
238
240
  fetchLessonContent,
241
+ fetchLessonsFeaturingThisContent,
239
242
  fetchLiveEvent,
240
243
  fetchMetadata,
241
244
  fetchMethod,
@@ -245,6 +248,7 @@ export {
245
248
  fetchNewReleases,
246
249
  fetchNextContentDataForParent,
247
250
  fetchNextPreviousLesson,
251
+ fetchOtherSongVersions,
248
252
  fetchOwnedChallenges,
249
253
  fetchPackAll,
250
254
  fetchPackData,
@@ -256,12 +260,14 @@ export {
256
260
  fetchPlaylistItems,
257
261
  fetchRecent,
258
262
  fetchRelatedLessons,
263
+ fetchRelatedRecommendedContent,
259
264
  fetchRelatedSongs,
260
265
  fetchReturning,
261
266
  fetchSanity,
262
267
  fetchScheduledAndNewReleases,
263
268
  fetchScheduledReleases,
264
269
  fetchShowsData,
270
+ fetchSimilarItems,
265
271
  fetchSongArtistCount,
266
272
  fetchSongById,
267
273
  fetchSongsInProgress,
@@ -307,7 +313,6 @@ export {
307
313
  pinPlaylist,
308
314
  playback,
309
315
  postChallengesCommunityNotification,
310
- postChallengesCompleteLesson,
311
316
  postChallengesEnroll,
312
317
  postChallengesEnrollmentNotification,
313
318
  postChallengesHideCompletedBanner,
@@ -315,7 +320,7 @@ export {
315
320
  postChallengesSetStartDate,
316
321
  postChallengesSoloNotification,
317
322
  postChallengesUnlock,
318
- postContentCompleted,
323
+ postContentComplete,
319
324
  postContentLiked,
320
325
  postContentReset,
321
326
  postContentUnliked,
@@ -328,7 +333,6 @@ export {
328
333
  reportPlaylist,
329
334
  reset,
330
335
  setStudentViewForUser,
331
- similarItems,
332
336
  unassignModeratorToComment,
333
337
  unlikeComment,
334
338
  unlikeContent,
File without changes
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  fetchContentProgress,
3
- postContentCompleted,
3
+ postContentComplete,
4
4
  postContentReset,
5
5
  postRecordWatchSession,
6
6
  } from './railcontent.js'
@@ -124,7 +124,7 @@ export async function assignmentStatusCompleted(assignmentId, parentContentId) {
124
124
  completeStatusInLocalContext(localContext, assignmentId, hierarchy)
125
125
  },
126
126
  async function () {
127
- return postContentCompleted(assignmentId)
127
+ return postContentComplete(assignmentId)
128
128
  }
129
129
  )
130
130
  }
@@ -136,7 +136,7 @@ export async function contentStatusCompleted(contentId) {
136
136
  completeStatusInLocalContext(localContext, contentId, hierarchy)
137
137
  },
138
138
  async function () {
139
- return postContentCompleted(contentId)
139
+ return postContentComplete(contentId)
140
140
  }
141
141
  )
142
142
  }
@@ -16,7 +16,7 @@ const excludeFromGeneratedIndex = [
16
16
  'postContentUnliked',
17
17
  'postRecordWatchSession',
18
18
  'postContentStarted',
19
- 'postContentCompleted',
19
+ 'postContentComplete',
20
20
  'postContentReset',
21
21
  'fetchUserPermissionsData',
22
22
  ]
@@ -343,18 +343,18 @@ export async function fetchHandler(url, method = 'get', dataVersion = null, body
343
343
  }
344
344
 
345
345
  export async function fetchUserLikes(currentVersion) {
346
- let url = `/content/user/likes/all`
346
+ let url = `/api/content/v1/user/likes`
347
347
  return fetchDataHandler(url, currentVersion)
348
348
  }
349
349
 
350
350
  export async function postContentLiked(contentId) {
351
- let url = `/content/user/likes/like/${contentId}`
351
+ let url = `/api/content/v1/user/likes/${contentId}`
352
352
  return await postDataHandler(url)
353
353
  }
354
354
 
355
355
  export async function postContentUnliked(contentId) {
356
- let url = `/content/user/likes/unlike/${contentId}`
357
- return await postDataHandler(url)
356
+ let url = `/api/content/v1/user/likes/${contentId}`
357
+ return await deleteDataHandler(url)
358
358
  }
359
359
 
360
360
  export async function fetchContentProgress(currentVersion) {
@@ -596,18 +596,6 @@ export async function postChallengesSoloNotification(contentId) {
596
596
  return await fetchHandler(url, 'post')
597
597
  }
598
598
 
599
- /**
600
- * Complete the challenge lesson and update challenge progress
601
- *
602
- * @param {int|string} contentId - railcontent id of the challenge
603
- * @returns {Promise<any|null>} - Modal data to display
604
- */
605
- export async function postChallengesCompleteLesson(contentId) {
606
- let url = `/challenges/complete_lesson/${contentId}`
607
- await contentStatusCompleted(contentId)
608
- return await fetchHandler(url, 'post')
609
- }
610
-
611
599
  /**
612
600
  * Hide challenge completed award bannare
613
601
  *
@@ -926,9 +914,9 @@ export async function fetchPlaylistItem(payload) {
926
914
  return await fetchHandler(url)
927
915
  }
928
916
 
929
- export async function postContentCompleted(contentId) {
930
- let url = `/content/user/progress/complete`
931
- return postDataHandler(url, { contentId: contentId })
917
+ export async function postContentComplete(contentId) {
918
+ let url = `/api/content/v1/user/progress/complete/${contentId}`
919
+ return postDataHandler(url)
932
920
  }
933
921
 
934
922
  export async function postContentReset(contentId) {
@@ -1084,7 +1072,7 @@ export async function playback(playlistId) {
1084
1072
  * Set a user's StudentView Flag
1085
1073
  *
1086
1074
  * @param {int|string} userId - id of the user (must be currently authenticated)
1087
- * @param {bool} enable - truthsy value to enable student view
1075
+ * @param {bool} enable - truthy value to enable student view
1088
1076
  * @returns {Promise<any|null>}
1089
1077
  */
1090
1078
  export async function setStudentViewForUser(userId, enable) {
@@ -1100,7 +1088,7 @@ export async function setStudentViewForUser(userId, enable) {
1100
1088
  * @returns {Promise<Object|null>} - A promise that resolves to an comment object
1101
1089
  */
1102
1090
  export async function fetchTopComment(railcontentId) {
1103
- const url = `/api/content/v1/comments/content/${railcontentId}/top`
1091
+ const url = `/api/content/v1/${railcontentId}/comments?filter=top`
1104
1092
  return await fetchHandler(url)
1105
1093
  }
1106
1094
 
@@ -1112,7 +1100,7 @@ export async function fetchTopComment(railcontentId) {
1112
1100
  * @returns {Promise<*|null>}
1113
1101
  */
1114
1102
  export async function fetchComments(railcontentId, page = 1, limit = 20) {
1115
- const url = `/api/content/v1/comments/content/${railcontentId}/all?page=${page}&limit=${limit}`
1103
+ const url = `/api/content/v1/${railcontentId}/comments?page=${page}&limit=${limit}`
1116
1104
  return await fetchHandler(url)
1117
1105
  }
1118
1106
 
@@ -14,8 +14,8 @@ const excludeFromGeneratedIndex = []
14
14
  /**
15
15
  * Fetches similar content to the provided content id
16
16
  *
17
- * @param {brand} brand - brand of the content to filter
18
17
  * @param {integer} content_id - The ID of the content to find similar items for
18
+ * @param {brand} brand - brand of the content to filter
19
19
  * @param {integer} count - number of items to return
20
20
  * @returns {Promise<Object|null>} - Returns the content_ids sorted by rank (most significant first)
21
21
  * @example
@@ -23,7 +23,7 @@ const excludeFromGeneratedIndex = []
23
23
  * .then(status => console.log(status))
24
24
  * .catch(error => console.error(error));
25
25
  */
26
- export async function similarItems(brand, content_id, count = 10) {
26
+ export async function fetchSimilarItems(content_id, brand, count = 10) {
27
27
  if (!content_id) {
28
28
  return []
29
29
  }
@@ -15,8 +15,9 @@ import {
15
15
  getNewReleasesTypes,
16
16
  coachLessonsTypes,
17
17
  getChildFieldsForContentType,
18
+ SONG_TYPES
18
19
  } from '../contentTypeConfig.js'
19
-
20
+ import { fetchSimilarItems } from './recommendations.js'
20
21
  import { processMetadata, typeWithSortOrder } from '../contentMetaData.js'
21
22
 
22
23
  import { globalConfig } from './config.js'
@@ -36,7 +37,7 @@ import { getAllCompleted, getAllStarted, getAllStartedOrCompleted } from './cont
36
37
  *
37
38
  * @type {string[]}
38
39
  */
39
- const excludeFromGeneratedIndex = ['handleCustomFetchAll']
40
+ const excludeFromGeneratedIndex = ['handleCustomFetchAll', 'fetchRelatedByLicense']
40
41
 
41
42
  /**
42
43
  * Fetch a song by its document ID from Sanity.
@@ -1313,6 +1314,78 @@ export async function fetchLessonContent(railContentId) {
1313
1314
  return fetchSanity(query, false, { customPostProcess: chapterProcess })
1314
1315
  }
1315
1316
 
1317
+ /**
1318
+ *
1319
+ * @param railContentId
1320
+ * @param brand
1321
+ * @param count
1322
+ * @returns {Promise<Array<Object>|null>}
1323
+ */
1324
+ export async function fetchRelatedRecommendedContent(railContentId, brand, count=10) {
1325
+ const recommendedItems = await fetchSimilarItems(railContentId, brand, count)
1326
+ return fetchByRailContentIds(recommendedItems)
1327
+ }
1328
+
1329
+ /**
1330
+ * Get song type (transcriptions, jam packs, play alongs, tutorial children) content documents that share content information with the provided railcontent document.
1331
+ * These are linked through content that shares a license with the provided railcontent document
1332
+ *
1333
+ * @param railcontentId
1334
+ * @param brand
1335
+ * @param count
1336
+ * @returns {Promise<*>}
1337
+ */
1338
+ export async function fetchOtherSongVersions(railcontentId, brand, count=3){
1339
+ return fetchRelatedByLicense(railcontentId, brand, true, count)
1340
+ }
1341
+
1342
+ /**
1343
+ * Get non-song content documents that share content information with the provided railcontent document.
1344
+ * These are linked through content that shares a license with the provided railcontent document
1345
+ *
1346
+ * @param {integer} railcontentId
1347
+ * @param {string} brand
1348
+ * @param {integer:3} count
1349
+ * @returns {Promise<*>}
1350
+ */
1351
+ export async function fetchLessonsFeaturingThisContent(railcontentId, brand, count=3){
1352
+ return fetchRelatedByLicense(railcontentId, brand, false, count)
1353
+ }
1354
+
1355
+ /**
1356
+ * Get content documents that share license information with the provided railcontent id
1357
+ *
1358
+ * @param {integer} railcontentId
1359
+ * @param {string} brand
1360
+ * @param {boolean} onlyUseSongTypes - if true, only return the song type documents. If false, return everything except those
1361
+ * @param {integer:3} count
1362
+ * @returns {Promise<*[]>}
1363
+ */
1364
+ async function fetchRelatedByLicense(railcontentId, brand, onlyUseSongTypes, count) {
1365
+ const typeCheck = `@->_type in [${arrayJoinWithQuotes(SONG_TYPES)}]`
1366
+ const typeCheckString = onlyUseSongTypes ? `${typeCheck}` : `!(${typeCheck})`
1367
+ const contentFromLicenseFilter =`_type == 'license' && references(^._id)].content[${typeCheckString} && @->railcontent_id != ${railcontentId}`
1368
+ let filterSongTypesWithSameLicense = await new FilterBuilder(
1369
+ contentFromLicenseFilter,
1370
+ {isChildrenFilter: true},
1371
+ ).buildFilter()
1372
+ let queryFields = getFieldsForContentType()
1373
+ const baseParentQuery = `railcontent_id == ${railcontentId}`
1374
+ let parentQuery = await new FilterBuilder(baseParentQuery).buildFilter()
1375
+
1376
+ // queryFields = 'railcontent_id, title'
1377
+ // parentQuery = baseParentQuery
1378
+ // filterSongTypesWithSameLicense = contentFromLicenseFilter
1379
+ const query = `*[${parentQuery}]{
1380
+ _type, railcontent_id,
1381
+ "related_by_license" :
1382
+ *[${filterSongTypesWithSameLicense}]->{${queryFields}}|order(published_on desc, title asc)[0...${count}],
1383
+ }[0...1]`
1384
+
1385
+ const results = await fetchSanity(query, false)
1386
+ return results['related_by_license'] ?? [];
1387
+ }
1388
+
1316
1389
  /**
1317
1390
  * Fetch related lessons for a specific lesson by RailContent ID and type.
1318
1391
  * @param {string} railContentId - The RailContent ID of the current lesson.
@@ -2272,9 +2345,9 @@ export async function fetchScheduledAndNewReleases(
2272
2345
  published_on,
2273
2346
  "type": _type,
2274
2347
  show_in_new_feed,
2275
- web_url_path,
2276
2348
  "permission_id": permission[]->railcontent_id
2277
2349
  }`
2278
2350
 
2279
2351
  return fetchSanity(query, true)
2280
2352
  }
2353
+
@@ -14,7 +14,7 @@ import {
14
14
  getAllStartedOrCompleted,
15
15
  } from '../src/services/contentProgress'
16
16
  import { initializeTestService } from './initializeTests'
17
- import {getLessonContentRows, postContentCompleted} from '../src'
17
+ import {getLessonContentRows, postContentComplete} from '../src'
18
18
  import {fetchRecent} from "../src/services/sanity";
19
19
  import {getRecent, getTabResults} from "../src/services/content";
20
20
  import {individualLessonsTypes, playAlongLessonTypes, transcriptionsLessonTypes, tutorialsLessonTypes} from "../src/contentTypeConfig";
@@ -40,7 +40,7 @@ describe('contentProgressDataContext', function () {
40
40
  let mock2 = jest.spyOn(railContentModule, 'postRecordWatchSession')
41
41
  mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`))
42
42
 
43
- let mock3 = jest.spyOn(railContentModule, 'postContentCompleted')
43
+ let mock3 = jest.spyOn(railContentModule, 'postContentComplete')
44
44
  mock3.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`))
45
45
 
46
46
  let mock4 = jest.spyOn(railContentModule, 'postContentReset')
@@ -120,7 +120,7 @@ describe('contentProgressDataContext', function () {
120
120
  // });
121
121
 
122
122
  // test('getAllCompletedWithUpdate', async () => {
123
- // let mock2 = jest.spyOn(railContentModule, 'postContentCompleted');
123
+ // let mock2 = jest.spyOn(railContentModule, 'postContentComplete');
124
124
  // let serverVersion = 2;
125
125
  // mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
126
126
  //
@@ -5,7 +5,7 @@ const railContentModule = require('../src/services/railcontent.js')
5
5
  let token = null
6
6
  let userId = process.env.RAILCONTENT_USER_ID ?? null
7
7
 
8
- export async function initializeTestService(useLive = false) {
8
+ export async function initializeTestService(useLive = false, isAdmin = false) {
9
9
  if (useLive && !token && process.env.RAILCONTENT_BASE_URL) {
10
10
  let data = await fetchLoginToken(
11
11
  process.env.RAILCONTENT_EMAIL,
@@ -39,7 +39,7 @@ export async function initializeTestService(useLive = false) {
39
39
  initializeService(config)
40
40
 
41
41
  let mock = jest.spyOn(railContentModule, 'fetchUserPermissionsData')
42
- let testData = { permissions: [78, 91, 92], isAdmin: false }
42
+ let testData = { permissions: [78, 91, 92], isAdmin: isAdmin }
43
43
  mock.mockImplementation(() => testData)
44
44
  }
45
45
 
File without changes
File without changes
File without changes
package/test/log.js CHANGED
File without changes
@@ -1,15 +1,12 @@
1
- import { getFieldsForContentType} from '../src/contentTypeConfig'
1
+ import { getFieldsForContentType, SONG_TYPES } from '../src/contentTypeConfig'
2
+
2
3
  const railContentModule = require('../src/services/railcontent.js')
3
4
 
4
- import {
5
- fetchCommentModContentData,
6
- fetchMethodPreviousNextLesson,
7
- fetchSanity,
8
- } from '../src/services/sanity'
9
5
  import { log } from './log.js'
10
6
  import { initializeTestService } from './initializeTests'
11
7
  import { dataContext } from '../src/services/contentProgress'
12
- import {fetchOwnedChallenges, getRecommendedForYou, globalConfig, recommendations} from '../src'
8
+ import { fetchOwnedChallenges, getRecommendedForYou, globalConfig, recommendations } from '../src'
9
+ import { fetchLessonsFeaturingThisContent } from '../src/services/sanity.js'
13
10
 
14
11
  const {
15
12
  fetchSongById,
@@ -42,13 +39,17 @@ const {
42
39
  fetchNextPreviousLesson,
43
40
  fetchHierarchy,
44
41
  fetchTopLevelParentId,
42
+ fetchOtherSongVersions,
43
+ fetchCommentModContentData,
44
+ fetchMethodPreviousNextLesson,
45
+ fetchSanity,
45
46
  } = require('../src/services/sanity.js')
46
47
 
47
48
  const { FilterBuilder } = require('../src/filterBuilder.js')
48
49
 
49
50
  const { processMetadata } = require('../src/contentMetaData.js')
50
51
 
51
- describe('Sanity Queries', function () {
52
+ describe('Sanity Queries', function() {
52
53
  beforeEach(() => {
53
54
  initializeTestService()
54
55
  })
@@ -62,21 +63,21 @@ describe('Sanity Queries', function () {
62
63
  test('fetchReturning', async () => {
63
64
  const brand = 'guitareo'
64
65
  const page = 1
65
- const response = await fetchReturning(brand, {pageNumber: 1})
66
+ const response = await fetchReturning(brand, { pageNumber: 1 })
66
67
  expect(response).toBeDefined()
67
- });
68
+ })
68
69
 
69
70
  test('fetchLeaving', async () => {
70
71
  const brand = 'guitareo'
71
- const response = await fetchLeaving(brand, {pageNumber: 1})
72
+ const response = await fetchLeaving(brand, { pageNumber: 1 })
72
73
  expect(response).toBeDefined()
73
- });
74
+ })
74
75
 
75
76
  test('fetchComingSoon', async () => {
76
77
  const brand = 'guitareo'
77
- const response = await fetchComingSoon(brand, {pageNumber: 2, contentPerPage: 20})
78
+ const response = await fetchComingSoon(brand, { pageNumber: 2, contentPerPage: 20 })
78
79
  expect(response).toBeDefined()
79
- });
80
+ })
80
81
 
81
82
 
82
83
  test('fetchArtists', async () => {
@@ -154,9 +155,7 @@ describe('Sanity Queries', function () {
154
155
 
155
156
  test('fetchAllSongsInProgress', async () => {
156
157
  var mock = jest.spyOn(dataContext, 'fetchData')
157
- var json = JSON.parse(
158
- `{"version":1,"config":{"key":1,"enabled":1,"checkInterval":1,"refreshInterval":2},"data":{"412941":{"s":"started","p":6,"t":20,"u":1731108082}}}`
159
- )
158
+ var json = JSON.parse(`{"version":1,"config":{"key":1,"enabled":1,"checkInterval":1,"refreshInterval":2},"data":{"412941":{"s":"started","p":6,"t":20,"u":1731108082}}}`)
160
159
  mock.mockImplementation(() => json)
161
160
  const response = await fetchAll('drumeo', 'song', { progress: 'in progress' })
162
161
  expect(response.entity[0].id).toBe(412941)
@@ -262,8 +261,7 @@ describe('Sanity Queries', function () {
262
261
  expect(response.entity[0].id).toBeDefined()
263
262
 
264
263
  response = await fetchAll('drumeo', 'challenge', {
265
- useDefaultFields: false,
266
- customFields: ['garbage'],
264
+ useDefaultFields: false, customFields: ['garbage'],
267
265
  })
268
266
  log(response)
269
267
  expect(response.entity[0].garbage).toBeDefined()
@@ -278,13 +276,11 @@ describe('Sanity Queries', function () {
278
276
  let relatedDoc = await fetchByRailContentId(response.related_lessons[0].id, 'song')
279
277
  // match on artist or any genre
280
278
  let isMatch = artist === relatedDoc.artist.name
281
- isMatch =
282
- isMatch ||
283
- document.genre.some((genre) => {
284
- return relatedDoc.genre.some((relatedGenre) => {
285
- return genre._ref === relatedGenre._ref
286
- })
279
+ isMatch = isMatch || document.genre.some((genre) => {
280
+ return relatedDoc.genre.some((relatedGenre) => {
281
+ return genre._ref === relatedGenre._ref
287
282
  })
283
+ })
288
284
  expect(isMatch).toBeTruthy()
289
285
  })
290
286
 
@@ -347,16 +343,14 @@ describe('Sanity Queries', function () {
347
343
  test('fetchAll-WithProgress', async () => {
348
344
  const ids = [410213, 410215]
349
345
  let response = await fetchAll('drumeo', 'song', {
350
- sort: 'slug',
351
- progressIds: ids,
346
+ sort: 'slug', progressIds: ids,
352
347
  })
353
348
  expect(response.entity.length).toBe(2)
354
349
  expect((response.entity[0].id = 410215))
355
350
  expect((response.entity[1].id = 410213))
356
351
  // change the type and we expect no results
357
352
  response = await fetchAll('drumeo', 'quick-tip', {
358
- sort: 'slug',
359
- progressIds: ids,
353
+ sort: 'slug', progressIds: ids,
360
354
  })
361
355
  expect(response.entity.length).toBe(0)
362
356
  })
@@ -400,16 +394,7 @@ describe('Sanity Queries', function () {
400
394
  expect(response.entity.length).toBeGreaterThan(0)
401
395
  })
402
396
  test('fetchCoachLessons-WithTypeFilters', async () => {
403
- const response = await fetchAllFilterOptions(
404
- 'drumeo',
405
- ['type,course', 'type,live'],
406
- '',
407
- '',
408
- 'coach-lessons',
409
- '',
410
- [],
411
- 31880
412
- )
397
+ const response = await fetchAllFilterOptions('drumeo', ['type,course', 'type,live'], '', '', 'coach-lessons', '', [], 31880)
413
398
  log(response)
414
399
  expect(response.meta.filterOptions.difficulty).toBeDefined()
415
400
  expect(response.meta.filterOptions.type).toBeDefined()
@@ -422,18 +407,7 @@ describe('Sanity Queries', function () {
422
407
  const coachId = 31880
423
408
  const invalidContentType = 'course' // Not 'coach-lessons'
424
409
 
425
- await expect(
426
- fetchAllFilterOptions(
427
- brand,
428
- ['type,course', 'type,live'],
429
- '',
430
- '',
431
- invalidContentType,
432
- '',
433
- [],
434
- coachId
435
- )
436
- ).rejects.toThrow("Invalid contentType: 'course' for coachId. It must be 'coach-lessons'.")
410
+ await expect(fetchAllFilterOptions(brand, ['type,course', 'type,live'], '', '', invalidContentType, '', [], coachId)).rejects.toThrow(`Invalid contentType: 'course' for coachId. It must be 'coach-lessons'.`)
437
411
  })
438
412
 
439
413
  test('fetchCoachLessons-IncludedFields', async () => {
@@ -637,15 +611,13 @@ describe('Sanity Queries', function () {
637
611
  let data = await fetchCommentModContentData([241251, 241252, 211153])
638
612
  expect(data[241251].title).toBe('Setting Up Your Space')
639
613
  expect(data[241251].type).toBe('learning-path-lesson')
640
- expect(data[241251].url).toBe(
641
- '/drumeo/method/drumeo-method/241247/getting-started-on-the-drums/241248/gear/241249/setting-up-your-space/241251'
642
- )
614
+ expect(data[241251].url).toBe('/drumeo/method/drumeo-method/241247/getting-started-on-the-drums/241248/gear/241249/setting-up-your-space/241251')
643
615
  expect(data[241251].parentTitle).toBe('Gear')
644
616
  expect(data[241252].title).toBe('Setting Up Your Pedals & Throne')
645
617
  })
646
618
  })
647
619
 
648
- describe('Filter Builder', function () {
620
+ describe('Filter Builder', function() {
649
621
  beforeEach(() => {
650
622
  initializeTestService()
651
623
  })
@@ -670,26 +642,21 @@ describe('Filter Builder', function () {
670
642
 
671
643
  test('withOnlyFilterAvailableStatuses', async () => {
672
644
  const filter = 'railcontent_id = 111'
673
- const builder = FilterBuilder.withOnlyFilterAvailableStatuses(
674
- filter,
675
- ['published', 'unlisted'],
676
- true
677
- )
645
+ const builder = FilterBuilder.withOnlyFilterAvailableStatuses(filter, ['published', 'unlisted'], true)
678
646
  const finalFilter = await builder.buildFilter()
679
647
  const clauses = spliceFilterForAnds(finalFilter)
680
648
  expect(clauses[0].phrase).toBe(filter)
681
649
  expect(clauses[1].field).toBe('status')
682
650
  expect(clauses[1].operator).toBe('in')
683
651
  // not sure I like this
684
- expect(clauses[1].condition).toBe("['published','unlisted']")
652
+ expect(clauses[1].condition).toBe(`['published','unlisted']`)
685
653
  expect(clauses[2].field).toBe('published_on')
686
654
  })
687
655
 
688
656
  test('withContentStatusAndFutureScheduledContent', async () => {
689
657
  const filter = 'railcontent_id = 111'
690
658
  const builder = new FilterBuilder(filter, {
691
- availableContentStatuses: ['published', 'unlisted', 'scheduled'],
692
- getFutureScheduledContentsOnly: true,
659
+ availableContentStatuses: ['published', 'unlisted', 'scheduled'], getFutureScheduledContentsOnly: true,
693
660
  })
694
661
  const finalFilter = await builder.buildFilter()
695
662
  const clauses = spliceFilterForAnds(finalFilter)
@@ -697,8 +664,7 @@ describe('Filter Builder', function () {
697
664
  expect(clauses[1].field).toBe('(status') // extra ( because it's a multi part filter
698
665
  expect(clauses[1].operator).toBe('in')
699
666
  // getFutureScheduledContentsOnly doesn't make a filter that's splicable, so we match on the more static string
700
- const expected =
701
- "['published','unlisted'] || (status == 'scheduled' && defined(published_on) && published_on >="
667
+ const expected = `['published','unlisted'] || (status == 'scheduled' && defined(published_on) && published_on >=`
702
668
  const isMatch = finalFilter.includes(expected)
703
669
  expect(isMatch).toBeTruthy()
704
670
  })
@@ -707,7 +673,7 @@ describe('Filter Builder', function () {
707
673
  const filter = 'railcontent_id = 111'
708
674
  const builder = new FilterBuilder(filter)
709
675
  const finalFilter = await builder.buildFilter()
710
- const expected = "references(*[_type == 'permission' && railcontent_id in [78,91,92]]._id)"
676
+ const expected = `references(*[_type == 'permission' && railcontent_id in [78,91,92]]._id)`
711
677
  const isMatch = finalFilter.includes(expected)
712
678
  expect(isMatch).toBeTruthy()
713
679
  })
@@ -716,7 +682,7 @@ describe('Filter Builder', function () {
716
682
  const filter = 'railcontent_id = 111'
717
683
  const builder = new FilterBuilder(filter)
718
684
  const finalFilter = await builder.buildFilter()
719
- const expected = "references(*[_type == 'permission' && railcontent_id in [78,91,92]]._id)"
685
+ const expected = `references(*[_type == 'permission' && railcontent_id in [78,91,92]]._id)`
720
686
  const isMatch = finalFilter.includes(expected)
721
687
  expect(isMatch).toBeTruthy()
722
688
  })
@@ -724,11 +690,10 @@ describe('Filter Builder', function () {
724
690
  test('withPermissionBypass', async () => {
725
691
  const filter = 'railcontent_id = 111'
726
692
  const builder = new FilterBuilder(filter, {
727
- bypassPermissions: true,
728
- pullFutureContent: false,
693
+ bypassPermissions: true, pullFutureContent: false,
729
694
  })
730
695
  const finalFilter = await builder.buildFilter()
731
- const expected = "references(*[_type == 'permission' && railcontent_id in [78,91,92]]._id)"
696
+ const expected = `references(*[_type == 'permission' && railcontent_id in [78,91,92]]._id)`
732
697
  const isMatch = finalFilter.includes(expected)
733
698
  expect(isMatch).toBeFalsy()
734
699
  const clauses = spliceFilterForAnds(finalFilter)
@@ -742,8 +707,7 @@ describe('Filter Builder', function () {
742
707
 
743
708
  const filter = 'railcontent_id = 111'
744
709
  let builder = new FilterBuilder(filter, {
745
- pullFutureContent: true,
746
- bypassPermissions: true,
710
+ pullFutureContent: true, bypassPermissions: true,
747
711
  })
748
712
 
749
713
  let finalFilter = await builder.buildFilter()
@@ -755,8 +719,7 @@ describe('Filter Builder', function () {
755
719
  expect(clauses[3].field).toBe('published_on')
756
720
 
757
721
  builder = new FilterBuilder(filter, {
758
- getFutureContentOnly: true,
759
- bypassPermissions: true,
722
+ getFutureContentOnly: true, bypassPermissions: true,
760
723
  })
761
724
  finalFilter = await builder.buildFilter()
762
725
  clauses = spliceFilterForAnds(finalFilter)
@@ -815,28 +778,13 @@ describe('Filter Builder', function () {
815
778
  })
816
779
 
817
780
  test('fetchAllFilterOptions-filter-selected', async () => {
818
- let response = await fetchAllFilterOptions(
819
- 'drumeo',
820
- [
821
- 'theory,notation',
822
- 'theory,time signatures',
823
- 'creativity,Grooves',
824
- 'creativity,Fills & Chops',
825
- 'difficulty,Beginner',
826
- 'difficulty,Intermediate',
827
- 'difficulty,Expert',
828
- ],
829
- '',
830
- '',
831
- 'course',
832
- ''
833
- )
781
+ let response = await fetchAllFilterOptions('drumeo', ['theory,notation', 'theory,time signatures', 'creativity,Grooves', 'creativity,Fills & Chops', 'difficulty,Beginner', 'difficulty,Intermediate', 'difficulty,Expert'], '', '', 'course', '')
834
782
  log(response)
835
783
  expect(response.meta.filterOptions).toBeDefined()
836
784
  })
837
785
  })
838
786
 
839
- describe('MetaData', function () {
787
+ describe('MetaData', function() {
840
788
  test('customBrandTypeExists', async () => {
841
789
  const metaData = processMetadata('guitareo', 'recording')
842
790
  expect(metaData.type).toBe('recording')
@@ -871,7 +819,7 @@ describe('MetaData', function () {
871
819
  })
872
820
  })
873
821
 
874
- describe('v2', function () {
822
+ describe('api.v1', function() {
875
823
  beforeEach(() => {
876
824
  initializeTestService()
877
825
  })
@@ -892,19 +840,13 @@ describe('v2', function () {
892
840
  })
893
841
 
894
842
  test('fetchAllFilterOptionsLessons', async () => {
895
- const response = await fetchAllFilterOptions(
896
- 'pianote',
897
- [],null,null,'lessons'
898
- )
843
+ const response = await fetchAllFilterOptions('pianote', [], null, null, 'lessons')
899
844
  log(response)
900
845
  expect(response.meta.filters).toBeDefined()
901
846
  })
902
847
 
903
848
  test('fetchAllFilterOptionsSongs', async () => {
904
- const response = await fetchAllFilterOptions(
905
- 'pianote',
906
- [],null,null,'songs'
907
- )
849
+ const response = await fetchAllFilterOptions('pianote', [], null, null, 'songs')
908
850
  log(response)
909
851
  expect(response.meta.filters).toBeDefined()
910
852
  })
@@ -914,9 +856,105 @@ describe('v2', function () {
914
856
  log(liveEvent)
915
857
  //expect(metaData).toBeNull()
916
858
  })
859
+
860
+
861
+
862
+ test('fetchRelatedLessons-pack-bundle-lessons', async () => {
863
+ //https://www.musora.com/singeo/packs/sing-harmony-in-30-days/410537/sing-harmony-in-30-days/410538/day-2/410541
864
+ const railContentId = 410541
865
+ const relatedLessons = await fetchRelatedLessons(railContentId, 'singeo')
866
+ log(relatedLessons)
867
+ const expectedPath = ['', 'singeo', 'packs', 'sing-harmony-in-30-days', '410537', 'sing-harmony-in-30-days', '410538']
868
+ expect(relatedLessons['related_lessons'].length).toBeGreaterThanOrEqual(1)
869
+ relatedLessons['related_lessons'].forEach(document => {
870
+ expect('pack-bundle-lesson').toStrictEqual(document.type)
871
+ // there are other ways to check that these all have the same parent, but I don't want to write it
872
+ let web_url_path = document.web_url_path.split('/')
873
+ // remove id and slug
874
+ web_url_path.pop()
875
+ web_url_path.pop()
876
+ expect(web_url_path).toEqual(expectedPath)
877
+ })
878
+ })
879
+
880
+ test('fetchRelatedLessons-course-parts', async () => {
881
+ ///drumeo/courses/ultra-compact-drum-set-gear-guide/295177/gigpig-standard-and-extendable/297929
882
+ const railContentId = 297929
883
+ const relatedLessons = await fetchRelatedLessons(railContentId, 'drumeo')
884
+ log(relatedLessons)
885
+ const expectedPath = ['', 'drumeo', 'courses', 'ultra-compact-drum-set-gear-guide', '295177']
886
+ expect(relatedLessons['related_lessons'].length).toBeGreaterThanOrEqual(1)
887
+ relatedLessons['related_lessons'].forEach(document => {
888
+ expect('course-part').toStrictEqual(document.type)
889
+ // there are other ways to check that these all have the same parent, but I don't want to write it
890
+ let web_url_path = document.web_url_path.split('/')
891
+ // remove id and slug
892
+ web_url_path.pop()
893
+ web_url_path.pop()
894
+ expect(web_url_path).toEqual(expectedPath)
895
+ })
896
+ })
897
+ })
898
+
899
+ describe('api.v1.admin', function() {
900
+ beforeEach(() => {
901
+ initializeTestService(false, true)
902
+ })
903
+
904
+ test('fetchOtherSongVersions', async () => {
905
+ // much of the licensed content is currently drafted, so this must be run in the admin test-suite
906
+ const railContentId = 386901
907
+ const licenseQuery = `*[railcontent_id == ${railContentId}]{
908
+ 'content_ids': *[_type == 'license' && references(^._id)].content[]->railcontent_id
909
+ }[0]`
910
+ const otherReferencedContent = (await fetchSanity(licenseQuery, true))['content_ids']
911
+ log(otherReferencedContent)
912
+ const relatedSongsTypes = await fetchOtherSongVersions(railContentId, 'drumeo', 100)
913
+ log(relatedSongsTypes)
914
+ expect(relatedSongsTypes.length).toBeGreaterThanOrEqual(1)
915
+ relatedSongsTypes.forEach(document => {
916
+ expect(SONG_TYPES).toContain(document.type)
917
+ expect(document.id).not.toStrictEqual(railContentId)
918
+ expect(otherReferencedContent).toContain(document.id)
919
+ })
920
+ })
921
+
922
+ test('fetchLessonsFeaturingThisContent', async () => {
923
+ // much of the licensed content is currently drafted, so this must be run in the admin test-suite
924
+ const railContentId = 386901
925
+ const licenseQuery = `*[railcontent_id == ${railContentId}]{
926
+ 'content_ids': *[_type == 'license' && references(^._id)].content[]->railcontent_id
927
+ }[0]`
928
+ const otherReferencedContent = (await fetchSanity(licenseQuery, true))['content_ids']
929
+ const relatedNotSongs = await fetchLessonsFeaturingThisContent(railContentId, 'drumeo', 100)
930
+ log(relatedNotSongs)
931
+ expect(relatedNotSongs.length).toBeGreaterThanOrEqual(1)
932
+ relatedNotSongs.forEach(document => {
933
+ expect(SONG_TYPES).not.toContain(document.type)
934
+ expect(document.id).not.toStrictEqual(railContentId)
935
+ expect(otherReferencedContent).toContain(document.id)
936
+ })
937
+ })
938
+
939
+ test('fetchRelatedLessons-song-tutorial-children', async () => {
940
+ // When I wrote this it didn't need admin, but something changed. Shrug
941
+ const railContentId = 222633
942
+ const relatedLessons = await fetchRelatedLessons(railContentId, 'pianote')
943
+ log(relatedLessons)
944
+ const expectedPath = ['', 'pianote', 'song-tutorials', 'hallelujah', '221831']
945
+ expect(relatedLessons['related_lessons'].length).toBeGreaterThanOrEqual(1)
946
+ relatedLessons['related_lessons'].forEach(document => {
947
+ expect('song-tutorial-children').toStrictEqual(document.type)
948
+ let web_url_path = document.web_url_path.split('/')
949
+ // remove id, slug
950
+ web_url_path.pop()
951
+ web_url_path.pop()
952
+ expect(web_url_path).toEqual(expectedPath)
953
+ })
954
+ })
917
955
  })
918
956
 
919
- describe('Recommended System', function () {
957
+ describe('Recommended System', function() {
920
958
  beforeEach(() => {
921
959
  initializeTestService()
922
960
  })
@@ -931,7 +969,7 @@ describe('Recommended System', function () {
931
969
  })
932
970
 
933
971
  test('getRecommendedForYou-SeeAll', async () => {
934
- const results = await getRecommendedForYou('drumeo', 'recommended', {page: 1, limit:20})
972
+ const results = await getRecommendedForYou('drumeo', 'recommended', { page: 1, limit: 20 })
935
973
  log(results)
936
974
  expect(results.type).toBeDefined()
937
975
  expect(results.data).toBeDefined()
@@ -939,3 +977,5 @@ describe('Recommended System', function () {
939
977
  expect(results.data.length).toBeGreaterThanOrEqual(1)
940
978
  })
941
979
  })
980
+
981
+