musora-content-services 2.167.2 → 2.167.3
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/.agent/decisions/2026-07-20-mu2-1724-songs-lessons-counts.md +33 -0
- package/.agent/decisions/2026-07-20-query-builder-tostring.md +37 -0
- package/CHANGELOG.md +8 -0
- package/package.json +2 -1
- package/src/contentTypeConfig.js +1 -0
- package/src/lib/sanity/query.ts +5 -0
- package/src/services/config.js +15 -0
- package/src/services/content/artist.ts +1 -3
- package/src/services/content/counts.ts +47 -0
- package/src/services/content/genre.ts +1 -3
- package/src/services/content/instructor.ts +1 -3
- package/src/services/userActivity.js +2 -0
- package/src/version-info.js +1 -0
- package/.claude/settings.local.json +0 -53
- package/test/initializeTests.js +0 -60
- package/test/integration/awards/award-exclusion-handling.test.ts +0 -275
- package/test/integration/awards/internal/image-utils.test.ts +0 -86
- package/test/integration/content-progress/__mocks__/mocks.ts +0 -104
- package/test/integration/content-progress/contentProgress.test.ts +0 -335
- package/test/integration/content-progress/e2eOfflineProgress.test.ts +0 -352
- package/test/integration/content-progress/e2eProgress.test.ts +0 -612
- package/test/integration/content-progress/getters.test.ts +0 -334
- package/test/integration/content-progress/helpers.test.ts +0 -263
- package/test/integration/content-progress/offlineContentProgress.test.ts +0 -226
- package/test/integration/forums.test.ts +0 -209
- package/test/integration/infrastructure/FetchRequestExecutor.test.ts +0 -88
- package/test/integration/initializeTestDB.ts +0 -80
- package/test/integration/learning-paths.test.ts +0 -854
- package/test/integration/notifications.test.ts +0 -178
- package/test/integration/progress-row/content-card.test.ts +0 -283
- package/test/integration/sync/adapters/idb-errors.test.ts +0 -144
- package/test/integration/sync/adapters/sqlite-errors.test.ts +0 -173
- package/test/integration/sync/fetch.test.ts +0 -224
- package/test/integration/sync/repositories/content-likes.test.ts +0 -99
- package/test/integration/sync/repositories/practices.test.ts +0 -179
- package/test/integration/sync/repositories/progress.test.ts +0 -245
- package/test/integration/sync/repositories/user-award-progress.static.test.ts +0 -68
- package/test/integration/sync/repositories/user-award-progress.test.ts +0 -387
- package/test/integration/sync/store/cross-user-protection.test.ts +0 -217
- package/test/integration/sync/store/store-idb.test.ts +0 -180
- package/test/integration/sync/store/store.test.ts +0 -361
- package/test/integration/user/permissions.test.js +0 -20
- package/test/integration/userActivity.test.ts +0 -102
- package/test/live/README.md +0 -29
- package/test/live/content.test.js +0 -116
- package/test/localStorageMock.js +0 -21
- package/test/log.js +0 -5
- package/test/mockData/award-definitions.js +0 -171
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -35
- package/test/mockData/mockData_progress_content.json +0 -182
- package/test/mockData/mockData_sanity_progress_content.json +0 -1451
- package/test/mockData/mockData_user_practices.json +0 -9
- package/test/setupConsole.js +0 -6
- package/test/setupNetworkGuard.js +0 -3
- package/test/setupTimers.js +0 -13
- package/test/unit/HttpClient.test.ts +0 -267
- package/test/unit/awards/award-alacarte-observer.test.ts +0 -199
- package/test/unit/awards/award-auto-refresh.test.ts +0 -84
- package/test/unit/awards/award-calculations.test.ts +0 -34
- package/test/unit/awards/award-callbacks.test.ts +0 -144
- package/test/unit/awards/award-certificate-display.test.ts +0 -330
- package/test/unit/awards/award-collection-edge-cases.test.ts +0 -212
- package/test/unit/awards/award-collection-filtering.test.ts +0 -287
- package/test/unit/awards/award-completion-flow.test.ts +0 -306
- package/test/unit/awards/award-multi-lesson.test.ts +0 -243
- package/test/unit/awards/award-observer-integration.test.ts +0 -327
- package/test/unit/awards/award-query-messages.test.ts +0 -448
- package/test/unit/awards/award-user-collection.test.ts +0 -416
- package/test/unit/awards/duplicate-prevention.test.ts +0 -120
- package/test/unit/awards/helpers/completion-mock.ts +0 -56
- package/test/unit/awards/helpers/index.ts +0 -3
- package/test/unit/awards/helpers/mock-setup.ts +0 -79
- package/test/unit/awards/helpers/progress-emitter.ts +0 -45
- package/test/unit/awards/message-generator.test.ts +0 -162
- package/test/unit/content-progress/bubbleTrickle.test.ts +0 -322
- package/test/unit/content-progress/helpers.test.ts +0 -329
- package/test/unit/content-progress/navigateTo.test.ts +0 -392
- package/test/unit/contentLikes.test.ts +0 -62
- package/test/unit/contentMetaData.test.ts +0 -58
- package/test/unit/dataContext.test.ts +0 -38
- package/test/unit/dateUtils.test.ts +0 -188
- package/test/unit/endScreen.test.js +0 -712
- package/test/unit/eventsAPI.test.ts +0 -159
- package/test/unit/imageSRCBuilder.test.ts +0 -37
- package/test/unit/imageSRCVerify.test.ts +0 -155
- package/test/unit/infrastructure/DefaultHeaderProvider.test.ts +0 -39
- package/test/unit/infrastructure/sanity/ContentClient.test.ts +0 -168
- package/test/unit/infrastructure/sanity/DefaultConfigProvider.test.ts +0 -93
- package/test/unit/infrastructure/sanity/FetchQueryExecutor.test.ts +0 -174
- package/test/unit/infrastructure/sanity/SanityClient.test.ts +0 -140
- package/test/unit/lib/__snapshots__/filter.test.ts.snap +0 -5
- package/test/unit/lib/filter.test.ts +0 -1154
- package/test/unit/lib/lastUpdated.test.ts +0 -21
- package/test/unit/lib/query.test.ts +0 -1469
- package/test/unit/lib/sanity/decorators/base.test.ts +0 -368
- package/test/unit/lib/sanity/decorators/navigate-to.test.ts +0 -361
- package/test/unit/lib/sanity/decorators/need-access.test.ts +0 -89
- package/test/unit/lib/sanity/decorators/need-lifetime-upgrade.test.ts +0 -85
- package/test/unit/lib/sanity/decorators/page-type.test.ts +0 -81
- package/test/unit/playerSettings.test.ts +0 -73
- package/test/unit/progress-row/content-card.test.ts +0 -314
- package/test/unit/progress-row/playlist-card.test.ts +0 -104
- package/test/unit/progressRows.test.ts +0 -243
- package/test/unit/recommendations.test.js +0 -47
- package/test/unit/sanityQueryService.test.ts +0 -200
- package/test/unit/search.test.ts +0 -44
- package/test/unit/sentry.test.ts +0 -62
- package/test/unit/services/progress-internal/bubble.test.ts +0 -279
- package/test/unit/services/progress.test.ts +0 -396
- package/test/unit/streakMessage.test.ts +0 -251
- package/test/unit/sync/context.test.ts +0 -51
- package/test/unit/sync/effects/logout-warning.test.ts +0 -158
- package/test/unit/sync/effects/push-failure-notification.test.ts +0 -196
- package/test/unit/sync/errors/sync-errors.test.ts +0 -106
- package/test/unit/sync/errors/validators.test.ts +0 -61
- package/test/unit/sync/helpers/TestModel.ts +0 -44
- package/test/unit/sync/helpers/index.ts +0 -172
- package/test/unit/sync/manager.test.ts +0 -303
- package/test/unit/sync/models/user-award-progress.test.ts +0 -82
- package/test/unit/sync/resolver.test.ts +0 -229
- package/test/unit/sync/retry.test.ts +0 -314
- package/test/unit/sync/run-scope.test.ts +0 -23
- package/test/unit/sync/stale-record-cleanup.test.ts +0 -107
- package/test/unit/sync/store/push-coalescer.test.ts +0 -156
- package/test/unit/sync/store-configs.test.ts +0 -37
- package/test/unit/sync/telemetry/sync-telemetry.test.ts +0 -118
- package/test/unit/sync/utils/event-emitter.test.ts +0 -64
- package/test/unit/sync/utils/throttle.test.ts +0 -245
- package/test/unit/url-builder.test.ts +0 -72
- package/test/unit/user/account.test.ts +0 -66
- package/test/unit/user/permissions.test.js +0 -191
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
let mockProgressRecords: any[] = []
|
|
2
|
-
let mockLastInteracted: number | null = null
|
|
3
|
-
|
|
4
|
-
jest.mock('../../../../../src/services/sync/repository-proxy', () => {
|
|
5
|
-
const mockFns = {
|
|
6
|
-
contentProgress: {
|
|
7
|
-
getOneProgressByContentId: jest.fn().mockImplementation((contentId) => {
|
|
8
|
-
const record = mockProgressRecords.find((r) => r.content_id === contentId)
|
|
9
|
-
return Promise.resolve({ data: record || null })
|
|
10
|
-
}),
|
|
11
|
-
getSomeProgressByContentIds: jest.fn().mockImplementation((contentIds) => {
|
|
12
|
-
const records = mockProgressRecords.filter((r) => contentIds.includes(r.content_id))
|
|
13
|
-
return Promise.resolve({ data: records })
|
|
14
|
-
}),
|
|
15
|
-
mostRecentlyUpdatedId: jest.fn().mockImplementation(() => {
|
|
16
|
-
return Promise.resolve({ data: mockLastInteracted })
|
|
17
|
-
}),
|
|
18
|
-
},
|
|
19
|
-
}
|
|
20
|
-
return { default: mockFns, ...mockFns }
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
import { initializeTestService } from '../../../../initializeTests.js'
|
|
24
|
-
import {
|
|
25
|
-
NAVIGATE_TO_FIELD,
|
|
26
|
-
decorateNavigateTo,
|
|
27
|
-
navigateToDecorator,
|
|
28
|
-
type NavigateToDecoratable,
|
|
29
|
-
} from '../../../../../src/lib/sanity/decorators/navigate-to'
|
|
30
|
-
import { COLLECTION_TYPE } from '../../../../../src/services/sync/models/ContentProgress'
|
|
31
|
-
|
|
32
|
-
const child = (id: number, type = 'course-lesson'): NavigateToDecoratable => ({
|
|
33
|
-
id,
|
|
34
|
-
type,
|
|
35
|
-
brand: 'drumeo',
|
|
36
|
-
thumbnail: '',
|
|
37
|
-
published_on: null,
|
|
38
|
-
status: 'published',
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const parent = (
|
|
42
|
-
id: number,
|
|
43
|
-
type: string,
|
|
44
|
-
children: NavigateToDecoratable[]
|
|
45
|
-
): NavigateToDecoratable => ({
|
|
46
|
-
id,
|
|
47
|
-
type,
|
|
48
|
-
brand: 'drumeo',
|
|
49
|
-
thumbnail: '',
|
|
50
|
-
published_on: null,
|
|
51
|
-
status: 'published',
|
|
52
|
-
children,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
beforeEach(() => {
|
|
56
|
-
jest.clearAllMocks()
|
|
57
|
-
initializeTestService()
|
|
58
|
-
mockProgressRecords = []
|
|
59
|
-
mockLastInteracted = null
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
describe('navigate-to decorator', () => {
|
|
63
|
-
describe('navigateToDecorator (factory)', () => {
|
|
64
|
-
test('field is navigateTo', () => {
|
|
65
|
-
expect(navigateToDecorator().field).toBe('navigateTo')
|
|
66
|
-
expect(NAVIGATE_TO_FIELD).toBe('navigateTo')
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
test('recurse is false', () => {
|
|
70
|
-
expect(navigateToDecorator().recurse).toBe(false)
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test('without ctx — navigates single item via DB', async () => {
|
|
74
|
-
const item = parent(1, 'course', [child(101), child(102)])
|
|
75
|
-
const result = await navigateToDecorator().compute(item)
|
|
76
|
-
expect(result).toMatchObject({ id: 101, child: null })
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test('with ctx — uses ctx states, not DB', async () => {
|
|
80
|
-
// DB says course is started; ctx overrides to not-started → should go to first child
|
|
81
|
-
mockProgressRecords = [
|
|
82
|
-
{ content_id: 1, state: 'started', progress_percent: 50, updated_at: 1000 },
|
|
83
|
-
]
|
|
84
|
-
const ctx = { states: new Map<number, string>([[1, '']]) }
|
|
85
|
-
const item = parent(1, 'course', [child(101), child(102)])
|
|
86
|
-
const result = await navigateToDecorator(ctx).compute(item)
|
|
87
|
-
expect(result).toMatchObject({ id: 101 })
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
describe('decorateNavigateTo', () => {
|
|
92
|
-
test('non-navigable type → null', async () => {
|
|
93
|
-
const item = parent(1, 'lesson', [child(101)])
|
|
94
|
-
const result = await decorateNavigateTo(item)
|
|
95
|
-
expect(result.navigateTo).toBeNull()
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
test('empty children → null', async () => {
|
|
99
|
-
const item = parent(1, 'course', [])
|
|
100
|
-
const result = await decorateNavigateTo(item)
|
|
101
|
-
expect(result.navigateTo).toBeNull()
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
test('not-started course → first child', async () => {
|
|
105
|
-
const item = parent(1, 'course', [child(101), child(102)])
|
|
106
|
-
const result = await decorateNavigateTo(item)
|
|
107
|
-
expect(result.navigateTo).toMatchObject({ id: 101, child: null })
|
|
108
|
-
})
|
|
109
|
-
|
|
110
|
-
test('course started, lastInteracted started → lastInteracted', async () => {
|
|
111
|
-
mockProgressRecords = [
|
|
112
|
-
{ content_id: 1, state: 'started', progress_percent: 50, updated_at: 1000 },
|
|
113
|
-
{ content_id: 101, state: 'started', progress_percent: 30, updated_at: 900 },
|
|
114
|
-
{ content_id: 102, state: 'started', progress_percent: 10, updated_at: 1000 },
|
|
115
|
-
]
|
|
116
|
-
mockLastInteracted = 101
|
|
117
|
-
const item = parent(1, 'course', [child(101), child(102)])
|
|
118
|
-
const result = await decorateNavigateTo(item)
|
|
119
|
-
expect(result.navigateTo).toMatchObject({ id: 101 })
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
test('course started, lastInteracted completed → first incomplete after', async () => {
|
|
123
|
-
mockProgressRecords = [
|
|
124
|
-
{ content_id: 1, state: 'started', progress_percent: 60, updated_at: 1000 },
|
|
125
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
126
|
-
{ content_id: 102, state: 'completed', progress_percent: 100, updated_at: 1000 },
|
|
127
|
-
{ content_id: 103, state: 'started', progress_percent: 20, updated_at: 800 },
|
|
128
|
-
]
|
|
129
|
-
mockLastInteracted = 101
|
|
130
|
-
const item = parent(1, 'course', [child(101), child(102), child(103)])
|
|
131
|
-
const result = await decorateNavigateTo(item)
|
|
132
|
-
expect(result.navigateTo).toMatchObject({ id: 103 })
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
test('guided-course started → first incomplete regardless of lastInteracted', async () => {
|
|
136
|
-
mockProgressRecords = [
|
|
137
|
-
{ content_id: 1, state: 'started', progress_percent: 50, updated_at: 1000 },
|
|
138
|
-
{ content_id: 101, state: '', progress_percent: 0, updated_at: 0 },
|
|
139
|
-
{ content_id: 102, state: 'completed', progress_percent: 100, updated_at: 1000 },
|
|
140
|
-
{ content_id: 103, state: '', progress_percent: 0, updated_at: 0 },
|
|
141
|
-
]
|
|
142
|
-
mockLastInteracted = 102
|
|
143
|
-
const item = parent(1, 'guided-course', [child(101), child(102), child(103)])
|
|
144
|
-
const result = await decorateNavigateTo(item)
|
|
145
|
-
expect(result.navigateTo).toMatchObject({ id: 101 })
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
test('learning-path-v2 started → first incomplete regardless of lastInteracted', async () => {
|
|
149
|
-
mockProgressRecords = [
|
|
150
|
-
{ content_id: 1, state: 'started', progress_percent: 50, updated_at: 1000 },
|
|
151
|
-
{ content_id: 101, state: '', progress_percent: 0, updated_at: 0 },
|
|
152
|
-
{ content_id: 102, state: 'completed', progress_percent: 100, updated_at: 1000 },
|
|
153
|
-
{ content_id: 103, state: '', progress_percent: 0, updated_at: 0 },
|
|
154
|
-
]
|
|
155
|
-
mockLastInteracted = 102
|
|
156
|
-
const item = parent(1, COLLECTION_TYPE.LEARNING_PATH, [child(101), child(102), child(103)])
|
|
157
|
-
const result = await decorateNavigateTo(item)
|
|
158
|
-
expect(result.navigateTo).toMatchObject({ id: 101 })
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
test('two-depth: not-started course-collection nests first child nav', async () => {
|
|
162
|
-
const courseChild = parent(101, 'course', [child(201), child(202)])
|
|
163
|
-
const collection = parent(1, 'course-collection', [courseChild, parent(102, 'course', [])])
|
|
164
|
-
const result = await decorateNavigateTo(collection)
|
|
165
|
-
expect(result.navigateTo).toMatchObject({
|
|
166
|
-
id: 101,
|
|
167
|
-
child: { id: 201 },
|
|
168
|
-
})
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
test('two-depth: started course-collection nests lastInteracted child nav', async () => {
|
|
172
|
-
mockProgressRecords = [
|
|
173
|
-
{ content_id: 1, state: 'started', progress_percent: 50, updated_at: 1000 },
|
|
174
|
-
{ content_id: 101, state: 'started', progress_percent: 50, updated_at: 1000 },
|
|
175
|
-
{ content_id: 102, state: '', progress_percent: 0, updated_at: 0 },
|
|
176
|
-
]
|
|
177
|
-
mockLastInteracted = 102
|
|
178
|
-
const courseA = parent(101, 'course', [child(201), child(202)])
|
|
179
|
-
const courseB = parent(102, 'course', [child(301), child(302)])
|
|
180
|
-
const collection = parent(1, 'course-collection', [courseA, courseB])
|
|
181
|
-
const result = await decorateNavigateTo(collection)
|
|
182
|
-
expect(result.navigateTo).toMatchObject({
|
|
183
|
-
id: 102,
|
|
184
|
-
child: { id: 301 },
|
|
185
|
-
})
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
test('two-depth: started course-collection, last-interacted course has completed lessons → first incomplete lesson, not lesson[0]', async () => {
|
|
189
|
-
mockProgressRecords = [
|
|
190
|
-
{ content_id: 1, state: 'started', progress_percent: 50, updated_at: 1000 },
|
|
191
|
-
{ content_id: 101, state: 'started', progress_percent: 50, updated_at: 900 },
|
|
192
|
-
{ content_id: 201, state: 'completed', progress_percent: 100, updated_at: 800 },
|
|
193
|
-
{ content_id: 202, state: '', progress_percent: 0, updated_at: 0 },
|
|
194
|
-
{ content_id: 203, state: '', progress_percent: 0, updated_at: 0 },
|
|
195
|
-
]
|
|
196
|
-
mockLastInteracted = 101
|
|
197
|
-
const course = parent(101, 'course', [child(201), child(202), child(203)])
|
|
198
|
-
const collection = parent(1, 'course-collection', [course])
|
|
199
|
-
const result = await decorateNavigateTo(collection)
|
|
200
|
-
expect(result.navigateTo).toMatchObject({ id: 101, child: { id: 202 } })
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
test('decorates every item in an array', async () => {
|
|
204
|
-
const items = [parent(1, 'course', [child(101)]), parent(2, 'lesson', [child(201)])]
|
|
205
|
-
const result = await decorateNavigateTo(items)
|
|
206
|
-
expect(result[0].navigateTo).toMatchObject({ id: 101 })
|
|
207
|
-
expect(result[1].navigateTo).toBeNull()
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
test('returns the same reference it was given', async () => {
|
|
211
|
-
const items = [parent(1, 'course', [child(101)])]
|
|
212
|
-
const result = await decorateNavigateTo(items)
|
|
213
|
-
expect(result).toBe(items)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
test('descendants of decorated items do not receive navigateTo field', async () => {
|
|
217
|
-
const item = parent(1, 'course', [child(101), child(102)])
|
|
218
|
-
const result = await decorateNavigateTo(item)
|
|
219
|
-
expect(result.navigateTo).not.toBeNull()
|
|
220
|
-
const children = result.children as NavigateToDecoratable[]
|
|
221
|
-
expect((children[0] as Record<string, unknown>).navigateTo).toBeUndefined()
|
|
222
|
-
expect((children[1] as Record<string, unknown>).navigateTo).toBeUndefined()
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
test('skill-pack uses course flow', async () => {
|
|
226
|
-
mockProgressRecords = [
|
|
227
|
-
{ content_id: 1, state: 'started', progress_percent: 60, updated_at: 1000 },
|
|
228
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
229
|
-
{ content_id: 102, state: 'started', progress_percent: 20, updated_at: 1000 },
|
|
230
|
-
]
|
|
231
|
-
mockLastInteracted = 102
|
|
232
|
-
const item = parent(1, 'skill-pack', [child(101), child(102)])
|
|
233
|
-
const result = await decorateNavigateTo(item)
|
|
234
|
-
expect(result.navigateTo).toMatchObject({ id: 102 })
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
test('song-tutorial uses course flow', async () => {
|
|
238
|
-
mockProgressRecords = [
|
|
239
|
-
{ content_id: 1, state: 'started', progress_percent: 50, updated_at: 1000 },
|
|
240
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
241
|
-
{ content_id: 102, state: '', progress_percent: 0, updated_at: 0 },
|
|
242
|
-
]
|
|
243
|
-
mockLastInteracted = 101
|
|
244
|
-
const item = parent(1, 'song-tutorial', [child(101), child(102)])
|
|
245
|
-
const result = await decorateNavigateTo(item)
|
|
246
|
-
expect(result.navigateTo).toMatchObject({ id: 102 })
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
test('guided-course STARTED, lastInteracted not in children → first incomplete', async () => {
|
|
250
|
-
mockProgressRecords = [
|
|
251
|
-
{ content_id: 1, state: 'started', progress_percent: 40, updated_at: 1000 },
|
|
252
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
253
|
-
{ content_id: 102, state: '', progress_percent: 0, updated_at: 0 },
|
|
254
|
-
{ content_id: 103, state: '', progress_percent: 0, updated_at: 0 },
|
|
255
|
-
]
|
|
256
|
-
mockLastInteracted = 999
|
|
257
|
-
const item = parent(1, 'guided-course', [child(101), child(102), child(103)])
|
|
258
|
-
const result = await decorateNavigateTo(item)
|
|
259
|
-
expect(result.navigateTo).toMatchObject({ id: 102 })
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
test('guided-course STARTED, lastInteracted not in children, all complete → first child', async () => {
|
|
263
|
-
mockProgressRecords = [
|
|
264
|
-
{ content_id: 1, state: 'started', progress_percent: 100, updated_at: 1000 },
|
|
265
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
266
|
-
{ content_id: 102, state: 'completed', progress_percent: 100, updated_at: 1000 },
|
|
267
|
-
]
|
|
268
|
-
mockLastInteracted = 999
|
|
269
|
-
const item = parent(1, 'guided-course', [child(101), child(102)])
|
|
270
|
-
const result = await decorateNavigateTo(item)
|
|
271
|
-
expect(result.navigateTo).toMatchObject({ id: 101 })
|
|
272
|
-
})
|
|
273
|
-
|
|
274
|
-
test('learning-path-v2 STARTED, lastInteracted not in children → first incomplete', async () => {
|
|
275
|
-
mockProgressRecords = [
|
|
276
|
-
{ content_id: 1, state: 'started', progress_percent: 40, updated_at: 1000 },
|
|
277
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
278
|
-
{ content_id: 102, state: '', progress_percent: 0, updated_at: 0 },
|
|
279
|
-
]
|
|
280
|
-
mockLastInteracted = 999
|
|
281
|
-
const item = parent(1, COLLECTION_TYPE.LEARNING_PATH, [child(101), child(102)])
|
|
282
|
-
const result = await decorateNavigateTo(item)
|
|
283
|
-
expect(result.navigateTo).toMatchObject({ id: 102 })
|
|
284
|
-
})
|
|
285
|
-
|
|
286
|
-
test('course STARTED, lastInteracted not in children → first incomplete', async () => {
|
|
287
|
-
mockProgressRecords = [
|
|
288
|
-
{ content_id: 1, state: 'started', progress_percent: 40, updated_at: 1000 },
|
|
289
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
290
|
-
{ content_id: 102, state: '', progress_percent: 0, updated_at: 0 },
|
|
291
|
-
{ content_id: 103, state: '', progress_percent: 0, updated_at: 0 },
|
|
292
|
-
]
|
|
293
|
-
mockLastInteracted = 999
|
|
294
|
-
const item = parent(1, 'course', [child(101), child(102), child(103)])
|
|
295
|
-
const result = await decorateNavigateTo(item)
|
|
296
|
-
expect(result.navigateTo).toMatchObject({ id: 102 })
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
test('skill-pack STARTED, lastInteracted not in children → first incomplete', async () => {
|
|
300
|
-
mockProgressRecords = [
|
|
301
|
-
{ content_id: 1, state: 'started', progress_percent: 40, updated_at: 1000 },
|
|
302
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
303
|
-
{ content_id: 102, state: '', progress_percent: 0, updated_at: 0 },
|
|
304
|
-
]
|
|
305
|
-
mockLastInteracted = 999
|
|
306
|
-
const item = parent(1, 'skill-pack', [child(101), child(102)])
|
|
307
|
-
const result = await decorateNavigateTo(item)
|
|
308
|
-
expect(result.navigateTo).toMatchObject({ id: 102 })
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
test('song-tutorial STARTED, lastInteracted not in children → first incomplete', async () => {
|
|
312
|
-
mockProgressRecords = [
|
|
313
|
-
{ content_id: 1, state: 'started', progress_percent: 40, updated_at: 1000 },
|
|
314
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
315
|
-
{ content_id: 102, state: '', progress_percent: 0, updated_at: 0 },
|
|
316
|
-
]
|
|
317
|
-
mockLastInteracted = 999
|
|
318
|
-
const item = parent(1, 'song-tutorial', [child(101), child(102)])
|
|
319
|
-
const result = await decorateNavigateTo(item)
|
|
320
|
-
expect(result.navigateTo).toMatchObject({ id: 102 })
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
test('course STARTED, lastInteracted not in children, all complete → first child', async () => {
|
|
324
|
-
mockProgressRecords = [
|
|
325
|
-
{ content_id: 1, state: 'started', progress_percent: 100, updated_at: 1000 },
|
|
326
|
-
{ content_id: 101, state: 'completed', progress_percent: 100, updated_at: 900 },
|
|
327
|
-
{ content_id: 102, state: 'completed', progress_percent: 100, updated_at: 1000 },
|
|
328
|
-
]
|
|
329
|
-
mockLastInteracted = 999
|
|
330
|
-
const item = parent(1, 'course', [child(101), child(102)])
|
|
331
|
-
const result = await decorateNavigateTo(item)
|
|
332
|
-
expect(result.navigateTo).toMatchObject({ id: 101 })
|
|
333
|
-
})
|
|
334
|
-
|
|
335
|
-
test('two-depth started but lastInteracted child not in collection → null', async () => {
|
|
336
|
-
mockProgressRecords = [
|
|
337
|
-
{ content_id: 1, state: 'started', progress_percent: 50, updated_at: 1000 },
|
|
338
|
-
]
|
|
339
|
-
mockLastInteracted = 999
|
|
340
|
-
const courseA = parent(101, 'course', [child(201)])
|
|
341
|
-
const collection = parent(1, 'course-collection', [courseA])
|
|
342
|
-
const result = await decorateNavigateTo(collection)
|
|
343
|
-
expect(result.navigateTo).toBeNull()
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
test('output shape matches NavigateTo interface', async () => {
|
|
347
|
-
const item = parent(1, 'course', [child(101)])
|
|
348
|
-
const result = await decorateNavigateTo(item)
|
|
349
|
-
expect(result.navigateTo).toEqual({
|
|
350
|
-
id: 101,
|
|
351
|
-
type: 'course-lesson',
|
|
352
|
-
brand: 'drumeo',
|
|
353
|
-
thumbnail: '',
|
|
354
|
-
published_on: null,
|
|
355
|
-
status: 'published',
|
|
356
|
-
child: null,
|
|
357
|
-
collection: null,
|
|
358
|
-
})
|
|
359
|
-
})
|
|
360
|
-
})
|
|
361
|
-
})
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
const mockDoesUserNeedAccess = jest.fn()
|
|
2
|
-
|
|
3
|
-
jest.mock('../../../../../src/services/permissions', () => ({
|
|
4
|
-
getPermissionsAdapter: () => ({
|
|
5
|
-
doesUserNeedAccess: mockDoesUserNeedAccess,
|
|
6
|
-
}),
|
|
7
|
-
}))
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
NEED_ACCESS_FIELD,
|
|
11
|
-
accessDecorator,
|
|
12
|
-
decorateAccess,
|
|
13
|
-
type AccessDecoratable,
|
|
14
|
-
} from '../../../../../src/lib/sanity/decorators/need-access'
|
|
15
|
-
|
|
16
|
-
const perms = {
|
|
17
|
-
permissions: [78, 91],
|
|
18
|
-
isAdmin: false,
|
|
19
|
-
isModerator: false,
|
|
20
|
-
isABasicMember: false,
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('need-access decorator', () => {
|
|
24
|
-
beforeEach(() => {
|
|
25
|
-
mockDoesUserNeedAccess.mockReset()
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
describe('accessDecorator (factory)', () => {
|
|
29
|
-
test('field is need_access', () => {
|
|
30
|
-
const dec = accessDecorator(perms)
|
|
31
|
-
expect(dec.field).toBe('need_access')
|
|
32
|
-
expect(NEED_ACCESS_FIELD).toBe('need_access')
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test('compute delegates to adapter with item + perms', () => {
|
|
36
|
-
mockDoesUserNeedAccess.mockReturnValue(true)
|
|
37
|
-
const dec = accessDecorator(perms)
|
|
38
|
-
const item: AccessDecoratable = { permission_id: [78] }
|
|
39
|
-
|
|
40
|
-
const result = dec.compute(item)
|
|
41
|
-
|
|
42
|
-
expect(result).toBe(true)
|
|
43
|
-
expect(mockDoesUserNeedAccess).toHaveBeenCalledWith(item, perms)
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
describe('decorateAccess', () => {
|
|
48
|
-
test('writes adapter result onto each item', () => {
|
|
49
|
-
mockDoesUserNeedAccess.mockImplementation((item) =>
|
|
50
|
-
item.permission_id?.includes(78) ? false : true
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
const items: AccessDecoratable[] = [
|
|
54
|
-
{ permission_id: [78] },
|
|
55
|
-
{ permission_id: [999] },
|
|
56
|
-
{},
|
|
57
|
-
]
|
|
58
|
-
const decorated = decorateAccess(items, perms)
|
|
59
|
-
|
|
60
|
-
expect(decorated.map((i) => i.need_access)).toEqual([false, true, true])
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
test('decorates children recursively', () => {
|
|
64
|
-
mockDoesUserNeedAccess.mockReturnValue(true)
|
|
65
|
-
const tree: AccessDecoratable = {
|
|
66
|
-
permission_id: [],
|
|
67
|
-
children: [
|
|
68
|
-
{
|
|
69
|
-
permission_id: [],
|
|
70
|
-
children: [{ permission_id: [] }],
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const decorated = decorateAccess(tree, perms)
|
|
76
|
-
|
|
77
|
-
expect(decorated.need_access).toBe(true)
|
|
78
|
-
expect(decorated.children![0].need_access).toBe(true)
|
|
79
|
-
expect(decorated.children![0].children![0].need_access).toBe(true)
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
test('returns the same reference it was given', () => {
|
|
83
|
-
mockDoesUserNeedAccess.mockReturnValue(false)
|
|
84
|
-
const items: AccessDecoratable[] = [{ permission_id: [78] }]
|
|
85
|
-
const decorated = decorateAccess(items, perms)
|
|
86
|
-
expect(decorated).toBe(items)
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
})
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
NEED_LIFETIME_UPGRADE_FIELD,
|
|
3
|
-
lifetimeUpgradeDecorator,
|
|
4
|
-
decorateLifetimeUpgrade,
|
|
5
|
-
} from '../../../../../src/lib/sanity/decorators/need-lifetime-upgrade'
|
|
6
|
-
|
|
7
|
-
const PLUS = 92
|
|
8
|
-
const LIFETIME = 108
|
|
9
|
-
const BASE = 91
|
|
10
|
-
|
|
11
|
-
const lifetimeBase = { permissions: [LIFETIME, BASE], isAdmin: false, isModerator: false, isABasicMember: true }
|
|
12
|
-
const lifetimeBasePlus = { permissions: [LIFETIME, BASE, PLUS], isAdmin: false, isModerator: false, isABasicMember: false }
|
|
13
|
-
const baseOnly = { permissions: [BASE], isAdmin: false, isModerator: false, isABasicMember: true }
|
|
14
|
-
|
|
15
|
-
const plusGatedLocked = { permission_id: [BASE, PLUS], need_access: true, membership_tier: 'plus' }
|
|
16
|
-
const plusGatedUnlocked = { permission_id: [BASE, PLUS], need_access: false, membership_tier: 'plus' }
|
|
17
|
-
|
|
18
|
-
describe('need-lifetime-upgrade decorator', () => {
|
|
19
|
-
describe('lifetimeUpgradeDecorator (factory)', () => {
|
|
20
|
-
test('field is need_lifetime_upgrade', () => {
|
|
21
|
-
const dec = lifetimeUpgradeDecorator(lifetimeBase)
|
|
22
|
-
expect(dec.field).toBe('need_lifetime_upgrade')
|
|
23
|
-
expect(NEED_LIFETIME_UPGRADE_FIELD).toBe('need_lifetime_upgrade')
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
test('true for lifetime member on Plus-gated content they cannot access', () => {
|
|
27
|
-
const dec = lifetimeUpgradeDecorator(lifetimeBase)
|
|
28
|
-
expect(dec.compute(plusGatedLocked)).toBe(true)
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
test('false when user already has Plus', () => {
|
|
32
|
-
const dec = lifetimeUpgradeDecorator(lifetimeBasePlus)
|
|
33
|
-
expect(dec.compute(plusGatedLocked)).toBe(false)
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
test('false when user does not have lifetime', () => {
|
|
37
|
-
const dec = lifetimeUpgradeDecorator(baseOnly)
|
|
38
|
-
expect(dec.compute(plusGatedLocked)).toBe(false)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
test('false when user already has access to content (need_access false)', () => {
|
|
42
|
-
const dec = lifetimeUpgradeDecorator(lifetimeBase)
|
|
43
|
-
expect(dec.compute(plusGatedUnlocked)).toBe(false)
|
|
44
|
-
})
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
describe('decorateLifetimeUpgrade', () => {
|
|
48
|
-
test('decorates array of items', () => {
|
|
49
|
-
const items = [plusGatedLocked, plusGatedUnlocked]
|
|
50
|
-
const decorated = decorateLifetimeUpgrade(items, lifetimeBase)
|
|
51
|
-
expect(decorated.map((i) => i.need_lifetime_upgrade)).toEqual([true, false])
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('decorates a single item', () => {
|
|
55
|
-
const decorated = decorateLifetimeUpgrade(plusGatedLocked, lifetimeBase)
|
|
56
|
-
expect(decorated.need_lifetime_upgrade).toBe(true)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
test('decorates children recursively', () => {
|
|
60
|
-
const tree = {
|
|
61
|
-
permission_id: [BASE, PLUS],
|
|
62
|
-
need_access: true,
|
|
63
|
-
membership_tier: 'plus',
|
|
64
|
-
children: [
|
|
65
|
-
{
|
|
66
|
-
permission_id: [BASE, PLUS],
|
|
67
|
-
need_access: true,
|
|
68
|
-
membership_tier: 'plus',
|
|
69
|
-
children: [{ permission_id: [BASE, PLUS], need_access: true, membership_tier: 'plus' }],
|
|
70
|
-
},
|
|
71
|
-
],
|
|
72
|
-
}
|
|
73
|
-
const decorated = decorateLifetimeUpgrade(tree, lifetimeBase)
|
|
74
|
-
expect(decorated.need_lifetime_upgrade).toBe(true)
|
|
75
|
-
expect(decorated.children![0].need_lifetime_upgrade).toBe(true)
|
|
76
|
-
expect(decorated.children![0].children![0].need_lifetime_upgrade).toBe(true)
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
test('returns the same reference it was given', () => {
|
|
80
|
-
const items = [plusGatedLocked]
|
|
81
|
-
const decorated = decorateLifetimeUpgrade(items, lifetimeBase)
|
|
82
|
-
expect(decorated).toBe(items)
|
|
83
|
-
})
|
|
84
|
-
})
|
|
85
|
-
})
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PAGE_TYPE_FIELD,
|
|
3
|
-
decoratePageType,
|
|
4
|
-
pageTypeDecorator,
|
|
5
|
-
type PageTypeDecoratable,
|
|
6
|
-
} from '../../../../../src/lib/sanity/decorators/page-type'
|
|
7
|
-
|
|
8
|
-
describe('page-type decorator', () => {
|
|
9
|
-
describe('pageTypeDecorator (const)', () => {
|
|
10
|
-
test('field is page_type', () => {
|
|
11
|
-
expect(pageTypeDecorator.field).toBe('page_type')
|
|
12
|
-
expect(PAGE_TYPE_FIELD).toBe('page_type')
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
test.each([
|
|
16
|
-
['song', 'song'],
|
|
17
|
-
['play-along', 'song'],
|
|
18
|
-
['jam-track', 'song'],
|
|
19
|
-
['song-tutorial', 'song'],
|
|
20
|
-
['song-tutorial-lesson', 'song'],
|
|
21
|
-
['course', 'lesson'],
|
|
22
|
-
['workout', 'lesson'],
|
|
23
|
-
['', 'lesson'],
|
|
24
|
-
])('type=%j -> %s', (type, expected) => {
|
|
25
|
-
expect(pageTypeDecorator.compute({ type })).toBe(expected)
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
test('missing type falls through to lesson', () => {
|
|
29
|
-
expect(pageTypeDecorator.compute({})).toBe('lesson')
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
describe('decoratePageType', () => {
|
|
34
|
-
test('decorates a single object', () => {
|
|
35
|
-
const item: PageTypeDecoratable = { type: 'song' }
|
|
36
|
-
const decorated = decoratePageType(item)
|
|
37
|
-
expect(decorated.page_type).toBe('song')
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
test('decorates every item in an array', () => {
|
|
41
|
-
const items: PageTypeDecoratable[] = [
|
|
42
|
-
{ type: 'song' },
|
|
43
|
-
{ type: 'course' },
|
|
44
|
-
{ type: 'play-along' },
|
|
45
|
-
]
|
|
46
|
-
const decorated = decoratePageType(items)
|
|
47
|
-
expect(decorated.map((i) => i.page_type)).toEqual(['song', 'lesson', 'song'])
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test('decorates nested children up to depth 3', () => {
|
|
51
|
-
const tree: PageTypeDecoratable = {
|
|
52
|
-
type: 'course',
|
|
53
|
-
children: [
|
|
54
|
-
{
|
|
55
|
-
type: 'song',
|
|
56
|
-
children: [
|
|
57
|
-
{
|
|
58
|
-
type: 'play-along',
|
|
59
|
-
children: [{ type: 'song' }],
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
}
|
|
65
|
-
const decorated = decoratePageType(tree)
|
|
66
|
-
expect(decorated.page_type).toBe('lesson')
|
|
67
|
-
const lvl1 = decorated.children![0]
|
|
68
|
-
const lvl2 = lvl1.children![0]
|
|
69
|
-
const lvl3 = lvl2.children![0]
|
|
70
|
-
expect(lvl1.page_type).toBe('song')
|
|
71
|
-
expect(lvl2.page_type).toBe('song')
|
|
72
|
-
expect(lvl3.page_type).toBeUndefined()
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test('preserves the input reference', () => {
|
|
76
|
-
const items: PageTypeDecoratable[] = [{ type: 'song' }]
|
|
77
|
-
const decorated = decoratePageType(items)
|
|
78
|
-
expect(decorated).toBe(items)
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
})
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { HttpClient } from '../../src/infrastructure/http/HttpClient'
|
|
2
|
-
import { fetchPlayerSettings, updatePlayerSettings } from '../../src/services/user/playerSettings'
|
|
3
|
-
import type { PlayerSettings } from '../../src/services/user/types'
|
|
4
|
-
|
|
5
|
-
jest.mock('../../src/infrastructure/http/HttpClient')
|
|
6
|
-
|
|
7
|
-
const mockPlayerSettings: PlayerSettings = {
|
|
8
|
-
auto_play: true,
|
|
9
|
-
auto_next: true,
|
|
10
|
-
auto_complete: true,
|
|
11
|
-
playlist_auto_next: true,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
describe('playerSettings', () => {
|
|
15
|
-
let mockGet: jest.Mock
|
|
16
|
-
let mockPatch: jest.Mock
|
|
17
|
-
|
|
18
|
-
beforeEach(() => {
|
|
19
|
-
mockGet = jest.fn().mockResolvedValue(mockPlayerSettings)
|
|
20
|
-
mockPatch = jest.fn().mockResolvedValue(mockPlayerSettings)
|
|
21
|
-
;(HttpClient as jest.Mock).mockImplementation(() => ({
|
|
22
|
-
get: mockGet,
|
|
23
|
-
patch: mockPatch,
|
|
24
|
-
}))
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
afterEach(() => {
|
|
28
|
-
jest.clearAllMocks()
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
describe('fetchPlayerSettings', () => {
|
|
32
|
-
test('calls GET on the correct endpoint', async () => {
|
|
33
|
-
await fetchPlayerSettings()
|
|
34
|
-
expect(mockGet).toHaveBeenCalledWith('/api/user-management-system/v1/user/player-settings')
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('returns the player settings from the API', async () => {
|
|
38
|
-
const result = await fetchPlayerSettings()
|
|
39
|
-
expect(result).toEqual(mockPlayerSettings)
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
describe('updatePlayerSettings', () => {
|
|
44
|
-
test('calls PATCH on the correct endpoint with provided data', async () => {
|
|
45
|
-
await updatePlayerSettings({ auto_next: false })
|
|
46
|
-
expect(mockPatch).toHaveBeenCalledWith(
|
|
47
|
-
'/api/user-management-system/v1/user/player-settings',
|
|
48
|
-
{ auto_next: false }
|
|
49
|
-
)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test('sends only the provided fields (partial update)', async () => {
|
|
53
|
-
await updatePlayerSettings({ playlist_auto_next: false })
|
|
54
|
-
expect(mockPatch).toHaveBeenCalledWith(
|
|
55
|
-
expect.any(String),
|
|
56
|
-
{ playlist_auto_next: false }
|
|
57
|
-
)
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('sends all fields when all are provided', async () => {
|
|
61
|
-
const allFields = { auto_play: false, auto_next: false, auto_complete: false, playlist_auto_next: false }
|
|
62
|
-
await updatePlayerSettings(allFields)
|
|
63
|
-
expect(mockPatch).toHaveBeenCalledWith(expect.any(String), allFields)
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('returns the updated player settings from the API', async () => {
|
|
67
|
-
const updated = { ...mockPlayerSettings, auto_next: false }
|
|
68
|
-
mockPatch.mockResolvedValue(updated)
|
|
69
|
-
const result = await updatePlayerSettings({ auto_next: false })
|
|
70
|
-
expect(result).toEqual(updated)
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
})
|