Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,9 @@ exports[`DeleteWalletModal bottom sheet renders matching snapshot 1`] = `
style={
{
"color": "#121314",
"fontFamily": "Geist Bold",
"fontFamily": "CentraNo1-Bold",
"fontSize": 18,
"fontWeight": "600",
"letterSpacing": 0,
"lineHeight": 24,
"marginLeft": "auto",
Expand Down Expand Up @@ -532,8 +533,9 @@ exports[`DeleteWalletModal bottom sheet renders matching snapshot 1`] = `
style={
{
"color": "#121314",
"fontFamily": "Geist Bold",
"fontFamily": "CentraNo1-Bold",
"fontSize": 16,
"fontWeight": "600",
"letterSpacing": 0,
"lineHeight": 24,
}
Expand Down Expand Up @@ -589,8 +591,9 @@ exports[`DeleteWalletModal bottom sheet renders matching snapshot 1`] = `
style={
{
"color": "#121314",
"fontFamily": "Geist Bold",
"fontFamily": "CentraNo1-Bold",
"fontSize": 16,
"fontWeight": "600",
"letterSpacing": 0,
"lineHeight": 24,
}
Expand Down
2 changes: 2 additions & 0 deletions app/components/UI/DeleteWalletModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ const DeleteWalletModal: React.FC = () => {
<Text
variant={TextVariant.BodyMDBold}
color={TextColor.Default}
style={styles.bold}
>
{strings('login.forgot_password_point_1_bold')}
</Text>{' '}
Expand All @@ -193,6 +194,7 @@ const DeleteWalletModal: React.FC = () => {
<Text
variant={TextVariant.BodyMDBold}
color={TextColor.Default}
style={styles.bold}
>
{strings('login.forgot_password_point_2_bold')}{' '}
</Text>
Expand Down
2 changes: 2 additions & 0 deletions app/components/UI/DeleteWalletModal/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const createStyles = (colors: any) =>
width: '80%',
marginLeft: 'auto',
marginRight: 'auto',
...fontStyles.bold,
},
red: {
marginHorizontal: 24,
Expand All @@ -79,6 +80,7 @@ export const createStyles = (colors: any) =>
warningText: {
textAlign: 'left',
width: '100%',
fontWeight: '500',
},
warningTextContainer: {
flexDirection: 'column',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { usePerpsPerformance } from '../../hooks';
import ButtonIcon, {
ButtonIconSizes,
} from '../../../../../component-library/components/Buttons/ButtonIcon';
import { DevLogger } from '../../../../../core/SDKConnect/utils/DevLogger';

const PerpsMarketRowItemSkeleton = () => {
const { styles } = useStyles(styleSheet, {});
Expand Down Expand Up @@ -201,16 +202,21 @@ const PerpsMarketListView = ({

// Track screen load performance
const hasTrackedMarketsView = useRef(false);
const hasTrackedSkeletonDisplay = useRef(false);
const hasTrackedDataDisplay = useRef(false);

// Track skeleton display immediately
// Track when actual market data is displayed (not just skeleton)
useEffect(() => {
if (isLoadingMarkets && !hasTrackedSkeletonDisplay.current) {
// Measure time to skeleton display (should be instant)
endMeasure(PerpsMeasurementName.MARKETS_SCREEN_LOADED);
hasTrackedSkeletonDisplay.current = true;
if (filteredMarkets.length > 0 && !hasTrackedDataDisplay.current) {
// End measurement when actual data is displayed
const loadTime = endMeasure(PerpsMeasurementName.MARKETS_SCREEN_LOADED);
DevLogger.log('PerpsMarketListView: Market data displayed', {
marketCount: filteredMarkets.length,
loadTimeMs: loadTime,
targetMs: 200,
});
hasTrackedDataDisplay.current = true;
}
}, [isLoadingMarkets, endMeasure]);
}, [filteredMarkets.length, endMeasure]);

useEffect(() => {
// Track markets screen viewed event - only once when data is loaded
Expand Down
112 changes: 112 additions & 0 deletions app/components/UI/Perps/Views/PerpsTabView/PerpsTabView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,118 @@ describe('PerpsTabViewWithProvider', () => {
});
});

describe('Visibility Callback Tests', () => {
it('should register visibility callback when onVisibilityChange is provided', () => {
const mockOnVisibilityChange = jest.fn();

render(
<PerpsTabViewWithProvider
isVisible={false}
onVisibilityChange={mockOnVisibilityChange}
/>,
);

// Verify callback was registered
expect(mockOnVisibilityChange).toHaveBeenCalledTimes(1);
expect(mockOnVisibilityChange).toHaveBeenCalledWith(expect.any(Function));
});

it('should update visibility state when callback is invoked', () => {
let visibilityCallback: ((visible: boolean) => void) | null = null;
const mockOnVisibilityChange = jest.fn((callback) => {
visibilityCallback = callback;
});

render(
<PerpsTabViewWithProvider
isVisible={false}
onVisibilityChange={mockOnVisibilityChange}
/>,
);

// Simulate parent calling the visibility callback
act(() => {
visibilityCallback?.(true);
});

// The callback should have been invoked
expect(visibilityCallback).toBeTruthy();
});

it('should not register callback when onVisibilityChange is not provided', () => {
const { rerender } = render(<PerpsTabViewWithProvider isVisible />);

// Component should render without errors
expect(screen.getByTestId('manage-balance-button')).toBeOnTheScreen();

// Rerender with different visibility
rerender(<PerpsTabViewWithProvider isVisible={false} />);

// Should still render without errors
expect(screen.getByTestId('manage-balance-button')).toBeOnTheScreen();
});

it('should use initial visibility value', () => {
const mockOnVisibilityChange = jest.fn();

// Test with initial visible = true
const { unmount: unmount1 } = render(
<PerpsTabViewWithProvider
isVisible
onVisibilityChange={mockOnVisibilityChange}
/>,
);

expect(screen.getByTestId('manage-balance-button')).toBeOnTheScreen();
unmount1();

// Test with initial visible = false
const { unmount: unmount2 } = render(
<PerpsTabViewWithProvider
isVisible={false}
onVisibilityChange={mockOnVisibilityChange}
/>,
);

expect(screen.getByTestId('manage-balance-button')).toBeOnTheScreen();
unmount2();
});

it('should handle multiple visibility changes', () => {
let visibilityCallback: ((visible: boolean) => void) | null = null;
let callCount = 0;
const mockOnVisibilityChange = jest.fn((callback) => {
visibilityCallback = callback;
});

render(
<PerpsTabViewWithProvider
isVisible={false}
onVisibilityChange={mockOnVisibilityChange}
/>,
);

// Simulate multiple visibility changes
act(() => {
visibilityCallback?.(true);
callCount++;
});

act(() => {
visibilityCallback?.(false);
callCount++;
});

act(() => {
visibilityCallback?.(true);
callCount++;
});

// Verify callback was called for each change
expect(callCount).toBe(3);
});
});

describe('Edge Cases', () => {
it('should handle component unmounting gracefully', () => {
const { unmount } = render(<PerpsTabViewWithProvider />);
Expand Down
38 changes: 30 additions & 8 deletions app/components/UI/Perps/Views/PerpsTabView/index.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { NavigationProp, ParamListBase } from '@react-navigation/native';
import { PerpsConnectionProvider } from '../../providers/PerpsConnectionProvider';
import { PerpsStreamProvider } from '../../providers/PerpsStreamManager';
import { DevLogger } from '../../../../../core/SDKConnect/utils/DevLogger';
import PerpsTabView from './PerpsTabView';

interface PerpsTabViewWithProviderProps {
navigation?: NavigationProp<ParamListBase>;
tabLabel?: string;
isVisible?: boolean;
onVisibilityChange?: (callback: (visible: boolean) => void) => void;
}

/**
* PerpsTabView wrapped with both PerpsConnectionProvider and PerpsStreamProvider
* This ensures the usePerpsConnection and usePerpsStream hooks work properly when used in the main wallet tab view
* Visibility is managed internally with updates from parent via callback
*/
const PerpsTabViewWithProvider: React.FC<PerpsTabViewWithProviderProps> = (
props,
) => (
<PerpsConnectionProvider>
<PerpsStreamProvider>
<PerpsTabView {...props} />
</PerpsStreamProvider>
</PerpsConnectionProvider>
);
) => {
const { isVisible: initialVisible = false, onVisibilityChange } = props;
const [isVisible, setIsVisible] = useState(initialVisible);

// Register callback with parent
useEffect(() => {
if (onVisibilityChange) {
onVisibilityChange((visible: boolean) => {
DevLogger.log('PerpsTabView: Visibility updated via callback', {
visible,
timestamp: new Date().toISOString(),
});
setIsVisible(visible);
});
}
}, [onVisibilityChange]);

return (
<PerpsConnectionProvider isVisible={isVisible}>
<PerpsStreamProvider>
<PerpsTabView {...props} />
</PerpsStreamProvider>
</PerpsConnectionProvider>
);
};

// Export the wrapped version as default
export default PerpsTabViewWithProvider;
Expand Down
5 changes: 5 additions & 0 deletions app/components/UI/Perps/constants/perpsConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const PERPS_CONSTANTS = {
FEATURE_FLAG_KEY: 'perpsEnabled',
WEBSOCKET_TIMEOUT: 5000, // 5 seconds
WEBSOCKET_CLEANUP_DELAY: 1000, // 1 second
BACKGROUND_DISCONNECT_DELAY: 20_000, // 20 seconds delay before disconnecting when app is backgrounded
DEFAULT_ASSET_PREVIEW_LIMIT: 5,
DEFAULT_MAX_LEVERAGE: 3 as number, // Default fallback max leverage when market data is unavailable - conservative default
FALLBACK_PRICE_DISPLAY: '$---', // Display when price data is unavailable
Expand Down Expand Up @@ -67,6 +68,10 @@ export const PERFORMANCE_CONFIG = {
// Order validation debounce delay (milliseconds)
// Prevents excessive validation calls during rapid form input changes
VALIDATION_DEBOUNCE_MS: 1000,

// Market data cache duration (milliseconds)
// How long to cache market list data before fetching fresh data
MARKET_DATA_CACHE_DURATION_MS: 5 * 60 * 1000, // 5 minutes
} as const;

/**
Expand Down
1 change: 1 addition & 0 deletions app/components/UI/Perps/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export { usePerpsDepositStatus } from './usePerpsDepositStatus';

// Connection management hooks
export { usePerpsConnection } from '../providers/PerpsConnectionProvider';
export { usePerpsConnectionLifecycle } from './usePerpsConnectionLifecycle';

// State hooks (Redux selectors)
export { usePerpsAccount } from './usePerpsAccount';
Expand Down
Loading
Loading