import { act, fireEvent, render, waitFor } from '@testing-library/react-native' import * as React from 'react' import { Platform } from 'react-native' import { RESULTS, check, request } from 'react-native-permissions' import { Provider } from 'react-redux' import AppAnalytics from 'src/analytics/AppAnalytics' import { JumpstartEvents, SendEvents } from 'src/analytics/Events' import { navigate } from 'src/navigator/NavigationService' import { Screens } from 'src/navigator/Screens' import SelectRecipientButtons from 'src/send/SelectRecipientButtons' import { getDynamicConfigParams, getFeatureGate } from 'src/statsig' import { StatsigFeatureGates } from 'src/statsig/types' import { navigateToPhoneSettings } from 'src/utils/linking' import { createMockStore } from 'test/utils' jest.mock('src/statsig') function renderComponent(phoneNumberVerified: boolean = false, defaultTokenIdOverride?: string) { const onPermissionsGranted = jest.fn() const tree = render( ) return { ...tree, onPermissionsGranted } } describe('SelectRecipientButtons', () => { beforeEach(() => { jest.clearAllMocks() jest.mocked(check).mockResolvedValue(RESULTS.DENIED) jest.mocked(getDynamicConfigParams).mockReturnValue({ showBalances: ['celo-alfajores'], jumpstartContracts: {}, }) }) it('renders the jumpstart button if it is enabled', async () => { jest .mocked(getFeatureGate) .mockImplementation((gate) => gate === StatsigFeatureGates.SHOW_JUMPSTART_SEND) const { getByText, findByTestId } = renderComponent() expect(await findByTestId('SelectRecipient/QR')).toBeTruthy() fireEvent.press(getByText('sendSelectRecipient.jumpstart.title')) expect(AppAnalytics.track).toHaveBeenCalledWith(JumpstartEvents.send_select_recipient_jumpstart) expect(navigate).toHaveBeenCalledWith(Screens.JumpstartEnterAmount) }) it('renders QR and contacts button with no check mark on contacts if phone number is not verified', async () => { const { getByTestId, queryByTestId, findByTestId } = renderComponent() // using findByTestId for first assertion in all tests to ensure rendering is finished after useAsync expect(await findByTestId('SelectRecipient/QR')).toBeTruthy() expect(getByTestId('SelectRecipient/Contacts')).toBeTruthy() expect(queryByTestId('SelectRecipient/Contacts/checkmark')).toBeFalsy() expect(check).toHaveBeenCalledTimes(1) }) it('renders QR and contacts button with no check mark on contacts if phone number is verified but contact permission is not granted', async () => { const { getByTestId, queryByTestId, findByTestId } = renderComponent(true) expect(await findByTestId('SelectRecipient/QR')).toBeTruthy() expect(getByTestId('SelectRecipient/Contacts')).toBeTruthy() expect(queryByTestId('SelectRecipient/Contacts/checkmark')).toBeFalsy() }) it('renders QR and contacts button with check mark on contacts if phone number is verified and contact permission is granted', async () => { jest.mocked(check).mockResolvedValue(RESULTS.GRANTED) const { getByTestId, findByTestId } = renderComponent(true) expect(await findByTestId('SelectRecipient/QR')).toBeTruthy() expect(getByTestId('SelectRecipient/Contacts')).toBeTruthy() expect(getByTestId('SelectRecipient/Contacts/checkmark')).toBeTruthy() }) it('navigates to QR screen when QR button is pressed', async () => { const { findByTestId } = renderComponent() fireEvent.press(await findByTestId('SelectRecipient/QR')) expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.send_select_recipient_scan_qr) expect(navigate).toHaveBeenCalledWith(Screens.QRNavigator, { screen: Screens.QRScanner, params: { defaultTokenIdOverride: undefined, }, }) }) it('navigates to QR screen with an override when QR button is pressed', async () => { const { findByTestId } = renderComponent(false, 'some-token-id') fireEvent.press(await findByTestId('SelectRecipient/QR')) expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.send_select_recipient_scan_qr) expect(navigate).toHaveBeenCalledWith(Screens.QRNavigator, { screen: Screens.QRScanner, params: { defaultTokenIdOverride: 'some-token-id', }, }) }) it('invokes permissions granted callback when contacts button is pressed with phone verified and contacts permission granted', async () => { jest.mocked(check).mockResolvedValue(RESULTS.GRANTED) const { findByTestId, onPermissionsGranted } = renderComponent(true) await act(async () => { fireEvent.press(await findByTestId('SelectRecipient/Contacts')) }) expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.send_select_recipient_contacts, { phoneNumberVerified: true, contactsPermissionStatus: RESULTS.GRANTED, }) expect(onPermissionsGranted).toHaveBeenCalledWith() expect(check).toHaveBeenCalledTimes(2) // one on load and one when pressing the button expect(request).not.toHaveBeenCalled() }) it('shows connect phone number modal if phone is not verified', async () => { const { findByTestId, getByTestId, queryByTestId, onPermissionsGranted } = renderComponent(false) await act(async () => { fireEvent.press(await findByTestId('SelectRecipient/Contacts')) }) expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.send_select_recipient_contacts, { phoneNumberVerified: false, contactsPermissionStatus: RESULTS.DENIED, }) expect(onPermissionsGranted).not.toHaveBeenCalled() expect(check).toHaveBeenCalledTimes(2) expect(request).not.toHaveBeenCalled() expect(getByTestId('SelectRecipient/PhoneNumberModal')).toBeVisible() await act(() => { fireEvent.press(getByTestId('SelectRecipient/PhoneNumberModal/SecondaryAction')) }) await waitFor(() => { expect(queryByTestId('SelectRecipient/PhoneNumberModal')).not.toBeOnTheScreen() }) await act(async () => { fireEvent.press(await findByTestId('SelectRecipient/Contacts')) }) expect(getByTestId('SelectRecipient/PhoneNumberModal')).toBeVisible() await act(() => { fireEvent.press(getByTestId('SelectRecipient/PhoneNumberModal/PrimaryAction')) }) await waitFor(() => { expect(queryByTestId('SelectRecipient/PhoneNumberModal')).not.toBeOnTheScreen() }) await waitFor(() => { expect(navigate).toHaveBeenCalledWith(Screens.VerificationStartScreen, { hasOnboarded: true }) }) }) it('shows enable contacts modal if phone verified but contacts permission is blocked', async () => { jest.mocked(check).mockResolvedValue(RESULTS.BLOCKED) const { findByTestId, getByTestId, queryByTestId, onPermissionsGranted } = renderComponent(true) await act(async () => { fireEvent.press(await findByTestId('SelectRecipient/Contacts')) }) expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.send_select_recipient_contacts, { phoneNumberVerified: true, contactsPermissionStatus: RESULTS.BLOCKED, }) expect(onPermissionsGranted).not.toHaveBeenCalled() expect(request).not.toHaveBeenCalled() expect(getByTestId('SelectRecipient/ContactsModal')).toBeVisible() await act(() => { fireEvent.press(getByTestId('SelectRecipient/ContactsModal/SecondaryAction')) }) await waitFor(() => { expect(queryByTestId('SelectRecipient/ContactsModal')).not.toBeOnTheScreen() }) await act(async () => { fireEvent.press(await findByTestId('SelectRecipient/Contacts')) }) expect(getByTestId('SelectRecipient/ContactsModal')).toBeVisible() await act(() => { fireEvent.press(getByTestId('SelectRecipient/ContactsModal/PrimaryAction')) }) expect(navigateToPhoneSettings).toHaveBeenCalled() }) it('requests permission if phone is verified but contacts permission is denied and invokes callback if request is granted', async () => { jest.mocked(request).mockResolvedValue(RESULTS.GRANTED) const { findByTestId, onPermissionsGranted } = renderComponent(true) await act(async () => { fireEvent.press(await findByTestId('SelectRecipient/Contacts')) }) expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.send_select_recipient_contacts, { phoneNumberVerified: true, contactsPermissionStatus: RESULTS.DENIED, }) expect(request).toHaveBeenCalled() expect(onPermissionsGranted).toHaveBeenCalled() expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.request_contacts_permission_started) expect(AppAnalytics.track).toHaveBeenCalledWith( SendEvents.request_contacts_permission_completed, { permissionStatus: RESULTS.GRANTED } ) }) it('requests permission if phone is verified but contacts permission is denied and does nothing if request is denied', async () => { jest.mocked(request).mockResolvedValue(RESULTS.DENIED) const { findByTestId, queryByTestId, onPermissionsGranted } = renderComponent(true) await act(async () => { fireEvent.press(await findByTestId('SelectRecipient/Contacts')) }) expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.send_select_recipient_contacts, { phoneNumberVerified: true, contactsPermissionStatus: RESULTS.DENIED, }) expect(request).toHaveBeenCalled() expect(onPermissionsGranted).not.toHaveBeenCalled() expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.request_contacts_permission_started) expect(AppAnalytics.track).toHaveBeenCalledWith( SendEvents.request_contacts_permission_completed, { permissionStatus: RESULTS.DENIED } ) expect(queryByTestId('SelectRecipient/ContactsModal')).toBeFalsy() }) it.each([ { os: 'ios' as const, showsModal: false, testName: 'does nothing' }, { os: 'android' as const, showsModal: true, testName: 'shows modal' }, ])( 'requests permission if phone is verified but contacts permission is denied and $testName if request is blocked on $os', async ({ os, showsModal }) => { Platform.OS = os jest.mocked(request).mockResolvedValue(RESULTS.BLOCKED) const { findByTestId, queryByTestId, onPermissionsGranted } = renderComponent(true) await act(async () => { fireEvent.press(await findByTestId('SelectRecipient/Contacts')) }) expect(AppAnalytics.track).toHaveBeenCalledWith(SendEvents.send_select_recipient_contacts, { phoneNumberVerified: true, contactsPermissionStatus: RESULTS.DENIED, }) expect(request).toHaveBeenCalled() expect(onPermissionsGranted).not.toHaveBeenCalled() expect(AppAnalytics.track).toHaveBeenCalledWith( SendEvents.request_contacts_permission_started ) expect(AppAnalytics.track).toHaveBeenCalledWith( SendEvents.request_contacts_permission_completed, { permissionStatus: RESULTS.BLOCKED } ) if (showsModal) { // eslint-disable-next-line jest/no-conditional-expect expect(queryByTestId('SelectRecipient/ContactsModal')).toBeTruthy() } else { // eslint-disable-next-line jest/no-conditional-expect expect(queryByTestId('SelectRecipient/ContactsModal')).toBeFalsy() } } ) })