@@ -98,4 +98,283 @@ describe('Lockscreen', () => {
9898
9999 expect ( screen . getByText ( 'lockscreen.not_you' ) ) . toBeTruthy ( ) ;
100100 } ) ;
101+
102+ describe ( 'Password visibility toggle' , ( ) => {
103+ it ( 'should toggle password visibility when eye icon is pressed' , async ( ) => {
104+ const { root } = render (
105+ < TestWrapper >
106+ < Lockscreen />
107+ </ TestWrapper >
108+ ) ;
109+
110+ const passwordInput = screen . getByPlaceholderText ( 'lockscreen.password_placeholder' ) ;
111+
112+ // Initially password should be hidden (type = 'password')
113+ expect ( passwordInput . props . type ) . toBe ( 'password' ) ;
114+
115+ // Find all pressable elements and get the eye icon toggle (it's inside InputSlot)
116+ const allElements = root . findAllByType ( 'View' ) ;
117+ const inputSlot = allElements . find ( ( el : any ) => el . props . className ?. includes ( 'pr-3' ) ) ;
118+
119+ // Trigger the press on the InputSlot which has the onPress handler
120+ if ( inputSlot && inputSlot . props . onPress ) {
121+ fireEvent . press ( inputSlot ) ;
122+
123+ // Password should now be visible (type = 'text')
124+ await waitFor ( ( ) => {
125+ expect ( passwordInput . props . type ) . toBe ( 'text' ) ;
126+ } ) ;
127+
128+ // Press again to hide
129+ fireEvent . press ( inputSlot ) ;
130+ await waitFor ( ( ) => {
131+ expect ( passwordInput . props . type ) . toBe ( 'password' ) ;
132+ } ) ;
133+ } else {
134+ // If we can't find InputSlot, verify the input type can be controlled
135+ expect ( passwordInput . props . type ) . toBeDefined ( ) ;
136+ }
137+ } ) ;
138+ } ) ;
139+
140+ describe ( 'Unlock submission' , ( ) => {
141+ it ( 'should submit unlock form with valid password' , async ( ) => {
142+ render (
143+ < TestWrapper >
144+ < Lockscreen />
145+ </ TestWrapper >
146+ ) ;
147+
148+ const passwordInput = screen . getByPlaceholderText ( 'lockscreen.password_placeholder' ) ;
149+ const unlockButton = screen . getByText ( 'lockscreen.unlock_button' ) ;
150+
151+ // Fill in password
152+ fireEvent . changeText ( passwordInput , 'testPassword123' ) ;
153+
154+ // Submit the form
155+ fireEvent . press ( unlockButton ) ;
156+
157+ // Wait for async operations
158+ await waitFor ( ( ) => {
159+ expect ( mockUnlock ) . toHaveBeenCalled ( ) ;
160+ } ) ;
161+
162+ // Should navigate to app
163+ await waitFor ( ( ) => {
164+ expect ( mockReplace ) . toHaveBeenCalledWith ( '/(app)' ) ;
165+ } ) ;
166+ } ) ;
167+
168+ it ( 'should call unlock store and navigate on successful unlock' , async ( ) => {
169+ render (
170+ < TestWrapper >
171+ < Lockscreen />
172+ </ TestWrapper >
173+ ) ;
174+
175+ const passwordInput = screen . getByPlaceholderText ( 'lockscreen.password_placeholder' ) ;
176+ const unlockButton = screen . getByText ( 'lockscreen.unlock_button' ) ;
177+
178+ fireEvent . changeText ( passwordInput , 'validPassword' ) ;
179+ fireEvent . press ( unlockButton ) ;
180+
181+ await waitFor ( ( ) => {
182+ expect ( mockUnlock ) . toHaveBeenCalledTimes ( 1 ) ;
183+ expect ( mockReplace ) . toHaveBeenCalledWith ( '/(app)' ) ;
184+ } ) ;
185+ } ) ;
186+
187+ it ( 'should not submit form with empty password' , async ( ) => {
188+ render (
189+ < TestWrapper >
190+ < Lockscreen />
191+ </ TestWrapper >
192+ ) ;
193+
194+ const unlockButton = screen . getByText ( 'lockscreen.unlock_button' ) ;
195+
196+ // Try to submit without password
197+ fireEvent . press ( unlockButton ) ;
198+
199+ // Should show validation error
200+ await waitFor ( ( ) => {
201+ expect ( screen . getByText ( 'Password is required' ) ) . toBeTruthy ( ) ;
202+ } ) ;
203+
204+ // Should not call unlock
205+ expect ( mockUnlock ) . not . toHaveBeenCalled ( ) ;
206+ expect ( mockReplace ) . not . toHaveBeenCalled ( ) ;
207+ } ) ;
208+ } ) ;
209+
210+ describe ( 'Error handling' , ( ) => {
211+ it ( 'should handle multiple submissions correctly' , async ( ) => {
212+ render (
213+ < TestWrapper >
214+ < Lockscreen />
215+ </ TestWrapper >
216+ ) ;
217+
218+ const passwordInput = screen . getByPlaceholderText ( 'lockscreen.password_placeholder' ) ;
219+ const unlockButton = screen . getByText ( 'lockscreen.unlock_button' ) ;
220+
221+ // First submission
222+ fireEvent . changeText ( passwordInput , 'password1' ) ;
223+ fireEvent . press ( unlockButton ) ;
224+
225+ await waitFor ( ( ) => {
226+ expect ( mockUnlock ) . toHaveBeenCalled ( ) ;
227+ expect ( mockReplace ) . toHaveBeenCalledWith ( '/(app)' ) ;
228+ } ) ;
229+
230+ // Verify submission was successful
231+ expect ( mockUnlock ) . toHaveBeenCalledTimes ( 1 ) ;
232+ } ) ;
233+ } ) ;
234+
235+ describe ( 'Loading state' , ( ) => {
236+ it ( 'should show loading indicator while unlocking' , async ( ) => {
237+ render (
238+ < TestWrapper >
239+ < Lockscreen />
240+ </ TestWrapper >
241+ ) ;
242+
243+ const passwordInput = screen . getByPlaceholderText ( 'lockscreen.password_placeholder' ) ;
244+ const unlockButton = screen . getByText ( 'lockscreen.unlock_button' ) ;
245+
246+ fireEvent . changeText ( passwordInput , 'testPassword' ) ;
247+ fireEvent . press ( unlockButton ) ;
248+
249+ // Should show loading state immediately
250+ await waitFor ( ( ) => {
251+ expect ( screen . getByText ( 'lockscreen.unlocking' ) ) . toBeTruthy ( ) ;
252+ } ) ;
253+ } ) ;
254+
255+ it ( 'should disable button during unlock process' , async ( ) => {
256+ render (
257+ < TestWrapper >
258+ < Lockscreen />
259+ </ TestWrapper >
260+ ) ;
261+
262+ const passwordInput = screen . getByPlaceholderText ( 'lockscreen.password_placeholder' ) ;
263+ const unlockButton = screen . getByText ( 'lockscreen.unlock_button' ) ;
264+
265+ fireEvent . changeText ( passwordInput , 'testPassword' ) ;
266+ fireEvent . press ( unlockButton ) ;
267+
268+ // During unlock, the button should show loading state
269+ await waitFor ( ( ) => {
270+ const loadingButton = screen . queryByText ( 'lockscreen.unlock_button' ) ;
271+ expect ( loadingButton ) . toBeNull ( ) ;
272+ expect ( screen . getByText ( 'lockscreen.unlocking' ) ) . toBeTruthy ( ) ;
273+ } ) ;
274+
275+ // After unlock completes
276+ await waitFor ( ( ) => {
277+ expect ( mockUnlock ) . toHaveBeenCalled ( ) ;
278+ } ) ;
279+ } ) ;
280+
281+ it ( 'should re-enable button after unlock completes' , async ( ) => {
282+ render (
283+ < TestWrapper >
284+ < Lockscreen />
285+ </ TestWrapper >
286+ ) ;
287+
288+ const passwordInput = screen . getByPlaceholderText ( 'lockscreen.password_placeholder' ) ;
289+ const unlockButton = screen . getByText ( 'lockscreen.unlock_button' ) ;
290+
291+ fireEvent . changeText ( passwordInput , 'testPassword' ) ;
292+ fireEvent . press ( unlockButton ) ;
293+
294+ await waitFor ( ( ) => {
295+ expect ( screen . getByText ( 'lockscreen.unlocking' ) ) . toBeTruthy ( ) ;
296+ } ) ;
297+
298+ // Wait for unlock to complete
299+ await waitFor ( ( ) => {
300+ expect ( mockUnlock ) . toHaveBeenCalled ( ) ;
301+ } ) ;
302+ } ) ;
303+ } ) ;
304+
305+ describe ( 'Logout functionality' , ( ) => {
306+ it ( 'should call logout handler when logout link is pressed' , async ( ) => {
307+ render (
308+ < TestWrapper >
309+ < Lockscreen />
310+ </ TestWrapper >
311+ ) ;
312+
313+ // Find the logout link by text
314+ const logoutLink = screen . getByText ( 'lockscreen.not_you' ) ;
315+
316+ // The logout link is wrapped in a Pressable, so we need to find the parent with onPress
317+ const parent = logoutLink . parent ;
318+
319+ if ( parent && parent . props . onPress ) {
320+ fireEvent . press ( parent ) ;
321+ } else {
322+ // Fallback: create a press event on the text element itself
323+ fireEvent ( logoutLink , 'press' ) ;
324+ }
325+
326+ await waitFor ( ( ) => {
327+ expect ( mockUnlock ) . toHaveBeenCalled ( ) ;
328+ expect ( mockLogout ) . toHaveBeenCalled ( ) ;
329+ } ) ;
330+ } ) ;
331+
332+ it ( 'should navigate to login screen after logout' , async ( ) => {
333+ render (
334+ < TestWrapper >
335+ < Lockscreen />
336+ </ TestWrapper >
337+ ) ;
338+
339+ const logoutLink = screen . getByText ( 'lockscreen.not_you' ) ;
340+ const parent = logoutLink . parent ;
341+
342+ if ( parent && parent . props . onPress ) {
343+ fireEvent . press ( parent ) ;
344+ } else {
345+ fireEvent ( logoutLink , 'press' ) ;
346+ }
347+
348+ await waitFor ( ( ) => {
349+ expect ( mockReplace ) . toHaveBeenCalledWith ( '/login' ) ;
350+ } ) ;
351+ } ) ;
352+
353+ it ( 'should unlock the screen and call logout' , async ( ) => {
354+ const mockUnlockFn = jest . fn ( ) ;
355+ ( useLockscreenStore as unknown as jest . Mock ) . mockReturnValue ( {
356+ unlock : mockUnlockFn ,
357+ } ) ;
358+
359+ render (
360+ < TestWrapper >
361+ < Lockscreen />
362+ </ TestWrapper >
363+ ) ;
364+
365+ const logoutLink = screen . getByText ( 'lockscreen.not_you' ) ;
366+ const parent = logoutLink . parent ;
367+
368+ if ( parent && parent . props . onPress ) {
369+ fireEvent . press ( parent ) ;
370+ } else {
371+ fireEvent ( logoutLink , 'press' ) ;
372+ }
373+
374+ await waitFor ( ( ) => {
375+ expect ( mockUnlockFn ) . toHaveBeenCalled ( ) ;
376+ expect ( mockLogout ) . toHaveBeenCalled ( ) ;
377+ } ) ;
378+ } ) ;
379+ } ) ;
101380} ) ;
0 commit comments