From 08bfa5ff81158ad7b80f998e6a168fc695f3f27a Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Tue, 30 Jun 2026 11:55:11 -0600 Subject: [PATCH 1/3] added delete member success and disclosure tests --- .../DeleteMemberSuccess-test.tsx | 94 +++++++ src/views/disclosure/Disclosure-test.tsx | 239 ++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 src/views/deleteMemberSuccess/DeleteMemberSuccess-test.tsx create mode 100644 src/views/disclosure/Disclosure-test.tsx diff --git a/src/views/deleteMemberSuccess/DeleteMemberSuccess-test.tsx b/src/views/deleteMemberSuccess/DeleteMemberSuccess-test.tsx new file mode 100644 index 0000000000..9f74389c03 --- /dev/null +++ b/src/views/deleteMemberSuccess/DeleteMemberSuccess-test.tsx @@ -0,0 +1,94 @@ +import React from 'react' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { render, screen, waitFor } from 'src/utilities/testingLibrary' +import RenderConnectStep from 'src/components/RenderConnectStep' +import { initialState, institutionData } from 'src/services/mockedData' +import { PostMessageContext } from 'src/ConnectWidget' +import { STEPS } from 'src/const/Connect' + +type RenderDeleteMemberSuccessStepOptions = { + institution?: typeof institutionData.institution +} + +const renderDeleteMemberSuccessStep = ({ + institution = institutionData.institution, +}: RenderDeleteMemberSuccessStepOptions = {}) => { + const onPostMessage = vi.fn() + + const preloadedState = { + ...initialState, + connect: { + ...initialState.connect, + location: [{ step: STEPS.DELETE_MEMBER_SUCCESS }], + selectedInstitution: institution, + }, + } as unknown as typeof initialState + + return { + ...render( + + {}} + handleCredentialsGoBack={() => {}} + navigationRef={React.createRef()} + onManualAccountAdded={() => {}} + onUpsertMember={() => {}} + setConnectLocalState={() => {}} + /> + , + { preloadedState }, + ), + onPostMessage, + } +} + +describe('', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('Content Display', () => { + it('renders the disconnected primary text', () => { + renderDeleteMemberSuccessStep() + + expect(screen.getByTestId('disconnected-primary-text')).toHaveTextContent('Disconnected') + }) + + it('renders the disconnected secondary text with the institution name', () => { + renderDeleteMemberSuccessStep({ + institution: { ...institutionData.institution, name: 'Custom Bank' }, + }) + + expect(screen.getByTestId('disconnected-secondary-text')).toHaveTextContent( + 'You have successfully disconnected Custom Bank.', + ) + }) + + it('renders the Done button', () => { + renderDeleteMemberSuccessStep() + + expect(screen.getByTestId('done-button')).toHaveTextContent('Done') + }) + + it('renders the PrivateAndSecure component', () => { + renderDeleteMemberSuccessStep() + + expect(screen.getByText('Private and secure')).toBeInTheDocument() + }) + }) + + describe('User Interactions', () => { + it('posts back to search and leaves the success screen when Done is clicked', async () => { + const { onPostMessage, user } = renderDeleteMemberSuccessStep() + + await user.click(screen.getByTestId('done-button')) + + expect(onPostMessage).toHaveBeenCalledWith('connect/backToSearch') + + await waitFor(() => { + expect(screen.queryByTestId('disconnected-primary-text')).not.toBeInTheDocument() + }) + }) + }) +}) diff --git a/src/views/disclosure/Disclosure-test.tsx b/src/views/disclosure/Disclosure-test.tsx new file mode 100644 index 0000000000..adf37e89dc --- /dev/null +++ b/src/views/disclosure/Disclosure-test.tsx @@ -0,0 +1,239 @@ +import React from 'react' +import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest' +import { render, screen, waitFor } from 'src/utilities/testingLibrary' +import { Disclosure } from 'src/views/disclosure/Disclosure' +import { initialState, institutionData } from 'src/services/mockedData' +import { STEPS } from 'src/const/Connect' + +type DisclosureHandle = { + handleBackButton: () => void + showBackButton: () => boolean +} + +const preloadedState = { + ...initialState, + browser: { + height: 0, + isMobile: false, + isTablet: false, + size: '', + width: 0, + }, + connect: { + ...initialState.connect, + selectedInstitution: institutionData.institution, + }, +} + +const stateWithExternalLinkPopup = { + ...preloadedState, + profiles: { + ...preloadedState.profiles, + clientProfile: { + ...preloadedState.profiles.clientProfile, + show_external_link_popup: true, + }, + }, +} + +describe('', () => { + let openSpy: MockInstance + + beforeEach(() => { + vi.clearAllMocks() + openSpy = vi.spyOn(window, 'open').mockReturnValue(null) + Element.prototype.scrollIntoView = vi.fn() + }) + + afterEach(() => { + openSpy.mockRestore() + }) + + describe('Content Display', () => { + it('renders the disclosure screen content', () => { + render(, { preloadedState }) + + expect(screen.getByTestId('disclosure-svg-header')).toBeInTheDocument() + expect(screen.getByTestId('disclosure-title')).toHaveTextContent('Connect your account') + expect(screen.getByTestId('disclosure-paragraph1')).toHaveTextContent( + 'This app will have access to the information below unless you choose to disconnect:', + ) + expect(screen.getByTestId('disclosure-paragraph-2')).toHaveTextContent( + 'Your information is protected with bank-level security.', + ) + expect(screen.getByTestId('disclosure-privacy-policy-text')).toHaveTextContent( + 'By clicking Continue, you agree to the', + ) + expect(screen.getByTestId('disclosure-privacy-policy-link')).toHaveTextContent( + 'MX Privacy Policy.', + ) + expect(screen.getByTestId('disclosure-continue')).toHaveTextContent('Continue') + expect(screen.getByTestId('disclosure-databymx')).toBeInTheDocument() + }) + }) + + describe('Mode-specific Content', () => { + it('renders aggregation mode list items', () => { + const aggState = { + ...preloadedState, + config: { + ...preloadedState.config, + mode: 'aggregation', + }, + } + + render(, { preloadedState: aggState }) + + expect(screen.getByTestId('disclosure-agg-mode-list-item1')).toHaveTextContent( + 'Account details', + ) + expect(screen.getByTestId('disclosure-agg-mode-list-item2')).toHaveTextContent( + 'Account balances and transactions', + ) + }) + + it('renders verification mode list items', () => { + const verifyState = { + ...preloadedState, + config: { + ...preloadedState.config, + mode: 'verification', + }, + } + + render(, { preloadedState: verifyState }) + + expect(screen.getByTestId('disclosure-ver-mode-list-item1')).toHaveTextContent( + 'Routing and account numbers', + ) + expect(screen.getByTestId('disclosure-ver-mode-list-item2')).toHaveTextContent( + 'Account balances', + ) + }) + + it('renders tax mode list items', () => { + const taxState = { + ...preloadedState, + config: { + ...preloadedState.config, + mode: 'tax', + }, + } + + render(, { preloadedState: taxState }) + + expect(screen.getByTestId('disclosure-tax-mode-list-item1')).toHaveTextContent( + 'Basic account information', + ) + expect(screen.getByTestId('disclosure-tax-mode-list-item2')).toHaveTextContent( + 'Tax documents', + ) + }) + }) + + describe('Privacy Policy Link', () => { + it('opens privacy policy externally when show_external_link_popup is false', async () => { + const stateWithoutPopup = { + ...preloadedState, + profiles: { + ...preloadedState.profiles, + clientProfile: { + ...preloadedState.profiles.clientProfile, + show_external_link_popup: false, + }, + }, + } + + const { user } = render(, { preloadedState: stateWithoutPopup }) + + await user.click(screen.getByTestId('disclosure-privacy-policy-link')) + + expect(openSpy).toHaveBeenCalledWith( + 'https://www.mx.com/privacy/', + '_blank', + 'noopener,noreferrer', + ) + }) + + it('shows inline privacy policy when show_external_link_popup is true', async () => { + const { user } = render( +
+ +
, + { preloadedState: stateWithExternalLinkPopup }, + ) + + await user.click(screen.getByTestId('disclosure-privacy-policy-link')) + + await waitFor(() => { + expect(screen.getByTestId('leaving-notice-flat-header')).toBeInTheDocument() + }) + + expect(openSpy).not.toHaveBeenCalled() + }) + }) + + describe('Continue Button', () => { + it('advances past the disclosure when Continue is clicked', async () => { + const { user, store } = render(, { preloadedState }) + + const continueButton = screen.getByTestId('disclosure-continue') + expect(continueButton).toBeEnabled() + + await user.click(continueButton) + + await waitFor(() => { + const { location } = store.getState().connect + expect(location[location.length - 1].step).toBe(STEPS.SEARCH) + }) + }) + }) + + describe('Imperative Handle', () => { + it('showBackButton reflects whether the privacy policy is shown', async () => { + const ref = React.createRef() + + const { user } = render( +
+ +
, + { preloadedState: stateWithExternalLinkPopup }, + ) + + expect(ref.current?.showBackButton()).toBe(false) + + await user.click(screen.getByTestId('disclosure-privacy-policy-link')) + + await waitFor(() => { + expect(ref.current?.showBackButton()).toBe(true) + }) + }) + + it('handleBackButton hides the privacy policy and returns to the disclosure', async () => { + const ref = React.createRef() + + const { user } = render( +
+ +
, + { preloadedState: stateWithExternalLinkPopup }, + ) + + await user.click(screen.getByTestId('disclosure-privacy-policy-link')) + + await waitFor(() => { + expect(screen.getByTestId('leaving-notice-flat-header')).toBeInTheDocument() + }) + + await waitFor(() => { + ref.current?.handleBackButton() + }) + + await waitFor(() => { + expect(screen.queryByTestId('leaving-notice-flat-header')).not.toBeInTheDocument() + expect(screen.getByTestId('disclosure-title')).toBeInTheDocument() + expect(ref.current?.showBackButton()).toBe(false) + }) + }) + }) +}) From 6dad022b9fc521137c0ab779ed233f321376c426 Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Tue, 30 Jun 2026 11:57:19 -0600 Subject: [PATCH 2/3] fix: add delete member success and disclosure tests From aa91c30f86018cf7d02ee7ba6ed5bb185b500858 Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Tue, 30 Jun 2026 16:31:26 -0600 Subject: [PATCH 3/3] combined similar tests and removed wait fors --- .../DeleteMemberSuccess-test.tsx | 28 +--- src/views/disclosure/Disclosure-test.tsx | 135 +++++++----------- 2 files changed, 58 insertions(+), 105 deletions(-) diff --git a/src/views/deleteMemberSuccess/DeleteMemberSuccess-test.tsx b/src/views/deleteMemberSuccess/DeleteMemberSuccess-test.tsx index 9f74389c03..69103832d5 100644 --- a/src/views/deleteMemberSuccess/DeleteMemberSuccess-test.tsx +++ b/src/views/deleteMemberSuccess/DeleteMemberSuccess-test.tsx @@ -1,6 +1,6 @@ import React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { render, screen, waitFor } from 'src/utilities/testingLibrary' +import { render, screen } from 'src/utilities/testingLibrary' import RenderConnectStep from 'src/components/RenderConnectStep' import { initialState, institutionData } from 'src/services/mockedData' import { PostMessageContext } from 'src/ConnectWidget' @@ -49,46 +49,30 @@ describe('', () => { }) describe('Content Display', () => { - it('renders the disconnected primary text', () => { - renderDeleteMemberSuccessStep() - - expect(screen.getByTestId('disconnected-primary-text')).toHaveTextContent('Disconnected') - }) - - it('renders the disconnected secondary text with the institution name', () => { + it('renders the success content with the institution name', () => { renderDeleteMemberSuccessStep({ institution: { ...institutionData.institution, name: 'Custom Bank' }, }) + expect(screen.getByTestId('disconnected-primary-text')).toHaveTextContent('Disconnected') expect(screen.getByTestId('disconnected-secondary-text')).toHaveTextContent( 'You have successfully disconnected Custom Bank.', ) - }) - - it('renders the Done button', () => { - renderDeleteMemberSuccessStep() - expect(screen.getByTestId('done-button')).toHaveTextContent('Done') - }) - - it('renders the PrivateAndSecure component', () => { - renderDeleteMemberSuccessStep() - expect(screen.getByText('Private and secure')).toBeInTheDocument() }) }) describe('User Interactions', () => { - it('posts back to search and leaves the success screen when Done is clicked', async () => { + it('posts back to search and returns to the search step when Done is clicked', async () => { const { onPostMessage, user } = renderDeleteMemberSuccessStep() await user.click(screen.getByTestId('done-button')) expect(onPostMessage).toHaveBeenCalledWith('connect/backToSearch') - await waitFor(() => { - expect(screen.queryByTestId('disconnected-primary-text')).not.toBeInTheDocument() - }) + expect(await screen.findByTestId('search-header')).toBeInTheDocument() + expect(screen.queryByTestId('disconnected-primary-text')).not.toBeInTheDocument() }) }) }) diff --git a/src/views/disclosure/Disclosure-test.tsx b/src/views/disclosure/Disclosure-test.tsx index adf37e89dc..f05d8738bb 100644 --- a/src/views/disclosure/Disclosure-test.tsx +++ b/src/views/disclosure/Disclosure-test.tsx @@ -2,6 +2,7 @@ import React from 'react' import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest' import { render, screen, waitFor } from 'src/utilities/testingLibrary' import { Disclosure } from 'src/views/disclosure/Disclosure' +import RenderConnectStep from 'src/components/RenderConnectStep' import { initialState, institutionData } from 'src/services/mockedData' import { STEPS } from 'src/const/Connect' @@ -73,61 +74,32 @@ describe('', () => { }) describe('Mode-specific Content', () => { - it('renders aggregation mode list items', () => { - const aggState = { - ...preloadedState, - config: { - ...preloadedState.config, - mode: 'aggregation', + it.each([ + { + mode: 'aggregation', + item1: { testId: 'disclosure-agg-mode-list-item1', text: 'Account details' }, + item2: { + testId: 'disclosure-agg-mode-list-item2', + text: 'Account balances and transactions', }, - } - - render(, { preloadedState: aggState }) - - expect(screen.getByTestId('disclosure-agg-mode-list-item1')).toHaveTextContent( - 'Account details', - ) - expect(screen.getByTestId('disclosure-agg-mode-list-item2')).toHaveTextContent( - 'Account balances and transactions', - ) - }) - - it('renders verification mode list items', () => { - const verifyState = { - ...preloadedState, - config: { - ...preloadedState.config, - mode: 'verification', - }, - } - - render(, { preloadedState: verifyState }) - - expect(screen.getByTestId('disclosure-ver-mode-list-item1')).toHaveTextContent( - 'Routing and account numbers', - ) - expect(screen.getByTestId('disclosure-ver-mode-list-item2')).toHaveTextContent( - 'Account balances', - ) - }) - - it('renders tax mode list items', () => { - const taxState = { - ...preloadedState, - config: { - ...preloadedState.config, - mode: 'tax', - }, - } - - render(, { preloadedState: taxState }) + }, + { + mode: 'verification', + item1: { testId: 'disclosure-ver-mode-list-item1', text: 'Routing and account numbers' }, + item2: { testId: 'disclosure-ver-mode-list-item2', text: 'Account balances' }, + }, + { + mode: 'tax', + item1: { testId: 'disclosure-tax-mode-list-item1', text: 'Basic account information' }, + item2: { testId: 'disclosure-tax-mode-list-item2', text: 'Tax documents' }, + }, + ])('renders $mode mode list items', ({ mode, item1, item2 }) => { + render(, { + preloadedState: { ...preloadedState, config: { ...preloadedState.config, mode } }, + }) - expect(screen.getByTestId('disclosure-tax-mode-list-item1')).toHaveTextContent( - 'Basic account information', - ) - expect(screen.getByTestId('disclosure-tax-mode-list-item2')).toHaveTextContent( - 'Tax documents', - ) + expect(screen.getByTestId(item1.testId)).toHaveTextContent(item1.text) + expect(screen.getByTestId(item2.testId)).toHaveTextContent(item2.text) }) }) @@ -165,32 +137,47 @@ describe('', () => { await user.click(screen.getByTestId('disclosure-privacy-policy-link')) - await waitFor(() => { - expect(screen.getByTestId('leaving-notice-flat-header')).toBeInTheDocument() - }) + expect(await screen.findByTestId('leaving-notice-flat-header')).toBeInTheDocument() expect(openSpy).not.toHaveBeenCalled() }) }) describe('Continue Button', () => { - it('advances past the disclosure when Continue is clicked', async () => { - const { user, store } = render(, { preloadedState }) + it('advances to the search step when Continue is clicked', async () => { + const { user } = render( + {}} + handleCredentialsGoBack={() => {}} + navigationRef={React.createRef()} + onManualAccountAdded={() => {}} + onUpsertMember={() => {}} + setConnectLocalState={() => {}} + />, + { + preloadedState: { + ...preloadedState, + connect: { + ...preloadedState.connect, + location: [{ step: STEPS.DISCLOSURE }], + }, + }, + }, + ) const continueButton = screen.getByTestId('disclosure-continue') expect(continueButton).toBeEnabled() await user.click(continueButton) - await waitFor(() => { - const { location } = store.getState().connect - expect(location[location.length - 1].step).toBe(STEPS.SEARCH) - }) + expect(await screen.findByTestId('search-header')).toBeInTheDocument() + expect(screen.queryByTestId('disclosure-title')).not.toBeInTheDocument() }) }) describe('Imperative Handle', () => { - it('showBackButton reflects whether the privacy policy is shown', async () => { + it('toggles the privacy policy and back button via the imperative handle', async () => { const ref = React.createRef() const { user } = render( @@ -204,26 +191,8 @@ describe('', () => { await user.click(screen.getByTestId('disclosure-privacy-policy-link')) - await waitFor(() => { - expect(ref.current?.showBackButton()).toBe(true) - }) - }) - - it('handleBackButton hides the privacy policy and returns to the disclosure', async () => { - const ref = React.createRef() - - const { user } = render( -
- -
, - { preloadedState: stateWithExternalLinkPopup }, - ) - - await user.click(screen.getByTestId('disclosure-privacy-policy-link')) - - await waitFor(() => { - expect(screen.getByTestId('leaving-notice-flat-header')).toBeInTheDocument() - }) + expect(await screen.findByTestId('leaving-notice-flat-header')).toBeInTheDocument() + expect(ref.current?.showBackButton()).toBe(true) await waitFor(() => { ref.current?.handleBackButton()