11import { act , render , screen , waitFor } from '@testing-library/react'
2+ import { toast } from 'react-hot-toast'
23import { describe , it , expect , beforeEach , vi } from 'vitest'
34
45import client , { setAuthToken , setAuthErrorHandler } from '../../api/client'
@@ -7,6 +8,13 @@ import { useAuth } from '../../hooks/useAuth'
78
89const TOKEN_KEY = 'charon_auth_token'
910
11+ vi . mock ( 'react-hot-toast' , ( ) => ( {
12+ toast : {
13+ error : vi . fn ( ) ,
14+ dismiss : vi . fn ( ) ,
15+ } ,
16+ } ) )
17+
1018vi . mock ( '../../api/client' , ( ) => ( {
1119 default : {
1220 post : vi . fn ( ) . mockResolvedValue ( { } ) ,
@@ -19,6 +27,7 @@ vi.mock('../../api/client', () => ({
1927const mockClient = vi . mocked ( client )
2028const mockSetAuthToken = vi . mocked ( setAuthToken )
2129const mockSetAuthErrorHandler = vi . mocked ( setAuthErrorHandler )
30+ const mockToast = vi . mocked ( toast )
2231
2332const AuthStateProbe = ( ) => {
2433 const { user, isAuthenticated, isLoading } = useAuth ( )
@@ -83,10 +92,9 @@ describe('<AuthProvider /> session validation on mount (page reload)', () => {
8392 expect ( mockSetAuthToken ) . toHaveBeenCalledWith ( 'valid-token' )
8493 } )
8594
86- it ( 'registers an auth-error handler that clears the session, and unregisters it on unmount' , async ( ) => {
95+ it ( 'registers an auth-error handler that clears the session, shows a toast, and unregisters on unmount' , async ( ) => {
8796 localStorage . setItem ( TOKEN_KEY , 'valid-token' )
8897 mockClient . get . mockResolvedValue ( { data : sessionUser , status : 200 } )
89- const warnSpy = vi . spyOn ( console , 'warn' ) . mockImplementation ( ( ) => { } )
9098
9199 const { unmount } = renderProvider ( )
92100 await waitFor ( ( ) => expect ( screen . getByTestId ( 'authenticated' ) . textContent ) . toBe ( 'true' ) )
@@ -99,10 +107,39 @@ describe('<AuthProvider /> session validation on mount (page reload)', () => {
99107 await waitFor ( ( ) => expect ( screen . getByTestId ( 'authenticated' ) . textContent ) . toBe ( 'false' ) )
100108 expect ( localStorage . getItem ( TOKEN_KEY ) ) . toBeNull ( )
101109 expect ( mockSetAuthToken ) . toHaveBeenLastCalledWith ( null )
110+ expect ( mockToast . error ) . toHaveBeenCalledWith (
111+ 'Session expired. Please log in again.' ,
112+ expect . objectContaining ( { id : 'auth-session-expired' } )
113+ )
102114
103115 unmount ( )
104116 expect ( mockSetAuthErrorHandler ) . toHaveBeenLastCalledWith ( null )
117+ } )
118+
119+ it ( 'dismisses the session-expired toast when login succeeds' , async ( ) => {
120+ mockClient . get . mockResolvedValue ( { data : sessionUser , status : 200 } )
105121
106- warnSpy . mockRestore ( )
122+ let loginFn : ( ( token ?: string ) => Promise < void > ) | undefined
123+ const LoginProbe = ( ) => {
124+ const { login } = useAuth ( )
125+ loginFn = login
126+ return null
127+ }
128+
129+ render (
130+ < AuthProvider >
131+ < LoginProbe />
132+ < AuthStateProbe />
133+ </ AuthProvider >
134+ )
135+
136+ await waitFor ( ( ) => expect ( screen . getByTestId ( 'loading' ) . textContent ) . toBe ( 'false' ) )
137+
138+ await act ( async ( ) => {
139+ await loginFn ?.( 'new-token' )
140+ } )
141+
142+ expect ( mockToast . dismiss ) . toHaveBeenCalledWith ( 'auth-session-expired' )
143+ expect ( screen . getByTestId ( 'authenticated' ) . textContent ) . toBe ( 'true' )
107144 } )
108145} )
0 commit comments