1+ import { useState } from 'react' ;
12import { logError } from '@edx/frontend-platform/logging' ;
2- import { renderHook } from '@testing-library/react-hooks' ;
3+ import { act , renderHook } from '@testing-library/react-hooks' ;
34import { useExamAccessToken , useFetchExamAccessToken , useIsExam } from '@edx/frontend-lib-special-exams' ;
45
56import { initializeMockApp } from '../../../../../setupTest' ;
@@ -16,10 +17,27 @@ jest.mock('@edx/frontend-lib-special-exams', () => ({
1617
1718const id = 'test-id' ;
1819
20+ // This object allows us to manipulate the value of the accessToken.
21+ const testAccessToken = { curr : '' } ;
22+
1923const mockFetchExamAccessToken = jest . fn ( ) . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
2024useFetchExamAccessToken . mockReturnValue ( mockFetchExamAccessToken ) ;
2125
22- const testAccessToken = 'test-access-token' ;
26+ const mockUseIsExam = ( initialState = false ) => {
27+ const [ isExam , setIsExam ] = useState ( initialState ) ;
28+
29+ // This setTimeout block is intended to replicate the case where a unit is an exam, but
30+ // the call to fetch exam metadata has not yet completed. That is the value of isExam starts
31+ // as false and transitions to true once the call resolves.
32+ if ( ! initialState ) {
33+ setTimeout (
34+ ( ) => setIsExam ( true ) ,
35+ 500 ,
36+ ) ;
37+ }
38+
39+ return isExam ;
40+ } ;
2341
2442describe ( 'useExamAccess hook' , ( ) => {
2543 beforeAll ( async ( ) => {
@@ -28,13 +46,13 @@ describe('useExamAccess hook', () => {
2846 } ) ;
2947 beforeEach ( ( ) => {
3048 jest . clearAllMocks ( ) ;
31-
32- // Mock implementations from previous test runs may not have been "consumed", so reset mock implementations.
49+ jest . useFakeTimers ( ) ;
3350 useExamAccessToken . mockReset ( ) ;
34- useExamAccessToken . mockReturnValueOnce ( '' ) ;
35- useExamAccessToken . mockReturnValueOnce ( testAccessToken ) ;
51+
52+ useIsExam . mockImplementation ( ( ) => mockUseIsExam ( ) ) ;
53+ useExamAccessToken . mockImplementation ( ( ) => testAccessToken . curr ) ;
3654 } ) ;
37- describe . only ( 'behavior' , ( ) => {
55+ describe ( 'behavior' , ( ) => {
3856 it ( 'returns accessToken and blockAccess and doesn\'t call token API if not an exam' , ( ) => {
3957 const { result } = renderHook ( ( ) => useExamAccess ( { id } ) ) ;
4058 const { accessToken, blockAccess } = result . current ;
@@ -43,31 +61,60 @@ describe('useExamAccess hook', () => {
4361 expect ( blockAccess ) . toBe ( false ) ;
4462 expect ( mockFetchExamAccessToken ) . not . toHaveBeenCalled ( ) ;
4563 } ) ;
46- it ( 'returns true for blockAccess if an exam but accessToken not yet fetched' , ( ) => {
47- useIsExam . mockImplementation ( ( ) => ( true ) ) ;
64+ it ( 'returns true for blockAccess if an exam but accessToken not yet fetched' , async ( ) => {
65+ useIsExam . mockImplementation ( ( ) => mockUseIsExam ( true ) ) ;
4866
49- const { result } = renderHook ( ( ) => useExamAccess ( { id } ) ) ;
67+ const { result, waitForNextUpdate } = renderHook ( ( ) => useExamAccess ( { id } ) ) ;
5068 const { accessToken, blockAccess } = result . current ;
5169
5270 expect ( accessToken ) . toEqual ( '' ) ;
5371 expect ( blockAccess ) . toBe ( true ) ;
5472 expect ( mockFetchExamAccessToken ) . toHaveBeenCalled ( ) ;
73+
74+ // This is to get rid of the act(...) warning.
75+ await act ( async ( ) => {
76+ await waitForNextUpdate ( ) ;
77+ } ) ;
5578 } ) ;
5679 it ( 'returns false for blockAccess if an exam and accessToken fetch succeeds' , async ( ) => {
57- useIsExam . mockImplementation ( ( ) => ( true ) ) ;
80+ useIsExam . mockImplementation ( ( ) => mockUseIsExam ( true ) ) ;
5881 const { result, waitForNextUpdate } = renderHook ( ( ) => useExamAccess ( { id } ) ) ;
5982
6083 // We wait for the promise to resolve and for updates to state to complete so that blockAccess is updated.
6184 await waitForNextUpdate ( ) ;
6285
6386 const { accessToken, blockAccess } = result . current ;
6487
65- expect ( accessToken ) . toEqual ( testAccessToken ) ;
88+ expect ( accessToken ) . toEqual ( testAccessToken . curr ) ;
89+ expect ( blockAccess ) . toBe ( false ) ;
90+ expect ( mockFetchExamAccessToken ) . toHaveBeenCalled ( ) ;
91+ } ) ;
92+ it ( 'in progress' , async ( ) => {
93+ const { result, waitForNextUpdate } = renderHook ( ( ) => useExamAccess ( { id } ) ) ;
94+
95+ let { accessToken, blockAccess } = result . current ;
96+
97+ expect ( accessToken ) . toEqual ( '' ) ;
98+ expect ( blockAccess ) . toBe ( false ) ;
99+ expect ( mockFetchExamAccessToken ) . not . toHaveBeenCalled ( ) ;
100+
101+ testAccessToken . curr = 'test-access-token' ;
102+
103+ // The runAllTimers will update the value of isExam, and the waitForNextUpdate will
104+ // wait for call to setBlockAccess in the finally clause of useEffect hook.
105+ await act ( async ( ) => {
106+ jest . runAllTimers ( ) ;
107+ await waitForNextUpdate ( ) ;
108+ } ) ;
109+
110+ ( { accessToken, blockAccess } = result . current ) ;
111+
112+ expect ( accessToken ) . toEqual ( 'test-access-token' ) ;
66113 expect ( blockAccess ) . toBe ( false ) ;
67114 expect ( mockFetchExamAccessToken ) . toHaveBeenCalled ( ) ;
68115 } ) ;
69116 it ( 'returns false for blockAccess if an exam and accessToken fetch fails' , async ( ) => {
70- useIsExam . mockImplementation ( ( ) => ( true ) ) ;
117+ useIsExam . mockImplementation ( ( ) => mockUseIsExam ( true ) ) ;
71118
72119 const testError = 'test-error' ;
73120 mockFetchExamAccessToken . mockImplementationOnce ( ( ) => Promise . reject ( testError ) ) ;
@@ -79,7 +126,7 @@ describe('useExamAccess hook', () => {
79126
80127 const { accessToken, blockAccess } = result . current ;
81128
82- expect ( accessToken ) . toEqual ( testAccessToken ) ;
129+ expect ( accessToken ) . toEqual ( testAccessToken . curr ) ;
83130 expect ( blockAccess ) . toBe ( false ) ;
84131 expect ( mockFetchExamAccessToken ) . toHaveBeenCalled ( ) ;
85132 expect ( logError ) . toHaveBeenCalledWith ( testError ) ;
0 commit comments