@@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event';
33import Allowances from '@src/specialExams/components/Allowances' ;
44import { renderWithAlertAndIntl } from '@src/testUtils' ;
55import messages from '@src/specialExams/messages' ;
6- import { useAddAllowance , useAllowances , useSpecialExams } from '@src/specialExams/data/apiHook' ;
6+ import { useAddAllowance , useAllowances , useAttempts , useSpecialExams } from '@src/specialExams/data/apiHook' ;
77import { useLearner } from '@src/data/apiHook' ;
88
99const mockLearnerData = {
@@ -15,13 +15,16 @@ const mockLearnerData = {
1515} ;
1616
1717const mockAllowance = {
18+ id : 1 ,
1819 user : {
1920 username : 'john_doe' ,
2021 email : 'john.doe@hotmail.com' ,
22+ id : 5 ,
2123 } ,
2224 proctoredExam : {
2325 examName : 'Midterm Exam' ,
2426 examType : 'proctored' ,
27+ id : 101 ,
2528 } ,
2629 value : '30 minutes' ,
2730 key : 'additional_time_granted' ,
@@ -47,14 +50,17 @@ jest.mock('@src/specialExams/data/apiHook', () => ({
4750 useAddAllowance : jest . fn ( ) ,
4851 useSpecialExams : jest . fn ( ) ,
4952 useDeleteAllowance : jest . fn ( ) . mockReturnValue ( { mutate : jest . fn ( ) } ) ,
53+ useAttempts : jest . fn ( ) ,
5054} ) ) ;
5155
5256describe ( 'Allowances' , ( ) => {
5357 let mockAddAllowance : jest . Mock ;
58+ let mockRefetchAttempts : jest . Mock ;
5459
5560 beforeEach ( ( ) => {
5661 jest . clearAllMocks ( ) ;
5762 mockAddAllowance = jest . fn ( ) ;
63+ mockRefetchAttempts = jest . fn ( ) ;
5864 ( useLearner as jest . Mock ) . mockReturnValue ( {
5965 data : mockLearnerData ,
6066 refetch : jest . fn ( ) . mockResolvedValue ( { data : mockLearnerData } ) ,
@@ -69,6 +75,10 @@ describe('Allowances', () => {
6975 data : mockSpecialExams ,
7076 refetch : jest . fn ( ) . mockResolvedValue ( { data : mockSpecialExams } ) ,
7177 } ) ;
78+ ( useAttempts as jest . Mock ) . mockReturnValue ( {
79+ data : { results : [ ] , count : 0 , numPages : 0 } ,
80+ refetch : mockRefetchAttempts . mockResolvedValue ( { data : { results : [ ] , count : 0 , numPages : 0 } } ) ,
81+ } ) ;
7282 } ) ;
7383
7484 it ( 'renders AllowancesList and AddAllowanceModal' , ( ) => {
@@ -273,4 +283,141 @@ describe('Allowances', () => {
273283 // DeleteAllowanceModal should not render without selectedAllowance
274284 expect ( screen . queryByRole ( 'dialog' , { name : messages . deleteAllowance . defaultMessage } ) ) . not . toBeInTheDocument ( ) ;
275285 } ) ;
286+
287+ describe ( 'checkAttemptAndProceed' , ( ) => {
288+ it ( 'shows warning modal when user has already attempted the exam on edit' , async ( ) => {
289+ const mockAttemptData = {
290+ results : [
291+ {
292+ id : 1 ,
293+ examId : 101 ,
294+ user : { username : 'john_doe' } ,
295+ examName : 'Midterm Exam' ,
296+ allowedTimeLimitMins : 60 ,
297+ type : 'proctored' ,
298+ startTime : '2023-01-01T10:00:00Z' ,
299+ endTime : '2023-01-01T11:00:00Z' ,
300+ status : 'completed' ,
301+ } ,
302+ ] ,
303+ count : 1 ,
304+ numPages : 1 ,
305+ } ;
306+ ( useAllowances as jest . Mock ) . mockReturnValue ( {
307+ data : { results : [ mockAllowance ] , count : 1 , numPages : 1 } ,
308+ isLoading : false ,
309+ } ) ;
310+ ( useAttempts as jest . Mock ) . mockReturnValue ( {
311+ data : mockAttemptData ,
312+ refetch : jest . fn ( ) . mockResolvedValue ( { data : mockAttemptData } ) ,
313+ } ) ;
314+ const user = userEvent . setup ( ) ;
315+ renderWithAlertAndIntl ( < Allowances /> ) ;
316+ await user . click ( screen . getByRole ( 'button' , { name : messages . actions . defaultMessage } ) ) ;
317+ const editBtn = await screen . findByRole ( 'button' , { name : / e d i t / i } ) ;
318+ await user . click ( editBtn ) ;
319+ await waitFor ( ( ) => {
320+ expect ( screen . getByText ( / C a n n o t e d i t a l l o w a n c e : j o h n _ d o e h a s a l r e a d y a t t e m p t e d t h e e x a m / i) ) . toBeInTheDocument ( ) ;
321+ } ) ;
322+ expect ( screen . queryByRole ( 'dialog' , { name : messages . editAllowance . defaultMessage } ) ) . not . toBeInTheDocument ( ) ;
323+ } ) ;
324+
325+ it ( 'shows warning modal when user has already attempted the exam on delete' , async ( ) => {
326+ const mockAttemptData = {
327+ results : [
328+ {
329+ id : 1 ,
330+ examId : 101 ,
331+ user : { username : 'john_doe' } ,
332+ examName : 'Midterm Exam' ,
333+ allowedTimeLimitMins : 60 ,
334+ type : 'proctored' ,
335+ startTime : '2023-01-01T10:00:00Z' ,
336+ endTime : '2023-01-01T11:00:00Z' ,
337+ status : 'completed' ,
338+ } ,
339+ ] ,
340+ count : 1 ,
341+ numPages : 1 ,
342+ } ;
343+ ( useAllowances as jest . Mock ) . mockReturnValue ( {
344+ data : { results : [ mockAllowance ] , count : 1 , numPages : 1 } ,
345+ isLoading : false ,
346+ } ) ;
347+ ( useAttempts as jest . Mock ) . mockReturnValue ( {
348+ data : mockAttemptData ,
349+ refetch : jest . fn ( ) . mockResolvedValue ( { data : mockAttemptData } ) ,
350+ } ) ;
351+ const user = userEvent . setup ( ) ;
352+ renderWithAlertAndIntl ( < Allowances /> ) ;
353+ await user . click ( screen . getByRole ( 'button' , { name : messages . actions . defaultMessage } ) ) ;
354+ const deleteBtn = await screen . findByRole ( 'button' , { name : / d e l e t e / i } ) ;
355+ await user . click ( deleteBtn ) ;
356+ await waitFor ( ( ) => {
357+ expect ( screen . getByText ( / C a n n o t d e l e t e a l l o w a n c e : j o h n _ d o e h a s a l r e a d y a t t e m p t e d t h e e x a m / i) ) . toBeInTheDocument ( ) ;
358+ } ) ;
359+ expect ( screen . queryByRole ( 'dialog' , { name : messages . deleteAllowance . defaultMessage } ) ) . not . toBeInTheDocument ( ) ;
360+ } ) ;
361+
362+ it ( 'opens edit modal when user has not attempted the exam' , async ( ) => {
363+ const mockAttemptData = {
364+ results : [
365+ {
366+ id : 1 ,
367+ examId : 999 , // different exam, not the one in the allowance
368+ user : { username : 'john_doe' } ,
369+ examName : 'Other Exam' ,
370+ allowedTimeLimitMins : 60 ,
371+ type : 'proctored' ,
372+ startTime : '2023-01-01T10:00:00Z' ,
373+ endTime : '2023-01-01T11:00:00Z' ,
374+ status : 'completed' ,
375+ } ,
376+ ] ,
377+ count : 1 ,
378+ numPages : 1 ,
379+ } ;
380+ ( useAllowances as jest . Mock ) . mockReturnValue ( {
381+ data : { results : [ mockAllowance ] , count : 1 , numPages : 1 } ,
382+ isLoading : false ,
383+ } ) ;
384+ ( useAttempts as jest . Mock ) . mockReturnValue ( {
385+ data : mockAttemptData ,
386+ refetch : jest . fn ( ) . mockResolvedValue ( { data : mockAttemptData } ) ,
387+ } ) ;
388+ const user = userEvent . setup ( ) ;
389+ renderWithAlertAndIntl ( < Allowances /> ) ;
390+ await user . click ( screen . getByRole ( 'button' , { name : messages . actions . defaultMessage } ) ) ;
391+ const editBtn = await screen . findByRole ( 'button' , { name : / e d i t / i } ) ;
392+ await user . click ( editBtn ) ;
393+ await waitFor ( ( ) => {
394+ expect ( screen . getByRole ( 'dialog' , { name : messages . editAllowance . defaultMessage } ) ) . toBeInTheDocument ( ) ;
395+ } ) ;
396+ } ) ;
397+
398+ it ( 'opens delete modal when user has not attempted the exam' , async ( ) => {
399+ const mockAttemptData = {
400+ results : [ ] , // no attempts
401+ count : 0 ,
402+ numPages : 0 ,
403+ } ;
404+
405+ ( useAllowances as jest . Mock ) . mockReturnValue ( {
406+ data : { results : [ mockAllowance ] , count : 1 , numPages : 1 } ,
407+ isLoading : false ,
408+ } ) ;
409+ ( useAttempts as jest . Mock ) . mockReturnValue ( {
410+ data : mockAttemptData ,
411+ refetch : jest . fn ( ) . mockResolvedValue ( { data : mockAttemptData } ) ,
412+ } ) ;
413+ const user = userEvent . setup ( ) ;
414+ renderWithAlertAndIntl ( < Allowances /> ) ;
415+ await user . click ( screen . getByRole ( 'button' , { name : messages . actions . defaultMessage } ) ) ;
416+ const deleteBtn = await screen . findByRole ( 'button' , { name : / d e l e t e / i } ) ;
417+ await user . click ( deleteBtn ) ;
418+ await waitFor ( ( ) => {
419+ expect ( screen . getByRole ( 'dialog' , { name : messages . deleteAllowance . defaultMessage } ) ) . toBeInTheDocument ( ) ;
420+ } ) ;
421+ } ) ;
422+ } ) ;
276423} ) ;
0 commit comments