From 3f7590f21a899a40840a9bcf0ae2688cb143651f Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Wed, 1 Jul 2026 11:08:20 -0600 Subject: [PATCH 1/3] fix: added login error tests --- .../loginError/ImpededMemberError-test.tsx | 115 +++++++++ src/views/loginError/LeavingAction-test.tsx | 94 ++++++++ src/views/loginError/LoginError-test.jsx | 220 ++++++++++++++++++ .../loginError/NoEligibleAccountsError.js | 154 ------------ .../loginError/__tests__/LoginError-test.jsx | 70 ------ 5 files changed, 429 insertions(+), 224 deletions(-) create mode 100644 src/views/loginError/ImpededMemberError-test.tsx create mode 100644 src/views/loginError/LeavingAction-test.tsx create mode 100644 src/views/loginError/LoginError-test.jsx delete mode 100644 src/views/loginError/NoEligibleAccountsError.js delete mode 100644 src/views/loginError/__tests__/LoginError-test.jsx diff --git a/src/views/loginError/ImpededMemberError-test.tsx b/src/views/loginError/ImpededMemberError-test.tsx new file mode 100644 index 0000000000..1a9a326ccf --- /dev/null +++ b/src/views/loginError/ImpededMemberError-test.tsx @@ -0,0 +1,115 @@ +import React from 'react' +import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest' +import { render, screen } from 'src/utilities/testingLibrary' +import { LoginError as LoginErrorComponent } from 'src/views/loginError/LoginError' +import { initialState, institutionData } from 'src/services/mockedData' +import { ReadableStatuses } from 'src/const/Statuses' + +const LoginError = LoginErrorComponent as unknown as React.ComponentType> + +describe('', () => { + const impededMember = { + guid: 'MBR-123', + error: {}, + name: 'Test Member', + connection_status: ReadableStatuses.IMPEDED, + } + + const preloadedState = { + ...initialState, + connect: { + ...initialState.connect, + selectedInstitution: institutionData.institution, + currentMemberGuid: impededMember.guid, + members: [impededMember], + location: [{ step: 'LOGIN_ERROR' }], + }, + } + + const defaultProps = { + institution: institutionData.institution, + isDeleteInstitutionOptionEnabled: true, + member: impededMember, + onDeleteConnectionClick: vi.fn(), + onRefreshClick: vi.fn(), + onUpdateCredentialsClick: vi.fn(), + showExternalLinkPopup: false, + showSupport: true, + size: 'medium', + } + + let openSpy: MockInstance + + beforeEach(() => { + vi.clearAllMocks() + openSpy = vi.spyOn(window, 'open').mockReturnValue(null) + }) + + afterEach(() => { + openSpy.mockRestore() + }) + + const renderImpededMemberError = (props: Partial = {}) => + render( +
+ +
, + { preloadedState }, + ) + + describe('Content Display', () => { + it('renders the impeded error with both resolution steps', () => { + renderImpededMemberError() + + expect(screen.getByText('Your attention is needed')).toBeInTheDocument() + expect( + screen.getByText( + 'Your login info was correct, but your attention is needed at Test Bank before we can proceed. You need to:', + ), + ).toBeInTheDocument() + + expect(screen.getByText('1')).toBeInTheDocument() + expect( + screen.getByText("Log in to Test Bank's website and resolve the issue."), + ).toBeInTheDocument() + expect(screen.getByText('Visit website')).toBeInTheDocument() + + expect(screen.getByText('2')).toBeInTheDocument() + expect( + screen.getByText('Come back here and try to connect your account again.'), + ).toBeInTheDocument() + expect(screen.getByText('Try again')).toBeInTheDocument() + }) + }) + + describe('Visit Website Link', () => { + it('opens the institution login URL in a new tab when external link popup is disabled', async () => { + const { user } = renderImpededMemberError({ showExternalLinkPopup: false }) + + await user.click(screen.getByText('Visit website')) + + expect(openSpy).toHaveBeenCalledWith('https://test.com', '_blank') + expect(screen.queryByTestId('leaving-notice-flat-header')).not.toBeInTheDocument() + }) + + it('shows the leaving notice instead of navigating when external link popup is enabled', async () => { + const { user } = renderImpededMemberError({ showExternalLinkPopup: true }) + + await user.click(screen.getByText('Visit website')) + + expect(screen.getByTestId('leaving-notice-flat-header')).toBeInTheDocument() + expect(openSpy).not.toHaveBeenCalled() + }) + }) + + describe('Try Again Link', () => { + it('calls onRefreshClick without showing the leaving notice', async () => { + const { user } = renderImpededMemberError() + + await user.click(screen.getByText('Try again')) + + expect(defaultProps.onRefreshClick).toHaveBeenCalledTimes(1) + expect(screen.queryByTestId('leaving-notice-flat-header')).not.toBeInTheDocument() + }) + }) +}) diff --git a/src/views/loginError/LeavingAction-test.tsx b/src/views/loginError/LeavingAction-test.tsx new file mode 100644 index 0000000000..da2cf155ff --- /dev/null +++ b/src/views/loginError/LeavingAction-test.tsx @@ -0,0 +1,94 @@ +import React from 'react' +import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest' +import { render, screen } from 'src/utilities/testingLibrary' +import RenderConnectStep from 'src/components/RenderConnectStep' +import { initialState, institutionData } from 'src/services/mockedData' +import { ReadableStatuses } from 'src/const/Statuses' +import { STEPS } from 'src/const/Connect' + +describe('', () => { + const impededMember = { + guid: 'MBR-123', + error: {}, + name: 'Test Member', + connection_status: ReadableStatuses.IMPEDED, + } + + const preloadedState = { + ...initialState, + connect: { + ...initialState.connect, + location: [{ step: STEPS.ACTIONABLE_ERROR }], + selectedInstitution: institutionData.institution, + currentMemberGuid: impededMember.guid, + members: [impededMember], + }, + } + + const renderStepProps = { + availableAccountTypes: [], + handleConsentGoBack: vi.fn(), + handleCredentialsGoBack: vi.fn(), + navigationRef: React.createRef(), + onManualAccountAdded: vi.fn(), + onUpsertMember: vi.fn(), + setConnectLocalState: vi.fn(), + } + + let openSpy: MockInstance + let portalRoot: HTMLDivElement + + beforeEach(() => { + vi.clearAllMocks() + openSpy = vi.spyOn(window, 'open').mockReturnValue(null) + + portalRoot = document.createElement('div') + portalRoot.setAttribute('id', 'connect-wrapper') + document.body.appendChild(portalRoot) + }) + + afterEach(() => { + openSpy.mockRestore() + document.body.removeChild(portalRoot) + }) + + const renderLeavingNotice = async () => { + const view = render(, { preloadedState }) + + await view.user.click(screen.getByText('Visit website')) + + return view + } + + describe('Content Display', () => { + it('renders the leaving notice with continue and cancel actions', async () => { + await renderLeavingNotice() + + expect(screen.getByTestId('leaving-notice-flat-header')).toBeInTheDocument() + expect(screen.getByTestId('leaving-notice-flat-continue-button')).toBeInTheDocument() + expect(screen.getByTestId('leaving-notice-flat-cancel-button')).toBeInTheDocument() + }) + }) + + describe('Cancel Button', () => { + it('dismisses the leaving notice and returns to the error without navigating', async () => { + const { user } = await renderLeavingNotice() + + await user.click(screen.getByTestId('leaving-notice-flat-cancel-button')) + + expect(await screen.findByText('Your attention is needed')).toBeInTheDocument() + expect(screen.queryByTestId('leaving-notice-flat-header')).not.toBeInTheDocument() + expect(openSpy).not.toHaveBeenCalled() + }) + }) + + describe('Continue Button', () => { + it("opens the institution's login page in a new tab", async () => { + const { user } = await renderLeavingNotice() + + await user.click(screen.getByTestId('leaving-notice-flat-continue-button')) + + expect(openSpy).toHaveBeenCalledWith('https://test.com', '_blank') + }) + }) +}) diff --git a/src/views/loginError/LoginError-test.jsx b/src/views/loginError/LoginError-test.jsx new file mode 100644 index 0000000000..a3817fabf5 --- /dev/null +++ b/src/views/loginError/LoginError-test.jsx @@ -0,0 +1,220 @@ +import React from 'react' +import { act, render, screen } from 'src/utilities/testingLibrary' +import { LoginError } from 'src/views/loginError/LoginError' +import { initialState as defaultState } from 'src/services/mockedData' +import { ReadableStatuses } from 'src/const/Statuses' +import { PostMessageContext } from 'src/ConnectWidget' +import { POST_MESSAGES } from 'src/const/postMessages' + +const institutionMock = { + name: 'Institution', + guid: 'INS-123', +} +const memberMock = { + guid: 'MEM-123', + error: {}, + name: 'Member', + connection_status: ReadableStatuses.EXPIRED, +} + +describe('LoginError', () => { + const initialState = { + ...defaultState, + connect: { + ...defaultState.connect, + selectedInstitution: institutionMock, + currentMemberGuid: memberMock.guid, + members: [memberMock], + location: [{ step: 'LOGIN_ERROR' }], + }, + } + const defaultProps = { + institution: institutionMock, + isDeleteInstitutionOptionEnabled: true, + member: memberMock, + onDeleteConnectionClick: vitest.fn(), + onRefreshClick: vitest.fn(), + onUpdateCredentialsClick: vitest.fn(), + showExternalLinkPopup: false, + showSupport: true, + size: 'medium', + } + let onPostMessage + + const renderLoginError = (props = {}, preloadedState = initialState) => + render( + + + , + { preloadedState }, + ) + + beforeEach(() => { + vitest.clearAllMocks() + onPostMessage = vitest.fn() + }) + + it('renders the institution logo without an error badge', () => { + renderLoginError() + + const institutionLogo = screen.getByRole('img') + expect(institutionLogo).toBeInTheDocument() + expect(institutionLogo).toHaveAttribute('alt', `${institutionMock.name} logo`) + expect(screen.queryByText('!')).not.toBeInTheDocument() + }) + + it('posts a member error message on mount', () => { + renderLoginError() + + expect(onPostMessage).toHaveBeenCalledWith('connect/memberError', { + member: { + guid: memberMock.guid, + connection_status: memberMock.connection_status, + }, + }) + }) + + describe('Connection Status Variants', () => { + it.each([ + { status: ReadableStatuses.PREVENTED, title: 'New credentials needed', button: 'Connect' }, + { + status: ReadableStatuses.DENIED, + title: 'Please re-enter your credentials', + button: 'Connect', + }, + { status: ReadableStatuses.REJECTED, title: 'Incorrect information', button: 'Try again' }, + { status: ReadableStatuses.LOCKED, title: 'Account is locked' }, + { status: ReadableStatuses.DEGRADED, title: 'Connection maintenance', button: 'OK' }, + { status: ReadableStatuses.DISCONNECTED, title: 'Connection maintenance' }, + { status: ReadableStatuses.DISCONTINUED, title: 'Connection discontinued' }, + { status: ReadableStatuses.CLOSED, title: 'Closed account' }, + { status: ReadableStatuses.FAILED, title: 'Connection failed' }, + { status: ReadableStatuses.DISABLED, title: 'Connection disabled' }, + { status: ReadableStatuses.IMPORTED, title: 'New credentials needed', button: 'Connect' }, + { status: ReadableStatuses.CHALLENGED, title: 'Something went wrong', button: 'Try again' }, + { status: ReadableStatuses.IMPAIRED, title: 'New credentials needed', button: 'Connect' }, + ])( + 'renders the "$title" view for the $status connection status', + ({ status, title, button }) => { + renderLoginError({ member: { ...memberMock, connection_status: status } }) + + expect(screen.getByText(title)).toBeInTheDocument() + if (button) { + expect(screen.getByRole('button', { name: button })).toBeInTheDocument() + } + }, + ) + + it('renders a generic error for an unknown connection status', () => { + renderLoginError({ member: { ...memberMock, connection_status: 'UNKNOWN_STATUS' } }) + + expect(screen.getByText('Something went wrong')).toBeInTheDocument() + expect( + screen.getByText( + "We've notified support and are looking into the issue. Please try again later.", + ), + ).toBeInTheDocument() + }) + }) + + describe('Primary Actions', () => { + it('calls onRefreshClick when the Try again button is clicked', async () => { + const { user } = renderLoginError({ + member: { ...memberMock, connection_status: ReadableStatuses.REJECTED }, + }) + + await user.click(screen.getByRole('button', { name: 'Try again' })) + + expect(defaultProps.onRefreshClick).toHaveBeenCalled() + }) + + it('calls onUpdateCredentialsClick when the Connect button is clicked', async () => { + const { user } = renderLoginError({ + member: { ...memberMock, connection_status: ReadableStatuses.PREVENTED }, + }) + + await user.click(screen.getByRole('button', { name: 'Connect' })) + + expect(defaultProps.onUpdateCredentialsClick).toHaveBeenCalled() + }) + + it('posts a primary action message when the OK button is clicked', async () => { + const member = { ...memberMock, connection_status: ReadableStatuses.DEGRADED } + const { user } = renderLoginError({ member }) + + await user.click(screen.getByRole('button', { name: 'OK' })) + + expect(onPostMessage).toHaveBeenCalledWith('connect/memberError/primaryAction', { + member: { + guid: member.guid, + connection_status: member.connection_status, + }, + }) + }) + + it('calls onUpdateCredentialsClick when OK is clicked and institution search is disabled', async () => { + const stateWithDisabledSearch = { + ...initialState, + config: { + ...initialState.config, + disable_institution_search: true, + }, + } + const { user } = renderLoginError( + { member: { ...memberMock, connection_status: ReadableStatuses.DEGRADED } }, + stateWithDisabledSearch, + ) + + await user.click(screen.getByRole('button', { name: 'OK' })) + + expect(defaultProps.onUpdateCredentialsClick).toHaveBeenCalled() + }) + }) + + describe('Secondary Actions', () => { + it('opens the support view when the Get help button is clicked', async () => { + const { user } = renderLoginError({ + member: { ...memberMock, connection_status: ReadableStatuses.PREVENTED }, + }) + + expect(screen.getByText('Get help')).toBeInTheDocument() + + await user.click(screen.getByText('Get help')) + + expect(await screen.findByText('Request support')).toBeInTheDocument() + expect(screen.queryByText('New credentials needed')).not.toBeInTheDocument() + }) + + it('posts back to search when the Try another institution action is clicked', async () => { + const { user } = renderLoginError({ + member: { ...memberMock, connection_status: ReadableStatuses.REJECTED }, + }) + + await user.click(screen.getByText('Try another institution')) + + expect(onPostMessage).toHaveBeenCalledWith(POST_MESSAGES.BACK_TO_SEARCH) + }) + }) + + describe('Navigation back button', () => { + it('exposes a back button in support and returns to the error when invoked', async () => { + const ref = React.createRef() + const { user } = renderLoginError({ + member: { ...memberMock, connection_status: ReadableStatuses.PREVENTED }, + ref, + }) + + expect(ref.current.showBackButton()).toBe(false) + + await user.click(screen.getByText('Get help')) + expect(await screen.findByText('Request support')).toBeInTheDocument() + expect(ref.current.showBackButton()).toBe(true) + + act(() => { + ref.current.handleBackButton() + }) + + expect(await screen.findByText('New credentials needed')).toBeInTheDocument() + }) + }) +}) diff --git a/src/views/loginError/NoEligibleAccountsError.js b/src/views/loginError/NoEligibleAccountsError.js deleted file mode 100644 index 01306e10f7..0000000000 --- a/src/views/loginError/NoEligibleAccountsError.js +++ /dev/null @@ -1,154 +0,0 @@ -import React, { useContext } from 'react' -import { useDispatch, useSelector } from 'react-redux' - -import { useTokens } from '@kyper/tokenprovider' -import { Text } from '@mxenabled/mxui' -import { AttentionFilled } from '@kyper/icon/AttentionFilled' -import { Button } from '@mui/material' - -import { __ } from 'src/utilities/Intl' -import { ActionTypes } from 'src/redux/actions/Connect' - -import { getCurrentMember, getSelectedInstitution } from 'src/redux/selectors/Connect' - -import { AriaLive } from 'src/components/AriaLive' -import { SlideDown } from 'src/components/SlideDown' -import { getDelay } from 'src/utilities/getDelay' -import useAnalyticsEvent from 'src/hooks/useAnalyticsEvent' -import { AnalyticEvents, AuthenticationMethods } from 'src/const/Analytics' -import { POST_MESSAGES } from 'src/const/postMessages' -import { PostMessageContext } from 'src/ConnectWidget' - -export const NoEligibleAccounts = () => { - const sendAnalyticsEvent = useAnalyticsEvent() - const tokens = useTokens() - const styles = getStyles(tokens) - const postMessageFunctions = useContext(PostMessageContext) - const dispatch = useDispatch() - - const currentMember = useSelector(getCurrentMember) - const selectedInstitution = useSelector(getSelectedInstitution) - - const postHogEventMetadata = { - authentication_method: currentMember.is_oauth - ? AuthenticationMethods.OAUTH - : AuthenticationMethods.NON_OAUTH, - institution_guid: selectedInstitution.guid, - institution_name: selectedInstitution.name, - } - - const getNextDelay = getDelay() - - return ( - - -
- - {__('Accounts not eligible for transfers')} - - -
-
- - {__( - "We've connected to your financial institution, but couldn't find eligible checking or savings accounts for money movement; however, other account information may still have been shared.", - )} - - - {__('Please try linking a checking or savings account.')} - - -
- -
- - -
- ) -} - -const getStyles = (tokens) => { - return { - headerText: { - fontWeight: tokens.FontWeight.Bold, - }, - headerContianer: { - display: 'flex', - alignItems: 'center', - }, - icon: { - marginLeft: tokens.Spacing.Small, - }, - paragraphOne: { - fontWeight: tokens.FontWeight.Regular, - fontSize: tokens.FontSize.Small, - marginTop: tokens.Spacing.XSmall, - }, - paragraphTwo: { - fontWeight: tokens.FontWeight.Regular, - fontSize: tokens.FontSize.Small, - marginTop: tokens.Spacing.Medium, - }, - tryAgainButton: { - background: tokens.BackgroundColor.ButtonPrimary, - color: tokens.Color.NeutralWhite, - marginTop: tokens.Spacing.XLarge, - borderRadius: tokens.BorderRadius.Medium, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - padding: '12px 16px', - gap: '10px', - height: '44px', - width: '100%', - }, - } -} - -NoEligibleAccounts.propTypes = {} - -export default NoEligibleAccounts diff --git a/src/views/loginError/__tests__/LoginError-test.jsx b/src/views/loginError/__tests__/LoginError-test.jsx deleted file mode 100644 index b79b4e461f..0000000000 --- a/src/views/loginError/__tests__/LoginError-test.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from 'react' -import { render, screen } from 'src/utilities/testingLibrary' -import { LoginError } from 'src/views/loginError/LoginError' -import { PageviewInfo } from 'src/const/Analytics' -import { useAnalyticsPath } from 'src/hooks/useAnalyticsPath' -import { initialState as defaultState } from 'src/services/mockedData' -import { ConnectionStatusMap, ReadableStatuses } from 'src/const/Statuses' - -const institutionMock = { - name: 'Institution', - guid: 'INS-123', -} -const memberMock = { - guid: 'MEM-123', - error: {}, - name: 'Member', - connection_status: ReadableStatuses.EXPIRED, -} - -vitest.mock('src/hooks/useAnalyticsPath', { spy: true }) - -describe('LoginError', () => { - const initialState = { - ...defaultState, - connect: { - ...defaultState.connect, - selectedInstitution: institutionMock, - currentMemberGuid: memberMock.guid, - members: [memberMock], - location: [{ step: 'LOGIN_ERROR' }], - }, - } - const defaultProps = { - institution: institutionMock, - isDeleteInstitutionOptionEnabled: true, - member: memberMock, - onDeleteConnectionClick: vitest.fn(), - onRefreshClick: vitest.fn(), - onUpdateCredentialsClick: vitest.fn(), - showExternalLinkPopup: false, - showSupport: true, - size: 'medium', - } - - beforeEach(() => { - vitest.clearAllMocks() - }) - - it('should fire a pageview event with correct parameters', () => { - render(, { - preloadedState: initialState, - }) - expect(useAnalyticsPath).toHaveBeenCalledWith(...PageviewInfo.CONNECT_LOGIN_ERROR, { - connection_status: memberMock.connection_status, - readable_status: ConnectionStatusMap[memberMock.connection_status], - }) - }) - - it('should render an institution logo without a badge', () => { - render(, { - preloadedState: initialState, - }) - const institutionLogo = screen.getByRole('img') - expect(institutionLogo).toBeInTheDocument() - expect(institutionLogo).toHaveAttribute('alt', `${institutionMock.name} logo`) - - const badge = screen.queryByText('!') - expect(badge).not.toBeInTheDocument() - }) -}) From dc5c312a4ffa1cdf8ce9277782d7f82560928ac6 Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Wed, 1 Jul 2026 12:01:30 -0600 Subject: [PATCH 2/3] removed check for props tests and made them more robust --- .../loginError/ImpededMemberError-test.tsx | 11 ---- src/views/loginError/LeavingAction-test.tsx | 13 ++-- src/views/loginError/LoginError-test.jsx | 60 +++++++++++++------ 3 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/views/loginError/ImpededMemberError-test.tsx b/src/views/loginError/ImpededMemberError-test.tsx index 1a9a326ccf..9ef225f5a2 100644 --- a/src/views/loginError/ImpededMemberError-test.tsx +++ b/src/views/loginError/ImpededMemberError-test.tsx @@ -101,15 +101,4 @@ describe('', () => { expect(openSpy).not.toHaveBeenCalled() }) }) - - describe('Try Again Link', () => { - it('calls onRefreshClick without showing the leaving notice', async () => { - const { user } = renderImpededMemberError() - - await user.click(screen.getByText('Try again')) - - expect(defaultProps.onRefreshClick).toHaveBeenCalledTimes(1) - expect(screen.queryByTestId('leaving-notice-flat-header')).not.toBeInTheDocument() - }) - }) }) diff --git a/src/views/loginError/LeavingAction-test.tsx b/src/views/loginError/LeavingAction-test.tsx index da2cf155ff..2f10b9d193 100644 --- a/src/views/loginError/LeavingAction-test.tsx +++ b/src/views/loginError/LeavingAction-test.tsx @@ -36,24 +36,23 @@ describe('', () => { } let openSpy: MockInstance - let portalRoot: HTMLDivElement beforeEach(() => { vi.clearAllMocks() openSpy = vi.spyOn(window, 'open').mockReturnValue(null) - - portalRoot = document.createElement('div') - portalRoot.setAttribute('id', 'connect-wrapper') - document.body.appendChild(portalRoot) }) afterEach(() => { openSpy.mockRestore() - document.body.removeChild(portalRoot) }) const renderLeavingNotice = async () => { - const view = render(, { preloadedState }) + const view = render( +
+ +
, + { preloadedState }, + ) await view.user.click(screen.getByText('Visit website')) diff --git a/src/views/loginError/LoginError-test.jsx b/src/views/loginError/LoginError-test.jsx index a3817fabf5..d97e647b36 100644 --- a/src/views/loginError/LoginError-test.jsx +++ b/src/views/loginError/LoginError-test.jsx @@ -1,10 +1,12 @@ import React from 'react' import { act, render, screen } from 'src/utilities/testingLibrary' +import RenderConnectStep from 'src/components/RenderConnectStep' import { LoginError } from 'src/views/loginError/LoginError' import { initialState as defaultState } from 'src/services/mockedData' import { ReadableStatuses } from 'src/const/Statuses' import { PostMessageContext } from 'src/ConnectWidget' import { POST_MESSAGES } from 'src/const/postMessages' +import { STEPS } from 'src/const/Connect' const institutionMock = { name: 'Institution', @@ -49,6 +51,33 @@ describe('LoginError', () => { { preloadedState }, ) + const renderStepProps = { + availableAccountTypes: [], + handleConsentGoBack: vitest.fn(), + handleCredentialsGoBack: vitest.fn(), + navigationRef: React.createRef(), + onManualAccountAdded: vitest.fn(), + onUpsertMember: vitest.fn(), + setConnectLocalState: vitest.fn(), + } + + const renderErrorStep = (connection_status, preloadedState = initialState) => { + const member = { ...memberMock, connection_status } + + return render(, { + preloadedState: { + ...preloadedState, + connect: { + ...preloadedState.connect, + location: [{ step: STEPS.ACTIONABLE_ERROR }], + selectedInstitution: institutionMock, + currentMemberGuid: member.guid, + members: [member], + }, + }, + }) + } + beforeEach(() => { vitest.clearAllMocks() onPostMessage = vitest.fn() @@ -118,24 +147,22 @@ describe('LoginError', () => { }) describe('Primary Actions', () => { - it('calls onRefreshClick when the Try again button is clicked', async () => { - const { user } = renderLoginError({ - member: { ...memberMock, connection_status: ReadableStatuses.REJECTED }, - }) + it('returns to connecting when the Try again button is clicked', async () => { + const { user } = renderErrorStep(ReadableStatuses.REJECTED) + + expect(await screen.findByText('Incorrect information')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: 'Try again' })) - expect(defaultProps.onRefreshClick).toHaveBeenCalled() + expect(screen.queryByText('Incorrect information')).not.toBeInTheDocument() }) - it('calls onUpdateCredentialsClick when the Connect button is clicked', async () => { - const { user } = renderLoginError({ - member: { ...memberMock, connection_status: ReadableStatuses.PREVENTED }, - }) + it('opens the update credentials form when the Connect button is clicked', async () => { + const { user } = renderErrorStep(ReadableStatuses.PREVENTED) - await user.click(screen.getByRole('button', { name: 'Connect' })) + await user.click(await screen.findByRole('button', { name: 'Connect' })) - expect(defaultProps.onUpdateCredentialsClick).toHaveBeenCalled() + expect(await screen.findByLabelText('Username *')).toBeInTheDocument() }) it('posts a primary action message when the OK button is clicked', async () => { @@ -152,7 +179,7 @@ describe('LoginError', () => { }) }) - it('calls onUpdateCredentialsClick when OK is clicked and institution search is disabled', async () => { + it('opens the update credentials form when OK is clicked and institution search is disabled', async () => { const stateWithDisabledSearch = { ...initialState, config: { @@ -160,14 +187,11 @@ describe('LoginError', () => { disable_institution_search: true, }, } - const { user } = renderLoginError( - { member: { ...memberMock, connection_status: ReadableStatuses.DEGRADED } }, - stateWithDisabledSearch, - ) + const { user } = renderErrorStep(ReadableStatuses.DEGRADED, stateWithDisabledSearch) - await user.click(screen.getByRole('button', { name: 'OK' })) + await user.click(await screen.findByRole('button', { name: 'OK' })) - expect(defaultProps.onUpdateCredentialsClick).toHaveBeenCalled() + expect(await screen.findByLabelText('Username *')).toBeInTheDocument() }) }) From 33374e9aa2b8f4a1a1552ab6af048b1cbdd5176e Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Wed, 1 Jul 2026 14:53:35 -0600 Subject: [PATCH 3/3] added a try again button test --- .../loginError/ImpededMemberError-test.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/views/loginError/ImpededMemberError-test.tsx b/src/views/loginError/ImpededMemberError-test.tsx index 9ef225f5a2..0728a8f121 100644 --- a/src/views/loginError/ImpededMemberError-test.tsx +++ b/src/views/loginError/ImpededMemberError-test.tsx @@ -1,9 +1,11 @@ import React from 'react' import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest' import { render, screen } from 'src/utilities/testingLibrary' +import RenderConnectStep from 'src/components/RenderConnectStep' import { LoginError as LoginErrorComponent } from 'src/views/loginError/LoginError' import { initialState, institutionData } from 'src/services/mockedData' import { ReadableStatuses } from 'src/const/Statuses' +import { STEPS } from 'src/const/Connect' const LoginError = LoginErrorComponent as unknown as React.ComponentType> @@ -57,6 +59,27 @@ describe('', () => { { preloadedState }, ) + const renderStepProps = { + availableAccountTypes: [], + handleConsentGoBack: vi.fn(), + handleCredentialsGoBack: vi.fn(), + navigationRef: React.createRef(), + onManualAccountAdded: vi.fn(), + onUpsertMember: vi.fn(), + setConnectLocalState: vi.fn(), + } + + const renderImpededErrorStep = () => + render(, { + preloadedState: { + ...preloadedState, + connect: { + ...preloadedState.connect, + location: [{ step: STEPS.ACTIONABLE_ERROR }], + }, + }, + }) + describe('Content Display', () => { it('renders the impeded error with both resolution steps', () => { renderImpededMemberError() @@ -82,6 +105,18 @@ describe('', () => { }) }) + describe('Try Again Link', () => { + it('leaves the error view and returns to connecting when clicked', async () => { + const { user } = renderImpededErrorStep() + + expect(await screen.findByText('Your attention is needed')).toBeInTheDocument() + + await user.click(screen.getByText('Try again')) + + expect(screen.queryByText('Your attention is needed')).not.toBeInTheDocument() + }) + }) + describe('Visit Website Link', () => { it('opens the institution login URL in a new tab when external link popup is disabled', async () => { const { user } = renderImpededMemberError({ showExternalLinkPopup: false })