From bb25221c450204200d39726e87e43653153eb5b5 Mon Sep 17 00:00:00 2001 From: Ruben Izmailyan Date: Thu, 16 Jul 2026 02:01:25 +0000 Subject: [PATCH] fix(oauth): respect disable_institution_search for the navigation back button OAuthStep and OAuthError always reported showBackButton() as true, so the global navigation back button rendered on OAuth screens even when the widget was loaded with disable_institution_search: true. Clicking it also reset the step stack to the (disabled) institution search. The non-OAuth credential screens already hide the button in this configuration. The button remains available for the interstitial disclosure and waiting-for-OAuth sub-views, which handle back within the OAuth step. Co-Authored-By: Claude Fable 5 --- src/views/oauth/OAuthError.js | 9 ++- src/views/oauth/OAuthStep.js | 16 +++++- src/views/oauth/__tests__/OAuthError-test.tsx | 27 ++++++++- src/views/oauth/__tests__/OAuthStep-test.tsx | 55 ++++++++++++++++++- 4 files changed, 101 insertions(+), 6 deletions(-) diff --git a/src/views/oauth/OAuthError.js b/src/views/oauth/OAuthError.js index 93a553e57e..64c32282bd 100644 --- a/src/views/oauth/OAuthError.js +++ b/src/views/oauth/OAuthError.js @@ -16,12 +16,14 @@ import { SlideDown } from 'src/components/SlideDown' import { getDelay } from 'src/utilities/getDelay' import { PostMessageContext } from 'src/ConnectWidget' import { getSelectedInstitution } from 'src/redux/selectors/Connect' +import { selectConnectConfig } from 'src/redux/reducers/configSlice' export const OAuthError = React.forwardRef((props, navigationRef) => { useAnalyticsPath(...PageviewInfo.CONNECT_OAUTH_ERROR) const { currentMember, onRetry, onReturnToSearch } = props const postMessageFunctions = useContext(PostMessageContext) + const connectConfig = useSelector(selectConnectConfig) const errorReason = useSelector((state) => state.connect.oauthErrorReason) const selectedInstitution = useSelector(getSelectedInstitution) const tokens = useTokens() @@ -35,10 +37,13 @@ export const OAuthError = React.forwardRef((props, navigationRef) => { onReturnToSearch() }, showBackButton() { - return true + // Going back leads to the institution search, which is not available + // when disable_institution_search is set. The "Try again" button + // remains as the primary action. + return !connectConfig.disable_institution_search }, } - }, []) + }, [connectConfig.disable_institution_search]) // If we have an oauth error, send the post message. useEffect(() => { diff --git a/src/views/oauth/OAuthStep.js b/src/views/oauth/OAuthStep.js index 9e7b936c63..49f48abb9d 100644 --- a/src/views/oauth/OAuthStep.js +++ b/src/views/oauth/OAuthStep.js @@ -78,10 +78,22 @@ export const OAuthStep = React.forwardRef((props, navigationRef) => { } }, showBackButton() { - return true + // The interstitial disclosure and waiting views handle "back" within this + // step, so the back button stays available for them. Otherwise, going back + // leads to the institution search, which is not available when + // disable_institution_search is set. + if (showInterstitialDisclosure || isWaitingForOAuth) { + return true + } + return !config.disable_institution_search }, } - }, [isWaitingForOAuth, oauthStartError, showInterstitialDisclosure]) + }, [ + isWaitingForOAuth, + oauthStartError, + showInterstitialDisclosure, + config.disable_institution_search, + ]) /** * Called when we succesfully generate an oauth window uri for the exsting diff --git a/src/views/oauth/__tests__/OAuthError-test.tsx b/src/views/oauth/__tests__/OAuthError-test.tsx index 456b50aa36..eecb03b102 100644 --- a/src/views/oauth/__tests__/OAuthError-test.tsx +++ b/src/views/oauth/__tests__/OAuthError-test.tsx @@ -3,7 +3,9 @@ import { render, screen, waitFor } from 'src/utilities/testingLibrary' import { OAuthError, getOAuthErrorMessage } from 'src/views/oauth/OAuthError' import { OAUTH_ERROR_REASONS } from 'src/const/Connect' -import { institutionData } from 'src/services/mockedData' +import { initialState as mockedState, institutionData } from 'src/services/mockedData' + +type NavigationHandle = { handleBackButton: () => void; showBackButton: () => boolean } describe('OAuthError', () => { const defaultProps = { @@ -33,6 +35,29 @@ describe('OAuthError', () => { expect(defaultProps.onRetry).toHaveBeenCalledTimes(1) }) + it('shows the navigation back button by default', () => { + const ref = React.createRef() + + render(, { + preloadedState: initialState, + }) + + expect(ref.current?.showBackButton()).toBe(true) + }) + + it('hides the navigation back button when disable_institution_search is true', () => { + const ref = React.createRef() + + render(, { + preloadedState: { + ...initialState, + config: { ...mockedState.config, disable_institution_search: true }, + }, + }) + + expect(ref.current?.showBackButton()).toBe(false) + }) + describe('getOAuthErrorMessage util', () => { it('DEFAULT message is returned when given a blank reason', () => { expect(getOAuthErrorMessage('Default/Fallback')).toEqual( diff --git a/src/views/oauth/__tests__/OAuthStep-test.tsx b/src/views/oauth/__tests__/OAuthStep-test.tsx index c70460582c..acc15664d7 100644 --- a/src/views/oauth/__tests__/OAuthStep-test.tsx +++ b/src/views/oauth/__tests__/OAuthStep-test.tsx @@ -3,7 +3,9 @@ import { render, screen, waitFor } from 'src/utilities/testingLibrary' import { OAuthStep } from 'src/views/oauth/OAuthStep' import { apiValue } from 'src/const/apiProviderMock' import { ApiProvider } from 'src/context/ApiContext' -import { OAUTH_STATE } from 'src/services/mockedData' +import { initialState, OAUTH_STATE } from 'src/services/mockedData' + +type NavigationHandle = { handleBackButton: () => void; showBackButton: () => boolean } describe('OauthStep view', () => { describe('Ensure OAuthDefault is rendered', () => { @@ -111,4 +113,55 @@ describe('OauthStep view', () => { ) }, 35000) }) + + describe('showBackButton', () => { + const defaultProps = { + institution: { guid: 'INS-123', name: 'MX Bank', instructional_data: {} }, + onGoBack: vi.fn(), + } + + const renderOAuthStep = async (configOverrides = {}) => { + const ref = React.createRef() + const { user } = render( + + + , + { + preloadedState: { + config: { ...initialState.config, ...configOverrides }, + connect: { + members: [], + currentMemberGuid: null, + location: [{ step: 'ENTER_CREDENTIALS' }], + }, + }, + }, + ) + + await screen.findByTestId('continue-button') + + return { ref, user } + } + + it('shows the back button by default', async () => { + const { ref } = await renderOAuthStep() + + expect(ref.current?.showBackButton()).toBe(true) + }) + + it('hides the back button when disable_institution_search is true', async () => { + const { ref } = await renderOAuthStep({ disable_institution_search: true }) + + expect(ref.current?.showBackButton()).toBe(false) + }) + + it('still shows the back button while waiting for OAuth when disable_institution_search is true', async () => { + const { ref, user } = await renderOAuthStep({ disable_institution_search: true }) + + await user.click(screen.getByTestId('continue-button')) + await screen.findByRole('button', { name: 'Try again' }) + + expect(ref.current?.showBackButton()).toBe(true) + }) + }) })