mirror of
https://github.com/trezor/trezor-suite.git
synced 2026-02-20 00:33:07 +01:00
feat(suite-native): Mobile Trade: Country of residence settings screen
This commit is contained in:
committed by
Jiří Bažant
parent
f8d35cf591
commit
0805b13ec8
@@ -87,6 +87,7 @@
|
||||
"@suite-native/test-utils": "workspace:*",
|
||||
"@suite-native/theme": "workspace:*",
|
||||
"@suite-native/toasts": "workspace:*",
|
||||
"@suite-native/trading-residence": "workspace:*",
|
||||
"@suite-native/transactions": "workspace:*",
|
||||
"@trezor/blockchain-link-types": "workspace:*",
|
||||
"@trezor/bundler-security": "workspace:*",
|
||||
|
||||
@@ -36,6 +36,10 @@ import {
|
||||
} from '@suite-native/navigation';
|
||||
import { ReceiveStackNavigator } from '@suite-native/receive';
|
||||
import { selectIsOnboardingFinished } from '@suite-native/settings';
|
||||
import {
|
||||
TradingLocationModalScreen,
|
||||
selectShouldDisplayTradingResidenceOnboarding,
|
||||
} from '@suite-native/trading-residence';
|
||||
import { TransactionDetailScreen } from '@suite-native/transactions';
|
||||
|
||||
import { AppTabNavigator } from './AppTabNavigator';
|
||||
@@ -45,13 +49,20 @@ const RootStack = createNativeStackNavigator<RootStackParamList>();
|
||||
|
||||
export const RootStackNavigator = () => {
|
||||
const isOnboardingFinished = useSelector(selectIsOnboardingFinished);
|
||||
const shouldDisplayTradingResidenceOnboarding = useSelector(
|
||||
selectShouldDisplayTradingResidenceOnboarding,
|
||||
);
|
||||
|
||||
const getInitialRouteName = () => {
|
||||
if (isOnboardingFinished) {
|
||||
return RootStackRoutes.AppTabs;
|
||||
if (!isOnboardingFinished) {
|
||||
return RootStackRoutes.OnboardingStack;
|
||||
}
|
||||
|
||||
return RootStackRoutes.OnboardingStack;
|
||||
if (shouldDisplayTradingResidenceOnboarding) {
|
||||
return RootStackRoutes.TradingLocationModal;
|
||||
}
|
||||
|
||||
return RootStackRoutes.AppTabs;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -157,6 +168,10 @@ export const RootStackNavigator = () => {
|
||||
name={RootStackRoutes.TradingWebView}
|
||||
component={TradingWebViewScreen}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name={RootStackRoutes.TradingLocationModal}
|
||||
component={TradingLocationModalScreen}
|
||||
/>
|
||||
</RootStack.Group>
|
||||
</RootStack.Navigator>
|
||||
);
|
||||
|
||||
@@ -36,6 +36,7 @@ describe('AppTabNavigator', () => {
|
||||
[FeatureFlag.IsTradingBuyEnabled]: false,
|
||||
[FeatureFlag.IsTradingExchangeEnabled]: false,
|
||||
[FeatureFlag.IsTradingSellEnabled]: false,
|
||||
[FeatureFlag.IsTradingResidenceCheckEnabled]: false,
|
||||
},
|
||||
messageSystem: {
|
||||
validMessages: {
|
||||
@@ -80,6 +81,7 @@ describe('AppTabNavigator', () => {
|
||||
featureFlags: {
|
||||
...featureFlagsInitialState,
|
||||
[FeatureFlag.IsTradingBuyEnabled]: true,
|
||||
[FeatureFlag.IsTradingResidenceCheckEnabled]: false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
{ "path": "../test-utils" },
|
||||
{ "path": "../theme" },
|
||||
{ "path": "../toasts" },
|
||||
{ "path": "../trading-residence" },
|
||||
{ "path": "../transactions" },
|
||||
{
|
||||
"path": "../../packages/blockchain-link-types"
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "2.9.1",
|
||||
"@suite-native/config": "workspace:*",
|
||||
"@trezor/env-utils": "workspace:*",
|
||||
"react-native": "0.81.4",
|
||||
"react-redux": "9.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
import { Platform } from 'react-native';
|
||||
|
||||
describe('featureFlagsSlice', () => {
|
||||
afterEach(() => {
|
||||
Platform.OS = 'ios';
|
||||
jest.resetModules();
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('initial state', () => {
|
||||
it('should have correct initial state', () => {
|
||||
it('should have correct initial state on iOS', () => {
|
||||
Platform.OS = 'ios';
|
||||
const { featureFlagsReducer } = require('../featureFlagsSlice');
|
||||
|
||||
const initialState = featureFlagsReducer(undefined, { type: 'undefined_action' });
|
||||
|
||||
expect(initialState).toEqual({
|
||||
areDebugOnlyNetworksEnabled: false,
|
||||
areExperimentalOnlyNetworksEnabled: false,
|
||||
areTradingExchangeDexesEnabled: false,
|
||||
isCardanoSendEnabled: false,
|
||||
isDebugKeysAllowed: false,
|
||||
isLocalFirstStorageEnabled: false,
|
||||
isLocalizationEnabled: false,
|
||||
isTradingBuyEnabled: false,
|
||||
isTradingExchangeEnabled: false,
|
||||
isTradingResidenceCheckEnabled: true,
|
||||
isTradingSellEnabled: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should have correct initial state on android', () => {
|
||||
Platform.OS = 'android';
|
||||
const { featureFlagsReducer } = require('../featureFlagsSlice');
|
||||
|
||||
const initialState = featureFlagsReducer(undefined, { type: 'undefined_action' });
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { isIOs } from '@trezor/env-utils';
|
||||
|
||||
export const FeatureFlag = {
|
||||
AreDebugOnlyNetworksEnabled: 'areDebugOnlyNetworksEnabled',
|
||||
AreExperimentalOnlyNetworksEnabled: 'areExperimentalOnlyNetworksEnabled',
|
||||
@@ -38,7 +40,8 @@ export const featureFlagsInitialState: FeatureFlagsState = {
|
||||
[FeatureFlag.AreTradingExchangeDexesEnabled]:
|
||||
process.env.EXPO_PUBLIC_FF_ARE_TRADING_EXCHANGE_DEXES_ENABLED === 'true',
|
||||
[FeatureFlag.IsTradingResidenceCheckEnabled]:
|
||||
process.env.EXPO_PUBLIC_FF_IS_TRADING_RESIDENCE_CHECK_ENABLED === 'true',
|
||||
process.env.EXPO_PUBLIC_FF_IS_TRADING_RESIDENCE_CHECK_ENABLED === 'true' ||
|
||||
(isIOs() && process.env.EXPO_PUBLIC_FF_IS_TRADING_RESIDENCE_CHECK_ENABLED !== 'false'),
|
||||
[FeatureFlag.IsLocalizationEnabled]:
|
||||
process.env.EXPO_PUBLIC_FF_IS_LOCALIZATION_ENABLED === 'true',
|
||||
[FeatureFlag.IsLocalFirstStorageEnabled]:
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": { "outDir": "libDev" },
|
||||
"references": [{ "path": "../config" }]
|
||||
"references": [
|
||||
{ "path": "../config" },
|
||||
{ "path": "../../packages/env-utils" }
|
||||
]
|
||||
}
|
||||
|
||||
5
suite-native/module-onboarding/jest.config.js
Normal file
5
suite-native/module-onboarding/jest.config.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const baseConfig = require('../../jest.config.native');
|
||||
|
||||
module.exports = {
|
||||
...baseConfig,
|
||||
};
|
||||
@@ -7,7 +7,8 @@
|
||||
"main": "src/index",
|
||||
"scripts": {
|
||||
"depcheck": "yarn g:depcheck",
|
||||
"type-check": "yarn g:tsc --build"
|
||||
"type-check": "yarn g:tsc --build",
|
||||
"test:unit": "yarn g:jest --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "7.12.4",
|
||||
@@ -21,6 +22,7 @@
|
||||
"@suite-native/link": "workspace:*",
|
||||
"@suite-native/navigation": "workspace:*",
|
||||
"@suite-native/settings": "workspace:*",
|
||||
"@suite-native/trading-residence": "workspace:*",
|
||||
"@trezor/env-utils": "workspace:*",
|
||||
"@trezor/styles": "workspace:*",
|
||||
"@trezor/theme": "workspace:*",
|
||||
@@ -31,5 +33,8 @@
|
||||
"react-native": "0.81.4",
|
||||
"react-native-reanimated": "~3.19.3",
|
||||
"react-redux": "9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@suite-native/test-utils": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { HomeStackRoutes, RootStackRoutes } from '@suite-native/navigation';
|
||||
import { setIsOnboardingFinished } from '@suite-native/settings';
|
||||
import {
|
||||
TestStore,
|
||||
act,
|
||||
initStore,
|
||||
renderHookWithStoreProviderAsync,
|
||||
} from '@suite-native/test-utils';
|
||||
|
||||
import { useExitOnboardingFlow } from '../useExitOnboardingFlow';
|
||||
|
||||
const mockNavigationDispatch = jest.fn();
|
||||
|
||||
jest.mock('@react-navigation/core', () => ({
|
||||
...jest.requireActual('@react-navigation/core'),
|
||||
useNavigation: () => ({
|
||||
dispatch: mockNavigationDispatch,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('useExitOnboardingFlow', () => {
|
||||
let store: TestStore;
|
||||
|
||||
const renderUseExitOnboardingFlow = () =>
|
||||
renderHookWithStoreProviderAsync(() => useExitOnboardingFlow(), { store });
|
||||
|
||||
beforeEach(async () => {
|
||||
store = await initStore();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should set onboarding flag and navigate', async () => {
|
||||
const dispatchSpy = jest.spyOn(store, 'dispatch');
|
||||
const { result } = await renderUseExitOnboardingFlow();
|
||||
|
||||
// call the returned callback
|
||||
act(() => {
|
||||
result.current();
|
||||
});
|
||||
|
||||
// assert that onboarding finished action was dispatched
|
||||
expect(dispatchSpy).toHaveBeenCalledWith(setIsOnboardingFinished());
|
||||
|
||||
// assert that navigation.reset was dispatched to navigate to AppTabs -> Home
|
||||
expect(mockNavigationDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(mockNavigationDispatch).toHaveBeenCalledWith({
|
||||
payload: {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: RootStackRoutes.AppTabs,
|
||||
params: { screen: HomeStackRoutes.Home },
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'RESET',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { CommonActions, useNavigation } from '@react-navigation/core';
|
||||
|
||||
import { HomeStackRoutes, RootStackRoutes } from '@suite-native/navigation';
|
||||
import { setIsOnboardingFinished } from '@suite-native/settings';
|
||||
|
||||
export const useExitOnboardingFlow = () => {
|
||||
const dispatch = useDispatch();
|
||||
const navigation = useNavigation();
|
||||
|
||||
return () => {
|
||||
dispatch(setIsOnboardingFinished());
|
||||
|
||||
// TODO: COSMETIC IMPROVEMENT: redirect to home only if there is no device connected. In case of device connected,
|
||||
// the redirect is handled in useHandleDeviceConnection hook. in reaction to the `setIsOnboardingFinished` call.
|
||||
navigation.dispatch(
|
||||
CommonActions.reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: RootStackRoutes.AppTabs,
|
||||
params: { screen: HomeStackRoutes.Home },
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
|
||||
import { AnalyticsConsentScreen } from '../screens/AnalyticsConsentScreen';
|
||||
import { BiometricsScreen } from '../screens/BiometricsScreen';
|
||||
import { TradingLocationScreen } from '../screens/TradingLocationScreen';
|
||||
import { WelcomeScreen } from '../screens/WelcomeScreen';
|
||||
|
||||
export const OnboardingStack = createNativeStackNavigator<OnboardingStackParamList>();
|
||||
@@ -26,5 +27,9 @@ export const OnboardingStackNavigator = () => (
|
||||
name={OnboardingStackRoutes.Biometrics}
|
||||
component={BiometricsScreen}
|
||||
/>
|
||||
<OnboardingStack.Screen
|
||||
name={OnboardingStackRoutes.TradingLocation}
|
||||
component={TradingLocationScreen}
|
||||
/>
|
||||
</OnboardingStack.Navigator>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { CommonActions } from '@react-navigation/core';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { EventType, analytics } from '@suite-native/analytics';
|
||||
import { Box, Button, HStack, Text, VStack } from '@suite-native/atoms';
|
||||
@@ -8,29 +6,33 @@ import { BiometricsSvg, useBiometricsSettings } from '@suite-native/biometrics';
|
||||
import { Icon } from '@suite-native/icons';
|
||||
import { Translation } from '@suite-native/intl';
|
||||
import {
|
||||
HomeStackRoutes,
|
||||
OnboardingStackParamList,
|
||||
OnboardingStackRoutes,
|
||||
RootStackRoutes,
|
||||
Screen,
|
||||
ScreenHeader,
|
||||
StackProps,
|
||||
} from '@suite-native/navigation';
|
||||
import { setIsOnboardingFinished } from '@suite-native/settings';
|
||||
import { selectIsTradingResidenceCheckEnabled } from '@suite-native/trading-residence';
|
||||
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
|
||||
|
||||
import { useExitOnboardingFlow } from '../hooks/useExitOnboardingFlow';
|
||||
|
||||
export type BiometricsScreenProps = StackProps<
|
||||
OnboardingStackParamList,
|
||||
OnboardingStackRoutes.Biometrics
|
||||
>;
|
||||
|
||||
const titleStyle = prepareNativeStyle(_ => ({
|
||||
// this title should have smaller letter spacing by design.
|
||||
letterSpacing: -1.4,
|
||||
}));
|
||||
|
||||
export const BiometricsScreen = ({
|
||||
navigation,
|
||||
}: StackProps<OnboardingStackParamList, OnboardingStackRoutes.Biometrics>) => {
|
||||
export const BiometricsScreen = ({ navigation }: BiometricsScreenProps) => {
|
||||
const { applyStyle } = useNativeStyles();
|
||||
const { toggleBiometricsOption } = useBiometricsSettings();
|
||||
const exitOnboardingFlow = useExitOnboardingFlow();
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const shouldDisplayTradingLocationScreen = useSelector(selectIsTradingResidenceCheckEnabled);
|
||||
|
||||
const enableBiometrics = async () => {
|
||||
const result = await toggleBiometricsOption();
|
||||
@@ -45,31 +47,21 @@ export const BiometricsScreen = ({
|
||||
}
|
||||
};
|
||||
|
||||
const exitOnboardingFlow = () => {
|
||||
dispatch(setIsOnboardingFinished());
|
||||
|
||||
navigation.dispatch(
|
||||
CommonActions.reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: RootStackRoutes.AppTabs,
|
||||
params: {
|
||||
screen: HomeStackRoutes.Home,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const handleRedirect = () => {
|
||||
if (shouldDisplayTradingLocationScreen) {
|
||||
navigation.navigate(OnboardingStackRoutes.TradingLocation);
|
||||
} else {
|
||||
exitOnboardingFlow();
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnableButtonPress = async () => {
|
||||
await enableBiometrics();
|
||||
exitOnboardingFlow();
|
||||
handleRedirect();
|
||||
};
|
||||
|
||||
const handleNotNowButtonPress = () => {
|
||||
exitOnboardingFlow();
|
||||
handleRedirect();
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Screen, ScreenHeader } from '@suite-native/navigation';
|
||||
import { OnboardingButtons, TradingLocationSettings } from '@suite-native/trading-residence';
|
||||
|
||||
import { useExitOnboardingFlow } from '../hooks/useExitOnboardingFlow';
|
||||
|
||||
export const TradingLocationScreen = () => {
|
||||
const exitOnboardingFlow = useExitOnboardingFlow();
|
||||
|
||||
return (
|
||||
<Screen header={<ScreenHeader />}>
|
||||
<TradingLocationSettings context="onboarding">
|
||||
<OnboardingButtons afterPress={exitOnboardingFlow} />
|
||||
</TradingLocationSettings>
|
||||
</Screen>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import { OnboardingStackRoutes } from '@suite-native/navigation';
|
||||
import {
|
||||
TestStore,
|
||||
initStore,
|
||||
renderWithStoreProviderAsync,
|
||||
userEvent,
|
||||
} from '@suite-native/test-utils';
|
||||
|
||||
import { BiometricsScreen, BiometricsScreenProps } from '../BiometricsScreen';
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
const mockNavigationDispatch = jest.fn();
|
||||
const mockRoute = {
|
||||
key: 'BiometricsScreen',
|
||||
name: OnboardingStackRoutes.Biometrics,
|
||||
params: undefined,
|
||||
} as const;
|
||||
|
||||
jest.mock('@react-navigation/core', () => ({
|
||||
...jest.requireActual('@react-navigation/core'),
|
||||
useNavigation: () => ({
|
||||
navigate: mockNavigate,
|
||||
dispatch: mockNavigationDispatch,
|
||||
}),
|
||||
useRoute: () => mockRoute,
|
||||
}));
|
||||
|
||||
describe('BiometricsScreen', () => {
|
||||
let store: TestStore;
|
||||
|
||||
const renderBiometricsScreen = () =>
|
||||
renderWithStoreProviderAsync(
|
||||
<BiometricsScreen
|
||||
navigation={
|
||||
{ navigate: mockNavigate } as unknown as BiometricsScreenProps['navigation']
|
||||
}
|
||||
route={mockRoute}
|
||||
/>,
|
||||
{ store },
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should redirect to TradingLocation screen on Skip press when isTradingResidenceCheckEnabled is set to true', async () => {
|
||||
store = await initStore({
|
||||
featureFlags: {
|
||||
isTradingResidenceCheckEnabled: true,
|
||||
},
|
||||
});
|
||||
const { getByText } = await renderBiometricsScreen();
|
||||
|
||||
await userEvent.press(getByText('Not now'));
|
||||
|
||||
expect(mockNavigate).toHaveBeenCalledWith(OnboardingStackRoutes.TradingLocation);
|
||||
});
|
||||
|
||||
it('should redirect to Home screen on Skip press when isTradingResidenceCheckEnabled is set to false', async () => {
|
||||
store = await initStore({
|
||||
featureFlags: {
|
||||
isTradingResidenceCheckEnabled: false,
|
||||
},
|
||||
});
|
||||
const { getByText } = await renderBiometricsScreen();
|
||||
|
||||
await userEvent.press(getByText('Not now'));
|
||||
|
||||
expect(mockNavigationDispatch).toHaveBeenCalledWith({
|
||||
payload: { index: 0, routes: [{ name: 'AppTabs', params: { screen: 'Home' } }] },
|
||||
type: 'RESET',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,25 +1,32 @@
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
|
||||
import { EventType, analytics } from '@suite-native/analytics';
|
||||
import { TradingStackParamList, TradingStackRoutes } from '@suite-native/navigation';
|
||||
import { renderWithStoreProviderAsync, userEvent } from '@suite-native/test-utils';
|
||||
|
||||
import { TradingLocationOnboardingScreen } from '../TradingLocationOnboardingScreen';
|
||||
import { TradingLocationScreen } from '../TradingLocationScreen';
|
||||
|
||||
const mockExitOnboardingFlow = jest.fn();
|
||||
|
||||
jest.mock('@react-navigation/core', () => ({
|
||||
...jest.requireActual('@react-navigation/core'),
|
||||
|
||||
jest.mock('@react-navigation/native', () => ({
|
||||
...jest.requireActual('@react-navigation/native'),
|
||||
useRoute: () =>
|
||||
({
|
||||
params: undefined,
|
||||
}) as RouteProp<TradingStackParamList, TradingStackRoutes.TradingHistory>,
|
||||
}));
|
||||
|
||||
jest.mock('../../hooks/useExitOnboardingFlow', () => ({
|
||||
useExitOnboardingFlow: () => mockExitOnboardingFlow,
|
||||
}));
|
||||
|
||||
describe('TradingLocationOnboardingScreen', () => {
|
||||
const renderTradingLocationOnboardingScreen = () =>
|
||||
renderWithStoreProviderAsync(<TradingLocationOnboardingScreen />);
|
||||
const renderTradingLocationScreen = () =>
|
||||
renderWithStoreProviderAsync(<TradingLocationScreen />);
|
||||
|
||||
it('should render all components', async () => {
|
||||
const { getByText, getByLabelText } = await renderTradingLocationOnboardingScreen();
|
||||
const { getByText, getByLabelText } = await renderTradingLocationScreen();
|
||||
|
||||
expect(getByText('Confirm your location to enable trading')).toBeOnTheScreen();
|
||||
expect(getByText('Confirm location')).toBeOnTheScreen();
|
||||
@@ -30,7 +37,7 @@ describe('TradingLocationOnboardingScreen', () => {
|
||||
|
||||
it('should log analytics event on country change', async () => {
|
||||
const analyticsSpy = jest.spyOn(analytics, 'report');
|
||||
const { getByText } = await renderTradingLocationOnboardingScreen();
|
||||
const { getByText } = await renderTradingLocationScreen();
|
||||
|
||||
await userEvent.press(getByText('Country of residence'));
|
||||
await userEvent.press(getByText('🇦🇷 Argentina'));
|
||||
@@ -44,4 +51,11 @@ describe('TradingLocationOnboardingScreen', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should use exitOnboardingFlow on button press', async () => {
|
||||
const { getByText } = await renderTradingLocationScreen();
|
||||
await userEvent.press(getByText('Not now'));
|
||||
|
||||
expect(mockExitOnboardingFlow).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -10,10 +10,12 @@
|
||||
{ "path": "../link" },
|
||||
{ "path": "../navigation" },
|
||||
{ "path": "../settings" },
|
||||
{ "path": "../trading-residence" },
|
||||
{ "path": "../../packages/env-utils" },
|
||||
{ "path": "../../packages/styles" },
|
||||
{ "path": "../../packages/theme" },
|
||||
{ "path": "../../packages/urls" },
|
||||
{ "path": "../../packages/utils" }
|
||||
{ "path": "../../packages/utils" },
|
||||
{ "path": "../test-utils" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { FeatureFlag, featureFlagsInitialState } from '@suite-native/feature-flags';
|
||||
|
||||
export const residenceCheckDisabledState = {
|
||||
featureFlags: {
|
||||
...featureFlagsInitialState,
|
||||
[FeatureFlag.IsTradingResidenceCheckEnabled]: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const residenceCheckEnabledState = {
|
||||
featureFlags: {
|
||||
...featureFlagsInitialState,
|
||||
[FeatureFlag.IsTradingResidenceCheckEnabled]: true,
|
||||
},
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
renderWithStoreProviderAsync,
|
||||
} from '@suite-native/test-utils';
|
||||
|
||||
import { residenceCheckDisabledState } from '../../../__fixtures__/residenceCheckState';
|
||||
import { btcAsset } from '../../../__fixtures__/tradeableAssets';
|
||||
import { getInitializedTradingState } from '../../../__fixtures__/tradingState';
|
||||
import { useBuyForm } from '../../../hooks/buy/useBuyForm';
|
||||
@@ -27,8 +28,11 @@ describe('BuyForm', () => {
|
||||
});
|
||||
|
||||
it('should render when buy data are not preloaded', async () => {
|
||||
const { result } = await renderFormHook({});
|
||||
const { queryByText, getByText, getByLabelText } = await renderBuyForm({}, result.current);
|
||||
const { result } = await renderFormHook(residenceCheckDisabledState);
|
||||
const { queryByText, getByText, getByLabelText } = await renderBuyForm(
|
||||
residenceCheckDisabledState,
|
||||
result.current,
|
||||
);
|
||||
|
||||
expect(getByText('You pay')).toBeTruthy();
|
||||
expect(getByLabelText('Select asset')).toHaveTextContent(/Select asset/);
|
||||
@@ -43,7 +47,10 @@ describe('BuyForm', () => {
|
||||
|
||||
describe('with preloaded buy data', () => {
|
||||
let form: BuyFormType;
|
||||
const preloadedState = { wallet: { trading: getInitializedTradingState() } };
|
||||
const preloadedState = {
|
||||
wallet: { trading: getInitializedTradingState() },
|
||||
...residenceCheckDisabledState,
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const { result } = await renderFormHook(preloadedState);
|
||||
|
||||
@@ -29,6 +29,7 @@ describe('Header', () => {
|
||||
[FeatureFlag.IsTradingExchangeEnabled]: exchangeEnabled,
|
||||
[FeatureFlag.IsTradingSellEnabled]: sellEnabled,
|
||||
[FeatureFlag.AreTradingExchangeDexesEnabled]: areTradingExchangeDexesEnabled,
|
||||
[FeatureFlag.IsTradingResidenceCheckEnabled]: false,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { TradingCountryOption } from '@suite-common/trading';
|
||||
import { yup } from '@suite-common/validators';
|
||||
import { EventType, analytics } from '@suite-native/analytics';
|
||||
import { FeatureFlag } from '@suite-native/feature-flags';
|
||||
import { Form, useForm } from '@suite-native/forms';
|
||||
import type { UseFormReturn } from '@suite-native/forms';
|
||||
import {
|
||||
@@ -12,14 +11,19 @@ import {
|
||||
userEvent,
|
||||
} from '@suite-native/test-utils';
|
||||
|
||||
import {
|
||||
residenceCheckDisabledState,
|
||||
residenceCheckEnabledState,
|
||||
} from '../../../__fixtures__/residenceCheckState';
|
||||
import { TradingCountryOfResidencePicker } from '../TradingCountryOfResidencePicker';
|
||||
|
||||
describe('TradingCountryOfResidencePicker', () => {
|
||||
let form: UseFormReturn<{ country: TradingCountryOption }>;
|
||||
|
||||
const renderForm = () =>
|
||||
renderHookWithStoreProviderAsync(() =>
|
||||
useForm<{ country: TradingCountryOption }>({ validation: yup.object() }),
|
||||
renderHookWithStoreProviderAsync(
|
||||
() => useForm<{ country: TradingCountryOption }>({ validation: yup.object() }),
|
||||
{ preloadedState: residenceCheckDisabledState },
|
||||
);
|
||||
|
||||
const renderCountryOfResidencePicker = (preloadedState: PreloadedState) =>
|
||||
@@ -41,7 +45,7 @@ describe('TradingCountryOfResidencePicker', () => {
|
||||
form.setValue('country', { value: 'US', label: 'United States' });
|
||||
});
|
||||
|
||||
const { getByTestId } = await renderCountryOfResidencePicker({});
|
||||
const { getByTestId } = await renderCountryOfResidencePicker(residenceCheckDisabledState);
|
||||
|
||||
expect(getByTestId('testID/value')).toHaveTextContent('United States');
|
||||
});
|
||||
@@ -49,7 +53,7 @@ describe('TradingCountryOfResidencePicker', () => {
|
||||
it('should call analytics on country change', async () => {
|
||||
const reportSpy = jest.spyOn(analytics, 'report');
|
||||
|
||||
const { getByText } = await renderCountryOfResidencePicker({});
|
||||
const { getByText } = await renderCountryOfResidencePicker(residenceCheckDisabledState);
|
||||
|
||||
await userEvent.press(getByText('Country of residence'));
|
||||
await userEvent.press(getByText(/Algeria/));
|
||||
@@ -64,9 +68,7 @@ describe('TradingCountryOfResidencePicker', () => {
|
||||
});
|
||||
|
||||
it('should render nothing when isTradingResidenceCheckEnabled FF is true', async () => {
|
||||
const { toJSON } = await renderCountryOfResidencePicker({
|
||||
featureFlags: { [FeatureFlag.IsTradingResidenceCheckEnabled]: true },
|
||||
});
|
||||
const { toJSON } = await renderCountryOfResidencePicker(residenceCheckEnabledState);
|
||||
|
||||
expect(toJSON()).toBeNull();
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from '@suite-native/test-utils';
|
||||
|
||||
import { getBtcAccount } from '../../../__fixtures__/account';
|
||||
import { residenceCheckDisabledState } from '../../../__fixtures__/residenceCheckState';
|
||||
import { sellQuotes } from '../../../__fixtures__/sellQuotes';
|
||||
import { btcAsset } from '../../../__fixtures__/tradeableAssets';
|
||||
import { getInitializedTradingState } from '../../../__fixtures__/tradingState';
|
||||
@@ -42,7 +43,10 @@ describe('SellForm', () => {
|
||||
let preloadedState: PreloadedState;
|
||||
|
||||
beforeEach(async () => {
|
||||
preloadedState = { wallet: { trading: getInitializedTradingState() } };
|
||||
preloadedState = {
|
||||
wallet: { trading: getInitializedTradingState() },
|
||||
...residenceCheckDisabledState,
|
||||
};
|
||||
preloadedState.wallet!.trading!.sell!.quotes = sellQuotes;
|
||||
|
||||
const { result } = await renderFormHook(preloadedState);
|
||||
|
||||
@@ -18,6 +18,7 @@ describe('TradingStackNavigator', () => {
|
||||
featureFlags: {
|
||||
...featureFlagsInitialState,
|
||||
[FeatureFlag.IsTradingBuyEnabled]: true,
|
||||
[FeatureFlag.IsTradingResidenceCheckEnabled]: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ const stateWithEnabledBuy = {
|
||||
featureFlags: {
|
||||
...featureFlagsInitialState,
|
||||
[FeatureFlag.IsTradingBuyEnabled]: true,
|
||||
[FeatureFlag.IsTradingResidenceCheckEnabled]: false,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -50,6 +51,7 @@ const stateWithDisabledTrading = {
|
||||
featureFlags: {
|
||||
...featureFlagsInitialState,
|
||||
[FeatureFlag.IsTradingBuyEnabled]: false,
|
||||
[FeatureFlag.IsTradingResidenceCheckEnabled]: false,
|
||||
},
|
||||
messageSystem: {
|
||||
validMessages: {
|
||||
|
||||
@@ -134,6 +134,7 @@ export type OnboardingStackParamList = {
|
||||
[OnboardingStackRoutes.Welcome]: undefined;
|
||||
[OnboardingStackRoutes.AnalyticsConsent]: undefined;
|
||||
[OnboardingStackRoutes.Biometrics]: undefined;
|
||||
[OnboardingStackRoutes.TradingLocation]: undefined;
|
||||
};
|
||||
|
||||
export type DeviceOnboardingStackParamList = {
|
||||
@@ -337,6 +338,7 @@ export type RootStackParamList = {
|
||||
orderId?: string;
|
||||
};
|
||||
[RootStackRoutes.BootloaderMode]: undefined;
|
||||
[RootStackRoutes.TradingLocationModal]: undefined;
|
||||
};
|
||||
|
||||
export type TradingStackParamList = {
|
||||
|
||||
@@ -23,6 +23,7 @@ export enum RootStackRoutes {
|
||||
BackupFailedModal = 'BackupFailedModal',
|
||||
TradingWebView = 'TradingWebView',
|
||||
BootloaderMode = 'BootloaderMode',
|
||||
TradingLocationModal = 'TradingLocationModal',
|
||||
}
|
||||
|
||||
export enum AppTabsRoutes {
|
||||
@@ -36,6 +37,7 @@ export enum OnboardingStackRoutes {
|
||||
Welcome = 'Welcome',
|
||||
AnalyticsConsent = 'AnalyticsConsent',
|
||||
Biometrics = 'Biometrics',
|
||||
TradingLocation = 'TradingLocation',
|
||||
}
|
||||
|
||||
export enum DeviceOnboardingStackRoutes {
|
||||
|
||||
@@ -20,7 +20,7 @@ export const ConfirmLocationButton = ({ afterConfirm }: ConfirmLocationButtonPro
|
||||
};
|
||||
|
||||
return (
|
||||
<Button colorScheme="primary" size="large" onPress={confirmLocation}>
|
||||
<Button colorScheme="primary" size="medium" onPress={confirmLocation}>
|
||||
<Translation id="tradingResidence.locationSettings.confirmButton" />
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { useNavigation } from '@react-navigation/native';
|
||||
|
||||
import { ConfirmLocationButton } from './ConfirmLocationButton';
|
||||
import { SkipButton } from './SkipButton';
|
||||
import { tradingResidenceActions } from '../reducers/residenceSlice';
|
||||
|
||||
export const OnboardingButtons = () => {
|
||||
const navigation = useNavigation();
|
||||
export type OnboardingButtonsProps = {
|
||||
afterPress: () => void;
|
||||
};
|
||||
|
||||
export const OnboardingButtons = ({ afterPress }: OnboardingButtonsProps) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleOnboardingComplete = () => {
|
||||
dispatch(tradingResidenceActions.setOnboardingVisited());
|
||||
|
||||
// todo 22469 navigate to dashboard
|
||||
navigation.goBack();
|
||||
afterPress();
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -6,7 +6,7 @@ export type SkipButtonProps = {
|
||||
};
|
||||
|
||||
export const SkipButton = ({ onPress }: SkipButtonProps) => (
|
||||
<Button colorScheme="tertiaryElevation0" size="large" onPress={onPress}>
|
||||
<Button colorScheme="tertiaryElevation0" size="medium" onPress={onPress}>
|
||||
<Translation id="tradingResidence.locationSettings.skipButton" />
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -20,7 +20,7 @@ export const TradingLocationSettings = ({ context, children }: TradingLocationSe
|
||||
<Box flex={1} alignItems="center" justifyContent="center">
|
||||
<GlobeSvg />
|
||||
</Box>
|
||||
<VStack paddingVertical="sp32" spacing="sp24">
|
||||
<VStack paddingTop="sp32" spacing="sp24">
|
||||
<VStack spacing="sp8">
|
||||
<Text variant="titleMedium" color="textDefault">
|
||||
<Translation id="tradingResidence.locationSettings.title" />
|
||||
@@ -38,7 +38,7 @@ export const TradingLocationSettings = ({ context, children }: TradingLocationSe
|
||||
</Card>
|
||||
<TradingAvailability />
|
||||
</VStack>
|
||||
<VStack spacing="sp8">{children}</VStack>
|
||||
<VStack spacing="sp12">{children}</VStack>
|
||||
</VStack>
|
||||
</VStack>
|
||||
</LocationForm>
|
||||
|
||||
@@ -10,27 +10,23 @@ import {
|
||||
selectWasTradingResidenceOnboardingVisited,
|
||||
} from '../../selectors/residenceSelectors';
|
||||
import { LocationForm } from '../LocationForm';
|
||||
import { OnboardingButtons } from '../OnboardingButtons';
|
||||
|
||||
jest.mock('@react-navigation/native', () => ({
|
||||
...jest.requireActual('@react-navigation/native'),
|
||||
useNavigation: () => ({
|
||||
goBack: () => jest.fn(),
|
||||
}),
|
||||
}));
|
||||
import { OnboardingButtons, OnboardingButtonsProps } from '../OnboardingButtons';
|
||||
|
||||
describe('OnboardingButtons', () => {
|
||||
let store: TestStore;
|
||||
|
||||
const renderOnboardingButtons = () =>
|
||||
renderWithStoreProviderAsync(<OnboardingButtons />, { wrapper: LocationForm, store });
|
||||
const renderOnboardingButtons = (props: OnboardingButtonsProps) =>
|
||||
renderWithStoreProviderAsync(<OnboardingButtons {...props} />, {
|
||||
wrapper: LocationForm,
|
||||
store,
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
store = await initStore();
|
||||
});
|
||||
|
||||
it('should render correctly', async () => {
|
||||
const { getByText } = await renderOnboardingButtons();
|
||||
const { getByText } = await renderOnboardingButtons({ afterPress: () => {} });
|
||||
|
||||
expect(getByText('Confirm location')).toBeOnTheScreen();
|
||||
expect(getByText('Not now')).toBeOnTheScreen();
|
||||
@@ -41,21 +37,25 @@ describe('OnboardingButtons', () => {
|
||||
});
|
||||
|
||||
it('should dispatch setResidenceCountry and setOnboardingVisited on `Confirm location` press', async () => {
|
||||
const { getByText } = await renderOnboardingButtons();
|
||||
const afterPressMock = jest.fn();
|
||||
const { getByText } = await renderOnboardingButtons({ afterPress: afterPressMock });
|
||||
|
||||
fireEvent.press(getByText('Confirm location'));
|
||||
|
||||
// from expo-localization mock
|
||||
expect(selectTradingResidenceCountry(store.getState())).toBe('PL');
|
||||
expect(selectWasTradingResidenceOnboardingVisited(store.getState())).toBe(true);
|
||||
expect(afterPressMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should dispatch only setOnboardingVisited on `Not now` press', async () => {
|
||||
const { getByText } = await renderOnboardingButtons();
|
||||
const afterPressMock = jest.fn();
|
||||
const { getByText } = await renderOnboardingButtons({ afterPress: afterPressMock });
|
||||
|
||||
fireEvent.press(getByText('Not now'));
|
||||
|
||||
expect(selectTradingResidenceCountry(store.getState())).toBeUndefined();
|
||||
expect(selectWasTradingResidenceOnboardingVisited(store.getState())).toBe(true);
|
||||
expect(afterPressMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,3 +7,5 @@ export * from './components/CountrySheet/CountryOfResidencePicker';
|
||||
export * from './components/TradingLocationSettings';
|
||||
export * from './components/ConfirmLocationButton';
|
||||
export * from './components/OnboardingButtons';
|
||||
|
||||
export * from './screens/TradingLocationModalScreen';
|
||||
|
||||
@@ -1,12 +1,41 @@
|
||||
import { Screen } from '@suite-native/navigation';
|
||||
import { CommonActions } from '@react-navigation/native';
|
||||
|
||||
import {
|
||||
HomeStackRoutes,
|
||||
RootStackParamList,
|
||||
RootStackRoutes,
|
||||
Screen,
|
||||
StackProps,
|
||||
} from '@suite-native/navigation';
|
||||
|
||||
import { OnboardingButtons } from '../components/OnboardingButtons';
|
||||
import { TradingLocationSettings } from '../components/TradingLocationSettings';
|
||||
|
||||
export const TradingLocationModalScreen = () => (
|
||||
<Screen>
|
||||
<TradingLocationSettings context="onboarding">
|
||||
<OnboardingButtons />
|
||||
</TradingLocationSettings>
|
||||
</Screen>
|
||||
);
|
||||
export type TradingLocationModalScreenProps = StackProps<
|
||||
RootStackParamList,
|
||||
RootStackRoutes.TradingLocationModal
|
||||
>;
|
||||
|
||||
export const TradingLocationModalScreen = ({ navigation }: TradingLocationModalScreenProps) => {
|
||||
const resetToHome = () => {
|
||||
navigation.dispatch(
|
||||
CommonActions.reset({
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: RootStackRoutes.AppTabs,
|
||||
params: { screen: HomeStackRoutes.Home },
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Screen>
|
||||
<TradingLocationSettings context="onboarding">
|
||||
<OnboardingButtons afterPress={resetToHome} />
|
||||
</TradingLocationSettings>
|
||||
</Screen>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { Screen, ScreenHeader } from '@suite-native/navigation';
|
||||
|
||||
import { OnboardingButtons } from '../components/OnboardingButtons';
|
||||
import { TradingLocationSettings } from '../components/TradingLocationSettings';
|
||||
|
||||
export const TradingLocationOnboardingScreen = () => (
|
||||
<Screen header={<ScreenHeader />}>
|
||||
<TradingLocationSettings context="onboarding">
|
||||
<OnboardingButtons />
|
||||
</TradingLocationSettings>
|
||||
</Screen>
|
||||
);
|
||||
@@ -1,10 +1,24 @@
|
||||
import { RouteProp } from '@react-navigation/native';
|
||||
|
||||
import { EventType, analytics } from '@suite-native/analytics';
|
||||
import { TradingStackParamList, TradingStackRoutes } from '@suite-native/navigation';
|
||||
import {
|
||||
RootStackRoutes,
|
||||
TradingStackParamList,
|
||||
TradingStackRoutes,
|
||||
} from '@suite-native/navigation';
|
||||
import { renderWithStoreProviderAsync, userEvent } from '@suite-native/test-utils';
|
||||
|
||||
import { TradingLocationModalScreen } from '../TradingLocationModalScreen';
|
||||
import {
|
||||
TradingLocationModalScreen,
|
||||
TradingLocationModalScreenProps,
|
||||
} from '../TradingLocationModalScreen';
|
||||
|
||||
const mockNavigationDispatch = jest.fn();
|
||||
const mockRoute: TradingLocationModalScreenProps['route'] = {
|
||||
name: RootStackRoutes.TradingLocationModal,
|
||||
key: 'TradingLocationModal',
|
||||
params: undefined,
|
||||
};
|
||||
|
||||
jest.mock('@react-navigation/native', () => ({
|
||||
...jest.requireActual('@react-navigation/native'),
|
||||
@@ -12,11 +26,23 @@ jest.mock('@react-navigation/native', () => ({
|
||||
({
|
||||
params: undefined,
|
||||
}) as RouteProp<TradingStackParamList, TradingStackRoutes.TradingHistory>,
|
||||
useNavigation: () => ({
|
||||
dispatch: mockNavigationDispatch,
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('TradingLocationModalScreen', () => {
|
||||
const renderTradingLocationModalScreen = () =>
|
||||
renderWithStoreProviderAsync(<TradingLocationModalScreen />);
|
||||
renderWithStoreProviderAsync(
|
||||
<TradingLocationModalScreen
|
||||
navigation={
|
||||
{
|
||||
dispatch: mockNavigationDispatch,
|
||||
} as unknown as TradingLocationModalScreenProps['navigation']
|
||||
}
|
||||
route={mockRoute}
|
||||
/>,
|
||||
);
|
||||
|
||||
it('should render all components', async () => {
|
||||
const { getByText, queryByLabelText } = await renderTradingLocationModalScreen();
|
||||
@@ -44,4 +70,24 @@ describe('TradingLocationModalScreen', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset navigation on button press', async () => {
|
||||
const { getByText } = await renderTradingLocationModalScreen();
|
||||
|
||||
await userEvent.press(getByText('Not now'));
|
||||
|
||||
expect(mockNavigationDispatch).toHaveBeenCalledTimes(1);
|
||||
expect(mockNavigationDispatch).toHaveBeenCalledWith({
|
||||
type: 'RESET',
|
||||
payload: {
|
||||
index: 0,
|
||||
routes: [
|
||||
{
|
||||
name: RootStackRoutes.AppTabs,
|
||||
params: { screen: 'Home' },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
selectIsTradingCountrySet,
|
||||
selectIsTradingEnabledForCountry,
|
||||
selectIsTradingResidenceCheckEnabled,
|
||||
selectShouldDisplayTradingResidenceOnboarding,
|
||||
selectTradingResidenceCountry,
|
||||
selectWasTradingResidenceOnboardingVisited,
|
||||
} from '../residenceSelectors';
|
||||
@@ -124,4 +125,42 @@ describe('residenceSelectors', () => {
|
||||
expect(selectIsTradingCountrySet(state)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('selectShouldDisplayTradingResidenceOnboarding', () => {
|
||||
it('should return false when residence check FF is disabled', () => {
|
||||
const state = {
|
||||
...getRootResidenceState(tradingResidenceInitialState),
|
||||
...getRootFFState(false),
|
||||
};
|
||||
|
||||
expect(selectShouldDisplayTradingResidenceOnboarding(state)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when onboarding was already visited (FF enabled)', () => {
|
||||
const state = {
|
||||
...getRootResidenceState(visitedState),
|
||||
...getRootFFState(true),
|
||||
};
|
||||
|
||||
expect(selectShouldDisplayTradingResidenceOnboarding(state)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when country is already set (FF enabled)', () => {
|
||||
const state = {
|
||||
...getRootResidenceState({ country: 'US' }),
|
||||
...getRootFFState(true),
|
||||
};
|
||||
|
||||
expect(selectShouldDisplayTradingResidenceOnboarding(state)).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when FF enabled, onboarding not visited and country not set', () => {
|
||||
const state = {
|
||||
...getRootResidenceState(tradingResidenceInitialState),
|
||||
...getRootFFState(true),
|
||||
};
|
||||
|
||||
expect(selectShouldDisplayTradingResidenceOnboarding(state)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,3 +34,13 @@ export const selectIsTradingEnabledForCountry = (
|
||||
|
||||
export const selectIsTradingCountrySet = (state: TradingResidenceRootState) =>
|
||||
selectTradingResidenceCountry(state) !== undefined;
|
||||
|
||||
export const selectShouldDisplayTradingResidenceOnboarding = (
|
||||
state: TradingResidenceRootState & FeatureFlagsRootState,
|
||||
) => {
|
||||
const isResidenceCheckEnabled = selectIsTradingResidenceCheckEnabled(state);
|
||||
const wasOnboardingVisited = selectWasTradingResidenceOnboardingVisited(state);
|
||||
const isCountrySet = selectIsTradingCountrySet(state);
|
||||
|
||||
return isResidenceCheckEnabled && !wasOnboardingVisited && !isCountrySet;
|
||||
};
|
||||
|
||||
@@ -11198,6 +11198,7 @@ __metadata:
|
||||
"@suite-native/test-utils": "workspace:*"
|
||||
"@suite-native/theme": "workspace:*"
|
||||
"@suite-native/toasts": "workspace:*"
|
||||
"@suite-native/trading-residence": "workspace:*"
|
||||
"@suite-native/transactions": "workspace:*"
|
||||
"@trezor/blockchain-link-types": "workspace:*"
|
||||
"@trezor/bundler-security": "workspace:*"
|
||||
@@ -11623,6 +11624,8 @@ __metadata:
|
||||
dependencies:
|
||||
"@reduxjs/toolkit": "npm:2.9.1"
|
||||
"@suite-native/config": "workspace:*"
|
||||
"@trezor/env-utils": "workspace:*"
|
||||
react-native: "npm:0.81.4"
|
||||
react-redux: "npm:9.2.0"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -12337,6 +12340,8 @@ __metadata:
|
||||
"@suite-native/link": "workspace:*"
|
||||
"@suite-native/navigation": "workspace:*"
|
||||
"@suite-native/settings": "workspace:*"
|
||||
"@suite-native/test-utils": "workspace:*"
|
||||
"@suite-native/trading-residence": "workspace:*"
|
||||
"@trezor/env-utils": "workspace:*"
|
||||
"@trezor/styles": "workspace:*"
|
||||
"@trezor/theme": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user