diff --git a/src/pages/domain/BaseVerifyDomainPage.tsx b/src/pages/domain/BaseVerifyDomainPage.tsx index 6e5ef2f35465..3e4058f74bd3 100644 --- a/src/pages/domain/BaseVerifyDomainPage.tsx +++ b/src/pages/domain/BaseVerifyDomainPage.tsx @@ -10,6 +10,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; +import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyAsset} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -26,6 +27,7 @@ import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; +import {isAdminSelector} from '@src/selectors/Domain'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; import type {PropsWithChildren} from 'react'; @@ -57,11 +59,15 @@ function BaseVerifyDomainPage({domainAccountID, forwardTo}: BaseVerifyDomainPage const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); + const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const [domain, domainMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.DOMAIN}${domainAccountID}`); const domainName = domain ? Str.extractEmailDomain(domain.email) : ''; const doesDomainExist = !!domain; + // A domain admin has nothing to verify once the domain is validated, so keep them out of the flow if they deep-link; non-admins still land here to re-verify + const isVerifiedDomainAdmin = !!domain?.validated && isAdminSelector(currentUserAccountID)(domain); + const {asset: Exclamation} = useMemoizedLazyAsset(() => loadExpensifyIcon('Exclamation')); useEffect(() => { @@ -72,18 +78,18 @@ function BaseVerifyDomainPage({domainAccountID, forwardTo}: BaseVerifyDomainPage }, [domainAccountID, domain?.hasValidationSucceeded, forwardTo]); useFocusEffect(() => { - if (!doesDomainExist || domain?.validated || domain?.validateCode || domain?.isValidateCodeLoading || domain?.validateCodeError) { + if (isVerifiedDomainAdmin || !doesDomainExist || domain?.validateCode || domain?.isValidateCodeLoading || domain?.validateCodeError) { return; } getDomainValidationCode(domainAccountID, domainName); }); useEffect(() => { - if (!doesDomainExist || domain?.validated) { + if (!doesDomainExist) { return; } resetDomainValidationError(domainAccountID); - }, [domainAccountID, doesDomainExist, domain?.validated]); + }, [domainAccountID, doesDomainExist]); const isLoadingDomain = isLoadingOnyxValue(domainMetadata); if (isLoadingDomain) { @@ -98,7 +104,7 @@ function BaseVerifyDomainPage({domainAccountID, forwardTo}: BaseVerifyDomainPage return Navigation.dismissModal()} />; } - if (domain.validated) { + if (isVerifiedDomainAdmin) { return ( Navigation.dismissModal()} diff --git a/tests/ui/BaseVerifyDomainPageTest.tsx b/tests/ui/BaseVerifyDomainPageTest.tsx new file mode 100644 index 000000000000..08661912de3f --- /dev/null +++ b/tests/ui/BaseVerifyDomainPageTest.tsx @@ -0,0 +1,145 @@ +import {act, render, screen} from '@testing-library/react-native'; + +import ComposeProviders from '@components/ComposeProviders'; +import {CurrentUserPersonalDetailsProvider} from '@components/CurrentUserPersonalDetailsProvider'; +import {LocaleContextProvider} from '@components/LocaleContextProvider'; +import OnyxListItemProvider from '@components/OnyxListItemProvider'; + +import * as API from '@libs/API'; +import {READ_COMMANDS} from '@libs/API/types'; +import {navigationRef} from '@libs/Navigation/Navigation'; +import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator'; +import type {WorkspacesDomainModalNavigatorParamList} from '@libs/Navigation/types'; + +import WorkspacesVerifyDomainPage from '@pages/domain/WorkspacesVerifyDomainPage'; + +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; + +import {PortalProvider} from '@gorhom/portal'; +import {NavigationContainer} from '@react-navigation/native'; +import React from 'react'; +import Onyx from 'react-native-onyx'; + +import * as TestHelper from '../utils/TestHelper'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; + +jest.mock('@components/RenderHTML', () => () => null); + +const DOMAIN_ACCOUNT_ID = 123456; +const DOMAIN_EMAIL = 'user@test.com'; +const DOMAIN_NAME = 'test.com'; +const TEST_USER_ACCOUNT_ID = 1; + +// Makes the signed-in test user an admin of the domain +const DOMAIN_ADMIN_ACCESS = { + [`${CONST.DOMAIN.EXPENSIFY_ADMIN_ACCESS_PREFIX}0`]: TEST_USER_ACCOUNT_ID, +}; + +const apiReadSpy = jest.spyOn(API, 'read').mockImplementation(() => {}); + +const Stack = createPlatformStackNavigator(); + +function renderVerifyDomainPage() { + return render( + + + + + + + + + , + ); +} + +describe('BaseVerifyDomainPage', () => { + beforeAll(async () => { + Onyx.init({keys: ONYXKEYS}); + await act(async () => { + await Onyx.set(ONYXKEYS.NVP_PREFERRED_LOCALE, CONST.LOCALES.EN); + }); + await waitForBatchedUpdatesWithAct(); + }); + + afterEach(async () => { + apiReadSpy.mockClear(); + await act(async () => { + await Onyx.clear(); + }); + await waitForBatchedUpdatesWithAct(); + }); + + it('renders the DNS verification screen for a non-admin on an already-validated domain instead of NotFoundPage', async () => { + // Given an already-validated domain the current user is not an admin of + await TestHelper.signInWithTestUser(); + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.DOMAIN}${DOMAIN_ACCOUNT_ID}`, { + accountID: DOMAIN_ACCOUNT_ID, + email: DOMAIN_EMAIL, + validated: true, + }); + }); + await waitForBatchedUpdatesWithAct(); + + // When the verify-domain page is opened + renderVerifyDomainPage(); + await waitForBatchedUpdatesWithAct(); + + // Then the verify screen renders (a validated domain must not dead-end on NotFoundPage) + expect(screen.getByTestId('BaseVerifyDomainPage')).toBeTruthy(); + + // And the validation code is fetched, so the DNS TXT field is not left empty + expect(apiReadSpy).toHaveBeenCalledWith(READ_COMMANDS.GET_DOMAIN_VALIDATE_CODE, {domainName: DOMAIN_NAME}, expect.anything()); + }); + + it('renders NotFoundPage for an admin on an already-validated domain and skips the code fetch', async () => { + // Given an already-validated domain the current user is an admin of + await TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID); + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.DOMAIN}${DOMAIN_ACCOUNT_ID}`, { + accountID: DOMAIN_ACCOUNT_ID, + email: DOMAIN_EMAIL, + validated: true, + ...DOMAIN_ADMIN_ACCESS, + }); + }); + await waitForBatchedUpdatesWithAct(); + + // When the verify-domain page is deep-linked + renderVerifyDomainPage(); + await waitForBatchedUpdatesWithAct(); + + // Then the verify screen is not shown (NotFoundPage), and no validation code is fetched + expect(screen.queryByTestId('BaseVerifyDomainPage')).toBeNull(); + expect(apiReadSpy).not.toHaveBeenCalledWith(READ_COMMANDS.GET_DOMAIN_VALIDATE_CODE, expect.anything(), expect.anything()); + }); + + it('renders the DNS verification screen for an admin on a not-yet-validated domain', async () => { + // Given a domain the current user is an admin of but that is NOT yet validated + await TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID); + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.DOMAIN}${DOMAIN_ACCOUNT_ID}`, { + accountID: DOMAIN_ACCOUNT_ID, + email: DOMAIN_EMAIL, + validated: false, + ...DOMAIN_ADMIN_ACCESS, + }); + }); + await waitForBatchedUpdatesWithAct(); + + // When the verify-domain page is opened + renderVerifyDomainPage(); + await waitForBatchedUpdatesWithAct(); + + // Then the verify screen renders + expect(screen.getByTestId('BaseVerifyDomainPage')).toBeTruthy(); + expect(apiReadSpy).toHaveBeenCalledWith(READ_COMMANDS.GET_DOMAIN_VALIDATE_CODE, {domainName: DOMAIN_NAME}, expect.anything()); + }); +});