From a747e5fb8dd6efb2645998573918674895ce19c9 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Thu, 23 Jan 2025 20:49:30 +0700 Subject: [PATCH 01/13] fix: app does not open admin room after onboarding --- src/libs/navigateAfterOnboarding.ts | 5 ++++- src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/navigateAfterOnboarding.ts b/src/libs/navigateAfterOnboarding.ts index a6509fcaba37..0121d33e325f 100644 --- a/src/libs/navigateAfterOnboarding.ts +++ b/src/libs/navigateAfterOnboarding.ts @@ -3,13 +3,16 @@ import Navigation from './Navigation/Navigation'; import shouldOpenOnAdminRoom from './Navigation/shouldOpenOnAdminRoom'; import * as ReportUtils from './ReportUtils'; -const navigateAfterOnboarding = (isSmallScreenWidth: boolean, canUseDefaultRooms: boolean | undefined, onboardingPolicyID?: string, activeWorkspaceID?: string) => { +const navigateAfterOnboarding = (isSmallScreenWidth: boolean, canUseDefaultRooms: boolean | undefined, onboardingPolicyID?: string, activeWorkspaceID?: string, onboardingAdminsChatReportID?: string, shouldOpenAdminRoom?: boolean) => { Navigation.dismissModal(); // When hasCompletedGuidedSetupFlow is true, OnboardingModalNavigator in AuthScreen is removed from the navigation stack. // On small screens, this removal redirects navigation to HOME. Dismissing the modal doesn't work properly, // so we need to specifically navigate to the last accessed report. if (!isSmallScreenWidth) { + if (shouldOpenAdminRoom && onboardingAdminsChatReportID) { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(onboardingAdminsChatReportID)); + } return; } diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index 3f52722fc30e..4db85da43647 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -183,7 +183,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount setOnboardingAdminsChatReportID(); setOnboardingPolicyID(); }); - navigateAfterOnboarding(isSmallScreenWidth, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID); + navigateAfterOnboarding(isSmallScreenWidth, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, onboardingAdminsChatReportID, true); }} pressOnEnter /> From c751d792d50a1ade5e59844248810feb6c2a75cf Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Fri, 24 Jan 2025 15:50:57 +0700 Subject: [PATCH 02/13] run prettier --- src/libs/navigateAfterOnboarding.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/navigateAfterOnboarding.ts b/src/libs/navigateAfterOnboarding.ts index 0121d33e325f..91b7bf5af4ca 100644 --- a/src/libs/navigateAfterOnboarding.ts +++ b/src/libs/navigateAfterOnboarding.ts @@ -3,7 +3,14 @@ import Navigation from './Navigation/Navigation'; import shouldOpenOnAdminRoom from './Navigation/shouldOpenOnAdminRoom'; import * as ReportUtils from './ReportUtils'; -const navigateAfterOnboarding = (isSmallScreenWidth: boolean, canUseDefaultRooms: boolean | undefined, onboardingPolicyID?: string, activeWorkspaceID?: string, onboardingAdminsChatReportID?: string, shouldOpenAdminRoom?: boolean) => { +const navigateAfterOnboarding = ( + isSmallScreenWidth: boolean, + canUseDefaultRooms: boolean | undefined, + onboardingPolicyID?: string, + activeWorkspaceID?: string, + onboardingAdminsChatReportID?: string, + shouldOpenAdminRoom?: boolean, +) => { Navigation.dismissModal(); // When hasCompletedGuidedSetupFlow is true, OnboardingModalNavigator in AuthScreen is removed from the navigation stack. From 0ca1ee868ad2cfc94de8365bbbb92813db62d974 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Thu, 30 Jan 2025 01:17:10 +0700 Subject: [PATCH 03/13] fix lint --- src/libs/navigateAfterOnboarding.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/navigateAfterOnboarding.ts b/src/libs/navigateAfterOnboarding.ts index 91b7bf5af4ca..c90f7d9b263e 100644 --- a/src/libs/navigateAfterOnboarding.ts +++ b/src/libs/navigateAfterOnboarding.ts @@ -1,7 +1,7 @@ import ROUTES from '@src/ROUTES'; import Navigation from './Navigation/Navigation'; import shouldOpenOnAdminRoom from './Navigation/shouldOpenOnAdminRoom'; -import * as ReportUtils from './ReportUtils'; +import {findLastAccessedReport, isConciergeChatReport} from './ReportUtils'; const navigateAfterOnboarding = ( isSmallScreenWidth: boolean, @@ -23,10 +23,10 @@ const navigateAfterOnboarding = ( return; } - const lastAccessedReport = ReportUtils.findLastAccessedReport(!canUseDefaultRooms, shouldOpenOnAdminRoom(), activeWorkspaceID); + const lastAccessedReport = findLastAccessedReport(!canUseDefaultRooms, shouldOpenOnAdminRoom(), activeWorkspaceID); const lastAccessedReportID = lastAccessedReport?.reportID; // we don't want to navigate to newly creaded workspace after onboarding completed. - if (!lastAccessedReportID || lastAccessedReport.policyID === onboardingPolicyID || ReportUtils.isConciergeChatReport(lastAccessedReport)) { + if (!lastAccessedReportID || lastAccessedReport.policyID === onboardingPolicyID || isConciergeChatReport(lastAccessedReport)) { return; } From c8cf4f8fa208e1e800e0197eec45a6215dc51f82 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Thu, 30 Jan 2025 01:17:23 +0700 Subject: [PATCH 04/13] add ui test for onboarding navigation --- tests/ui/OnboardingModalTest.tsx | 132 +++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 tests/ui/OnboardingModalTest.tsx diff --git a/tests/ui/OnboardingModalTest.tsx b/tests/ui/OnboardingModalTest.tsx new file mode 100644 index 000000000000..9ada687ef28e --- /dev/null +++ b/tests/ui/OnboardingModalTest.tsx @@ -0,0 +1,132 @@ +import {act, render, screen, userEvent, waitFor} from '@testing-library/react-native'; +import React from 'react'; +import Onyx from 'react-native-onyx'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; +import * as Localize from '@libs/Localize'; +import Navigation from '@libs/Navigation/Navigation'; +import * as AppActions from '@userActions/App'; +import * as User from '@userActions/User'; +import App from '@src/App'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import PusherHelper from '../utils/PusherHelper'; +import * as TestHelper from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; + +// We need a large timeout here as we are lazy loading React Navigation screens and this test is running against the entire mounted App +jest.setTimeout(60000); + +jest.mock('@hooks/useResponsiveLayout', () => jest.fn()); +jest.mock('@rnmapbox/maps', () => { + return { + default: jest.fn(), + MarkerView: jest.fn(), + setAccessToken: jest.fn(), + }; +}); +jest.mock('@react-native-community/geolocation', () => ({ + setRNConfiguration: jest.fn(), +})); +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useIsFocused: jest.fn(), + triggerTransitionEnd: jest.fn(), + }; +}); + +const DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE: ResponsiveLayoutResult = { + shouldUseNarrowLayout: true, + isSmallScreenWidth: false, + isInNarrowPaneModal: false, + isExtraSmallScreenHeight: false, + isMediumScreenWidth: false, + isLargeScreenWidth: false, + isExtraSmallScreenWidth: false, + isSmallScreen: false, + onboardingIsMediumOrLargerScreenWidth: false, +}; + +const mockedUseResponsiveLayout = useResponsiveLayout as jest.MockedFunction; + +TestHelper.setupApp(); + +async function signInAndGetApp(): Promise { + // Render the App and sign in as a test user. + render(); + await waitForBatchedUpdatesWithAct(); + + const hintText = Localize.translateLocal('loginForm.loginForm'); + const loginForm = await screen.findAllByLabelText(hintText); + expect(loginForm).toHaveLength(1); + + await act(async () => { + await TestHelper.signInWithTestUser(); + }); + await waitForBatchedUpdatesWithAct(); + + User.subscribeToUserEvents(); + await waitForBatchedUpdates(); + + AppActions.setSidebarLoaded(); + + await waitForBatchedUpdatesWithAct(); +} + +async function completeOnboarding(): Promise { + Onyx.set(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: false}); + await waitForBatchedUpdatesWithAct(); + + const user = userEvent.setup(); + user.press(screen.getByText(Localize.translateLocal('onboarding.purpose.newDotManageTeam'))); + expect(await screen.findByTestId('BaseOnboardingEmployees')).toBeOnTheScreen(); + user.press(screen.getByText(Localize.translateLocal('onboarding.employees.1-10'))); + user.press(screen.getByText(Localize.translateLocal('common.continue'))); + expect(await screen.findByTestId('BaseOnboardingAccounting')).toBeOnTheScreen(); + user.press(screen.getByText(Localize.translateLocal('onboarding.accounting.noneOfAbove'))); + user.press(screen.getByText(Localize.translateLocal('common.continue'))); +} + +describe('OnboardingModal', () => { + beforeEach(() => { + jest.clearAllMocks(); + Onyx.clear(); + // Unsubscribe to pusher channels + PusherHelper.teardown(); + }); + + it('navigates to #admins room if onboarding purpose is "Manage my team\'s expenses" and has 1-10 employees (vsb) on large screens', async () => { + mockedUseResponsiveLayout.mockReturnValue(DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE); + await signInAndGetApp(); + await completeOnboarding(); + + // This value will be cleared once onboarding modal is hidden so we need to get it before + await waitForBatchedUpdates(); + const onboardingAdminsChatReportID = await new Promise((resolve) => { + const connection = Onyx.connect({ + key: ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID, + callback: (value) => { + Onyx.disconnect(connection); + resolve(value ?? ''); + }, + }); + }); + + // Wait for onboarding modal to be hidden + await waitFor(() => expect(screen.queryByTestId('BaseOnboardingAccounting')).not.toBeOnTheScreen()); + expect(Navigation.isActiveRoute(ROUTES.REPORT_WITH_ID.getRoute(onboardingAdminsChatReportID))).toBeTruthy(); + }); + + it('navigates to LHN if onboarding purpose is "Manage my team\'s expenses" and has 1-10 employees (vsb) on small screens', async () => { + mockedUseResponsiveLayout.mockReturnValue({...DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE, isSmallScreenWidth: true}); + await signInAndGetApp(); + await completeOnboarding(); + + // Wait for onboarding modal to be hidden + await waitFor(() => expect(screen.queryByTestId('BaseOnboardingAccounting')).not.toBeOnTheScreen()); + expect(Navigation.isActiveRoute(ROUTES.HOME)).toBeTruthy(); + }); +}); From 0949defea6b613195d792b22925e9f4d04ea5006 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Thu, 30 Jan 2025 01:21:28 +0700 Subject: [PATCH 05/13] fix lint --- tests/ui/OnboardingModalTest.tsx | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/ui/OnboardingModalTest.tsx b/tests/ui/OnboardingModalTest.tsx index 9ada687ef28e..99f3d011ef44 100644 --- a/tests/ui/OnboardingModalTest.tsx +++ b/tests/ui/OnboardingModalTest.tsx @@ -3,15 +3,15 @@ import React from 'react'; import Onyx from 'react-native-onyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; -import * as Localize from '@libs/Localize'; +import {translateLocal} from '@libs/Localize'; import Navigation from '@libs/Navigation/Navigation'; -import * as AppActions from '@userActions/App'; -import * as User from '@userActions/User'; +import {setSidebarLoaded} from '@userActions/App'; +import {subscribeToUserEvents} from '@userActions/User'; import App from '@src/App'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import PusherHelper from '../utils/PusherHelper'; -import * as TestHelper from '../utils/TestHelper'; +import {setupApp, signInWithTestUser} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; @@ -52,26 +52,26 @@ const DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE: ResponsiveLayoutResult = { const mockedUseResponsiveLayout = useResponsiveLayout as jest.MockedFunction; -TestHelper.setupApp(); +setupApp(); async function signInAndGetApp(): Promise { // Render the App and sign in as a test user. render(); await waitForBatchedUpdatesWithAct(); - const hintText = Localize.translateLocal('loginForm.loginForm'); + const hintText = translateLocal('loginForm.loginForm'); const loginForm = await screen.findAllByLabelText(hintText); expect(loginForm).toHaveLength(1); await act(async () => { - await TestHelper.signInWithTestUser(); + await signInWithTestUser(); }); await waitForBatchedUpdatesWithAct(); - User.subscribeToUserEvents(); + subscribeToUserEvents(); await waitForBatchedUpdates(); - AppActions.setSidebarLoaded(); + setSidebarLoaded(); await waitForBatchedUpdatesWithAct(); } @@ -81,13 +81,13 @@ async function completeOnboarding(): Promise { await waitForBatchedUpdatesWithAct(); const user = userEvent.setup(); - user.press(screen.getByText(Localize.translateLocal('onboarding.purpose.newDotManageTeam'))); + user.press(screen.getByText(translateLocal('onboarding.purpose.newDotManageTeam'))); expect(await screen.findByTestId('BaseOnboardingEmployees')).toBeOnTheScreen(); - user.press(screen.getByText(Localize.translateLocal('onboarding.employees.1-10'))); - user.press(screen.getByText(Localize.translateLocal('common.continue'))); + user.press(screen.getByText(translateLocal('onboarding.employees.1-10'))); + user.press(screen.getByText(translateLocal('common.continue'))); expect(await screen.findByTestId('BaseOnboardingAccounting')).toBeOnTheScreen(); - user.press(screen.getByText(Localize.translateLocal('onboarding.accounting.noneOfAbove'))); - user.press(screen.getByText(Localize.translateLocal('common.continue'))); + user.press(screen.getByText(translateLocal('onboarding.accounting.noneOfAbove'))); + user.press(screen.getByText(translateLocal('common.continue'))); } describe('OnboardingModal', () => { From a0fd853a4f255224e00ad8dd2b0c465e11fd92f8 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Thu, 30 Jan 2025 01:43:00 +0700 Subject: [PATCH 06/13] replace with fireEvent --- tests/ui/OnboardingModalTest.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/ui/OnboardingModalTest.tsx b/tests/ui/OnboardingModalTest.tsx index 99f3d011ef44..f71bea6a1b1b 100644 --- a/tests/ui/OnboardingModalTest.tsx +++ b/tests/ui/OnboardingModalTest.tsx @@ -1,4 +1,4 @@ -import {act, render, screen, userEvent, waitFor} from '@testing-library/react-native'; +import {act, fireEvent, render, screen, waitFor} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -80,14 +80,13 @@ async function completeOnboarding(): Promise { Onyx.set(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: false}); await waitForBatchedUpdatesWithAct(); - const user = userEvent.setup(); - user.press(screen.getByText(translateLocal('onboarding.purpose.newDotManageTeam'))); + fireEvent.press(screen.getByText(translateLocal('onboarding.purpose.newDotManageTeam'))); expect(await screen.findByTestId('BaseOnboardingEmployees')).toBeOnTheScreen(); - user.press(screen.getByText(translateLocal('onboarding.employees.1-10'))); - user.press(screen.getByText(translateLocal('common.continue'))); + fireEvent.press(screen.getByText(translateLocal('onboarding.employees.1-10'))); + fireEvent.press(screen.getByText(translateLocal('common.continue'))); expect(await screen.findByTestId('BaseOnboardingAccounting')).toBeOnTheScreen(); - user.press(screen.getByText(translateLocal('onboarding.accounting.noneOfAbove'))); - user.press(screen.getByText(translateLocal('common.continue'))); + fireEvent.press(screen.getByText(translateLocal('onboarding.accounting.noneOfAbove'))); + fireEvent.press(screen.getByText(translateLocal('common.continue'))); } describe('OnboardingModal', () => { From e30e2337c767f9785fa28998f31de689a9532f81 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Thu, 30 Jan 2025 01:55:30 +0700 Subject: [PATCH 07/13] get by label text --- .../BaseOnboardingAccounting.tsx | 1 + .../OnboardingEmployees/BaseOnboardingEmployees.tsx | 1 + tests/ui/OnboardingModalTest.tsx | 13 +++++++------ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index 4db85da43647..0035839394c3 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -157,6 +157,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount success large text={translate('common.continue')} + accessibilityLabel={translate('common.continue')} onPress={() => { if (userReportedIntegration === undefined) { setError(translate('onboarding.errorSelection')); diff --git a/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx b/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx index 12722e87f05a..bf34edaf66ad 100644 --- a/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx +++ b/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx @@ -64,6 +64,7 @@ function BaseOnboardingEmployees({shouldUseNativeStyles, route}: BaseOnboardingE success large text={translate('common.continue')} + accessibilityLabel={translate('common.continue')} onPress={() => { if (!selectedCompanySize) { setError(translate('onboarding.errorSelection')); diff --git a/tests/ui/OnboardingModalTest.tsx b/tests/ui/OnboardingModalTest.tsx index f71bea6a1b1b..523e874dd730 100644 --- a/tests/ui/OnboardingModalTest.tsx +++ b/tests/ui/OnboardingModalTest.tsx @@ -1,4 +1,4 @@ -import {act, fireEvent, render, screen, waitFor} from '@testing-library/react-native'; +import {act, render, screen, userEvent, waitFor} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -80,13 +80,14 @@ async function completeOnboarding(): Promise { Onyx.set(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: false}); await waitForBatchedUpdatesWithAct(); - fireEvent.press(screen.getByText(translateLocal('onboarding.purpose.newDotManageTeam'))); + const user = userEvent.setup(); + user.press(screen.getByText(translateLocal('onboarding.purpose.newDotManageTeam'))); expect(await screen.findByTestId('BaseOnboardingEmployees')).toBeOnTheScreen(); - fireEvent.press(screen.getByText(translateLocal('onboarding.employees.1-10'))); - fireEvent.press(screen.getByText(translateLocal('common.continue'))); + user.press(screen.getByText(translateLocal('onboarding.employees.1-10'))); + user.press(screen.getByLabelText(translateLocal('common.continue'))); expect(await screen.findByTestId('BaseOnboardingAccounting')).toBeOnTheScreen(); - fireEvent.press(screen.getByText(translateLocal('onboarding.accounting.noneOfAbove'))); - fireEvent.press(screen.getByText(translateLocal('common.continue'))); + user.press(screen.getByText(translateLocal('onboarding.accounting.noneOfAbove'))); + user.press(screen.getByLabelText(translateLocal('common.continue'))); } describe('OnboardingModal', () => { From b7c8c15b4d67cca8e76cd0bf974359b220738267 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Thu, 30 Jan 2025 18:38:48 +0700 Subject: [PATCH 08/13] revert: add unit test --- .../BaseOnboardingAccounting.tsx | 1 - .../BaseOnboardingEmployees.tsx | 1 - tests/ui/OnboardingModalTest.tsx | 132 ------------------ 3 files changed, 134 deletions(-) delete mode 100644 tests/ui/OnboardingModalTest.tsx diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index 0035839394c3..4db85da43647 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -157,7 +157,6 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount success large text={translate('common.continue')} - accessibilityLabel={translate('common.continue')} onPress={() => { if (userReportedIntegration === undefined) { setError(translate('onboarding.errorSelection')); diff --git a/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx b/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx index bf34edaf66ad..12722e87f05a 100644 --- a/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx +++ b/src/pages/OnboardingEmployees/BaseOnboardingEmployees.tsx @@ -64,7 +64,6 @@ function BaseOnboardingEmployees({shouldUseNativeStyles, route}: BaseOnboardingE success large text={translate('common.continue')} - accessibilityLabel={translate('common.continue')} onPress={() => { if (!selectedCompanySize) { setError(translate('onboarding.errorSelection')); diff --git a/tests/ui/OnboardingModalTest.tsx b/tests/ui/OnboardingModalTest.tsx deleted file mode 100644 index 523e874dd730..000000000000 --- a/tests/ui/OnboardingModalTest.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import {act, render, screen, userEvent, waitFor} from '@testing-library/react-native'; -import React from 'react'; -import Onyx from 'react-native-onyx'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; -import {translateLocal} from '@libs/Localize'; -import Navigation from '@libs/Navigation/Navigation'; -import {setSidebarLoaded} from '@userActions/App'; -import {subscribeToUserEvents} from '@userActions/User'; -import App from '@src/App'; -import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import PusherHelper from '../utils/PusherHelper'; -import {setupApp, signInWithTestUser} from '../utils/TestHelper'; -import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; - -// We need a large timeout here as we are lazy loading React Navigation screens and this test is running against the entire mounted App -jest.setTimeout(60000); - -jest.mock('@hooks/useResponsiveLayout', () => jest.fn()); -jest.mock('@rnmapbox/maps', () => { - return { - default: jest.fn(), - MarkerView: jest.fn(), - setAccessToken: jest.fn(), - }; -}); -jest.mock('@react-native-community/geolocation', () => ({ - setRNConfiguration: jest.fn(), -})); -jest.mock('@react-navigation/native', () => { - const actualNav = jest.requireActual('@react-navigation/native'); - return { - ...actualNav, - useIsFocused: jest.fn(), - triggerTransitionEnd: jest.fn(), - }; -}); - -const DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE: ResponsiveLayoutResult = { - shouldUseNarrowLayout: true, - isSmallScreenWidth: false, - isInNarrowPaneModal: false, - isExtraSmallScreenHeight: false, - isMediumScreenWidth: false, - isLargeScreenWidth: false, - isExtraSmallScreenWidth: false, - isSmallScreen: false, - onboardingIsMediumOrLargerScreenWidth: false, -}; - -const mockedUseResponsiveLayout = useResponsiveLayout as jest.MockedFunction; - -setupApp(); - -async function signInAndGetApp(): Promise { - // Render the App and sign in as a test user. - render(); - await waitForBatchedUpdatesWithAct(); - - const hintText = translateLocal('loginForm.loginForm'); - const loginForm = await screen.findAllByLabelText(hintText); - expect(loginForm).toHaveLength(1); - - await act(async () => { - await signInWithTestUser(); - }); - await waitForBatchedUpdatesWithAct(); - - subscribeToUserEvents(); - await waitForBatchedUpdates(); - - setSidebarLoaded(); - - await waitForBatchedUpdatesWithAct(); -} - -async function completeOnboarding(): Promise { - Onyx.set(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: false}); - await waitForBatchedUpdatesWithAct(); - - const user = userEvent.setup(); - user.press(screen.getByText(translateLocal('onboarding.purpose.newDotManageTeam'))); - expect(await screen.findByTestId('BaseOnboardingEmployees')).toBeOnTheScreen(); - user.press(screen.getByText(translateLocal('onboarding.employees.1-10'))); - user.press(screen.getByLabelText(translateLocal('common.continue'))); - expect(await screen.findByTestId('BaseOnboardingAccounting')).toBeOnTheScreen(); - user.press(screen.getByText(translateLocal('onboarding.accounting.noneOfAbove'))); - user.press(screen.getByLabelText(translateLocal('common.continue'))); -} - -describe('OnboardingModal', () => { - beforeEach(() => { - jest.clearAllMocks(); - Onyx.clear(); - // Unsubscribe to pusher channels - PusherHelper.teardown(); - }); - - it('navigates to #admins room if onboarding purpose is "Manage my team\'s expenses" and has 1-10 employees (vsb) on large screens', async () => { - mockedUseResponsiveLayout.mockReturnValue(DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE); - await signInAndGetApp(); - await completeOnboarding(); - - // This value will be cleared once onboarding modal is hidden so we need to get it before - await waitForBatchedUpdates(); - const onboardingAdminsChatReportID = await new Promise((resolve) => { - const connection = Onyx.connect({ - key: ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID, - callback: (value) => { - Onyx.disconnect(connection); - resolve(value ?? ''); - }, - }); - }); - - // Wait for onboarding modal to be hidden - await waitFor(() => expect(screen.queryByTestId('BaseOnboardingAccounting')).not.toBeOnTheScreen()); - expect(Navigation.isActiveRoute(ROUTES.REPORT_WITH_ID.getRoute(onboardingAdminsChatReportID))).toBeTruthy(); - }); - - it('navigates to LHN if onboarding purpose is "Manage my team\'s expenses" and has 1-10 employees (vsb) on small screens', async () => { - mockedUseResponsiveLayout.mockReturnValue({...DEFAULT_USE_RESPONSIVE_LAYOUT_VALUE, isSmallScreenWidth: true}); - await signInAndGetApp(); - await completeOnboarding(); - - // Wait for onboarding modal to be hidden - await waitFor(() => expect(screen.queryByTestId('BaseOnboardingAccounting')).not.toBeOnTheScreen()); - expect(Navigation.isActiveRoute(ROUTES.HOME)).toBeTruthy(); - }); -}); From 0fce8a1c5fe571b4d220b141efca32f69a63c71e Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Mon, 3 Feb 2025 22:31:49 +0700 Subject: [PATCH 09/13] update comment --- src/libs/navigateAfterOnboarding.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/navigateAfterOnboarding.ts b/src/libs/navigateAfterOnboarding.ts index c90f7d9b263e..fbe17f62efdb 100644 --- a/src/libs/navigateAfterOnboarding.ts +++ b/src/libs/navigateAfterOnboarding.ts @@ -25,7 +25,7 @@ const navigateAfterOnboarding = ( const lastAccessedReport = findLastAccessedReport(!canUseDefaultRooms, shouldOpenOnAdminRoom(), activeWorkspaceID); const lastAccessedReportID = lastAccessedReport?.reportID; - // we don't want to navigate to newly creaded workspace after onboarding completed. + // we don't want to navigate to newly created workspaces after onboarding is completed. if (!lastAccessedReportID || lastAccessedReport.policyID === onboardingPolicyID || isConciergeChatReport(lastAccessedReport)) { return; } From a631139062a7f7b72831689eb4884d53563e4f3b Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Mon, 3 Feb 2025 22:31:59 +0700 Subject: [PATCH 10/13] add unit test for navigateAfterOnboarding --- tests/unit/navigateAfterOnboardingTest.ts | 91 +++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tests/unit/navigateAfterOnboardingTest.ts diff --git a/tests/unit/navigateAfterOnboardingTest.ts b/tests/unit/navigateAfterOnboardingTest.ts new file mode 100644 index 000000000000..6437f1c6cf94 --- /dev/null +++ b/tests/unit/navigateAfterOnboardingTest.ts @@ -0,0 +1,91 @@ +import navigateAfterOnboarding from '@libs/navigateAfterOnboarding'; +import Navigation from '@libs/Navigation/Navigation'; +import ROUTES from '@src/ROUTES'; +import type * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; +import type { OnyxEntry } from 'react-native-onyx'; +import type { Report } from '@src/types/onyx'; + +const ONBOARDING_ADMINS_CHAT_REPORT_ID = '1'; +const ONBOARDING_POLICY_ID = '2'; +const ACTIVE_WORKSPACE_ID = '3'; +const REPORT_ID = '4'; +const USER_ID = '5' +const mockFindLastAccessedReport = jest.fn(); +const mockShouldOpenOnAdminRoom = jest.fn(); + +jest.mock('@react-navigation/native', () => { + const actualNav = jest.requireActual('@react-navigation/native'); + return { + ...actualNav, + useIsFocused: jest.fn(), + triggerTransitionEnd: jest.fn(), + }; +}); + +jest.mock('@libs/ReportUtils', () => ({ + findLastAccessedReport: () => mockFindLastAccessedReport() as OnyxEntry, + parseReportRouteParams: jest.fn(() => ({})), + isConciergeChatReport: jest.requireActual('@libs/ReportUtils').isConciergeChatReport, +})); + +jest.mock('@libs/Navigation/shouldOpenOnAdminRoom', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + __esModule: true, + default: () => mockShouldOpenOnAdminRoom() as boolean, +})); + +describe('navigateAfterOnboarding', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should navigate to the admin room report if shouldOpenAdminRoom and has onboardingAdminsChatReportID', () => { + const navigate = jest.spyOn(Navigation, 'navigate'); + + navigateAfterOnboarding(false, true, undefined, undefined, ONBOARDING_ADMINS_CHAT_REPORT_ID, true); + expect(navigate).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(ONBOARDING_ADMINS_CHAT_REPORT_ID)); + }); + + it('should not navigate if shouldOpenAdminRoom is false', () => { + navigateAfterOnboarding(false, true, undefined, undefined, ONBOARDING_ADMINS_CHAT_REPORT_ID, false); + expect(Navigation.navigate).not.toHaveBeenCalled(); + }); + + it('should navigate to LHN on small screens', () => { + const navigate = jest.spyOn(Navigation, 'navigate'); + const lastAccessedReport = { reportID: REPORT_ID, policyID: 'policyID' }; + mockFindLastAccessedReport.mockReturnValue(lastAccessedReport); + mockShouldOpenOnAdminRoom.mockReturnValue(false); + + navigateAfterOnboarding(true, true, ONBOARDING_POLICY_ID, ACTIVE_WORKSPACE_ID, undefined, false); + expect(navigate).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(REPORT_ID)); + }); + + it('should not navigate to the last accessed report if it is a concierge chat report', () => { + const navigate = jest.spyOn(Navigation, 'navigate'); + const lastAccessedReport = { + reportID: REPORT_ID, + participants: { + [CONST.ACCOUNT_ID.CONCIERGE.toString()]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + [USER_ID]: {notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS}, + }, + reportName: 'Concierge', + type: CONST.REPORT.TYPE.CHAT, + }; + mockFindLastAccessedReport.mockReturnValue(lastAccessedReport); + mockShouldOpenOnAdminRoom.mockReturnValue(false); + + navigateAfterOnboarding(true, true, ONBOARDING_POLICY_ID, ACTIVE_WORKSPACE_ID, undefined, false); + expect(navigate).not.toHaveBeenCalled(); + }); + + it('should not navigate to the last accessed report if it matches the onboarding policy ID', () => { + const lastAccessedReport = { reportID: REPORT_ID, policyID: ONBOARDING_POLICY_ID }; + mockFindLastAccessedReport.mockReturnValue(lastAccessedReport); + mockShouldOpenOnAdminRoom.mockReturnValue(false); + + navigateAfterOnboarding(true, true, ONBOARDING_POLICY_ID, ACTIVE_WORKSPACE_ID, undefined, false); + expect(Navigation.navigate).not.toHaveBeenCalled(); + }); +}); From b2bbe6b0da5fcc5c4dd32b7153604b05ead5fbf4 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Mon, 3 Feb 2025 22:35:50 +0700 Subject: [PATCH 11/13] remove redundant case --- tests/unit/navigateAfterOnboardingTest.ts | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/tests/unit/navigateAfterOnboardingTest.ts b/tests/unit/navigateAfterOnboardingTest.ts index 6437f1c6cf94..6d722dbeb57d 100644 --- a/tests/unit/navigateAfterOnboardingTest.ts +++ b/tests/unit/navigateAfterOnboardingTest.ts @@ -52,17 +52,7 @@ describe('navigateAfterOnboarding', () => { expect(Navigation.navigate).not.toHaveBeenCalled(); }); - it('should navigate to LHN on small screens', () => { - const navigate = jest.spyOn(Navigation, 'navigate'); - const lastAccessedReport = { reportID: REPORT_ID, policyID: 'policyID' }; - mockFindLastAccessedReport.mockReturnValue(lastAccessedReport); - mockShouldOpenOnAdminRoom.mockReturnValue(false); - - navigateAfterOnboarding(true, true, ONBOARDING_POLICY_ID, ACTIVE_WORKSPACE_ID, undefined, false); - expect(navigate).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(REPORT_ID)); - }); - - it('should not navigate to the last accessed report if it is a concierge chat report', () => { + it('should navigate to LHN if it is a concierge chat on small screens', () => { const navigate = jest.spyOn(Navigation, 'navigate'); const lastAccessedReport = { reportID: REPORT_ID, @@ -80,7 +70,7 @@ describe('navigateAfterOnboarding', () => { expect(navigate).not.toHaveBeenCalled(); }); - it('should not navigate to the last accessed report if it matches the onboarding policy ID', () => { + it('should navigate to LHN if it is onboarding workspace chat on small screens', () => { const lastAccessedReport = { reportID: REPORT_ID, policyID: ONBOARDING_POLICY_ID }; mockFindLastAccessedReport.mockReturnValue(lastAccessedReport); mockShouldOpenOnAdminRoom.mockReturnValue(false); From dce0d915f4b321539730b89d300d7b506d28b467 Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Mon, 3 Feb 2025 22:39:01 +0700 Subject: [PATCH 12/13] fix lint --- tests/unit/navigateAfterOnboardingTest.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/unit/navigateAfterOnboardingTest.ts b/tests/unit/navigateAfterOnboardingTest.ts index 6d722dbeb57d..1bb1f506448e 100644 --- a/tests/unit/navigateAfterOnboardingTest.ts +++ b/tests/unit/navigateAfterOnboardingTest.ts @@ -1,16 +1,17 @@ +import type {OnyxEntry} from 'react-native-onyx'; import navigateAfterOnboarding from '@libs/navigateAfterOnboarding'; import Navigation from '@libs/Navigation/Navigation'; -import ROUTES from '@src/ROUTES'; +// eslint-disable-next-line no-restricted-syntax import type * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; -import type { OnyxEntry } from 'react-native-onyx'; -import type { Report } from '@src/types/onyx'; +import ROUTES from '@src/ROUTES'; +import type {Report} from '@src/types/onyx'; const ONBOARDING_ADMINS_CHAT_REPORT_ID = '1'; const ONBOARDING_POLICY_ID = '2'; const ACTIVE_WORKSPACE_ID = '3'; const REPORT_ID = '4'; -const USER_ID = '5' +const USER_ID = '5'; const mockFindLastAccessedReport = jest.fn(); const mockShouldOpenOnAdminRoom = jest.fn(); @@ -71,7 +72,7 @@ describe('navigateAfterOnboarding', () => { }); it('should navigate to LHN if it is onboarding workspace chat on small screens', () => { - const lastAccessedReport = { reportID: REPORT_ID, policyID: ONBOARDING_POLICY_ID }; + const lastAccessedReport = {reportID: REPORT_ID, policyID: ONBOARDING_POLICY_ID}; mockFindLastAccessedReport.mockReturnValue(lastAccessedReport); mockShouldOpenOnAdminRoom.mockReturnValue(false); From 7e793cee2865bd619386696e7a6ac2781d956d0d Mon Sep 17 00:00:00 2001 From: mkzie2 Date: Tue, 4 Feb 2025 18:27:19 +0700 Subject: [PATCH 13/13] remove shouldOpenAdminRoom param and add a test case --- src/libs/navigateAfterOnboarding.ts | 3 +-- .../BaseOnboardingAccounting.tsx | 2 +- tests/unit/navigateAfterOnboardingTest.ts | 22 ++++++++++++++----- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/libs/navigateAfterOnboarding.ts b/src/libs/navigateAfterOnboarding.ts index fbe17f62efdb..8c74439fb00a 100644 --- a/src/libs/navigateAfterOnboarding.ts +++ b/src/libs/navigateAfterOnboarding.ts @@ -9,7 +9,6 @@ const navigateAfterOnboarding = ( onboardingPolicyID?: string, activeWorkspaceID?: string, onboardingAdminsChatReportID?: string, - shouldOpenAdminRoom?: boolean, ) => { Navigation.dismissModal(); @@ -17,7 +16,7 @@ const navigateAfterOnboarding = ( // On small screens, this removal redirects navigation to HOME. Dismissing the modal doesn't work properly, // so we need to specifically navigate to the last accessed report. if (!isSmallScreenWidth) { - if (shouldOpenAdminRoom && onboardingAdminsChatReportID) { + if (onboardingAdminsChatReportID) { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(onboardingAdminsChatReportID)); } return; diff --git a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx index 616055aa7515..ad050e23b3b1 100644 --- a/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx +++ b/src/pages/OnboardingAccounting/BaseOnboardingAccounting.tsx @@ -183,7 +183,7 @@ function BaseOnboardingAccounting({shouldUseNativeStyles}: BaseOnboardingAccount setOnboardingAdminsChatReportID(); setOnboardingPolicyID(); }); - navigateAfterOnboarding(isSmallScreenWidth, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, onboardingAdminsChatReportID, true); + navigateAfterOnboarding(isSmallScreenWidth, canUseDefaultRooms, onboardingPolicyID, activeWorkspaceID, onboardingAdminsChatReportID); }} pressOnEnter /> diff --git a/tests/unit/navigateAfterOnboardingTest.ts b/tests/unit/navigateAfterOnboardingTest.ts index 1bb1f506448e..0cd0f76eb362 100644 --- a/tests/unit/navigateAfterOnboardingTest.ts +++ b/tests/unit/navigateAfterOnboardingTest.ts @@ -41,15 +41,15 @@ describe('navigateAfterOnboarding', () => { jest.clearAllMocks(); }); - it('should navigate to the admin room report if shouldOpenAdminRoom and has onboardingAdminsChatReportID', () => { + it('should navigate to the admin room report if onboardingAdminsChatReportID is provided', () => { const navigate = jest.spyOn(Navigation, 'navigate'); - navigateAfterOnboarding(false, true, undefined, undefined, ONBOARDING_ADMINS_CHAT_REPORT_ID, true); + navigateAfterOnboarding(false, true, undefined, undefined, ONBOARDING_ADMINS_CHAT_REPORT_ID); expect(navigate).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(ONBOARDING_ADMINS_CHAT_REPORT_ID)); }); - it('should not navigate if shouldOpenAdminRoom is false', () => { - navigateAfterOnboarding(false, true, undefined, undefined, ONBOARDING_ADMINS_CHAT_REPORT_ID, false); + it('should not navigate if onboardingAdminsChatReportID is not provided', () => { + navigateAfterOnboarding(false, true, undefined, undefined); expect(Navigation.navigate).not.toHaveBeenCalled(); }); @@ -67,7 +67,7 @@ describe('navigateAfterOnboarding', () => { mockFindLastAccessedReport.mockReturnValue(lastAccessedReport); mockShouldOpenOnAdminRoom.mockReturnValue(false); - navigateAfterOnboarding(true, true, ONBOARDING_POLICY_ID, ACTIVE_WORKSPACE_ID, undefined, false); + navigateAfterOnboarding(true, true, ONBOARDING_POLICY_ID, ACTIVE_WORKSPACE_ID, ONBOARDING_ADMINS_CHAT_REPORT_ID); expect(navigate).not.toHaveBeenCalled(); }); @@ -76,7 +76,17 @@ describe('navigateAfterOnboarding', () => { mockFindLastAccessedReport.mockReturnValue(lastAccessedReport); mockShouldOpenOnAdminRoom.mockReturnValue(false); - navigateAfterOnboarding(true, true, ONBOARDING_POLICY_ID, ACTIVE_WORKSPACE_ID, undefined, false); + navigateAfterOnboarding(true, true, ONBOARDING_POLICY_ID, ACTIVE_WORKSPACE_ID, ONBOARDING_ADMINS_CHAT_REPORT_ID); expect(Navigation.navigate).not.toHaveBeenCalled(); }); + + it('should navigate to last accessed report if shouldOpenOnAdminRoom is true on small screens', () => { + const navigate = jest.spyOn(Navigation, 'navigate'); + const lastAccessedReport = {reportID: REPORT_ID}; + mockFindLastAccessedReport.mockReturnValue(lastAccessedReport); + mockShouldOpenOnAdminRoom.mockReturnValue(true); + + navigateAfterOnboarding(true, true, ONBOARDING_POLICY_ID, ACTIVE_WORKSPACE_ID, ONBOARDING_ADMINS_CHAT_REPORT_ID); + expect(navigate).toHaveBeenCalledWith(ROUTES.REPORT_WITH_ID.getRoute(REPORT_ID)); + }); });