@@ -4,9 +4,11 @@ import { ToastContext } from '../../../../component-library/components/Toast';
44import { usePerpsTPSLUpdate } from './usePerpsTPSLUpdate' ;
55import { usePerpsTrading } from './usePerpsTrading' ;
66import usePerpsToasts from './usePerpsToasts' ;
7+ import { usePerpsStream } from '../providers/PerpsStreamManager' ;
78
89jest . mock ( './usePerpsTrading' ) ;
910jest . mock ( './usePerpsToasts' ) ;
11+ jest . mock ( '../providers/PerpsStreamManager' ) ;
1012jest . mock ( '../../../../../locales/i18n' , ( ) => ( {
1113 strings : jest . fn ( ( key ) => key ) ,
1214} ) ) ;
@@ -40,6 +42,7 @@ jest.mock('expo-haptics', () => ({
4042describe ( 'usePerpsTPSLUpdate' , ( ) => {
4143 const mockUpdatePositionTPSL = jest . fn ( ) ;
4244 const mockShowToast = jest . fn ( ) ;
45+ const mockUpdatePositionTPSLOptimistic = jest . fn ( ) ;
4346 const mockToastContext = {
4447 toastRef : {
4548 current : {
@@ -49,6 +52,12 @@ describe('usePerpsTPSLUpdate', () => {
4952 } ,
5053 } ;
5154
55+ const mockStream = {
56+ positions : {
57+ updatePositionTPSLOptimistic : mockUpdatePositionTPSLOptimistic ,
58+ } ,
59+ } ;
60+
5261 const mockPerpsToastOptions = {
5362 positionManagement : {
5463 tpsl : {
@@ -126,6 +135,8 @@ describe('usePerpsTPSLUpdate', () => {
126135 showToast : mockShowToast ,
127136 PerpsToastOptions : mockPerpsToastOptions ,
128137 } ) ;
138+
139+ ( usePerpsStream as jest . Mock ) . mockReturnValue ( mockStream ) ;
129140 } ) ;
130141
131142 const renderHookWithToast = ( options = { } ) => {
@@ -267,7 +278,7 @@ describe('usePerpsTPSLUpdate', () => {
267278 ) ;
268279 } ) ;
269280
270- it ( 'should use toast configurations with correct haptics types' , ( ) => {
281+ it ( 'uses toast configurations with correct haptics types' , ( ) => {
271282 // Verify success toast has correct haptics type
272283 expect (
273284 mockPerpsToastOptions . positionManagement . tpsl . updateTPSLSuccess
@@ -281,4 +292,145 @@ describe('usePerpsTPSLUpdate', () => {
281292 ) ;
282293 expect ( errorToast . hapticsType ) . toBe ( 'error' ) ;
283294 } ) ;
295+
296+ describe ( 'optimistic updates' , ( ) => {
297+ it ( 'applies optimistic update to position cache on successful API response' , async ( ) => {
298+ const { result } = renderHookWithToast ( ) ;
299+ const position = createMockPosition ( ) ;
300+ const takeProfitPrice = '3300' ;
301+ const stopLossPrice = '2700' ;
302+
303+ mockUpdatePositionTPSL . mockResolvedValue ( { success : true } ) ;
304+
305+ await act ( async ( ) => {
306+ await result . current . handleUpdateTPSL (
307+ position ,
308+ takeProfitPrice ,
309+ stopLossPrice ,
310+ ) ;
311+ } ) ;
312+
313+ expect ( mockUpdatePositionTPSLOptimistic ) . toHaveBeenCalledWith (
314+ 'ETH' ,
315+ takeProfitPrice ,
316+ stopLossPrice ,
317+ ) ;
318+ } ) ;
319+
320+ it ( 'applies optimistic update with undefined take profit price' , async ( ) => {
321+ const { result } = renderHookWithToast ( ) ;
322+ const position = createMockPosition ( ) ;
323+ const stopLossPrice = '2700' ;
324+
325+ mockUpdatePositionTPSL . mockResolvedValue ( { success : true } ) ;
326+
327+ await act ( async ( ) => {
328+ await result . current . handleUpdateTPSL (
329+ position ,
330+ undefined ,
331+ stopLossPrice ,
332+ ) ;
333+ } ) ;
334+
335+ expect ( mockUpdatePositionTPSLOptimistic ) . toHaveBeenCalledWith (
336+ 'ETH' ,
337+ undefined ,
338+ stopLossPrice ,
339+ ) ;
340+ } ) ;
341+
342+ it ( 'applies optimistic update with undefined stop loss price' , async ( ) => {
343+ const { result } = renderHookWithToast ( ) ;
344+ const position = createMockPosition ( ) ;
345+ const takeProfitPrice = '3300' ;
346+
347+ mockUpdatePositionTPSL . mockResolvedValue ( { success : true } ) ;
348+
349+ await act ( async ( ) => {
350+ await result . current . handleUpdateTPSL (
351+ position ,
352+ takeProfitPrice ,
353+ undefined ,
354+ ) ;
355+ } ) ;
356+
357+ expect ( mockUpdatePositionTPSLOptimistic ) . toHaveBeenCalledWith (
358+ 'ETH' ,
359+ takeProfitPrice ,
360+ undefined ,
361+ ) ;
362+ } ) ;
363+
364+ it ( 'applies optimistic update with both TP/SL undefined (removing both)' , async ( ) => {
365+ const { result } = renderHookWithToast ( ) ;
366+ const position = createMockPosition ( {
367+ takeProfitPrice : '3500' ,
368+ stopLossPrice : '2500' ,
369+ } ) ;
370+
371+ mockUpdatePositionTPSL . mockResolvedValue ( { success : true } ) ;
372+
373+ await act ( async ( ) => {
374+ await result . current . handleUpdateTPSL ( position , undefined , undefined ) ;
375+ } ) ;
376+
377+ expect ( mockUpdatePositionTPSLOptimistic ) . toHaveBeenCalledWith (
378+ 'ETH' ,
379+ undefined ,
380+ undefined ,
381+ ) ;
382+ } ) ;
383+
384+ it ( 'does not apply optimistic update on API failure response' , async ( ) => {
385+ const { result } = renderHookWithToast ( ) ;
386+ const position = createMockPosition ( ) ;
387+
388+ mockUpdatePositionTPSL . mockResolvedValue ( {
389+ success : false ,
390+ error : 'Network error' ,
391+ } ) ;
392+
393+ await act ( async ( ) => {
394+ await result . current . handleUpdateTPSL ( position , '3300' , '2700' ) ;
395+ } ) ;
396+
397+ expect ( mockUpdatePositionTPSLOptimistic ) . not . toHaveBeenCalled ( ) ;
398+ } ) ;
399+
400+ it ( 'does not apply optimistic update on exception' , async ( ) => {
401+ const { result } = renderHookWithToast ( ) ;
402+ const position = createMockPosition ( ) ;
403+ const error = new Error ( 'Network error' ) ;
404+
405+ mockUpdatePositionTPSL . mockRejectedValue ( error ) ;
406+
407+ await act ( async ( ) => {
408+ await result . current . handleUpdateTPSL ( position , '3300' , '2700' ) ;
409+ } ) ;
410+
411+ expect ( mockUpdatePositionTPSLOptimistic ) . not . toHaveBeenCalled ( ) ;
412+ } ) ;
413+
414+ it ( 'applies optimistic update before showing success toast' , async ( ) => {
415+ const callOrder : string [ ] = [ ] ;
416+
417+ mockUpdatePositionTPSLOptimistic . mockImplementation ( ( ) => {
418+ callOrder . push ( 'optimistic' ) ;
419+ } ) ;
420+ mockShowToast . mockImplementation ( ( ) => {
421+ callOrder . push ( 'toast' ) ;
422+ } ) ;
423+
424+ const { result } = renderHookWithToast ( ) ;
425+ const position = createMockPosition ( ) ;
426+
427+ mockUpdatePositionTPSL . mockResolvedValue ( { success : true } ) ;
428+
429+ await act ( async ( ) => {
430+ await result . current . handleUpdateTPSL ( position , '3300' , '2700' ) ;
431+ } ) ;
432+
433+ expect ( callOrder ) . toEqual ( [ 'optimistic' , 'toast' ] ) ;
434+ } ) ;
435+ } ) ;
284436} ) ;
0 commit comments