diff --git a/src/hooks/useCompleteOnboarding.ts b/src/hooks/useCompleteOnboarding.ts index 6addbe7952fd..489c1e2b710a 100644 --- a/src/hooks/useCompleteOnboarding.ts +++ b/src/hooks/useCompleteOnboarding.ts @@ -29,6 +29,7 @@ import useResponsiveLayout from './useResponsiveLayout'; type CompleteOnboardingParams = { featuresMap: OnboardingFeatureMapItem[]; userReportedIntegration?: OnboardingAccounting; + userReportedIntegrationName?: string; }; function useCompleteOnboarding() { @@ -60,7 +61,7 @@ function useCompleteOnboarding() { const groupPolicy = Object.values(allPolicies ?? {}).find((policy) => isGroupPolicy(policy) && isPolicyAdmin(policy, session?.email)); - const completeOnboardingFlow = async ({featuresMap, userReportedIntegration}: CompleteOnboardingParams) => { + const completeOnboardingFlow = async ({featuresMap, userReportedIntegration, userReportedIntegrationName}: CompleteOnboardingParams) => { if (!onboardingPurposeSelected || !onboardingCompanySize) { return; } @@ -71,6 +72,7 @@ function useCompleteOnboarding() { const shouldCreateWorkspace = !onboardingPolicyID && !groupPolicy; const isAccountingEnabled = featuresMap.some((feature) => feature.id === CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED && feature.enabled); const resolvedIntegration = isAccountingEnabled ? userReportedIntegration : undefined; + const resolvedIntegrationName = resolvedIntegration === 'other' ? userReportedIntegrationName : undefined; const email = currentUserPersonalDetails.email ?? ''; const {adminsChatReportID, policyID} = shouldCreateWorkspace @@ -85,6 +87,7 @@ function useCompleteOnboarding() { shouldAddOnboardingTasks: false, companySize: onboardingCompanySize, userReportedIntegration: resolvedIntegration, + userReportedIntegrationName: resolvedIntegrationName, featuresMap, introSelected, activePolicy, @@ -110,6 +113,7 @@ function useCompleteOnboarding() { onboardingPolicyID: policyID, companySize: onboardingCompanySize, userReportedIntegration: resolvedIntegration, + userReportedIntegrationName: resolvedIntegrationName, firstName: currentUserPersonalDetails?.firstName, lastName: currentUserPersonalDetails?.lastName, selectedInterestedFeatures: featuresMap.filter((feature) => feature.enabled).map((feature) => feature.id), diff --git a/src/languages/de.ts b/src/languages/de.ts index 9dc76a1e4123..98dc8337d9d7 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -3190,6 +3190,7 @@ ${amount} für ${merchant} – ${date}`, accounting: { title: 'Verwendest du eine Buchhaltungssoftware?', none: 'Keine', + otherAccountingSoftware: 'Deine Buchhaltungssoftware', }, interestedFeatures: { title: 'An welchen Funktionen bist du interessiert?', diff --git a/src/languages/el.ts b/src/languages/el.ts index 6a92a3741f69..3d9d8d4d9215 100644 --- a/src/languages/el.ts +++ b/src/languages/el.ts @@ -3248,6 +3248,7 @@ ${amount} για ${merchant} - ${date}`, accounting: { title: 'Χρησιμοποιείτε κάποιο λογιστικό λογισμικό;', none: 'Κανένα', + otherAccountingSoftware: 'Το λογιστικό σας λογισμικό', }, interestedFeatures: { title: 'Σε ποιες δυνατότητες ενδιαφέρεστε;', diff --git a/src/languages/en.ts b/src/languages/en.ts index 2f236444964a..4d579119acdc 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3282,6 +3282,7 @@ const translations = { accounting: { title: 'Do you use any accounting software?', none: 'None', + otherAccountingSoftware: 'Your accounting software', }, interestedFeatures: { title: 'What features are you interested in?', diff --git a/src/languages/es.ts b/src/languages/es.ts index dccbd68d1f27..8eed196753cd 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3096,6 +3096,7 @@ ${amount} para ${merchant} - ${date}`, accounting: { title: '¿Utilizas algún software de contabilidad?', none: 'Ninguno', + otherAccountingSoftware: 'Tu software de contabilidad', }, interestedFeatures: { title: '¿Qué funciones te interesan?', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 09a9ff5a7be5..a8cc0fc2a1d5 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -3197,6 +3197,7 @@ ${amount} pour ${merchant} - ${date}`, accounting: { title: 'Utilisez-vous un logiciel de comptabilité ?', none: 'Aucun', + otherAccountingSoftware: 'Votre logiciel de comptabilité', }, interestedFeatures: { title: 'Quelles fonctionnalités vous intéressent ?', diff --git a/src/languages/it.ts b/src/languages/it.ts index c4b872f2159e..952e2f9e66eb 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -3179,6 +3179,7 @@ ${amount} per ${merchant} - ${date}`, accounting: { title: 'Usi un software di contabilità?', none: 'Nessuno', + otherAccountingSoftware: 'Il tuo software di contabilità', }, interestedFeatures: { title: 'A quali funzionalità sei interessato?', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index adaccf059f0b..12cc40818e25 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -3151,6 +3151,7 @@ ${date} の ${merchant} への ${amount}`, accounting: { title: '会計ソフトを利用していますか?', none: 'なし', + otherAccountingSoftware: 'ご利用の会計ソフト', }, interestedFeatures: { title: 'どの機能にご興味がありますか?', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 40398c2fe6c8..104d48f0cb5d 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -3180,6 +3180,7 @@ ${amount} voor ${merchant} - ${date}`, accounting: { title: 'Gebruikt u een boekhoudprogramma?', none: 'Geen', + otherAccountingSoftware: 'Je boekhoudsoftware', }, interestedFeatures: { title: 'In welke functies ben je geïnteresseerd?', diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 38a4dae9bad2..498a3282ef5c 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -3166,6 +3166,7 @@ ${amount} dla ${merchant} - ${date}`, accounting: { title: 'Czy korzystasz z jakiegoś oprogramowania księgowego?', none: 'Brak', + otherAccountingSoftware: 'Twoje oprogramowanie księgowe', }, interestedFeatures: { title: 'Jakie funkcje Cię interesują?', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index dcad147ca7d9..831fe4d1e328 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -3169,6 +3169,7 @@ ${amount} para ${merchant} - ${date}`, accounting: { title: 'Você usa algum software de contabilidade?', none: 'Nenhum', + otherAccountingSoftware: 'Seu software de contabilidade', }, interestedFeatures: { title: 'Em quais recursos você tem interesse?', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index d53d3e5603d2..3b37c7b0fcf2 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -3071,6 +3071,7 @@ ${amount},商户:${merchant} - 日期:${date}`, accounting: { title: '你是否使用任何会计软件?', none: '无', + otherAccountingSoftware: '你的会计软件', }, interestedFeatures: { title: '你对哪些功能感兴趣?', diff --git a/src/libs/API/parameters/CompleteGuidedSetupParams.ts b/src/libs/API/parameters/CompleteGuidedSetupParams.ts index 248abbefa982..ba78a9689fb6 100644 --- a/src/libs/API/parameters/CompleteGuidedSetupParams.ts +++ b/src/libs/API/parameters/CompleteGuidedSetupParams.ts @@ -12,6 +12,7 @@ type CompleteGuidedSetupParams = { paymentSelected?: string; companySize?: OnboardingCompanySize; userReportedIntegration?: OnboardingAccounting; + userReportedIntegrationName?: string; policyID?: string; selfDMReportID?: string; selfDMCreatedReportActionID?: string; diff --git a/src/libs/API/parameters/CreateWorkspaceParams.ts b/src/libs/API/parameters/CreateWorkspaceParams.ts index 5f778fc61620..d8ef5c72edec 100644 --- a/src/libs/API/parameters/CreateWorkspaceParams.ts +++ b/src/libs/API/parameters/CreateWorkspaceParams.ts @@ -16,6 +16,7 @@ type CreateWorkspaceParams = { file?: File; companySize?: string; userReportedIntegration?: string; + userReportedIntegrationName?: string; memberData?: string; features?: string; shouldAddGuideWelcomeMessage?: boolean; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index ff14f77dedd9..1bb653c6f6e4 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -251,6 +251,7 @@ type BuildPolicyDataOptions = { shouldAddOnboardingTasks?: boolean; companySize?: OnboardingCompanySize; userReportedIntegration?: OnboardingAccounting; + userReportedIntegrationName?: string; isAnnualSubscription?: boolean; featuresMap?: Array>; lastUsedPaymentMethod?: LastPaymentMethodType; @@ -2692,6 +2693,7 @@ function buildPolicyData(options: BuildPolicyDataOptions): OnyxData(initialSelectedIntegration); + const [userReportedIntegrationName, setUserReportedIntegrationName] = useState(''); + const [shouldScrollToOtherInput, setShouldScrollToOtherInput] = useState(initialSelectedIntegration === 'other'); const [error, setError] = useState(''); + const scrollViewRef = useRef(null); + const otherAccountingSoftwareInputRef = useRef(null); + const isOtherSelected = selectedIntegration === 'other'; const groupPolicy = Object.values(allPolicies ?? {}).find((policy) => isGroupPolicy(policy) && isPolicyAdmin(policy, session?.email)); const {isOffline} = useNetwork(); @@ -175,7 +186,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount additionalStyles={[StyleUtils.getAvatarBorderStyle(CONST.AVATAR_SIZE.DEFAULT, CONST.ICON_TYPE_AVATAR), styles.mr3, styles.onboardingSmallIcon]} /> ), - isSelected: selectedIntegration === 'other', + isSelected: isOtherSelected, }; const accountingOptions: OnboardingListItem[] = [...integrations.map(createAccountingOption), othersAccountingOption]; @@ -187,16 +198,41 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount } const integrationValue: OnboardingAccounting = selectedIntegration; + const trimmedIntegrationName = userReportedIntegrationName.trim(); + const integrationName = integrationValue === 'other' && trimmedIntegrationName ? trimmedIntegrationName : undefined; setOnboardingAccountingEnabled(true); setOnboardingUserReportedIntegration(integrationValue); - await completeOnboardingFlow({featuresMap: onboardingFeaturesMap ?? getDefaultOnboardingFeaturesMap(), userReportedIntegration: integrationValue}); + await completeOnboardingFlow({ + featuresMap: onboardingFeaturesMap ?? getDefaultOnboardingFeaturesMap(), + userReportedIntegration: integrationValue, + userReportedIntegrationName: integrationName, + }); }; const handleIntegrationSelect = (integrationKey: OnboardingListItem['keyForList']) => { + if (integrationKey === 'other' && isOtherSelected) { + otherAccountingSoftwareInputRef.current?.focus(); + setError(''); + return; + } + setSelectedIntegration(integrationKey); + setShouldScrollToOtherInput(integrationKey === 'other'); + if (integrationKey !== 'other') { + setUserReportedIntegrationName(''); + } setError(''); }; + const handleContentSizeChange = useCallback(() => { + if (!shouldScrollToOtherInput) { + return; + } + + scrollViewRef.current?.scrollToEnd({animated: false}); + setShouldScrollToOtherInput(false); + }, [shouldScrollToOtherInput]); + function renderOption(item: OnboardingListItem) { return ( Navigation.goBack(ROUTES.ONBOARDING_INTERESTED_FEATURES.getRoute())} /> @@ -242,10 +279,31 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount - {accountingOptions.map(renderOption)} + + {accountingOptions.map(renderOption)} + {/* Keep Other from expanding across the empty second column on wide layouts. */} + {!isSmallScreenWidth && ( + + )} + + {isOtherSelected && ( + + )} {!!error && ( diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 7188f77a1234..e3d7bb005efc 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -83,6 +83,69 @@ describe('actions/Policy', () => { mockFetch?.resume?.(); }); + it('forwards the user-reported integration name to CreateWorkspace', async () => { + const apiWriteSpy = jest.spyOn(require('@libs/API'), 'write').mockImplementation(() => Promise.resolve()); + + Policy.createWorkspace({ + conciergeChat: undefined, + policyOwnerEmail: ESH_EMAIL, + makeMeAdmin: true, + policyName: WORKSPACE_NAME, + policyID: Policy.generatePolicyID(), + engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, + currentUserAccountIDParam: ESH_ACCOUNT_ID, + currentUserEmailParam: ESH_EMAIL, + currency: CONST.CURRENCY.USD, + userReportedIntegration: 'other', + userReportedIntegrationName: 'Acme Books', + isSelfTourViewed: false, + betas: undefined, + hasActiveAdminPolicies: false, + activePolicy: undefined, + }); + await waitForBatchedUpdates(); + + expect(apiWriteSpy).toHaveBeenCalledWith( + WRITE_COMMANDS.CREATE_WORKSPACE, + expect.objectContaining({ + userReportedIntegration: 'other', + userReportedIntegrationName: 'Acme Books', + }), + expect.anything(), + ); + + apiWriteSpy.mockRestore(); + }); + + it('omits the user-reported integration name from CreateWorkspace when it is undefined', async () => { + Policy.createWorkspace({ + conciergeChat: undefined, + policyOwnerEmail: ESH_EMAIL, + makeMeAdmin: true, + policyName: WORKSPACE_NAME, + policyID: Policy.generatePolicyID(), + engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + introSelected: {choice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM}, + currentUserAccountIDParam: ESH_ACCOUNT_ID, + currentUserEmailParam: ESH_EMAIL, + currency: CONST.CURRENCY.USD, + userReportedIntegration: 'other', + isSelfTourViewed: false, + betas: undefined, + hasActiveAdminPolicies: false, + activePolicy: undefined, + }); + await waitForBatchedUpdates(); + + const calls = TestHelper.getFetchMockCalls(WRITE_COMMANDS.CREATE_WORKSPACE); + expect(calls.length).toBeGreaterThan(0); + const body = calls.at(-1)?.[1]?.body; + expect(body).toBeInstanceOf(FormData); + const formEntries = body instanceof FormData ? Object.fromEntries(body) : {}; + expect(formEntries.userReportedIntegrationName).toBeUndefined(); + }); + it('creates a new workspace', async () => { mockFetch?.pause?.(); await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID}); diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index ecd04062b6e4..45184b51b3df 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -3088,6 +3088,75 @@ describe('actions/Report', () => { expect(formEntries.selectedInterestedFeatures).toBe(JSON.stringify(selectedInterestedFeatures)); }); + it('should forward the user-reported integration name to the CompleteGuidedSetup API call', async () => { + await Onyx.set(ONYXKEYS.SESSION, {email: TEST_USER_LOGIN, accountID: TEST_USER_ACCOUNT_ID}); + jest.mocked(global.fetch).mockClear(); + await waitForBatchedUpdates(); + + const engagementChoice = CONST.INTRO_CHOICES.MANAGE_TEAM; + const {onboardingMessages} = getOnboardingMessages(); + + Report.completeOnboarding({ + conciergeChat: undefined, + engagementChoice, + onboardingMessage: onboardingMessages[engagementChoice], + adminsChatReportID: '7957055873634069', + onboardingPolicyID: 'A70D00C752416809', + companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, + userReportedIntegration: 'other', + userReportedIntegrationName: 'Acme Books', + introSelected: {choice: engagementChoice}, + isSelfTourViewed: false, + }); + + await waitForBatchedUpdates(); + + const calls = TestHelper.getFetchMockCalls(WRITE_COMMANDS.COMPLETE_GUIDED_SETUP); + expect(calls.length).toBeGreaterThan(0); + const body = calls.at(-1)?.[1]?.body; + expect(body).toBeInstanceOf(FormData); + if (!(body instanceof FormData)) { + throw new Error('Expected CompleteGuidedSetup request body to be FormData'); + } + const formEntries = Object.fromEntries(body); + expect(formEntries.userReportedIntegration).toBe('other'); + expect(formEntries.userReportedIntegrationName).toBe('Acme Books'); + }); + + it('should omit the user-reported integration name from CompleteGuidedSetup when it is undefined', async () => { + await Onyx.set(ONYXKEYS.SESSION, {email: TEST_USER_LOGIN, accountID: TEST_USER_ACCOUNT_ID}); + jest.mocked(global.fetch).mockClear(); + await waitForBatchedUpdates(); + + const engagementChoice = CONST.INTRO_CHOICES.MANAGE_TEAM; + const {onboardingMessages} = getOnboardingMessages(); + + Report.completeOnboarding({ + conciergeChat: undefined, + engagementChoice, + onboardingMessage: onboardingMessages[engagementChoice], + adminsChatReportID: '7957055873634070', + onboardingPolicyID: 'A70D00C752416810', + companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, + userReportedIntegration: 'other', + introSelected: {choice: engagementChoice}, + isSelfTourViewed: false, + }); + + await waitForBatchedUpdates(); + + const calls = TestHelper.getFetchMockCalls(WRITE_COMMANDS.COMPLETE_GUIDED_SETUP); + expect(calls.length).toBeGreaterThan(0); + const body = calls.at(-1)?.[1]?.body; + expect(body).toBeInstanceOf(FormData); + if (!(body instanceof FormData)) { + throw new Error('Expected CompleteGuidedSetup request body to be FormData'); + } + const formEntries = Object.fromEntries(body); + expect(formEntries.userReportedIntegration).toBe('other'); + expect(formEntries.userReportedIntegrationName).toBeUndefined(); + }); + it('should post onboarding tasks to the existing Concierge chat', async () => { await Onyx.set(ONYXKEYS.SESSION, {email: TEST_USER_LOGIN, accountID: TEST_USER_ACCOUNT_ID}); await waitForBatchedUpdates(); diff --git a/tests/ui/OnboardingAccountingAndInterestedFeatures.tsx b/tests/ui/OnboardingAccountingAndInterestedFeatures.tsx index 3935fd98342e..49800908bd4d 100644 --- a/tests/ui/OnboardingAccountingAndInterestedFeatures.tsx +++ b/tests/ui/OnboardingAccountingAndInterestedFeatures.tsx @@ -3,11 +3,14 @@ import {act, fireEvent, render, screen, waitFor} from '@testing-library/react-na import ComposeProviders from '@components/ComposeProviders'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; +import ScreenWrapper from '@components/ScreenWrapper'; +import TextInput from '@components/TextInput'; import {CurrentReportIDContextProvider} from '@hooks/useCurrentReportID'; import * as useResponsiveLayoutModule from '@hooks/useResponsiveLayout'; import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; +import * as Browser from '@libs/Browser'; import Navigation from '@libs/Navigation/Navigation'; import BaseOnboardingAccounting from '@pages/OnboardingAccounting/BaseOnboardingAccounting'; @@ -22,6 +25,8 @@ import SCREENS from '@src/SCREENS'; import {NavigationContainer} from '@react-navigation/native'; import {createStackNavigator} from '@react-navigation/stack'; import React from 'react'; +// eslint-disable-next-line no-restricted-imports -- React Native primitives are imported directly to inspect their test instances +import {ScrollView} from 'react-native'; import Onyx from 'react-native-onyx'; import * as TestHelper from '../utils/TestHelper'; @@ -30,6 +35,7 @@ import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct' type MockCompleteOnboardingParams = { featuresMap: Array<{id: string; enabled: boolean}>; userReportedIntegration?: string | null; + userReportedIntegrationName?: string; }; const mockCompleteOnboardingFlow = jest.fn(); @@ -44,6 +50,7 @@ TestHelper.setupGlobalFetchMock(); const Stack = createStackNavigator(); const navigate = jest.spyOn(Navigation, 'navigate'); const goBack = jest.spyOn(Navigation, 'goBack'); +const isMobileSafari = jest.spyOn(Browser, 'isMobileSafari'); jest.spyOn(Navigation, 'getTopmostReportId').mockReturnValue(undefined); function renderInterestedFeaturesPage() { @@ -92,6 +99,7 @@ describe('Onboarding interested features and accounting pages', () => { }); beforeEach(() => { + isMobileSafari.mockReturnValue(false); jest.spyOn(useResponsiveLayoutModule, 'default').mockReturnValue({ isSmallScreenWidth: false, shouldUseNarrowLayout: false, @@ -145,19 +153,82 @@ describe('Onboarding interested features and accounting pages', () => { expect(navigate).not.toHaveBeenCalledWith(ROUTES.ONBOARDING_ACCOUNTING.getRoute()); }); - it('completes direct accounting access with Other and fallback features', async () => { - renderAccountingPage(); + it('keeps Other half-width, auto-focuses its input, and completes with a trimmed integration name', async () => { + const scrollToEndSpy = jest.spyOn(ScrollView.prototype, 'scrollToEnd'); + const renderResult = renderAccountingPage(); await waitForBatchedUpdatesWithAct(); expect(screen.queryByText(TestHelper.translateLocal('onboarding.accounting.none'))).not.toBeOnTheScreen(); + expect(screen.getByTestId('onboarding-accounting-wide-layout-spacer')).toHaveStyle({backgroundColor: 'transparent', flexBasis: '35%', flexGrow: 1}); fireEvent.press(screen.getByText(TestHelper.translateLocal('workspace.accounting.other'))); + const otherAccountingSoftwareLabel = TestHelper.translateLocal('onboarding.accounting.otherAccountingSoftware'); + const otherAccountingSoftwareInput = screen.getByLabelText(otherAccountingSoftwareLabel); + expect(otherAccountingSoftwareInput.props.autoFocus).toBe(true); + expect(renderResult.UNSAFE_getByType(TextInput).props.forceActiveLabel).toBeFalsy(); + const accountingScrollView = renderResult.UNSAFE_getByType(ScrollView); + fireEvent(accountingScrollView, 'onContentSizeChange', 0, 0); + expect(scrollToEndSpy).toHaveBeenCalledWith({animated: false}); + fireEvent(accountingScrollView, 'onContentSizeChange', 0, 0); + expect(scrollToEndSpy).toHaveBeenCalledTimes(1); + fireEvent.changeText(otherAccountingSoftwareInput, ' Acme Books '); + fireEvent.press(screen.getByText(TestHelper.translateLocal('workspace.accounting.other'))); + expect(screen.getByLabelText(otherAccountingSoftwareLabel).props.value).toBe(' Acme Books '); fireEvent.press(screen.getByText(TestHelper.translateLocal('common.continue'))); await waitFor(() => { expect(mockCompleteOnboardingFlow).toHaveBeenCalledWith({ featuresMap: expect.arrayContaining([{id: CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED, enabled: true, enabledByDefault: true}]), userReportedIntegration: 'other', + userReportedIntegrationName: 'Acme Books', + }); + }); + }); + + it('disables max-height and virtual-viewport scroll suppression on mobile Safari', async () => { + isMobileSafari.mockReturnValue(true); + const renderResult = renderAccountingPage(); + + await waitForBatchedUpdatesWithAct(); + const screenWrapper = renderResult.UNSAFE_getByType(ScreenWrapper); + expect(screenWrapper.props.shouldEnableMaxHeight).toBe(false); + expect(screenWrapper.props.shouldAvoidScrollOnVirtualViewport).toBe(false); + }); + + it('omits a whitespace-only Other integration name', async () => { + renderAccountingPage(); + + await waitForBatchedUpdatesWithAct(); + fireEvent.press(screen.getByText(TestHelper.translateLocal('workspace.accounting.other'))); + fireEvent.changeText(screen.getByLabelText(TestHelper.translateLocal('onboarding.accounting.otherAccountingSoftware')), ' '); + fireEvent.press(screen.getByText(TestHelper.translateLocal('common.continue'))); + + await waitFor(() => { + expect(mockCompleteOnboardingFlow).toHaveBeenCalledWith({ + featuresMap: expect.arrayContaining([{id: CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED, enabled: true, enabledByDefault: true}]), + userReportedIntegration: 'other', + userReportedIntegrationName: undefined, + }); + }); + }); + + it('clears the Other integration name when a supported integration is selected', async () => { + renderAccountingPage(); + + await waitForBatchedUpdatesWithAct(); + fireEvent.press(screen.getByText(TestHelper.translateLocal('workspace.accounting.other'))); + fireEvent.changeText(screen.getByLabelText(TestHelper.translateLocal('onboarding.accounting.otherAccountingSoftware')), 'Acme Books'); + fireEvent.press(screen.getByText(TestHelper.translateLocal('workspace.accounting.qbo'))); + + expect(screen.queryByLabelText(TestHelper.translateLocal('onboarding.accounting.otherAccountingSoftware'))).not.toBeOnTheScreen(); + + fireEvent.press(screen.getByText(TestHelper.translateLocal('common.continue'))); + + await waitFor(() => { + expect(mockCompleteOnboardingFlow).toHaveBeenCalledWith({ + featuresMap: expect.arrayContaining([{id: CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED, enabled: true, enabledByDefault: true}]), + userReportedIntegration: 'quickbooksOnline', + userReportedIntegrationName: undefined, }); }); });