@@ -49,24 +49,27 @@ describe('useAccessToken', () => {
4949 ) ;
5050 } ;
5151
52- it ( 'should fetch an access token on mount' , async ( ) => {
52+ it ( 'should fetch an access token on mount without showing loading state ' , async ( ) => {
5353 const mockToken =
5454 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature' ;
5555 ( getAccessTokenAction as jest . Mock ) . mockResolvedValueOnce ( mockToken ) ;
5656
5757 const { getByTestId } = render ( < TestComponent /> ) ;
5858
59- expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'true' ) ;
59+ // Loading should remain false for background fetches
60+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
61+
62+ await waitFor ( ( ) => {
63+ expect ( getAccessTokenAction ) . toHaveBeenCalledTimes ( 1 ) ;
64+ } ) ;
6065
6166 await waitFor ( ( ) => {
6267 expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
6368 expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( mockToken ) ;
6469 } ) ;
65-
66- expect ( getAccessTokenAction ) . toHaveBeenCalledTimes ( 1 ) ;
6770 } ) ;
6871
69- it ( 'should handle token refresh when an expiring token is received' , async ( ) => {
72+ it ( 'should handle token refresh when an expiring token is received without showing loading ' , async ( ) => {
7073 // Create a token that's about to expire (exp is very close to current time)
7174 const currentTimeInSeconds = Math . floor ( Date . now ( ) / 1000 ) ;
7275 const payload = { sub : '1234567890' , sid : 'session_123' , exp : currentTimeInSeconds + 30 } ;
@@ -80,6 +83,9 @@ describe('useAccessToken', () => {
8083
8184 const { getByTestId } = render ( < TestComponent /> ) ;
8285
86+ // Loading should remain false throughout
87+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
88+
8389 await waitFor ( ( ) => {
8490 expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
8591 expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( refreshedToken ) ;
@@ -89,7 +95,7 @@ describe('useAccessToken', () => {
8995 expect ( refreshAccessTokenAction ) . toHaveBeenCalledTimes ( 1 ) ;
9096 } ) ;
9197
92- it ( 'should handle token refresh on manual refresh' , async ( ) => {
98+ it ( 'should handle token refresh on manual refresh and show loading state ' , async ( ) => {
9399 const initialToken =
94100 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature' ;
95101 const refreshedToken =
@@ -108,16 +114,21 @@ describe('useAccessToken', () => {
108114
109115 await waitFor ( ( ) => {
110116 expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( initialToken ) ;
117+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
111118 } ) ;
112119
113120 act ( ( ) => {
114121 getByTestId ( 'refresh' ) . click ( ) ;
115122 } ) ;
116123
124+ // Should show loading for user-initiated refresh
125+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'true' ) ;
126+
117127 await waitFor ( ( ) => {
118128 expect ( mockRefreshAuth ) . toHaveBeenCalledTimes ( 1 ) ;
119129 expect ( getAccessTokenAction ) . toHaveBeenCalledTimes ( 2 ) ;
120130 expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( refreshedToken ) ;
131+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
121132 } ) ;
122133 } ) ;
123134
@@ -164,12 +175,15 @@ describe('useAccessToken', () => {
164175 } ) ;
165176 } ) ;
166177
167- it ( 'should handle errors during token fetch' , async ( ) => {
178+ it ( 'should handle errors during token fetch without showing loading ' , async ( ) => {
168179 const error = new Error ( 'Failed to fetch token' ) ;
169180 ( getAccessTokenAction as jest . Mock ) . mockRejectedValueOnce ( error ) ;
170181
171182 const { getByTestId } = render ( < TestComponent /> ) ;
172183
184+ // Loading should remain false even when there's an error
185+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
186+
173187 await waitFor ( ( ) => {
174188 expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
175189 expect ( getByTestId ( 'error' ) ) . toHaveTextContent ( 'Failed to fetch token' ) ;
@@ -250,7 +264,7 @@ describe('useAccessToken', () => {
250264 } ) ;
251265 } ) ;
252266
253- it ( 'should retry fetching when an error occurs' , async ( ) => {
267+ it ( 'should retry fetching when an error occurs without showing loading ' , async ( ) => {
254268 const error = new Error ( 'Failed to fetch token' ) ;
255269 const mockToken =
256270 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwic2lkIjoic2Vzc2lvbl8xMjMiLCJleHAiOjk5OTk5OTk5OTl9.mock-signature' ;
@@ -261,16 +275,21 @@ describe('useAccessToken', () => {
261275
262276 await waitFor ( ( ) => {
263277 expect ( getByTestId ( 'error' ) ) . toHaveTextContent ( 'Failed to fetch token' ) ;
278+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
279+ expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( 'no-token' ) ;
264280 } ) ;
265281
266282 act ( ( ) => {
267283 jest . advanceTimersByTime ( 5 * 60 * 1000 ) ; // RETRY_DELAY
268284 } ) ;
269285
286+ // Loading should remain false during retry
287+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
288+
270289 await waitFor ( ( ) => {
271290 expect ( getAccessTokenAction ) . toHaveBeenCalledTimes ( 2 ) ;
272291 expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( mockToken ) ;
273- expect ( getByTestId ( 'error ' ) ) . toHaveTextContent ( 'no-error ' ) ;
292+ expect ( getByTestId ( 'loading ' ) ) . toHaveTextContent ( 'false ' ) ;
274293 } ) ;
275294 } ) ;
276295
@@ -376,7 +395,8 @@ describe('useAccessToken', () => {
376395
377396 const { getByTestId } = render ( < TestComponent /> ) ;
378397
379- expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'true' ) ;
398+ // Loading should remain false for background operations
399+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
380400
381401 await waitFor ( ( ) => {
382402 expect ( fetchCalls ) . toBe ( 1 ) ;
@@ -441,6 +461,42 @@ describe('useAccessToken', () => {
441461 expect ( refreshAuthCalls ) . toBe ( 1 ) ;
442462 } ) ;
443463
464+ it ( 'should not show loading state for background refresh when token is unchanged' , async ( ) => {
465+ // Create a token that expires in 2 minutes
466+ const currentTimeInSeconds = Math . floor ( Date . now ( ) / 1000 ) ;
467+ const expTimeInSeconds = currentTimeInSeconds + 120 ;
468+ const payload = { sub : '1234567890' , sid : 'session_123' , exp : expTimeInSeconds } ;
469+ const sameToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.${ btoa ( JSON . stringify ( payload ) ) } .mock-signature` ;
470+
471+ // Mock to return the same token twice
472+ ( getAccessTokenAction as jest . Mock ) . mockResolvedValue ( sameToken ) ;
473+
474+ const { getByTestId } = render ( < TestComponent /> ) ;
475+
476+ // Wait for initial token to be set
477+ await waitFor ( ( ) => {
478+ expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( sameToken ) ;
479+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
480+ } ) ;
481+
482+ // Clear previous mock calls
483+ ( getAccessTokenAction as jest . Mock ) . mockClear ( ) ;
484+
485+ // Trigger scheduled refresh by advancing time to just before expiry
486+ act ( ( ) => {
487+ jest . advanceTimersByTime ( ( 120 - 60 ) * 1000 ) ; // Advance to trigger refresh (2 min - buffer)
488+ } ) ;
489+
490+ await waitFor ( ( ) => {
491+ expect ( getAccessTokenAction ) . toHaveBeenCalledTimes ( 1 ) ; // Called once for the refresh
492+ } ) ;
493+
494+ // Should not show loading during background refresh
495+ expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
496+ // Token should remain the same
497+ expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( sameToken ) ;
498+ } ) ;
499+
444500 it ( 'should handle non-Error objects thrown during token fetch' , async ( ) => {
445501 // Simulate a string error being thrown
446502 ( getAccessTokenAction as jest . Mock ) . mockImplementation ( ( ) => {
@@ -452,6 +508,7 @@ describe('useAccessToken', () => {
452508 await waitFor ( ( ) => {
453509 expect ( getByTestId ( 'loading' ) ) . toHaveTextContent ( 'false' ) ;
454510 expect ( getByTestId ( 'error' ) ) . toHaveTextContent ( 'String error message' ) ;
511+ expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( 'no-token' ) ;
455512 } ) ;
456513 } ) ;
457514
@@ -476,14 +533,17 @@ describe('useAccessToken', () => {
476533 refreshAuth : mockRefreshAuth ,
477534 } ) ) ;
478535
479- ( getAccessTokenAction as jest . Mock ) . mockResolvedValueOnce ( initialToken ) . mockResolvedValueOnce ( refreshedToken ) ;
536+ ( getAccessTokenAction as jest . Mock ) . mockResolvedValue ( initialToken ) ;
480537
481538 const { getByTestId } = render ( < TestComponent /> ) ;
482539
483540 await waitFor ( ( ) => {
484541 expect ( getByTestId ( 'token' ) ) . toHaveTextContent ( initialToken ) ;
485542 } ) ;
486543
544+ // Now update the mock for the refresh
545+ ( getAccessTokenAction as jest . Mock ) . mockResolvedValue ( refreshedToken ) ;
546+
487547 act ( ( ) => {
488548 getByTestId ( 'refresh' ) . click ( ) ;
489549 } ) ;
0 commit comments