Skip to content

Commit

Permalink
pulled function from component and wrote tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel-Cross committed Oct 21, 2024
1 parent f4425ec commit 2dd0c6a
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 33 deletions.
39 changes: 6 additions & 33 deletions app/components/Nav/Main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,7 @@ import {
stopIncomingTransactionPolling,
} from '../../../util/transaction-controller';
import isNetworkUiRedesignEnabled from '../../../util/networks/isNetworkUiRedesignEnabled';
import {
MetaMetricsEvents,
useMetrics,
} from '../../../components/hooks/useMetrics';
import { useConnectionHandler } from '../../../util/navigation/useConnectionHandler';

const Stack = createStackNavigator();

Expand All @@ -102,7 +99,6 @@ const createStyles = (colors) =>
});

const Main = (props) => {
const [connected, setConnected] = useState(true);
const [forceReload, setForceReload] = useState(false);
const [showRemindLaterModal, setShowRemindLaterModal] = useState(false);
const [skipCheckbox, setSkipCheckbox] = useState(false);
Expand All @@ -113,7 +109,10 @@ const Main = (props) => {
const locale = useRef(I18n.locale);
const removeConnectionStatusListener = useRef();

const { trackEvent } = useMetrics();
const { connectionChangeHandler } = useConnectionHandler(
props.navigation,
true,
);

const removeNotVisibleNotifications = props.removeNotVisibleNotifications;
useNotificationHandler(props.navigation);
Expand All @@ -138,32 +137,6 @@ const Main = (props) => {
}
}, [props.showIncomingTransactionsNetworks, props.chainId]);

const connectionChangeHandler = (state) => {
try {
if (!state) return;
const { isConnected } = state;

// Only navigate to OfflineModeView after a sustained offline period (e.g., 3 seconds)
const debounceTimeout = setTimeout(() => {
if (connected && isConnected === false) {
props.navigation.navigate('OfflineModeView');
}
}, 3000);

// Clear timeout if connection stabilizes
if (isConnected === true) {
clearTimeout(debounceTimeout);
}

if (connected !== isConnected && isConnected !== null) {
setConnected(isConnected);
}
} catch (e) {
console.error('User dropped connection', e);
trackEvent(MetaMetricsEvents.CONNECTION_DROPPED);
}
};

const checkInfuraAvailability = useCallback(async () => {
if (props.providerType !== 'rpc') {
try {
Expand Down Expand Up @@ -352,7 +325,7 @@ const Main = (props) => {
removeConnectionStatusListener.current();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [connectionChangeHandler]);

const termsOfUse = useCallback(async () => {
if (props.navigation) {
Expand Down
137 changes: 137 additions & 0 deletions app/components/Nav/Main/useConnectionHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { useConnectionHandler } from '../../../util/navigation/useConnectionHandler';
import { MetaMetricsEvents } from '../../../components/hooks/useMetrics';

const mockTrackEvent = jest.fn();

jest.mock('../../../components/hooks/useMetrics', () => ({
useMetrics: () => ({
trackEvent: mockTrackEvent,
}),
MetaMetricsEvents: {
CONNECTION_DROPPED: 'CONNECTION_DROPPED',
CONNECTION_RESTORED: 'CONNECTION_RESTORED',
},
}));

describe('useConnectionHandler', () => {
const mockNavigation = { navigate: jest.fn() };

beforeEach(() => {
jest.useFakeTimers();
jest.clearAllMocks();
});

afterEach(() => {
jest.useRealTimers();
});

it('should not navigate to OfflineModeView immediately when connection is lost', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: false });
});

expect(mockNavigation.navigate).not.toHaveBeenCalled();
expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_DROPPED,
);
});

it('should navigate to OfflineModeView after sustained offline period', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: false });
});

jest.advanceTimersByTime(3000);

expect(mockNavigation.navigate).toHaveBeenCalledWith('OfflineModeView');
expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_DROPPED,
);
});

it('should not navigate to OfflineModeView if connection is restored within timeout', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: false });
});

expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_DROPPED,
);

jest.advanceTimersByTime(1000);

act(() => {
result.current.connectionChangeHandler({ isConnected: true });
});

jest.advanceTimersByTime(2000);

expect(mockNavigation.navigate).not.toHaveBeenCalled();
expect(mockTrackEvent).toHaveBeenCalledTimes(2);
expect(mockTrackEvent).toHaveBeenNthCalledWith(
2,
MetaMetricsEvents.CONNECTION_RESTORED,
);
});

it('should do nothing if state is null', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler(null);
});

expect(mockNavigation.navigate).not.toHaveBeenCalled();
expect(mockTrackEvent).not.toHaveBeenCalled();
});

it('should not track events if connection state does not change', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: true });
});

expect(mockTrackEvent).not.toHaveBeenCalled();

act(() => {
result.current.connectionChangeHandler({ isConnected: true });
});

expect(mockTrackEvent).not.toHaveBeenCalled();
});

it('should clear timeout if connection is restored before navigation', () => {
const { result } = renderHook(() => useConnectionHandler(mockNavigation));

act(() => {
result.current.connectionChangeHandler({ isConnected: false });
});

expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_DROPPED,
);

jest.advanceTimersByTime(2000);

act(() => {
result.current.connectionChangeHandler({ isConnected: true });
});

expect(mockTrackEvent).toHaveBeenCalledWith(
MetaMetricsEvents.CONNECTION_RESTORED,
);

jest.advanceTimersByTime(1000);

expect(mockNavigation.navigate).not.toHaveBeenCalled();
expect(mockTrackEvent).toHaveBeenCalledTimes(2);
});
});
2 changes: 2 additions & 0 deletions app/core/Analytics/MetaMetrics.events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,7 @@ enum EVENT_NAME {

// Connection
CONNECTION_DROPPED = 'Connection dropped',
CONNECTION_RESTORED = 'Connection restored',
}

enum ACTIONS {
Expand Down Expand Up @@ -860,6 +861,7 @@ const events = {

// Connection
CONNECTION_DROPPED: generateOpt(EVENT_NAME.CONNECTION_DROPPED),
CONNECTION_RESTORED: generateOpt(EVENT_NAME.CONNECTION_RESTORED),
};

/**
Expand Down
39 changes: 39 additions & 0 deletions app/util/navigation/useConnectionHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useRef, useCallback } from 'react';
import {
useMetrics,
MetaMetricsEvents,
} from '../../components/hooks/useMetrics';

export const useConnectionHandler = (navigation: any) => {

Check failure on line 7 in app/util/navigation/useConnectionHandler.tsx

View workflow job for this annotation

GitHub Actions / scripts (lint)

Unexpected any. Specify a different type
const connectedRef = useRef(true);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);

const { trackEvent } = useMetrics();

const connectionChangeHandler = useCallback(
(state: { isConnected: boolean } | null) => {
if (!state) return;
const { isConnected } = state;

if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}

if (connectedRef.current !== isConnected) {
if (isConnected === false) {
trackEvent(MetaMetricsEvents.CONNECTION_DROPPED);
timeoutRef.current = setTimeout(() => {
navigation.navigate('OfflineModeView');
}, 3000);
} else {
trackEvent(MetaMetricsEvents.CONNECTION_RESTORED);
}
connectedRef.current = isConnected;
}
},
[navigation, trackEvent],
);

return { connectionChangeHandler };
};

0 comments on commit 2dd0c6a

Please sign in to comment.