From 86c486bcf6d9a50affe640d82700aefdbd9d678f Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 6 Dec 2024 12:49:54 +0100 Subject: [PATCH 01/12] Add poor connection simulation --- src/components/TestToolMenu.tsx | 10 ++++++ src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/actions/Network.ts | 55 ++++++++++++++++++++++++++++++++- src/types/onyx/Network.ts | 6 ++++ 5 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index e81405d026b4..12b6da2c4c9c 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -74,6 +74,16 @@ function TestToolMenu({network}: TestToolMenuProps) { accessibilityLabel="Force offline" isOn={!!network?.shouldForceOffline} onToggle={() => Network.setShouldForceOffline(!network?.shouldForceOffline)} + disabled={isUsingImportedState || network?.shouldSimulatePoorConnection} + /> + + + {/* When toggled the app will randomly change internet connection every 2-5 seconds */} + + Network.simulatePoorConnection(!network?.shouldSimulatePoorConnection, network?.poorConnectionTimeoutID)} disabled={isUsingImportedState} /> diff --git a/src/languages/en.ts b/src/languages/en.ts index d79695ed8b48..8dfa9c43f358 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1231,6 +1231,7 @@ const translations = { testingPreferences: 'Testing preferences', useStagingServer: 'Use Staging Server', forceOffline: 'Force offline', + simulatePoorConnection: 'Simulate poor internet connection', simulatFailingNetworkRequests: 'Simulate failing network requests', authenticationStatus: 'Authentication status', deviceCredentials: 'Device credentials', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5ce47db18d35..049749be10fd 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1230,6 +1230,7 @@ const translations = { testingPreferences: 'Preferencias para Tests', useStagingServer: 'Usar servidor “staging”', forceOffline: 'Forzar desconexión', + simulatePoorConnection: 'Simular una conexión a internet deficiente', simulatFailingNetworkRequests: 'Simular fallos en solicitudes de red', authenticationStatus: 'Estado de autenticación', deviceCredentials: 'Credenciales del dispositivo', diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index d8a87aff551d..638eef84aff6 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -1,8 +1,28 @@ import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import type {NetworkStatus} from '@libs/NetworkConnection'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +let isPoorConnectionSimulated: boolean | undefined; +Onyx.connect({ + key: ONYXKEYS.NETWORK, + callback: (value) => { + if (!value) { + return; + } + + // Starts random network status change when shouldSimulatePoorConnection is turned into true + // or after app restart if shouldSimulatePoorConnection is true already + if (!isPoorConnectionSimulated && !!value.shouldSimulatePoorConnection) { + clearTimeout(value.poorConnectionTimeoutID); + setRandomNetworkStatus(true); + } + + isPoorConnectionSimulated = !!value.shouldSimulatePoorConnection; + }, +}); + function setIsOffline(isOffline: boolean, reason = '') { if (reason) { let textToLog = '[Network] Client is'; @@ -32,4 +52,37 @@ function setShouldFailAllRequests(shouldFailAllRequests: boolean) { Onyx.merge(ONYXKEYS.NETWORK, {shouldFailAllRequests}); } -export {setIsOffline, setShouldForceOffline, setShouldFailAllRequests, setTimeSkew, setNetWorkStatus}; +function setPoorConnectionTimeoutID(poorConnectionTimeoutID: NodeJS.Timeout | undefined) { + Onyx.merge(ONYXKEYS.NETWORK, {poorConnectionTimeoutID}); +} + +function setRandomNetworkStatus(initialCall = false) { + // The check to ensure no new timeouts are scheduled after poor connection simulation is stopped + if (!isPoorConnectionSimulated && !initialCall) { + setShouldForceOffline(false); + return; + } + + const statuses = [CONST.NETWORK.NETWORK_STATUS.OFFLINE, CONST.NETWORK.NETWORK_STATUS.ONLINE]; + const randomStatus = statuses[Math.floor(Math.random() * statuses.length)]; + const randomInterval = Math.random() * (5000 - 2000) + 2000; // random interval between 2-5 seconds + Log.info(`[NetworkConnection] Set connection status "${randomStatus}" for ${randomInterval} sec`); + + setShouldForceOffline(randomStatus === CONST.NETWORK.NETWORK_STATUS.OFFLINE); + + const timeoutID = setTimeout(setRandomNetworkStatus, randomInterval); + + setPoorConnectionTimeoutID(timeoutID); +} + +function simulatePoorConnection(shouldSimulatePoorConnection: boolean, poorConnectionTimeoutID: NodeJS.Timeout | undefined) { + if (!shouldSimulatePoorConnection) { + clearTimeout(poorConnectionTimeoutID); + setPoorConnectionTimeoutID(undefined); + setShouldForceOffline(false); + } + + Onyx.merge(ONYXKEYS.NETWORK, {shouldSimulatePoorConnection}); +} + +export {setIsOffline, setShouldForceOffline, setShouldFailAllRequests, setTimeSkew, setNetWorkStatus, simulatePoorConnection}; diff --git a/src/types/onyx/Network.ts b/src/types/onyx/Network.ts index 680c6c468c00..d834da50cab7 100644 --- a/src/types/onyx/Network.ts +++ b/src/types/onyx/Network.ts @@ -8,6 +8,12 @@ type Network = { /** Should the network be forced offline */ shouldForceOffline?: boolean; + /** Whether we should simulate poor connection */ + shouldSimulatePoorConnection?: boolean; + + /** Poor connection timeout id */ + poorConnectionTimeoutID?: NodeJS.Timeout; + /** Whether we should fail all network requests */ shouldFailAllRequests?: boolean; From 43330f8a5b33f8a50e6872bbc2cea01c4e4772ef Mon Sep 17 00:00:00 2001 From: VickyStash Date: Fri, 6 Dec 2024 12:55:24 +0100 Subject: [PATCH 02/12] Lint fix --- src/components/TestToolMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 12b6da2c4c9c..6b020657116e 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -74,7 +74,7 @@ function TestToolMenu({network}: TestToolMenuProps) { accessibilityLabel="Force offline" isOn={!!network?.shouldForceOffline} onToggle={() => Network.setShouldForceOffline(!network?.shouldForceOffline)} - disabled={isUsingImportedState || network?.shouldSimulatePoorConnection} + disabled={!!isUsingImportedState || network?.shouldSimulatePoorConnection} /> From 5e52653975b8eeb83dfa441ea46498858c6f7f7f Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 9 Dec 2024 09:38:59 +0100 Subject: [PATCH 03/12] Log connection changes --- src/libs/actions/Network.ts | 30 ++++++++++++++++++++++++++++++ src/types/onyx/Network.ts | 13 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 638eef84aff6..6c85edf8e048 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -1,10 +1,13 @@ +import {differenceInHours} from 'date-fns/differenceInHours'; import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import type {NetworkStatus} from '@libs/NetworkConnection'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ConnectionChanges} from '@src/types/onyx/Network'; let isPoorConnectionSimulated: boolean | undefined; +let connectionChanges: ConnectionChanges | undefined; Onyx.connect({ key: ONYXKEYS.NETWORK, callback: (value) => { @@ -20,9 +23,33 @@ Onyx.connect({ } isPoorConnectionSimulated = !!value.shouldSimulatePoorConnection; + connectionChanges = value.connectionChanges; }, }); +function trackConnectionChanges() { + if (!connectionChanges?.startTime) { + Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges: {startTime: new Date().getTime(), amount: 1}}); + return; + } + + const diffInHours = differenceInHours(new Date(), connectionChanges.startTime); + const newAmount = (connectionChanges.amount ?? 0) + 1; + + if (diffInHours < 1) { + Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges: {amount: newAmount}}); + return; + } + + Log.info( + `[NetworkConnection] Connection has changed ${newAmount} time(s) for the last ${diffInHours} hour(s). Poor connection simulation is turned ${ + isPoorConnectionSimulated ? 'on' : 'off' + }`, + ); + + Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges: {startTime: new Date().getTime(), amount: 0}}); +} + function setIsOffline(isOffline: boolean, reason = '') { if (reason) { let textToLog = '[Network] Client is'; @@ -30,6 +57,9 @@ function setIsOffline(isOffline: boolean, reason = '') { textToLog += ` because: ${reason}`; Log.info(textToLog); } + + trackConnectionChanges(); + Onyx.merge(ONYXKEYS.NETWORK, {isOffline}); } diff --git a/src/types/onyx/Network.ts b/src/types/onyx/Network.ts index d834da50cab7..74fb1202a8a2 100644 --- a/src/types/onyx/Network.ts +++ b/src/types/onyx/Network.ts @@ -1,5 +1,14 @@ import type {NetworkStatus} from '@libs/NetworkConnection'; +/** The value where connection changes are tracked */ +type ConnectionChanges = { + /** Amount of connection changes */ + amount?: number; + + /** Start time in milliseconds */ + startTime?: number; +}; + /** Model of network state */ type Network = { /** Is the network currently offline or not */ @@ -14,6 +23,9 @@ type Network = { /** Poor connection timeout id */ poorConnectionTimeoutID?: NodeJS.Timeout; + /** The value where connection changes are tracked */ + connectionChanges?: ConnectionChanges; + /** Whether we should fail all network requests */ shouldFailAllRequests?: boolean; @@ -25,3 +37,4 @@ type Network = { }; export default Network; +export type {ConnectionChanges}; From 2b9fb2df463c445de4efe8cef5c18e60f15d09be Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 10 Dec 2024 17:18:23 +0100 Subject: [PATCH 04/12] Move the logic to NetworkConnection file --- src/components/TestToolMenu.tsx | 3 +- src/libs/NetworkConnection.ts | 70 +++++++++++++++++++++++++++++ src/libs/actions/Network.ts | 79 +++------------------------------ 3 files changed, 77 insertions(+), 75 deletions(-) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 6b020657116e..ea21a383d0cd 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -4,6 +4,7 @@ import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ApiUtils from '@libs/ApiUtils'; +import NetworkConnection from '@libs/NetworkConnection'; import * as Network from '@userActions/Network'; import * as Session from '@userActions/Session'; import * as User from '@userActions/User'; @@ -83,7 +84,7 @@ function TestToolMenu({network}: TestToolMenuProps) { Network.simulatePoorConnection(!network?.shouldSimulatePoorConnection, network?.poorConnectionTimeoutID)} + onToggle={() => NetworkConnection.simulatePoorConnection(!network?.shouldSimulatePoorConnection, network?.poorConnectionTimeoutID)} disabled={isUsingImportedState} /> diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index cb9faae31ddd..e03f81abc508 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -1,4 +1,5 @@ import NetInfo from '@react-native-community/netinfo'; +import {differenceInHours} from 'date-fns/differenceInHours'; import isBoolean from 'lodash/isBoolean'; import throttle from 'lodash/throttle'; import Onyx from 'react-native-onyx'; @@ -6,6 +7,7 @@ import type {ValueOf} from 'type-fest'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ConnectionChanges} from '@src/types/onyx/Network'; import * as NetworkActions from './actions/Network'; import AppStateMonitor from './AppStateMonitor'; import Log from './Log'; @@ -51,6 +53,7 @@ const triggerReconnectionCallbacks = throttle( * then all of the reconnection callbacks are triggered */ function setOfflineStatus(isCurrentlyOffline: boolean, reason = ''): void { + trackConnectionChanges(); NetworkActions.setIsOffline(isCurrentlyOffline, reason); // When reconnecting, ie, going from offline to online, all the reconnection callbacks @@ -64,12 +67,25 @@ function setOfflineStatus(isCurrentlyOffline: boolean, reason = ''): void { // Update the offline status in response to changes in shouldForceOffline let shouldForceOffline = false; +let isPoorConnectionSimulated: boolean | undefined; +let connectionChanges: ConnectionChanges | undefined; Onyx.connect({ key: ONYXKEYS.NETWORK, callback: (network) => { if (!network) { return; } + + // Starts random network status change when shouldSimulatePoorConnection is turned into true + // or after app restart if shouldSimulatePoorConnection is true already + if (!isPoorConnectionSimulated && !!network.shouldSimulatePoorConnection) { + clearTimeout(network.poorConnectionTimeoutID); + setRandomNetworkStatus(true); + } + + isPoorConnectionSimulated = !!network.shouldSimulatePoorConnection; + connectionChanges = network.connectionChanges; + const currentShouldForceOffline = !!network.shouldForceOffline; if (currentShouldForceOffline === shouldForceOffline) { return; @@ -104,6 +120,59 @@ Onyx.connect({ }, }); +/** Sets online/offline connection randomly every 2-5 seconds */ +function setRandomNetworkStatus(initialCall = false) { + // The check to ensure no new timeouts are scheduled after poor connection simulation is stopped + if (!isPoorConnectionSimulated && !initialCall) { + setOfflineStatus(false); + return; + } + + const statuses = [CONST.NETWORK.NETWORK_STATUS.OFFLINE, CONST.NETWORK.NETWORK_STATUS.ONLINE]; + const randomStatus = statuses[Math.floor(Math.random() * statuses.length)]; + const randomInterval = Math.random() * (5000 - 2000) + 2000; // random interval between 2-5 seconds + Log.info(`[NetworkConnection] Set connection status "${randomStatus}" for ${randomInterval} sec`); + + setOfflineStatus(randomStatus === CONST.NETWORK.NETWORK_STATUS.OFFLINE); + + const timeoutID = setTimeout(setRandomNetworkStatus, randomInterval); + NetworkActions.setPoorConnectionTimeoutID(timeoutID); +} + +function simulatePoorConnection(shouldSimulatePoorConnection: boolean, poorConnectionTimeoutID: NodeJS.Timeout | undefined) { + if (!shouldSimulatePoorConnection) { + clearTimeout(poorConnectionTimeoutID); + NetworkActions.setPoorConnectionTimeoutID(undefined); + setOfflineStatus(false); + } + + NetworkActions.setShouldSimulatePoorConnection(shouldSimulatePoorConnection); +} + +/** Tracks how many times the connection has changed within the time period */ +function trackConnectionChanges() { + if (!connectionChanges?.startTime) { + NetworkActions.setConnectionChanges({startTime: new Date().getTime(), amount: 1}); + return; + } + + const diffInHours = differenceInHours(new Date(), connectionChanges.startTime); + const newAmount = (connectionChanges.amount ?? 0) + 1; + + if (diffInHours < 1) { + NetworkActions.setConnectionChanges({amount: newAmount}); + return; + } + + Log.info( + `[NetworkConnection] Connection has changed ${newAmount} time(s) for the last ${diffInHours} hour(s). Poor connection simulation is turned ${ + isPoorConnectionSimulated ? 'on' : 'off' + }`, + ); + + NetworkActions.setConnectionChanges({startTime: new Date().getTime(), amount: 0}); +} + /** * Set up the event listener for NetInfo to tell whether the user has * internet connectivity or not. This is more reliable than the Pusher @@ -227,5 +296,6 @@ export default { triggerReconnectionCallbacks, recheckNetworkConnection, subscribeToNetInfo, + simulatePoorConnection, }; export type {NetworkStatus}; diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 6c85edf8e048..6d75ec85aa0f 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -1,55 +1,9 @@ -import {differenceInHours} from 'date-fns/differenceInHours'; import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import type {NetworkStatus} from '@libs/NetworkConnection'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ConnectionChanges} from '@src/types/onyx/Network'; -let isPoorConnectionSimulated: boolean | undefined; -let connectionChanges: ConnectionChanges | undefined; -Onyx.connect({ - key: ONYXKEYS.NETWORK, - callback: (value) => { - if (!value) { - return; - } - - // Starts random network status change when shouldSimulatePoorConnection is turned into true - // or after app restart if shouldSimulatePoorConnection is true already - if (!isPoorConnectionSimulated && !!value.shouldSimulatePoorConnection) { - clearTimeout(value.poorConnectionTimeoutID); - setRandomNetworkStatus(true); - } - - isPoorConnectionSimulated = !!value.shouldSimulatePoorConnection; - connectionChanges = value.connectionChanges; - }, -}); - -function trackConnectionChanges() { - if (!connectionChanges?.startTime) { - Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges: {startTime: new Date().getTime(), amount: 1}}); - return; - } - - const diffInHours = differenceInHours(new Date(), connectionChanges.startTime); - const newAmount = (connectionChanges.amount ?? 0) + 1; - - if (diffInHours < 1) { - Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges: {amount: newAmount}}); - return; - } - - Log.info( - `[NetworkConnection] Connection has changed ${newAmount} time(s) for the last ${diffInHours} hour(s). Poor connection simulation is turned ${ - isPoorConnectionSimulated ? 'on' : 'off' - }`, - ); - - Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges: {startTime: new Date().getTime(), amount: 0}}); -} - function setIsOffline(isOffline: boolean, reason = '') { if (reason) { let textToLog = '[Network] Client is'; @@ -58,8 +12,6 @@ function setIsOffline(isOffline: boolean, reason = '') { Log.info(textToLog); } - trackConnectionChanges(); - Onyx.merge(ONYXKEYS.NETWORK, {isOffline}); } @@ -86,33 +38,12 @@ function setPoorConnectionTimeoutID(poorConnectionTimeoutID: NodeJS.Timeout | un Onyx.merge(ONYXKEYS.NETWORK, {poorConnectionTimeoutID}); } -function setRandomNetworkStatus(initialCall = false) { - // The check to ensure no new timeouts are scheduled after poor connection simulation is stopped - if (!isPoorConnectionSimulated && !initialCall) { - setShouldForceOffline(false); - return; - } - - const statuses = [CONST.NETWORK.NETWORK_STATUS.OFFLINE, CONST.NETWORK.NETWORK_STATUS.ONLINE]; - const randomStatus = statuses[Math.floor(Math.random() * statuses.length)]; - const randomInterval = Math.random() * (5000 - 2000) + 2000; // random interval between 2-5 seconds - Log.info(`[NetworkConnection] Set connection status "${randomStatus}" for ${randomInterval} sec`); - - setShouldForceOffline(randomStatus === CONST.NETWORK.NETWORK_STATUS.OFFLINE); - - const timeoutID = setTimeout(setRandomNetworkStatus, randomInterval); - - setPoorConnectionTimeoutID(timeoutID); +function setShouldSimulatePoorConnection(shouldSimulatePoorConnection: boolean) { + Onyx.merge(ONYXKEYS.NETWORK, {shouldSimulatePoorConnection}); } -function simulatePoorConnection(shouldSimulatePoorConnection: boolean, poorConnectionTimeoutID: NodeJS.Timeout | undefined) { - if (!shouldSimulatePoorConnection) { - clearTimeout(poorConnectionTimeoutID); - setPoorConnectionTimeoutID(undefined); - setShouldForceOffline(false); - } - - Onyx.merge(ONYXKEYS.NETWORK, {shouldSimulatePoorConnection}); +function setConnectionChanges(connectionChanges: ConnectionChanges) { + Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges}); } -export {setIsOffline, setShouldForceOffline, setShouldFailAllRequests, setTimeSkew, setNetWorkStatus, simulatePoorConnection}; +export {setIsOffline, setShouldForceOffline, setConnectionChanges, setShouldSimulatePoorConnection, setPoorConnectionTimeoutID, setShouldFailAllRequests, setTimeSkew, setNetWorkStatus}; From 714a2ecf0ba7564d20bcaffaece6db9299683fb2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 10 Dec 2024 17:44:15 +0100 Subject: [PATCH 05/12] Make it possible to turn on only one out of three network test preferences per time --- src/components/TestToolMenu.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index ea21a383d0cd..5277ba539374 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -75,7 +75,7 @@ function TestToolMenu({network}: TestToolMenuProps) { accessibilityLabel="Force offline" isOn={!!network?.shouldForceOffline} onToggle={() => Network.setShouldForceOffline(!network?.shouldForceOffline)} - disabled={!!isUsingImportedState || network?.shouldSimulatePoorConnection} + disabled={!!isUsingImportedState || !!network?.shouldSimulatePoorConnection || network?.shouldFailAllRequests} /> @@ -85,7 +85,7 @@ function TestToolMenu({network}: TestToolMenuProps) { accessibilityLabel="Simulate poor internet connection" isOn={!!network?.shouldSimulatePoorConnection} onToggle={() => NetworkConnection.simulatePoorConnection(!network?.shouldSimulatePoorConnection, network?.poorConnectionTimeoutID)} - disabled={isUsingImportedState} + disabled={!!isUsingImportedState || !!network?.shouldFailAllRequests || network?.shouldForceOffline} /> @@ -95,6 +95,7 @@ function TestToolMenu({network}: TestToolMenuProps) { accessibilityLabel="Simulate failing network requests" isOn={!!network?.shouldFailAllRequests} onToggle={() => Network.setShouldFailAllRequests(!network?.shouldFailAllRequests)} + disabled={!!network?.shouldForceOffline || network?.shouldSimulatePoorConnection} /> From 2be5a976bb14757089de5438a8c5bd002e107e29 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 11 Dec 2024 12:14:56 +0100 Subject: [PATCH 06/12] Set the correct connection after simulation is turned off --- src/components/TestToolMenu.tsx | 3 +-- src/libs/NetworkConnection.ts | 23 ++++++++++++----------- src/libs/actions/Network.ts | 6 +++++- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 5277ba539374..9e438c0b9688 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -4,7 +4,6 @@ import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ApiUtils from '@libs/ApiUtils'; -import NetworkConnection from '@libs/NetworkConnection'; import * as Network from '@userActions/Network'; import * as Session from '@userActions/Session'; import * as User from '@userActions/User'; @@ -84,7 +83,7 @@ function TestToolMenu({network}: TestToolMenuProps) { NetworkConnection.simulatePoorConnection(!network?.shouldSimulatePoorConnection, network?.poorConnectionTimeoutID)} + onToggle={() => Network.setShouldSimulatePoorConnection(!network?.shouldSimulatePoorConnection, network?.poorConnectionTimeoutID)} disabled={!!isUsingImportedState || !!network?.shouldFailAllRequests || network?.shouldForceOffline} /> diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index e03f81abc508..69b79502fa17 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -83,6 +83,18 @@ Onyx.connect({ setRandomNetworkStatus(true); } + // Stops random network status change when shouldSimulatePoorConnection is turned into false + if (isPoorConnectionSimulated && !network.shouldSimulatePoorConnection) { + NetInfo.fetch().then((state) => { + const isInternetUnreachable = !state.isInternetReachable; + const stringifiedState = JSON.stringify(state); + setOfflineStatus(isInternetUnreachable || !isServerUp, 'NetInfo checked if the internet is reachable'); + Log.info( + `[NetworkStatus] The poor connection simulation mode was turned off. Getting the device network status from NetInfo. Network state: ${stringifiedState}. Setting the offline status to: ${isInternetUnreachable}.`, + ); + }); + } + isPoorConnectionSimulated = !!network.shouldSimulatePoorConnection; connectionChanges = network.connectionChanges; @@ -139,16 +151,6 @@ function setRandomNetworkStatus(initialCall = false) { NetworkActions.setPoorConnectionTimeoutID(timeoutID); } -function simulatePoorConnection(shouldSimulatePoorConnection: boolean, poorConnectionTimeoutID: NodeJS.Timeout | undefined) { - if (!shouldSimulatePoorConnection) { - clearTimeout(poorConnectionTimeoutID); - NetworkActions.setPoorConnectionTimeoutID(undefined); - setOfflineStatus(false); - } - - NetworkActions.setShouldSimulatePoorConnection(shouldSimulatePoorConnection); -} - /** Tracks how many times the connection has changed within the time period */ function trackConnectionChanges() { if (!connectionChanges?.startTime) { @@ -296,6 +298,5 @@ export default { triggerReconnectionCallbacks, recheckNetworkConnection, subscribeToNetInfo, - simulatePoorConnection, }; export type {NetworkStatus}; diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 6d75ec85aa0f..52efb08f5e2e 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -38,7 +38,11 @@ function setPoorConnectionTimeoutID(poorConnectionTimeoutID: NodeJS.Timeout | un Onyx.merge(ONYXKEYS.NETWORK, {poorConnectionTimeoutID}); } -function setShouldSimulatePoorConnection(shouldSimulatePoorConnection: boolean) { +function setShouldSimulatePoorConnection(shouldSimulatePoorConnection: boolean, poorConnectionTimeoutID: NodeJS.Timeout | undefined) { + if (!shouldSimulatePoorConnection) { + clearTimeout(poorConnectionTimeoutID); + Onyx.merge(ONYXKEYS.NETWORK, {shouldSimulatePoorConnection, poorConnectionTimeoutID: undefined}); + } Onyx.merge(ONYXKEYS.NETWORK, {shouldSimulatePoorConnection}); } From 3e6f41997dcf946d0499c8367ff0e61478193e83 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 11 Dec 2024 12:16:57 +0100 Subject: [PATCH 07/12] Update comment --- src/libs/NetworkConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 69b79502fa17..1febaee4fb88 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -83,7 +83,7 @@ Onyx.connect({ setRandomNetworkStatus(true); } - // Stops random network status change when shouldSimulatePoorConnection is turned into false + // Fetch the NetInfo state to set the correct offline status when shouldSimulatePoorConnection is turned into false if (isPoorConnectionSimulated && !network.shouldSimulatePoorConnection) { NetInfo.fetch().then((state) => { const isInternetUnreachable = !state.isInternetReachable; From 4f0f128e32fd062a14769213aa258b47f325a061 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 11 Dec 2024 12:20:34 +0100 Subject: [PATCH 08/12] Clean up --- src/libs/NetworkConnection.ts | 1 - src/libs/actions/Network.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index 1febaee4fb88..c5d22e19dc28 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -136,7 +136,6 @@ Onyx.connect({ function setRandomNetworkStatus(initialCall = false) { // The check to ensure no new timeouts are scheduled after poor connection simulation is stopped if (!isPoorConnectionSimulated && !initialCall) { - setOfflineStatus(false); return; } diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 52efb08f5e2e..5bfcd411975f 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -11,7 +11,6 @@ function setIsOffline(isOffline: boolean, reason = '') { textToLog += ` because: ${reason}`; Log.info(textToLog); } - Onyx.merge(ONYXKEYS.NETWORK, {isOffline}); } From e2ed84639d4ea98e89410970c57e8bf1f6431107 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Wed, 11 Dec 2024 18:42:03 +0100 Subject: [PATCH 09/12] Applying reviewer feedback --- src/libs/NetworkConnection.ts | 42 ++++++++++++++++++++--------------- src/libs/actions/Network.ts | 1 + 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index c5d22e19dc28..d5e0b356d7ac 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -7,6 +7,7 @@ import type {ValueOf} from 'type-fest'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type Network from '@src/types/onyx/Network'; import type {ConnectionChanges} from '@src/types/onyx/Network'; import * as NetworkActions from './actions/Network'; import AppStateMonitor from './AppStateMonitor'; @@ -76,24 +77,7 @@ Onyx.connect({ return; } - // Starts random network status change when shouldSimulatePoorConnection is turned into true - // or after app restart if shouldSimulatePoorConnection is true already - if (!isPoorConnectionSimulated && !!network.shouldSimulatePoorConnection) { - clearTimeout(network.poorConnectionTimeoutID); - setRandomNetworkStatus(true); - } - - // Fetch the NetInfo state to set the correct offline status when shouldSimulatePoorConnection is turned into false - if (isPoorConnectionSimulated && !network.shouldSimulatePoorConnection) { - NetInfo.fetch().then((state) => { - const isInternetUnreachable = !state.isInternetReachable; - const stringifiedState = JSON.stringify(state); - setOfflineStatus(isInternetUnreachable || !isServerUp, 'NetInfo checked if the internet is reachable'); - Log.info( - `[NetworkStatus] The poor connection simulation mode was turned off. Getting the device network status from NetInfo. Network state: ${stringifiedState}. Setting the offline status to: ${isInternetUnreachable}.`, - ); - }); - } + simulatePoorConnection(network); isPoorConnectionSimulated = !!network.shouldSimulatePoorConnection; connectionChanges = network.connectionChanges; @@ -132,6 +116,28 @@ Onyx.connect({ }, }); +/** Controls poor connection simulation */ +function simulatePoorConnection(network: Network) { + // Starts random network status change when shouldSimulatePoorConnection is turned into true + // or after app restart if shouldSimulatePoorConnection is true already + if (!isPoorConnectionSimulated && !!network.shouldSimulatePoorConnection) { + clearTimeout(network.poorConnectionTimeoutID); + setRandomNetworkStatus(true); + } + + // Fetch the NetInfo state to set the correct offline status when shouldSimulatePoorConnection is turned into false + if (isPoorConnectionSimulated && !network.shouldSimulatePoorConnection) { + NetInfo.fetch().then((state) => { + const isInternetUnreachable = !state.isInternetReachable; + const stringifiedState = JSON.stringify(state); + setOfflineStatus(isInternetUnreachable || !isServerUp, 'NetInfo checked if the internet is reachable'); + Log.info( + `[NetworkStatus] The poor connection simulation mode was turned off. Getting the device network status from NetInfo. Network state: ${stringifiedState}. Setting the offline status to: ${isInternetUnreachable}.`, + ); + }); + } +} + /** Sets online/offline connection randomly every 2-5 seconds */ function setRandomNetworkStatus(initialCall = false) { // The check to ensure no new timeouts are scheduled after poor connection simulation is stopped diff --git a/src/libs/actions/Network.ts b/src/libs/actions/Network.ts index 5bfcd411975f..f2228a008dad 100644 --- a/src/libs/actions/Network.ts +++ b/src/libs/actions/Network.ts @@ -41,6 +41,7 @@ function setShouldSimulatePoorConnection(shouldSimulatePoorConnection: boolean, if (!shouldSimulatePoorConnection) { clearTimeout(poorConnectionTimeoutID); Onyx.merge(ONYXKEYS.NETWORK, {shouldSimulatePoorConnection, poorConnectionTimeoutID: undefined}); + return; } Onyx.merge(ONYXKEYS.NETWORK, {shouldSimulatePoorConnection}); } From 10ee0d8b3c0b799567dd2b8efff4ce901d02c5ff Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 16 Dec 2024 14:19:09 +0100 Subject: [PATCH 10/12] Remove extra comment --- src/libs/NetworkConnection.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/NetworkConnection.ts b/src/libs/NetworkConnection.ts index d5e0b356d7ac..b91190e4ee81 100644 --- a/src/libs/NetworkConnection.ts +++ b/src/libs/NetworkConnection.ts @@ -116,7 +116,6 @@ Onyx.connect({ }, }); -/** Controls poor connection simulation */ function simulatePoorConnection(network: Network) { // Starts random network status change when shouldSimulatePoorConnection is turned into true // or after app restart if shouldSimulatePoorConnection is true already From 53b39c58dca9e3d47fa683945b68ce8001802810 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 16 Dec 2024 18:45:01 +0100 Subject: [PATCH 11/12] Add poor connection simulation tests --- src/components/TestToolMenu.tsx | 13 +++----- .../unit/{NetworkTest.ts => NetworkTest.tsx} | 30 +++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) rename tests/unit/{NetworkTest.ts => NetworkTest.tsx} (91%) diff --git a/src/components/TestToolMenu.tsx b/src/components/TestToolMenu.tsx index 9e438c0b9688..89f3fbc528ef 100644 --- a/src/components/TestToolMenu.tsx +++ b/src/components/TestToolMenu.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -9,18 +8,13 @@ import * as Session from '@userActions/Session'; import * as User from '@userActions/User'; import CONFIG from '@src/CONFIG'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Network as NetworkOnyx, User as UserOnyx} from '@src/types/onyx'; +import type {User as UserOnyx} from '@src/types/onyx'; import Button from './Button'; -import {withNetwork} from './OnyxProvider'; import Switch from './Switch'; import TestCrash from './TestCrash'; import TestToolRow from './TestToolRow'; import Text from './Text'; -type TestToolMenuProps = { - /** Network object in Onyx */ - network: OnyxEntry; -}; const USER_DEFAULT: UserOnyx = { shouldUseStagingServer: undefined, isSubscribedToNewsletter: false, @@ -30,7 +24,8 @@ const USER_DEFAULT: UserOnyx = { isDebugModeEnabled: false, }; -function TestToolMenu({network}: TestToolMenuProps) { +function TestToolMenu() { + const [network] = useOnyx(ONYXKEYS.NETWORK); const [user = USER_DEFAULT] = useOnyx(ONYXKEYS.USER); const [isUsingImportedState] = useOnyx(ONYXKEYS.IS_USING_IMPORTED_STATE); const shouldUseStagingServer = user?.shouldUseStagingServer ?? ApiUtils.isUsingStagingApi(); @@ -123,4 +118,4 @@ function TestToolMenu({network}: TestToolMenuProps) { TestToolMenu.displayName = 'TestToolMenu'; -export default withNetwork()(TestToolMenu); +export default TestToolMenu; diff --git a/tests/unit/NetworkTest.ts b/tests/unit/NetworkTest.tsx similarity index 91% rename from tests/unit/NetworkTest.ts rename to tests/unit/NetworkTest.tsx index 2998aa0e8a25..e0eae70051b7 100644 --- a/tests/unit/NetworkTest.ts +++ b/tests/unit/NetworkTest.tsx @@ -1,9 +1,13 @@ +import {render, screen} from '@testing-library/react-native'; +import {sub as dateSubtract} from 'date-fns/sub'; import type {Mock} from 'jest-mock'; import type {OnyxEntry} from 'react-native-onyx'; import MockedOnyx from 'react-native-onyx'; +import TestToolMenu from '@components/TestToolMenu'; import * as App from '@libs/actions/App'; import {resetReauthentication} from '@libs/Middleware/Reauthentication'; import CONST from '@src/CONST'; +import * as NetworkActions from '@src/libs/actions/Network'; import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import * as PersistedRequests from '@src/libs/actions/PersistedRequests'; import * as PersonalDetails from '@src/libs/actions/PersonalDetails'; @@ -391,4 +395,30 @@ describe('NetworkTests', () => { expect(xhr.mock.calls.length).toBe(3); }); }); + + test('poor connection simulation', async () => { + const logSpy = jest.spyOn(Log, 'info'); + + render(); + + expect(screen.getByAccessibilityHint('Force offline')).not.toBeDisabled(); + expect(screen.getByAccessibilityHint('Simulate failing network requests')).not.toBeDisabled(); + + NetworkActions.setShouldSimulatePoorConnection(true, undefined); + await waitForBatchedUpdates(); + + expect(logSpy).toHaveBeenCalledWith(expect.stringMatching(/\[NetworkConnection\] Set connection status "(online|offline)" for (\d+(?:\.\d+)?) sec/)); + expect(screen.getByAccessibilityHint('Force offline')).toBeDisabled(); + expect(screen.getByAccessibilityHint('Simulate failing network requests')).toBeDisabled(); + }); + + test('connection changes tracking', async () => { + const logSpy = jest.spyOn(Log, 'info'); + + Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges: {amount: 5, startTime: dateSubtract(new Date(), {hours: 1}).getTime()}}); + await waitForBatchedUpdates(); + NetworkConnection.setOfflineStatus(true); + + expect(logSpy).toHaveBeenCalledWith('[NetworkConnection] Connection has changed 6 time(s) for the last 1 hour(s). Poor connection simulation is turned off'); + }); }); From ba653602c4557cdffa98d987c13a8da36e308467 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 17 Dec 2024 09:14:01 +0100 Subject: [PATCH 12/12] Add tests comments --- tests/unit/NetworkTest.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/unit/NetworkTest.tsx b/tests/unit/NetworkTest.tsx index e0eae70051b7..abd5011769a4 100644 --- a/tests/unit/NetworkTest.tsx +++ b/tests/unit/NetworkTest.tsx @@ -399,14 +399,16 @@ describe('NetworkTests', () => { test('poor connection simulation', async () => { const logSpy = jest.spyOn(Log, 'info'); + // Given an opened test tool menu render(); - expect(screen.getByAccessibilityHint('Force offline')).not.toBeDisabled(); expect(screen.getByAccessibilityHint('Simulate failing network requests')).not.toBeDisabled(); + // When the connection simulation is turned on NetworkActions.setShouldSimulatePoorConnection(true, undefined); await waitForBatchedUpdates(); + // Then the connection status change log should be displayed as well as Force offline/Simulate failing network requests toggles should be disabled expect(logSpy).toHaveBeenCalledWith(expect.stringMatching(/\[NetworkConnection\] Set connection status "(online|offline)" for (\d+(?:\.\d+)?) sec/)); expect(screen.getByAccessibilityHint('Force offline')).toBeDisabled(); expect(screen.getByAccessibilityHint('Simulate failing network requests')).toBeDisabled(); @@ -415,10 +417,14 @@ describe('NetworkTests', () => { test('connection changes tracking', async () => { const logSpy = jest.spyOn(Log, 'info'); + // Given tracked connection changes started at least an hour ago Onyx.merge(ONYXKEYS.NETWORK, {connectionChanges: {amount: 5, startTime: dateSubtract(new Date(), {hours: 1}).getTime()}}); await waitForBatchedUpdates(); + + // When the connection is changed one more time NetworkConnection.setOfflineStatus(true); + // Then the log with information about connection changes since the start time should be shown expect(logSpy).toHaveBeenCalledWith('[NetworkConnection] Connection has changed 6 time(s) for the last 1 hour(s). Poor connection simulation is turned off'); }); });