11import { render , screen , waitFor } from '@testing-library/react' ;
2+ import { AppContext } from '@edx/frontend-platform/react' ;
23import userEvent from '@testing-library/user-event' ;
34import { MemoryRouter , Route , Routes } from 'react-router-dom' ;
45import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth' ;
56import { IntlProvider } from '@edx/frontend-platform/i18n' ;
67import { QueryClient , QueryClientProvider } from '@tanstack/react-query' ;
8+ import { ToastManagerProvider } from '@src/components/ToastManager/ToastManagerContext' ;
79import AuditUserPage from './index' ;
810
911jest . mock ( '@edx/frontend-platform/auth' , ( ) => ( {
1012 getAuthenticatedHttpClient : jest . fn ( ) ,
1113 configure : jest . fn ( ) ,
1214} ) ) ;
1315
16+ jest . mock ( '@edx/frontend-platform/logging' , ( ) => ( {
17+ logError : jest . fn ( ) ,
18+ } ) ) ;
19+
1420const mockUser = {
1521 username : 'johndoe' ,
1622@@ -40,17 +46,32 @@ const renderWithRouter = (route = '/audit/johndoe') => {
4046 } ,
4147 } ) ;
4248
49+ const mockAppContext = {
50+ authenticatedUser : {
51+ username : 'testuser' ,
52+ 53+ } ,
54+ config : {
55+ // @ts -ignore
56+ ...process . env ,
57+ } ,
58+ } ;
59+
4360 return render (
44- < QueryClientProvider client = { queryClient } >
45- < IntlProvider locale = "en" >
46- < MemoryRouter initialEntries = { [ route ] } >
47- < Routes >
48- < Route path = "/audit/:username" element = { < AuditUserPage /> } />
49- < Route path = "/authz" element = { < div > Home Page</ div > } />
50- </ Routes >
51- </ MemoryRouter >
52- </ IntlProvider >
53- </ QueryClientProvider > ,
61+ < AppContext . Provider value = { mockAppContext } >
62+ < QueryClientProvider client = { queryClient } >
63+ < IntlProvider locale = "en" >
64+ < ToastManagerProvider >
65+ < MemoryRouter initialEntries = { [ route ] } >
66+ < Routes >
67+ < Route path = "/audit/:username" element = { < AuditUserPage /> } />
68+ < Route path = "/authz" element = { < div > Home Page</ div > } />
69+ </ Routes >
70+ </ MemoryRouter >
71+ </ ToastManagerProvider >
72+ </ IntlProvider >
73+ </ QueryClientProvider >
74+ </ AppContext . Provider > ,
5475 ) ;
5576} ;
5677
@@ -59,6 +80,11 @@ describe('AuditUserPage', () => {
5980 jest . clearAllMocks ( ) ;
6081 } ) ;
6182
83+ beforeAll ( ( ) => {
84+ // @ts -ignore
85+ global . logError = jest . fn ( ) ;
86+ } ) ;
87+
6288 it ( 'renders user info and table when data is loaded' , async ( ) => {
6389 ( getAuthenticatedHttpClient as jest . Mock ) . mockReturnValue ( {
6490 get : jest
@@ -185,4 +211,177 @@ describe('AuditUserPage', () => {
185211 expect ( screen . getByText ( mockUser . username , { selector : 'li[aria-current="page"]' } ) ) . toBeInTheDocument ( ) ;
186212 } ) ;
187213 } ) ;
214+
215+ it ( 'opens and closes the ConfirmDeletionModal when delete is clicked and cancel is pressed' , async ( ) => {
216+ ( getAuthenticatedHttpClient as jest . Mock ) . mockReturnValue ( {
217+ get : jest
218+ . fn ( )
219+ . mockResolvedValueOnce ( { data : mockUser } )
220+ . mockResolvedValueOnce ( { data : mockAssignments } ) ,
221+ } ) ;
222+
223+ renderWithRouter ( ) ;
224+
225+ await waitFor ( ( ) => {
226+ expect ( screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ) . toBeInTheDocument ( ) ;
227+ } ) ;
228+
229+ const user = userEvent . setup ( ) ;
230+ const deleteButton = screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ;
231+ await user . click ( deleteButton ) ;
232+
233+ await waitFor ( ( ) => {
234+ expect ( screen . getByRole ( 'dialog' ) ) . toBeInTheDocument ( ) ;
235+ expect ( screen . getByText ( / r e m o v e r o l e \? / i) ) . toBeInTheDocument ( ) ;
236+ expect ( screen . getByRole ( 'button' , { name : / c a n c e l / i } ) ) . toBeInTheDocument ( ) ;
237+ } ) ;
238+
239+ const cancelButton = screen . getByRole ( 'button' , { name : / c a n c e l / i } ) ;
240+ await user . click ( cancelButton ) ;
241+
242+ await waitFor ( ( ) => {
243+ expect ( screen . queryByRole ( 'dialog' ) ) . not . toBeInTheDocument ( ) ;
244+ } ) ;
245+ } ) ;
246+
247+ it ( 'calls onSave when confirming deletion in ConfirmDeletionModal' , async ( ) => {
248+ ( getAuthenticatedHttpClient as jest . Mock ) . mockReturnValue ( {
249+ get : jest
250+ . fn ( )
251+ . mockResolvedValueOnce ( { data : mockUser } )
252+ . mockResolvedValueOnce ( { data : mockAssignments } ) ,
253+ delete : jest . fn ( ) . mockResolvedValue ( { data : { errors : [ ] } } ) ,
254+ } ) ;
255+
256+ renderWithRouter ( ) ;
257+
258+ await waitFor ( ( ) => {
259+ expect ( screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ) . toBeInTheDocument ( ) ;
260+ } ) ;
261+
262+ const user = userEvent . setup ( ) ;
263+ const deleteButton = screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ;
264+ await user . click ( deleteButton ) ;
265+
266+ await waitFor ( ( ) => {
267+ expect ( screen . getByRole ( 'dialog' ) ) . toBeInTheDocument ( ) ;
268+ expect ( screen . getByRole ( 'button' , { name : / r e m o v e / i } ) ) . toBeInTheDocument ( ) ;
269+ } ) ;
270+
271+ const removeButton = screen . getByRole ( 'button' , { name : / r e m o v e / i } ) ;
272+ await user . click ( removeButton ) ;
273+
274+ await waitFor ( ( ) => {
275+ expect ( screen . queryByRole ( 'dialog' ) ) . not . toBeInTheDocument ( ) ;
276+ expect ( screen . getByText ( / r o l e h a s b e e n s u c c e s s f u l l y r e m o v e d / i) ) . toBeInTheDocument ( ) ;
277+ } ) ;
278+ } ) ;
279+
280+ it ( 'shows error toast when role revocation succeeds but returns errors' , async ( ) => {
281+ ( getAuthenticatedHttpClient as jest . Mock ) . mockReturnValue ( {
282+ get : jest
283+ . fn ( )
284+ . mockResolvedValueOnce ( { data : mockUser } )
285+ . mockResolvedValueOnce ( { data : mockAssignments } ) ,
286+ delete : jest . fn ( ) . mockResolvedValue ( {
287+ data : {
288+ errors : [ 'Failed to revoke user role' ] ,
289+ completed : [ ] ,
290+ } ,
291+ } ) ,
292+ } ) ;
293+
294+ renderWithRouter ( ) ;
295+
296+ await waitFor ( ( ) => {
297+ expect ( screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ) . toBeInTheDocument ( ) ;
298+ } ) ;
299+
300+ const user = userEvent . setup ( ) ;
301+ const deleteButton = screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ;
302+ await user . click ( deleteButton ) ;
303+
304+ await waitFor ( ( ) => {
305+ expect ( screen . getByRole ( 'dialog' ) ) . toBeInTheDocument ( ) ;
306+ expect ( screen . getByRole ( 'button' , { name : / r e m o v e / i } ) ) . toBeInTheDocument ( ) ;
307+ } ) ;
308+
309+ const removeButton = screen . getByRole ( 'button' , { name : / r e m o v e / i } ) ;
310+ await user . click ( removeButton ) ;
311+
312+ await waitFor ( ( ) => {
313+ expect ( screen . getByText ( / s o m e t h i n g w e n t w r o n g / i) ) . toBeInTheDocument ( ) ;
314+ } ) ;
315+ } ) ;
316+
317+ it ( 'shows error toast with retry when role revocation fails' , async ( ) => {
318+ ( getAuthenticatedHttpClient as jest . Mock ) . mockReturnValue ( {
319+ get : jest
320+ . fn ( )
321+ . mockResolvedValueOnce ( { data : mockUser } )
322+ . mockResolvedValueOnce ( { data : mockAssignments } ) ,
323+ delete : jest . fn ( ) . mockRejectedValue ( new Error ( 'Network error' ) ) ,
324+ } ) ;
325+
326+ renderWithRouter ( ) ;
327+
328+ await waitFor ( ( ) => {
329+ expect ( screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ) . toBeInTheDocument ( ) ;
330+ } ) ;
331+
332+ const user = userEvent . setup ( ) ;
333+ const deleteButton = screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ;
334+ await user . click ( deleteButton ) ;
335+
336+ await waitFor ( ( ) => {
337+ expect ( screen . getByRole ( 'dialog' ) ) . toBeInTheDocument ( ) ;
338+ expect ( screen . getByRole ( 'button' , { name : / r e m o v e / i } ) ) . toBeInTheDocument ( ) ;
339+ } ) ;
340+
341+ const removeButton = screen . getByRole ( 'button' , { name : / r e m o v e / i } ) ;
342+ await user . click ( removeButton ) ;
343+
344+ await waitFor ( ( ) => {
345+ expect ( screen . getByText ( / s o m e t h i n g w e n t w r o n g o n o u r e n d / i) ) . toBeInTheDocument ( ) ;
346+ expect ( screen . getByText ( / t r y a g a i n l a t e r / i) ) . toBeInTheDocument ( ) ;
347+ } ) ;
348+ } ) ;
349+
350+ it ( 'shows the extra warning when rolesCount is 1' , async ( ) => {
351+ ( getAuthenticatedHttpClient as jest . Mock ) . mockReturnValue ( {
352+ get : jest
353+ . fn ( )
354+ . mockResolvedValueOnce ( { data : mockUser } )
355+ . mockResolvedValueOnce ( {
356+ data : {
357+ count : 1 ,
358+ results : [
359+ {
360+ id : '1' ,
361+ role : 'library_admin' ,
362+ org : 'Test Org' ,
363+ scope : 'lib:test' ,
364+ permissionCount : 5 ,
365+ } ,
366+ ] ,
367+ next : null ,
368+ previous : null ,
369+ } ,
370+ } ) ,
371+ } ) ;
372+
373+ renderWithRouter ( ) ;
374+
375+ await waitFor ( ( ) => {
376+ expect ( screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ) . toBeInTheDocument ( ) ;
377+ } ) ;
378+
379+ const user = userEvent . setup ( ) ;
380+ const deleteButton = screen . getByRole ( 'button' , { name : / d e l e t e r o l e a c t i o n / i } ) ;
381+ await user . click ( deleteButton ) ;
382+
383+ await waitFor ( ( ) => {
384+ expect ( screen . getByText ( / t h i s i s t h e u s e r ' s o n l y r o l e / i) ) . toBeInTheDocument ( ) ;
385+ } ) ;
386+ } ) ;
188387} ) ;
0 commit comments