1
+ import { useState } from 'react' ;
1
2
import { logError } from '@edx/frontend-platform/logging' ;
2
- import { renderHook } from '@testing-library/react-hooks' ;
3
+ import { act , renderHook } from '@testing-library/react-hooks' ;
3
4
import { useExamAccessToken , useFetchExamAccessToken , useIsExam } from '@edx/frontend-lib-special-exams' ;
4
5
5
6
import { initializeMockApp } from '../../../../../setupTest' ;
@@ -16,10 +17,27 @@ jest.mock('@edx/frontend-lib-special-exams', () => ({
16
17
17
18
const id = 'test-id' ;
18
19
20
+ // This object allows us to manipulate the value of the accessToken.
21
+ const testAccessToken = { curr : '' } ;
22
+
19
23
const mockFetchExamAccessToken = jest . fn ( ) . mockImplementation ( ( ) => Promise . resolve ( ) ) ;
20
24
useFetchExamAccessToken . mockReturnValue ( mockFetchExamAccessToken ) ;
21
25
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
+ } ;
23
41
24
42
describe ( 'useExamAccess hook' , ( ) => {
25
43
beforeAll ( async ( ) => {
@@ -28,13 +46,13 @@ describe('useExamAccess hook', () => {
28
46
} ) ;
29
47
beforeEach ( ( ) => {
30
48
jest . clearAllMocks ( ) ;
31
-
32
- // Mock implementations from previous test runs may not have been "consumed", so reset mock implementations.
49
+ jest . useFakeTimers ( ) ;
33
50
useExamAccessToken . mockReset ( ) ;
34
- useExamAccessToken . mockReturnValueOnce ( '' ) ;
35
- useExamAccessToken . mockReturnValueOnce ( testAccessToken ) ;
51
+
52
+ useIsExam . mockImplementation ( ( ) => mockUseIsExam ( ) ) ;
53
+ useExamAccessToken . mockImplementation ( ( ) => testAccessToken . curr ) ;
36
54
} ) ;
37
- describe . only ( 'behavior' , ( ) => {
55
+ describe ( 'behavior' , ( ) => {
38
56
it ( 'returns accessToken and blockAccess and doesn\'t call token API if not an exam' , ( ) => {
39
57
const { result } = renderHook ( ( ) => useExamAccess ( { id } ) ) ;
40
58
const { accessToken, blockAccess } = result . current ;
@@ -43,31 +61,60 @@ describe('useExamAccess hook', () => {
43
61
expect ( blockAccess ) . toBe ( false ) ;
44
62
expect ( mockFetchExamAccessToken ) . not . toHaveBeenCalled ( ) ;
45
63
} ) ;
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 ) ) ;
48
66
49
- const { result } = renderHook ( ( ) => useExamAccess ( { id } ) ) ;
67
+ const { result, waitForNextUpdate } = renderHook ( ( ) => useExamAccess ( { id } ) ) ;
50
68
const { accessToken, blockAccess } = result . current ;
51
69
52
70
expect ( accessToken ) . toEqual ( '' ) ;
53
71
expect ( blockAccess ) . toBe ( true ) ;
54
72
expect ( mockFetchExamAccessToken ) . toHaveBeenCalled ( ) ;
73
+
74
+ // This is to get rid of the act(...) warning.
75
+ await act ( async ( ) => {
76
+ await waitForNextUpdate ( ) ;
77
+ } ) ;
55
78
} ) ;
56
79
it ( 'returns false for blockAccess if an exam and accessToken fetch succeeds' , async ( ) => {
57
- useIsExam . mockImplementation ( ( ) => ( true ) ) ;
80
+ useIsExam . mockImplementation ( ( ) => mockUseIsExam ( true ) ) ;
58
81
const { result, waitForNextUpdate } = renderHook ( ( ) => useExamAccess ( { id } ) ) ;
59
82
60
83
// We wait for the promise to resolve and for updates to state to complete so that blockAccess is updated.
61
84
await waitForNextUpdate ( ) ;
62
85
63
86
const { accessToken, blockAccess } = result . current ;
64
87
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' ) ;
66
113
expect ( blockAccess ) . toBe ( false ) ;
67
114
expect ( mockFetchExamAccessToken ) . toHaveBeenCalled ( ) ;
68
115
} ) ;
69
116
it ( 'returns false for blockAccess if an exam and accessToken fetch fails' , async ( ) => {
70
- useIsExam . mockImplementation ( ( ) => ( true ) ) ;
117
+ useIsExam . mockImplementation ( ( ) => mockUseIsExam ( true ) ) ;
71
118
72
119
const testError = 'test-error' ;
73
120
mockFetchExamAccessToken . mockImplementationOnce ( ( ) => Promise . reject ( testError ) ) ;
@@ -79,7 +126,7 @@ describe('useExamAccess hook', () => {
79
126
80
127
const { accessToken, blockAccess } = result . current ;
81
128
82
- expect ( accessToken ) . toEqual ( testAccessToken ) ;
129
+ expect ( accessToken ) . toEqual ( testAccessToken . curr ) ;
83
130
expect ( blockAccess ) . toBe ( false ) ;
84
131
expect ( mockFetchExamAccessToken ) . toHaveBeenCalled ( ) ;
85
132
expect ( logError ) . toHaveBeenCalledWith ( testError ) ;
0 commit comments