From bfea385ef8ead189c0b5fe89f943f084b5323ce7 Mon Sep 17 00:00:00 2001 From: "Nguyen, Tom CITZ:EX" Date: Thu, 24 Oct 2024 23:20:23 -0700 Subject: [PATCH 1/9] chore: prompt for system biometrics permission when toggling biometrics switch during onboarding, add new strings. Signed-off-by: Nguyen, Tom CITZ:EX --- .../legacy/core/App/localization/en/index.ts | 7 +- .../legacy/core/App/screens/UseBiometry.tsx | 85 +++++++++++++++++-- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index cf989e6539..14ac3d5efb 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -283,7 +283,12 @@ const translation = { "Warning": "Ensure only you have access to your wallet.", "UseToUnlock": "Use biometrics to unlock wallet?", "UnlockPromptTitle": "Wallet Unlock", - "UnlockPromptDescription": "Use biometrics to unlock your wallet" + "UnlockPromptDescription": "Use biometrics to unlock your wallet", + "AllowBiometricsTitle": "Allow use of biometrics", + "AllowBiometricsDesc": "To unlock BC Wallet with your biometrics, please allow biometrics use within your device's settings.", + "SetupBiometricsTitle": "Set up your biometrics", + "SetupBiometricsDesc": "To unlock BC Wallet with your biometrics, please set up your biometrics in your device's settings.", + "OpenSettings": "Open settings" }, "ActivityHistory": { "Header": "Activity history", diff --git a/packages/legacy/core/App/screens/UseBiometry.tsx b/packages/legacy/core/App/screens/UseBiometry.tsx index fe0f8c99a0..bd4a51c041 100644 --- a/packages/legacy/core/App/screens/UseBiometry.tsx +++ b/packages/legacy/core/App/screens/UseBiometry.tsx @@ -2,7 +2,8 @@ import { CommonActions, useNavigation } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' import React, { useState, useEffect, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { StyleSheet, Text, View, Modal, ScrollView, DeviceEventEmitter } from 'react-native' +import { StyleSheet, Text, View, Modal, ScrollView, DeviceEventEmitter, Linking } from 'react-native' +import { PERMISSIONS, RESULTS, request, check, PermissionStatus } from 'react-native-permissions' import { SafeAreaView } from 'react-native-safe-area-context' import Button, { ButtonType } from '../components/buttons/Button' @@ -15,6 +16,7 @@ import { useStore } from '../contexts/store' import { useTheme } from '../contexts/theme' import { OnboardingStackParams, Screens } from '../types/navigators' import { testIdWithKey } from '../utils/testable' +import DismissiblePopupModal from '../components/modals/DismissiblePopupModal' import PINEnter, { PINEntryUsage } from './PINEnter' import { TOKENS, useServices } from '../container-api' @@ -32,6 +34,7 @@ const UseBiometry: React.FC = () => { const [biometryAvailable, setBiometryAvailable] = useState(false) const [biometryEnabled, setBiometryEnabled] = useState(store.preferences.useBiometry) const [continueEnabled, setContinueEnabled] = useState(true) + const [settingsPopupConfig, setSettingsPopupConfig] = useState(null) const [canSeeCheckPIN, setCanSeeCheckPIN] = useState(false) const { ColorPallet, TextTheme, Assets } = useTheme() const { ButtonLoading } = useAnimatedComponents() @@ -40,6 +43,8 @@ const UseBiometry: React.FC = () => { return store.onboarding.didCompleteOnboarding ? UseBiometryUsage.ToggleOnOff : UseBiometryUsage.InitialSetup }, [store.onboarding.didCompleteOnboarding]) + const BIOMETRY_PERMISSION = PERMISSIONS.IOS.FACE_ID; + const styles = StyleSheet.create({ container: { height: '100%', @@ -102,17 +107,76 @@ const UseBiometry: React.FC = () => { } }, [biometryEnabled, commitPIN, dispatch, enablePushNotifications, navigation]) - const toggleSwitch = useCallback(() => { - // If the user is toggling biometrics on/off they need - // to first authenticate before this action is accepted + // TODO: Add tests + + const onOpenSettingsTouched = async () => { + await Linking.openSettings() + onOpenSettingsDismissed() + } + + const onOpenSettingsDismissed = () => { + setSettingsPopupConfig(null) + } + + const onSwitchToggleAllowed = useCallback((newValue: boolean) => { if (screenUsage === UseBiometryUsage.ToggleOnOff) { setCanSeeCheckPIN(true) DeviceEventEmitter.emit(EventTypes.BIOMETRY_UPDATE, true) + } else { + setBiometryEnabled(newValue) + } + }, [screenUsage]) + + const onRequestSystemBiometrics = useCallback(async (newToggleValue: boolean) => { + const permissionResult: PermissionStatus = await request(BIOMETRY_PERMISSION) + switch (permissionResult) { + case RESULTS.GRANTED: + case RESULTS.LIMITED: + // Granted + onSwitchToggleAllowed(newToggleValue) + break + default: + break + } + }, [onSwitchToggleAllowed]) + + const toggleSwitch = useCallback(async (newValue: boolean) => { + if (!newValue) { + // Turning off doesn't require OS'es biometrics enabled + onSwitchToggleAllowed(newValue) return } - setBiometryEnabled((previousState) => !previousState) - }, [screenUsage]) + // If the user is turning it on, they need + // to first authenticate the OS'es biometrics before this action is accepted + const permissionResult: PermissionStatus = await check(BIOMETRY_PERMISSION) + switch (permissionResult) { + case RESULTS.GRANTED: + case RESULTS.LIMITED: + // Already granted + onSwitchToggleAllowed(newValue) + break + case RESULTS.UNAVAILABLE: + setSettingsPopupConfig({ + title: t('Biometry.SetupBiometricsTitle'), + description: t('Biometry.SetupBiometricsDesc') + }) + break + case RESULTS.BLOCKED: + // Previously denied + setSettingsPopupConfig({ + title: t('Biometry.AllowBiometricsTitle'), + description: t('Biometry.AllowBiometricsDesc') + }) + break + case RESULTS.DENIED: + // Has not been requested + await onRequestSystemBiometrics(newValue) + break + default: + break + } + }, [onSwitchToggleAllowed, onRequestSystemBiometrics]) const onAuthenticationComplete = useCallback((status: boolean) => { // If successfully authenticated the toggle may proceed. @@ -125,6 +189,15 @@ const UseBiometry: React.FC = () => { return ( + {settingsPopupConfig && ( + + )} From c28af290ceed38eef0c5d2df39b18eb71d077b6e Mon Sep 17 00:00:00 2001 From: "Nguyen, Tom CITZ:EX" Date: Thu, 31 Oct 2024 11:46:02 -0700 Subject: [PATCH 2/9] chore: improve code to reflect upstream changes, enable toggle when biometric is unavailable. Signed-off-by: Nguyen, Tom CITZ:EX --- packages/legacy/core/App/screens/UseBiometry.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/legacy/core/App/screens/UseBiometry.tsx b/packages/legacy/core/App/screens/UseBiometry.tsx index bd4a51c041..896a309021 100644 --- a/packages/legacy/core/App/screens/UseBiometry.tsx +++ b/packages/legacy/core/App/screens/UseBiometry.tsx @@ -140,7 +140,9 @@ const UseBiometry: React.FC = () => { } }, [onSwitchToggleAllowed]) - const toggleSwitch = useCallback(async (newValue: boolean) => { + const toggleSwitch = useCallback(async () => { + const newValue = !biometryEnabled + if (!newValue) { // Turning off doesn't require OS'es biometrics enabled onSwitchToggleAllowed(newValue) @@ -176,7 +178,7 @@ const UseBiometry: React.FC = () => { default: break } - }, [onSwitchToggleAllowed, onRequestSystemBiometrics]) + }, [onSwitchToggleAllowed, onRequestSystemBiometrics, biometryEnabled]) const onAuthenticationComplete = useCallback((status: boolean) => { // If successfully authenticated the toggle may proceed. @@ -226,9 +228,9 @@ const UseBiometry: React.FC = () => { From 5b920c9852e854220f1fef783fd4c9d869e652d2 Mon Sep 17 00:00:00 2001 From: "Nguyen, Tom CITZ:EX" Date: Thu, 31 Oct 2024 11:46:49 -0700 Subject: [PATCH 3/9] chore: add french translation, modify english translation Signed-off-by: Nguyen, Tom CITZ:EX --- packages/legacy/core/App/localization/en/index.ts | 4 ++-- packages/legacy/core/App/localization/fr/index.ts | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index 14ac3d5efb..395acfaa93 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -284,9 +284,9 @@ const translation = { "UseToUnlock": "Use biometrics to unlock wallet?", "UnlockPromptTitle": "Wallet Unlock", "UnlockPromptDescription": "Use biometrics to unlock your wallet", - "AllowBiometricsTitle": "Allow use of biometrics", + "AllowBiometricsTitle": "Enable biometrics", "AllowBiometricsDesc": "To unlock BC Wallet with your biometrics, please allow biometrics use within your device's settings.", - "SetupBiometricsTitle": "Set up your biometrics", + "SetupBiometricsTitle": "Biometrics is not enabled", "SetupBiometricsDesc": "To unlock BC Wallet with your biometrics, please set up your biometrics in your device's settings.", "OpenSettings": "Open settings" }, diff --git a/packages/legacy/core/App/localization/fr/index.ts b/packages/legacy/core/App/localization/fr/index.ts index 141cdb1487..5a07f9496d 100644 --- a/packages/legacy/core/App/localization/fr/index.ts +++ b/packages/legacy/core/App/localization/fr/index.ts @@ -282,7 +282,12 @@ const translation = { "Warning": "\n\nAssurez-vous que vous seul avez accès à votre portefeuille.", "UseToUnlock": "Utiliser la biométrie pour déverrouiller le portefeuille ?", "UnlockPromptTitle": "Déverrouillage du portefeuille", - "UnlockPromptDescription": "Utilisez la biométrie pour déverrouiller votre portefeuille" + "UnlockPromptDescription": "Utilisez la biométrie pour déverrouiller votre portefeuille", + "AllowBiometricsTitle": "Activer la biométrie", + "AllowBiometricsDesc": "Pour déverrouiller BC Wallet avec votre biométrie, permettrez la biométrie dans les paramètres de votre appareil.", + "SetupBiometricsTitle": "La biométrie n'est pas activée", + "SetupBiometricsDesc": "Pour déverrouiller BC Wallet avec votre biométrie, configurez votre biométrie dans les paramètres de votre appareil.", + "OpenSettings": "Ouvrir les paramètres" }, "ActivityHistory": { "Header": "Activity history(fr)", From 4bc37c180d60c324e30b4381348fa066c5145542 Mon Sep 17 00:00:00 2001 From: "Nguyen, Tom CITZ:EX" Date: Thu, 31 Oct 2024 14:10:16 -0700 Subject: [PATCH 4/9] chore: handle biometry check logic on Android Signed-off-by: Nguyen, Tom CITZ:EX --- .../legacy/core/App/screens/UseBiometry.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/legacy/core/App/screens/UseBiometry.tsx b/packages/legacy/core/App/screens/UseBiometry.tsx index 896a309021..c93c9ba084 100644 --- a/packages/legacy/core/App/screens/UseBiometry.tsx +++ b/packages/legacy/core/App/screens/UseBiometry.tsx @@ -2,7 +2,7 @@ import { CommonActions, useNavigation } from '@react-navigation/native' import { StackNavigationProp } from '@react-navigation/stack' import React, { useState, useEffect, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { StyleSheet, Text, View, Modal, ScrollView, DeviceEventEmitter, Linking } from 'react-native' +import { StyleSheet, Text, View, Modal, ScrollView, DeviceEventEmitter, Linking, Platform } from 'react-native' import { PERMISSIONS, RESULTS, request, check, PermissionStatus } from 'react-native-permissions' import { SafeAreaView } from 'react-native-safe-area-context' @@ -140,6 +140,18 @@ const UseBiometry: React.FC = () => { } }, [onSwitchToggleAllowed]) + const onCheckSystemBiometrics = useCallback(async (): Promise => { + if (Platform.OS === 'android') { + // Android doesn't need to prompt biometric permission + // for an app to use it. + return biometryAvailable ? RESULTS.GRANTED : RESULTS.UNAVAILABLE + } else if (Platform.OS === 'ios') { + return await check(BIOMETRY_PERMISSION) + } + + return RESULTS.UNAVAILABLE + }, [biometryAvailable]) + const toggleSwitch = useCallback(async () => { const newValue = !biometryEnabled @@ -151,7 +163,7 @@ const UseBiometry: React.FC = () => { // If the user is turning it on, they need // to first authenticate the OS'es biometrics before this action is accepted - const permissionResult: PermissionStatus = await check(BIOMETRY_PERMISSION) + const permissionResult: PermissionStatus = await onCheckSystemBiometrics() switch (permissionResult) { case RESULTS.GRANTED: case RESULTS.LIMITED: @@ -178,7 +190,7 @@ const UseBiometry: React.FC = () => { default: break } - }, [onSwitchToggleAllowed, onRequestSystemBiometrics, biometryEnabled]) + }, [onSwitchToggleAllowed, onRequestSystemBiometrics, onCheckSystemBiometrics, biometryEnabled]) const onAuthenticationComplete = useCallback((status: boolean) => { // If successfully authenticated the toggle may proceed. From 5ebf4e4b9b1bf635184f6a3ddf3398ee86a270e5 Mon Sep 17 00:00:00 2001 From: "Nguyen, Tom CITZ:EX" Date: Thu, 31 Oct 2024 14:23:48 -0700 Subject: [PATCH 5/9] fix: AndroidManifest USE_BIOMETRIC typo Signed-off-by: Nguyen, Tom CITZ:EX --- packages/legacy/app/android/app/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/legacy/app/android/app/src/main/AndroidManifest.xml b/packages/legacy/app/android/app/src/main/AndroidManifest.xml index 8685f74d6b..29b22aa521 100644 --- a/packages/legacy/app/android/app/src/main/AndroidManifest.xml +++ b/packages/legacy/app/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - + From 84bfaa25600a19a5ac1982200699c6359f9577cc Mon Sep 17 00:00:00 2001 From: "Nguyen, Tom CITZ:EX" Date: Sat, 2 Nov 2024 21:20:59 -0700 Subject: [PATCH 6/9] chore: fix linting errors Signed-off-by: Nguyen, Tom CITZ:EX --- packages/legacy/core/App/screens/UseBiometry.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/legacy/core/App/screens/UseBiometry.tsx b/packages/legacy/core/App/screens/UseBiometry.tsx index c93c9ba084..d955dab47e 100644 --- a/packages/legacy/core/App/screens/UseBiometry.tsx +++ b/packages/legacy/core/App/screens/UseBiometry.tsx @@ -138,7 +138,7 @@ const UseBiometry: React.FC = () => { default: break } - }, [onSwitchToggleAllowed]) + }, [onSwitchToggleAllowed, BIOMETRY_PERMISSION]) const onCheckSystemBiometrics = useCallback(async (): Promise => { if (Platform.OS === 'android') { @@ -150,7 +150,7 @@ const UseBiometry: React.FC = () => { } return RESULTS.UNAVAILABLE - }, [biometryAvailable]) + }, [biometryAvailable, BIOMETRY_PERMISSION]) const toggleSwitch = useCallback(async () => { const newValue = !biometryEnabled @@ -190,7 +190,7 @@ const UseBiometry: React.FC = () => { default: break } - }, [onSwitchToggleAllowed, onRequestSystemBiometrics, onCheckSystemBiometrics, biometryEnabled]) + }, [onSwitchToggleAllowed, onRequestSystemBiometrics, onCheckSystemBiometrics, biometryEnabled, t]) const onAuthenticationComplete = useCallback((status: boolean) => { // If successfully authenticated the toggle may proceed. From e4ce05d5be7a099d0d59f369d7923ff04e016da4 Mon Sep 17 00:00:00 2001 From: "Nguyen, Tom CITZ:EX" Date: Mon, 4 Nov 2024 19:05:04 -0800 Subject: [PATCH 7/9] chore: improve accessibilityState for ToggleButton Signed-off-by: Nguyen, Tom CITZ:EX --- packages/legacy/core/App/components/buttons/ToggleButton.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/legacy/core/App/components/buttons/ToggleButton.tsx b/packages/legacy/core/App/components/buttons/ToggleButton.tsx index e9ad3aa27e..2dcf1def5c 100644 --- a/packages/legacy/core/App/components/buttons/ToggleButton.tsx +++ b/packages/legacy/core/App/components/buttons/ToggleButton.tsx @@ -49,6 +49,9 @@ const ToggleButton: React.FC = ({ testID={testID} accessibilityLabel="Toggle Button" accessibilityRole="switch" + accessibilityState={{ + checked: isEnabled + }} onPress={isAvailable && !disabled ? toggleAction : undefined} // Prevent onPress if not available or disabled disabled={!isAvailable || disabled} > From 4378a1341bf3048f3a4d1030e8054822578522e2 Mon Sep 17 00:00:00 2001 From: "Nguyen, Tom CITZ:EX" Date: Mon, 4 Nov 2024 19:16:20 -0800 Subject: [PATCH 8/9] chore: add tests for UseBiometry, update snapshots. Signed-off-by: Nguyen, Tom CITZ:EX --- .../legacy/core/App/screens/UseBiometry.tsx | 2 - .../__tests__/screens/UseBiometry.test.tsx | 277 ++++++++++++++++++ .../__snapshots__/UseBiometry.test.tsx.snap | 10 +- 3 files changed, 282 insertions(+), 7 deletions(-) diff --git a/packages/legacy/core/App/screens/UseBiometry.tsx b/packages/legacy/core/App/screens/UseBiometry.tsx index d955dab47e..c5848fc28d 100644 --- a/packages/legacy/core/App/screens/UseBiometry.tsx +++ b/packages/legacy/core/App/screens/UseBiometry.tsx @@ -107,8 +107,6 @@ const UseBiometry: React.FC = () => { } }, [biometryEnabled, commitPIN, dispatch, enablePushNotifications, navigation]) - // TODO: Add tests - const onOpenSettingsTouched = async () => { await Linking.openSettings() onOpenSettingsDismissed() diff --git a/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx b/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx index 4fef48b670..337040e64e 100644 --- a/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx +++ b/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx @@ -7,7 +7,27 @@ import { testIdWithKey } from '../../App/utils/testable' import authContext from '../contexts/auth' import timeTravel from '../helpers/timetravel' import { BasicAppContext } from '../helpers/app' +import { Linking } from 'react-native' +import { useTranslation } from 'react-i18next' +import { testDefaultState } from '../contexts/store' +import { StoreProvider } from '../../App/contexts/store' +import { RESULTS, check, request } from 'react-native-permissions' +jest.mock('react-native-permissions', () => require('react-native-permissions/mock')) +const mockedCheck = check as jest.MockedFunction; +const mockedRequest = request as jest.MockedFunction; + +jest.spyOn(Linking, 'openSettings').mockImplementation(() => Promise.resolve()) + +const customStore = { + ...testDefaultState, + preferences: { + ...testDefaultState.preferences, + useBiometry: false + }, +} + +const { t } = useTranslation() describe('UseBiometry Screen', () => { beforeAll(() => { @@ -15,6 +35,15 @@ describe('UseBiometry Screen', () => { jest.spyOn(global.console, 'error').mockImplementation(() => { }) }) + beforeEach(() => { + jest.clearAllMocks() + + authContext.isBiometricsActive = jest.fn().mockResolvedValue(true) + customStore.preferences.useBiometry = false + mockedCheck.mockResolvedValue(RESULTS.UNAVAILABLE) + mockedRequest.mockResolvedValue(RESULTS.BLOCKED) + }) + test('Renders correctly when biometry available', async () => { authContext.isBiometricsActive = jest.fn().mockResolvedValueOnce(true) const tree = render( @@ -80,4 +109,252 @@ describe('UseBiometry Screen', () => { expect(authContext.commitPIN).toBeCalledTimes(1) expect(tree).toMatchSnapshot() }) + + describe('ToggleButton Availability', () => { + test('ToggleButton is enabled when biometry is available', async () => { + authContext.isBiometricsActive = jest.fn().mockResolvedValueOnce(true) + + const { getByTestId } = render( + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) + expect(toggleButton.props.accessibilityState.disabled).toBe(false) + }) + + test('ToggleButton is enabled even when biometry is not available', async () => { + authContext.isBiometricsActive = jest.fn().mockResolvedValueOnce(false) + + const { getByTestId } = render( + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) + expect(toggleButton.props.accessibilityState.disabled).toBe(false) + }) + }) + + describe('ToggleSwitch Behavior', () => { + test('can turn off biometrics regardless of permission status', async () => { + // Setup with toggle switch is on, + // and biometric is not available + customStore.preferences.useBiometry = true + authContext.isBiometricsActive = jest.fn().mockResolvedValueOnce(false) + + const { getByTestId } = render( + + + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) + + await waitFor(() => { + fireEvent(toggleButton, 'press') + }) + + expect(toggleButton.props.accessibilityState.checked).toBe(false) + }) + + test('turns on biometrics when permission is GRANTED', async () => { + mockedCheck.mockResolvedValueOnce(RESULTS.GRANTED) + + const { getByTestId } = render( + + + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) + + await waitFor(() => { + fireEvent(toggleButton, 'press') + }) + + expect(toggleButton.props.accessibilityState.checked).toBe(true) + }) + + test('shows settings popup when permission is UNAVAILABLE', async () => { + mockedCheck.mockResolvedValueOnce(RESULTS.UNAVAILABLE) + + const { getByTestId, getByText } = render( + + + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) + + await waitFor(() => { + fireEvent(toggleButton, 'press') + }) + + expect(getByText(t('Biometry.SetupBiometricsTitle'))).toBeTruthy() + expect(getByText(t('Biometry.SetupBiometricsDesc'))).toBeTruthy() + // Toggle should remain off + expect(toggleButton.props.accessibilityState.checked).toBe(false) + }) + + test('shows settings popup when permission is BLOCKED', async () => { + mockedCheck.mockResolvedValueOnce(RESULTS.BLOCKED) + + const { getByTestId, getByText } = render( + + + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) + + await waitFor(() => { + fireEvent(toggleButton, 'press') + }) + + expect(getByText(t('Biometry.AllowBiometricsTitle'))).toBeTruthy() + expect(getByText(t('Biometry.AllowBiometricsDesc'))).toBeTruthy() + // Toggle should remain off + expect(toggleButton.props.accessibilityState.checked).toBe(false) + }) + + test('requests permission when status is DENIED and enables when request is GRANTED', async () => { + mockedCheck.mockResolvedValueOnce(RESULTS.DENIED) + mockedRequest.mockResolvedValueOnce(RESULTS.GRANTED) + + const { getByTestId } = render( + + + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) + + await waitFor(() => { + fireEvent(toggleButton, 'press') + }) + + expect(request).toHaveBeenCalledTimes(1) + expect(toggleButton.props.accessibilityState.checked).toBe(true) + }) + }) + + test('requests permission when status is DENIED and switch stays off when request is BLOCKED', async () => { + mockedCheck.mockResolvedValueOnce(RESULTS.DENIED) + mockedRequest.mockResolvedValueOnce(RESULTS.BLOCKED) + + const { getByTestId } = render( + + + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) + + await waitFor(() => { + fireEvent(toggleButton, 'press') + }) + + expect(request).toHaveBeenCalledTimes(1) + // Switch stays off + expect(toggleButton.props.accessibilityState.checked).toBe(false) + }) + + describe('Settings Popup Behavior', () => { + test('opens app settings when Open Settings is pressed', async () => { + // Mock permission check to return BLOCKED to trigger settings popup + mockedCheck.mockResolvedValueOnce(RESULTS.BLOCKED) + + const { getByTestId, getByText } = render( + + + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + // Trigger the settings popup + const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) + + await waitFor(() => { + fireEvent(toggleButton, 'press') + }) + + // Press the Open Settings button + const openSettingsButton = getByText(t('Biometry.OpenSettings')) + await waitFor(() => { + fireEvent(openSettingsButton, 'press') + }) + + expect(Linking.openSettings).toHaveBeenCalledTimes(1) + }) + }) }) diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/UseBiometry.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/UseBiometry.test.tsx.snap index c2e1ba5cc3..87f87a06f2 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/UseBiometry.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/UseBiometry.test.tsx.snap @@ -116,7 +116,7 @@ exports[`UseBiometry Screen Renders correctly when biometry available 1`] = ` accessibilityState={ Object { "busy": undefined, - "checked": undefined, + "checked": false, "disabled": false, "expanded": undefined, "selected": undefined, @@ -399,8 +399,8 @@ exports[`UseBiometry Screen Renders correctly when biometry not available 1`] = accessibilityState={ Object { "busy": undefined, - "checked": undefined, - "disabled": true, + "checked": false, + "disabled": false, "expanded": undefined, "selected": undefined, } @@ -435,7 +435,7 @@ exports[`UseBiometry Screen Renders correctly when biometry not available 1`] = "borderRadius": 25, "height": 30, "justifyContent": "center", - "opacity": 0.5, + "opacity": 1, "padding": 3, "width": 55, } @@ -694,7 +694,7 @@ exports[`UseBiometry Screen Toggles use biometrics ok 1`] = ` accessibilityState={ Object { "busy": undefined, - "checked": undefined, + "checked": false, "disabled": false, "expanded": undefined, "selected": undefined, From f4eb26c26140c70f286974c4b4042f0f54332c9b Mon Sep 17 00:00:00 2001 From: "Nguyen, Tom CITZ:EX" Date: Mon, 4 Nov 2024 19:54:07 -0800 Subject: [PATCH 9/9] chore: improve tests in UseBiometry, update snapshots in ToggleButton Signed-off-by: Nguyen, Tom CITZ:EX --- .../__snapshots__/ToggleButton.test.tsx.snap | 6 +- .../__tests__/screens/UseBiometry.test.tsx | 61 +++---------------- 2 files changed, 12 insertions(+), 55 deletions(-) diff --git a/packages/legacy/core/__tests__/components/__snapshots__/ToggleButton.test.tsx.snap b/packages/legacy/core/__tests__/components/__snapshots__/ToggleButton.test.tsx.snap index 375b951f69..ba55974da2 100644 --- a/packages/legacy/core/__tests__/components/__snapshots__/ToggleButton.test.tsx.snap +++ b/packages/legacy/core/__tests__/components/__snapshots__/ToggleButton.test.tsx.snap @@ -7,7 +7,7 @@ exports[`ToggleButton Component renders correctly when disabled 1`] = ` accessibilityState={ Object { "busy": undefined, - "checked": undefined, + "checked": false, "disabled": false, "expanded": undefined, "selected": undefined, @@ -100,7 +100,7 @@ exports[`ToggleButton Component renders correctly when enabled 1`] = ` accessibilityState={ Object { "busy": undefined, - "checked": undefined, + "checked": true, "disabled": false, "expanded": undefined, "selected": undefined, @@ -193,7 +193,7 @@ exports[`ToggleButton Component renders correctly when not available 1`] = ` accessibilityState={ Object { "busy": undefined, - "checked": undefined, + "checked": false, "disabled": true, "expanded": undefined, "selected": undefined, diff --git a/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx b/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx index 337040e64e..cd08e5290d 100644 --- a/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx +++ b/packages/legacy/core/__tests__/screens/UseBiometry.test.tsx @@ -8,7 +8,6 @@ import authContext from '../contexts/auth' import timeTravel from '../helpers/timetravel' import { BasicAppContext } from '../helpers/app' import { Linking } from 'react-native' -import { useTranslation } from 'react-i18next' import { testDefaultState } from '../contexts/store' import { StoreProvider } from '../../App/contexts/store' import { RESULTS, check, request } from 'react-native-permissions' @@ -27,8 +26,6 @@ const customStore = { }, } -const { t } = useTranslation() - describe('UseBiometry Screen', () => { beforeAll(() => { // eslint-disable-next-line @typescript-eslint/no-empty-function @@ -88,20 +85,16 @@ describe('UseBiometry Screen', () => { ) - await waitFor(() => { - timeTravel(1000) - }) - - const useBiometryToggle = await tree.getByTestId(testIdWithKey('ToggleBiometrics')) + const useBiometryToggle = tree.getByTestId(testIdWithKey('ToggleBiometrics')) await waitFor(async () => { - await fireEvent(useBiometryToggle, 'valueChange', true) + fireEvent(useBiometryToggle, 'valueChange', true) }) - const continueButton = await tree.getByTestId(testIdWithKey('Continue')) + const continueButton = tree.getByTestId(testIdWithKey('Continue')) await waitFor(async () => { - await fireEvent(continueButton, 'press') + fireEvent(continueButton, 'press') }) expect(useBiometryToggle).not.toBeNull() @@ -122,10 +115,6 @@ describe('UseBiometry Screen', () => { ) - await waitFor(() => { - timeTravel(1000) - }) - const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) expect(toggleButton.props.accessibilityState.disabled).toBe(false) }) @@ -141,10 +130,6 @@ describe('UseBiometry Screen', () => { ) - await waitFor(() => { - timeTravel(1000) - }) - const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) expect(toggleButton.props.accessibilityState.disabled).toBe(false) }) @@ -166,10 +151,6 @@ describe('UseBiometry Screen', () => { ) - - await waitFor(() => { - timeTravel(1000) - }) const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) @@ -193,10 +174,6 @@ describe('UseBiometry Screen', () => { ) - await waitFor(() => { - timeTravel(1000) - }) - const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) await waitFor(() => { @@ -219,18 +196,14 @@ describe('UseBiometry Screen', () => { ) - await waitFor(() => { - timeTravel(1000) - }) - const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) await waitFor(() => { fireEvent(toggleButton, 'press') }) - expect(getByText(t('Biometry.SetupBiometricsTitle'))).toBeTruthy() - expect(getByText(t('Biometry.SetupBiometricsDesc'))).toBeTruthy() + expect(getByText('Biometry.SetupBiometricsTitle')).toBeTruthy() + expect(getByText('Biometry.SetupBiometricsDesc')).toBeTruthy() // Toggle should remain off expect(toggleButton.props.accessibilityState.checked).toBe(false) }) @@ -248,18 +221,14 @@ describe('UseBiometry Screen', () => { ) - await waitFor(() => { - timeTravel(1000) - }) - const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) await waitFor(() => { fireEvent(toggleButton, 'press') }) - expect(getByText(t('Biometry.AllowBiometricsTitle'))).toBeTruthy() - expect(getByText(t('Biometry.AllowBiometricsDesc'))).toBeTruthy() + expect(getByText('Biometry.AllowBiometricsTitle')).toBeTruthy() + expect(getByText('Biometry.AllowBiometricsDesc')).toBeTruthy() // Toggle should remain off expect(toggleButton.props.accessibilityState.checked).toBe(false) }) @@ -278,10 +247,6 @@ describe('UseBiometry Screen', () => { ) - await waitFor(() => { - timeTravel(1000) - }) - const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) await waitFor(() => { @@ -307,10 +272,6 @@ describe('UseBiometry Screen', () => { ) - await waitFor(() => { - timeTravel(1000) - }) - const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) await waitFor(() => { @@ -337,10 +298,6 @@ describe('UseBiometry Screen', () => { ) - await waitFor(() => { - timeTravel(1000) - }) - // Trigger the settings popup const toggleButton = getByTestId(testIdWithKey('ToggleBiometrics')) @@ -349,7 +306,7 @@ describe('UseBiometry Screen', () => { }) // Press the Open Settings button - const openSettingsButton = getByText(t('Biometry.OpenSettings')) + const openSettingsButton = getByText('Biometry.OpenSettings') await waitFor(() => { fireEvent(openSettingsButton, 'press') })