1- import { render , screen , waitFor , fireEvent } from '@testing-library/preact' ;
2- import { describe , it , expect , vi , beforeEach , type Mock } from 'vitest' ;
1+ import { render , screen , waitFor , fireEvent , cleanup } from '@testing-library/preact' ;
2+ import { describe , it , expect , vi , beforeEach , afterEach , type Mock } from 'vitest' ;
33
44vi . mock ( 'argon2-browser' , ( ) => ( {
55 hash : vi . fn ( async ( ) => ( { hash : new Uint8Array ( [ 1 , 2 , 3 ] ) } ) ) ,
@@ -26,17 +26,52 @@ describe('Tokens Component', () => {
2626
2727 let fetchClient : typeof import ( '../../utils' ) . fetchClient ;
2828 let showAlert : Mock ;
29+ let restoreIntervalMocks : ( ( ) => void ) | null = null ;
2930
3031 beforeEach ( async ( ) => {
3132 vi . clearAllMocks ( ) ;
3233
34+ const originalSetInterval = globalThis . setInterval ;
35+ const originalClearInterval = globalThis . clearInterval ;
36+ globalThis . setInterval = ( ( handler : ( ...args : unknown [ ] ) => void ) => {
37+ return { __fake : 'interval' } as unknown as NodeJS . Timeout ;
38+ } ) as typeof setInterval ;
39+ globalThis . clearInterval = ( ( ) => undefined ) as typeof clearInterval ;
40+ restoreIntervalMocks = ( ) => {
41+ globalThis . setInterval = originalSetInterval ;
42+ globalThis . clearInterval = originalClearInterval ;
43+ } ;
44+
3345 const utils = await import ( '../../utils' ) ;
3446 fetchClient = utils . fetchClient ;
47+ ( fetchClient . GET as unknown as Mock ) . mockImplementation ( ( path : string ) => {
48+ if ( path === '/user/me' ) {
49+ return Promise . resolve ( {
50+ data : mockUserData ,
51+ error : null ,
52+ response : { status : 200 } ,
53+ } ) ;
54+ }
55+ if ( path === '/user/get_authorization_tokens' ) {
56+ return Promise . resolve ( {
57+ data : { tokens : [ ] } ,
58+ error : null ,
59+ response : { status : 200 } ,
60+ } ) ;
61+ }
62+ return Promise . resolve ( { data : null , error : 'Not found' , response : { status : 404 } } ) ;
63+ } ) ;
3564
3665 const alertModule = await import ( '../../components/Alert' ) ;
3766 showAlert = alertModule . showAlert as unknown as Mock ;
3867 } ) ;
3968
69+ afterEach ( ( ) => {
70+ cleanup ( ) ;
71+ restoreIntervalMocks ?.( ) ;
72+ restoreIntervalMocks = null ;
73+ } ) ;
74+
4075 it ( 'renders loading spinner initially' , ( ) => {
4176 render ( < Tokens /> ) ;
4277 expect ( screen . getByText ( 'Loading...' ) ) . toBeTruthy ( ) ;
@@ -161,4 +196,75 @@ describe('Tokens Component', () => {
161196 expect ( getTokenNames ( ) ) . toEqual ( [ 'Charlie' , 'Bravo' , 'Alpha' ] ) ;
162197 } ) ;
163198 } ) ;
199+
200+ it ( 'filters tokens using the search field' , async ( ) => {
201+ const base64 = Base64 as unknown as { toUint8Array : Mock } ;
202+ const encoder = new TextEncoder ( ) ;
203+ base64 . toUint8Array . mockImplementation ( ( value : string ) => encoder . encode ( value ) ) ;
204+
205+ const libsodium = sodium as unknown as { crypto_box_seal_open : Mock } ;
206+ libsodium . crypto_box_seal_open . mockImplementation ( ( binary : Uint8Array ) => binary ) ;
207+
208+ ( fetchClient . GET as unknown as Mock ) . mockImplementation ( ( path : string ) => {
209+ if ( path === '/user/me' ) {
210+ return Promise . resolve ( {
211+ data : mockUserData ,
212+ error : null ,
213+ response : { status : 200 } ,
214+ } ) ;
215+ }
216+ if ( path === '/user/get_authorization_tokens' ) {
217+ return Promise . resolve ( {
218+ data : {
219+ tokens : [
220+ { token : 'token-alpha' , use_once : false , id : '1' , name : 'Alpha' , created_at : 1_000 , last_used_at : null } ,
221+ { token : 'token-bravo' , use_once : false , id : '2' , name : 'Bravo' , created_at : 2_000 , last_used_at : 1_900 } ,
222+ { token : 'token-charlie' , use_once : false , id : '3' , name : 'Charlie' , created_at : 1_500 , last_used_at : 1_950 } ,
223+ ] ,
224+ } ,
225+ error : null ,
226+ response : { status : 200 } ,
227+ } ) ;
228+ }
229+ return Promise . resolve ( { data : null , error : 'Not found' , response : { status : 404 } } ) ;
230+ } ) ;
231+
232+ render ( < Tokens /> ) ;
233+
234+ await waitFor ( ( ) => {
235+ expect ( screen . getByLabelText ( 'tokens.search_label' ) ) . toBeTruthy ( ) ;
236+ } ) ;
237+
238+ const getTokenNames = ( ) => screen
239+ . queryAllByRole ( 'heading' , { level : 6 } )
240+ . map ( ( heading ) => heading . textContent ?. trim ( ) )
241+ . filter ( Boolean ) ;
242+
243+ await waitFor ( ( ) => {
244+ expect ( getTokenNames ( ) ) . toEqual ( [ 'Bravo' , 'Charlie' , 'Alpha' ] ) ;
245+ } ) ;
246+
247+ const searchInput = screen . getByLabelText ( 'tokens.search_label' ) ;
248+ const changeSearch = ( value : string ) => fireEvent . change ( searchInput , { target : { value } } ) ;
249+
250+ changeSearch ( 'char' ) ;
251+ await waitFor ( ( ) => {
252+ expect ( getTokenNames ( ) ) . toEqual ( [ 'Charlie' ] ) ;
253+ } ) ;
254+
255+ changeSearch ( 'brav' ) ;
256+ await waitFor ( ( ) => {
257+ expect ( getTokenNames ( ) ) . toEqual ( [ 'Bravo' ] ) ;
258+ } ) ;
259+
260+ changeSearch ( 'zzz' ) ;
261+ await waitFor ( ( ) => {
262+ expect ( screen . getByText ( 'tokens.no_tokens_search' ) ) . toBeTruthy ( ) ;
263+ } ) ;
264+
265+ changeSearch ( '' ) ;
266+ await waitFor ( ( ) => {
267+ expect ( getTokenNames ( ) ) . toEqual ( [ 'Bravo' , 'Charlie' , 'Alpha' ] ) ;
268+ } ) ;
269+ } ) ;
164270} ) ;
0 commit comments