1
1
import { faker } from "@faker-js/faker/locale/en"
2
2
import { factories , urls , makeRequest } from "api/test-utils"
3
- import type {
4
- LearningPathResource ,
5
- PaginatedLearningResourceTopicList ,
3
+ import {
4
+ PrivacyLevelEnum ,
5
+ type LearningPathResource ,
6
+ type PaginatedLearningResourceTopicList ,
7
+ type UserList ,
6
8
} from "api"
7
9
import { allowConsoleErrors , getDescriptionFor } from "ol-test-utilities"
8
- import { manageLearningPathDialogs } from "./ManageListDialogs"
10
+ import { manageListDialogs } from "./ManageListDialogs"
9
11
import {
10
12
screen ,
11
13
renderWithProviders ,
@@ -39,6 +41,11 @@ const inputs = {
39
41
const element = screen . getByLabelText ( value ? "Public" : "Private" )
40
42
return element as HTMLInputElement
41
43
} ,
44
+ privacy_level : ( value ?: string ) => {
45
+ invariant ( value !== undefined )
46
+ const element = screen . getByDisplayValue ( value )
47
+ return element as HTMLInputElement
48
+ } ,
42
49
title : ( ) => screen . getByLabelText ( "Title" , { exact : false } ) ,
43
50
description : ( ) => screen . getByLabelText ( "Description" , { exact : false } ) ,
44
51
topics : ( ) => screen . getByLabelText ( "Subjects" , { exact : false } ) ,
@@ -47,7 +54,7 @@ const inputs = {
47
54
delete : ( ) => screen . getByRole ( "button" , { name : "Yes, delete" } ) ,
48
55
}
49
56
50
- describe ( "manageListDialogs.upsert " , ( ) => {
57
+ describe ( "manageListDialogs.upsertLearningPath " , ( ) => {
51
58
const setup = ( {
52
59
resource,
53
60
topics = factories . learningResources . topics ( { count : 10 } ) ,
@@ -70,7 +77,7 @@ describe("manageListDialogs.upsert", () => {
70
77
renderWithProviders ( null , opts )
71
78
72
79
act ( ( ) => {
73
- manageLearningPathDialogs . upsert ( resource )
80
+ manageListDialogs . upsertLearningPath ( resource )
74
81
} )
75
82
76
83
return { topics }
@@ -211,12 +218,145 @@ describe("manageListDialogs.upsert", () => {
211
218
} )
212
219
} )
213
220
214
- describe ( "manageListDialogs.destroy" , ( ) => {
221
+ describe ( "manageListDialogs.upsertUserList" , ( ) => {
222
+ const setup = ( {
223
+ userList,
224
+ opts = {
225
+ user : { is_authenticated : true } ,
226
+ } ,
227
+ } : {
228
+ userList ?: UserList
229
+ opts ?: Partial < TestAppOptions >
230
+ } = { } ) => {
231
+ renderWithProviders ( null , opts )
232
+
233
+ act ( ( ) => {
234
+ manageListDialogs . upsertUserList ( userList )
235
+ } )
236
+ }
237
+
238
+ test . each ( [
239
+ {
240
+ userList : undefined ,
241
+ expectedTitle : "Create User List" ,
242
+ } ,
243
+ {
244
+ userList : factories . userLists . userList ( ) ,
245
+ expectedTitle : "Edit User List" ,
246
+ } ,
247
+ ] ) (
248
+ "Dialog title is $expectedTitle when userList=$userList" ,
249
+ async ( { userList, expectedTitle } ) => {
250
+ setup ( { userList } )
251
+ const dialog = screen . getByRole ( "heading" , { name : expectedTitle } )
252
+ expect ( dialog ) . toBeVisible ( )
253
+ } ,
254
+ )
255
+
256
+ test ( "'Cancel' closes dialog (and does not make request)" , async ( ) => {
257
+ // behavior does not depend on stafflist / userlist, so just pick one
258
+ setup ( {
259
+ userList : factories . userLists . userList ( ) ,
260
+ } )
261
+ const dialog = screen . getByRole ( "dialog" )
262
+ await user . click ( inputs . cancel ( ) )
263
+ expect ( makeRequest ) . not . toHaveBeenCalledWith (
264
+ "patch" ,
265
+ expect . anything ( ) ,
266
+ expect . anything ( ) ,
267
+ )
268
+ await waitForElementToBeRemoved ( dialog )
269
+ } )
270
+
271
+ test ( "Validates required fields" , async ( ) => {
272
+ setup ( )
273
+ await user . click ( inputs . submit ( ) )
274
+
275
+ const titleInput = inputs . title ( )
276
+ const titleFeedback = getDescriptionFor ( titleInput )
277
+ expect ( titleInput ) . toBeInvalid ( )
278
+ expect ( titleFeedback ) . toHaveTextContent ( "Title is required." )
279
+
280
+ const descriptionInput = inputs . description ( )
281
+ const descriptionFeedback = getDescriptionFor ( descriptionInput )
282
+ expect ( descriptionInput ) . toBeInvalid ( )
283
+ expect ( descriptionFeedback ) . toHaveTextContent ( "Description is required." )
284
+ } )
285
+
286
+ test ( "Form defaults are set" , ( ) => {
287
+ setup ( )
288
+ expect ( inputs . title ( ) ) . toHaveValue ( "" )
289
+ expect ( inputs . description ( ) ) . toHaveValue ( "" )
290
+ expect ( inputs . privacy_level ( PrivacyLevelEnum . Private ) . checked ) . toBe ( true )
291
+ expect ( inputs . privacy_level ( PrivacyLevelEnum . Unlisted ) . checked ) . toBe ( false )
292
+ } )
293
+
294
+ test ( "Editing form values" , async ( ) => {
295
+ const userList = factories . userLists . userList ( )
296
+ setup ( { userList : userList } )
297
+ const patch = {
298
+ title : faker . lorem . words ( ) ,
299
+ description : faker . lorem . paragraph ( ) ,
300
+ privacy_level : PrivacyLevelEnum . Unlisted ,
301
+ }
302
+
303
+ // Title
304
+ expect ( inputs . title ( ) ) . toHaveValue ( userList . title )
305
+ await user . click ( inputs . title ( ) )
306
+ await user . clear ( inputs . title ( ) )
307
+ await user . paste ( patch . title )
308
+
309
+ // Description
310
+ expect ( inputs . description ( ) ) . toHaveValue ( userList . description )
311
+ await user . click ( inputs . description ( ) )
312
+ await user . clear ( inputs . description ( ) )
313
+ await user . paste ( patch . description )
314
+
315
+ // Privacy Level
316
+ expect ( inputs . privacy_level ( PrivacyLevelEnum . Private ) . checked ) . toBe ( true )
317
+ expect ( inputs . privacy_level ( PrivacyLevelEnum . Unlisted ) . checked ) . toBe ( false )
318
+ await user . click ( inputs . privacy_level ( patch . privacy_level ) )
319
+
320
+ // Submit
321
+ const patchUrl = urls . userLists . details ( { id : userList . id } )
322
+ setMockResponse . patch ( patchUrl , { ...userList , ...patch } )
323
+ await user . click ( inputs . submit ( ) )
324
+
325
+ expect ( makeRequest ) . toHaveBeenCalledWith (
326
+ "patch" ,
327
+ patchUrl ,
328
+ expect . objectContaining ( { ...patch } ) ,
329
+ )
330
+ } )
331
+
332
+ test ( "Displays overall error if form validates but API call fails" , async ( ) => {
333
+ allowConsoleErrors ( )
334
+ const userList = factories . userLists . userList ( )
335
+ await setup ( { userList : userList } )
336
+
337
+ const patchUrl = urls . userLists . details ( { id : userList . id } )
338
+ setMockResponse . patch ( patchUrl , { } , { code : 408 } )
339
+ await user . click ( inputs . submit ( ) )
340
+
341
+ expect ( makeRequest ) . toHaveBeenCalledWith (
342
+ "patch" ,
343
+ patchUrl ,
344
+ expect . anything ( ) ,
345
+ )
346
+ const alertMessage = await screen . findByRole ( "alert" )
347
+
348
+ expect ( alertMessage ) . toHaveTextContent (
349
+ "There was a problem saving your list." ,
350
+ )
351
+ } )
352
+ } )
353
+
354
+ describe ( "manageListDialogs.destroyLearningPath" , ( ) => {
215
355
const setup = ( ) => {
216
356
const resource = factories . learningResources . learningPath ( )
217
357
renderWithProviders ( null )
218
358
act ( ( ) => {
219
- manageLearningPathDialogs . destroy ( resource )
359
+ manageListDialogs . destroyLearningPath ( resource )
220
360
} )
221
361
return { resource }
222
362
}
@@ -249,3 +389,42 @@ describe("manageListDialogs.destroy", () => {
249
389
await waitForElementToBeRemoved ( dialog )
250
390
} )
251
391
} )
392
+
393
+ describe ( "manageListDialogs.destroyUserList" , ( ) => {
394
+ const setup = ( ) => {
395
+ const userList = factories . userLists . userList ( )
396
+ renderWithProviders ( null )
397
+ act ( ( ) => {
398
+ manageListDialogs . destroyUserList ( userList )
399
+ } )
400
+ return { userList : userList }
401
+ }
402
+
403
+ test ( "Dialog title is 'Delete list'" , async ( ) => {
404
+ setup ( )
405
+ const dialog = screen . getByRole ( "heading" , { name : "Delete User List" } )
406
+ expect ( dialog ) . toBeVisible ( )
407
+ } )
408
+
409
+ test ( "Deleting a $label calls correct API" , async ( ) => {
410
+ const { userList } = setup ( )
411
+
412
+ const dialog = screen . getByRole ( "dialog" )
413
+ const url = urls . userLists . details ( { id : userList . id } )
414
+ setMockResponse . delete ( url , undefined )
415
+ await user . click ( inputs . delete ( ) )
416
+
417
+ expect ( makeRequest ) . toHaveBeenCalledWith ( "delete" , url , undefined )
418
+ await waitForElementToBeRemoved ( dialog )
419
+ } )
420
+
421
+ test ( "Clicking cancel does not delete list" , async ( ) => {
422
+ setup ( )
423
+
424
+ const dialog = screen . getByRole ( "dialog" )
425
+ await user . click ( inputs . cancel ( ) )
426
+
427
+ expect ( makeRequest ) . not . toHaveBeenCalled ( )
428
+ await waitForElementToBeRemoved ( dialog )
429
+ } )
430
+ } )
0 commit comments