From e64ec4d67704869b69f8e34c4567b144bdb829ba Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Mon, 29 Jun 2026 15:54:51 -0600 Subject: [PATCH 1/4] added credential tests --- .../credentials/CreateMemberForm-test.tsx | 358 ++++++++++++++++++ .../credentials/UpdateMemberForm-test.tsx | 248 ++++++++++++ 2 files changed, 606 insertions(+) create mode 100644 src/views/credentials/CreateMemberForm-test.tsx create mode 100644 src/views/credentials/UpdateMemberForm-test.tsx diff --git a/src/views/credentials/CreateMemberForm-test.tsx b/src/views/credentials/CreateMemberForm-test.tsx new file mode 100644 index 0000000000..85ba75b5ce --- /dev/null +++ b/src/views/credentials/CreateMemberForm-test.tsx @@ -0,0 +1,358 @@ +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, member } from 'src/services/mockedData' +import { apiValue as baseApiValue } from 'src/const/apiProviderMock' +import { PostMessageContext } from 'src/ConnectWidget' +import { STEPS } from 'src/const/Connect' +import { ReadableStatuses } from 'src/const/Statuses' + +type RenderCredentialsStepOptions = { + apiOverrides?: Partial + members?: unknown[] + onUpsertMember?: ReturnType +} + +const renderCredentialsStep = ({ + apiOverrides = {}, + members = [], + onUpsertMember = vi.fn(), +}: RenderCredentialsStepOptions = {}) => { + const onPostMessage = vi.fn() + const navigationRef = React.createRef() + + const mockApi = { + ...baseApiValue, + addMember: vi.fn(baseApiValue.addMember), + getInstitutionCredentials: vi.fn(baseApiValue.getInstitutionCredentials), + loadMemberByGuid: vi.fn(baseApiValue.loadMemberByGuid), + updateMember: vi.fn(baseApiValue.updateMember), + ...apiOverrides, + } + + const preloadedState = { + ...initialState, + profiles: { + ...initialState.profiles, + clientProfile: { ...initialState.profiles.clientProfile, uses_oauth: false }, + }, + connect: { + ...initialState.connect, + location: [{ step: STEPS.ENTER_CREDENTIALS }], + selectedInstitution: institutionData.institution, + members, + }, + app: { humanEvent: true }, + } as unknown as typeof initialState + + return { + ...render( + + {}} + handleCredentialsGoBack={() => {}} + navigationRef={navigationRef} + onManualAccountAdded={() => {}} + onUpsertMember={onUpsertMember} + setConnectLocalState={() => {}} + /> + , + { + apiValue: mockApi, + preloadedState, + }, + ), + mockApi, + onPostMessage, + onUpsertMember, + navigationRef, + } +} + +describe('', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('Loading State', () => { + it('displays loading spinner while fetching credentials', () => { + renderCredentialsStep({ + apiOverrides: { + getInstitutionCredentials: vi.fn().mockImplementation(() => new Promise(() => {})), + }, + }) + + expect(screen.queryByText('Continue')).not.toBeInTheDocument() + }) + + it('calls getInstitutionCredentials on mount', () => { + const { mockApi } = renderCredentialsStep() + + expect(mockApi.getInstitutionCredentials).toHaveBeenCalledWith( + institutionData.institution.guid, + ) + }) + }) + + describe('Credentials Display', () => { + it('renders Credentials component after loading credentials', async () => { + renderCredentialsStep() + + expect(await screen.findByText('Continue')).toBeInTheDocument() + }) + + it('passes credentials to Credentials component', async () => { + renderCredentialsStep() + + expect(await screen.findByLabelText('Username *')).toBeInTheDocument() + expect(await screen.findByLabelText('Password *')).toBeInTheDocument() + }) + + it('renders institution header', async () => { + renderCredentialsStep() + + await screen.findByText('Continue') + + expect(screen.getByTestId('institution-block')).toBeInTheDocument() + }) + }) + + describe('Error Handling', () => { + it('handles error when fetching credentials fails', async () => { + const error = new Error('Failed to fetch credentials') + renderCredentialsStep({ + apiOverrides: { + getInstitutionCredentials: vi.fn().mockRejectedValue(error), + }, + }) + + await waitFor(() => { + expect(screen.queryByText('Continue')).not.toBeInTheDocument() + }) + + expect(screen.getByTestId('institution-block')).toBeInTheDocument() + }) + }) + + describe('Member Creation', () => { + it('submits credentials and creates member', async () => { + const { mockApi, user } = renderCredentialsStep() + + await user.type(await screen.findByLabelText('Username *'), 'testuser') + await user.type(await screen.findByLabelText('Password *'), 'testpass') + await user.click(screen.getByText('Continue')) + + await waitFor(() => { + expect(mockApi.addMember).toHaveBeenCalled() + }) + }) + + it('posts connect/enterCredentials message when creating member', async () => { + const { onPostMessage, user } = renderCredentialsStep() + + await user.type(await screen.findByLabelText('Username *'), 'testuser') + await user.type(await screen.findByLabelText('Password *'), 'testpass') + await user.click(screen.getByText('Continue')) + + await waitFor(() => { + expect(onPostMessage).toHaveBeenCalledWith('connect/enterCredentials', { + institution: { + guid: institutionData.institution.guid, + code: institutionData.institution.code, + }, + }) + }) + }) + + it('calls onUpsertMember callback when member is created', async () => { + const { onUpsertMember, user } = renderCredentialsStep() + + await user.type(await screen.findByLabelText('Username *'), 'testuser') + await user.type(await screen.findByLabelText('Password *'), 'testpass') + await user.click(screen.getByText('Continue')) + + await waitFor( + () => { + expect(onUpsertMember).toHaveBeenCalledWith(member.member) + }, + { timeout: 1000 }, + ) + }) + + it('includes institution data in member creation request', async () => { + const { mockApi, user } = renderCredentialsStep() + + await user.type(await screen.findByLabelText('Username *'), 'testuser') + await user.type(await screen.findByLabelText('Password *'), 'testpass') + await user.click(screen.getByText('Continue')) + + await waitFor(() => { + expect(mockApi.addMember).toHaveBeenCalledWith( + expect.objectContaining({ + institution_guid: institutionData.institution.guid, + rawInstitutionData: institutionData.institution, + }), + expect.any(Object), + true, + ) + }) + }) + }) + + describe('409 Conflict Handling', () => { + it('handles 409 error when member already exists and is challenged', async () => { + const existingMemberGuid = 'MBR-EXISTING' + const challengedMember = { + guid: existingMemberGuid, + connection_status: ReadableStatuses.CHALLENGED, + } + + const { mockApi, user } = renderCredentialsStep({ + members: [challengedMember], + apiOverrides: { + addMember: vi.fn().mockRejectedValue({ + response: { + status: 409, + data: { guid: existingMemberGuid }, + }, + }), + loadMemberByGuid: vi.fn().mockResolvedValue(challengedMember), + }, + }) + + await user.type(await screen.findByLabelText('Username *'), 'testuser') + await user.type(await screen.findByLabelText('Password *'), 'testpass') + await user.click(screen.getByText('Continue')) + + await waitFor(() => { + expect(mockApi.loadMemberByGuid).toHaveBeenCalledWith(existingMemberGuid, 'en') + }) + }) + + it('handles 409 error when member exists and needs update', async () => { + const existingMemberGuid = 'MBR-EXISTING' + const existingMember = { + guid: existingMemberGuid, + connection_status: ReadableStatuses.CONNECTED, + use_cases: ['verification'], + } + const updatedMember = { + ...existingMember, + connection_status: ReadableStatuses.CONNECTED, + } + + const { mockApi, user } = renderCredentialsStep({ + members: [existingMember], + apiOverrides: { + addMember: vi.fn().mockRejectedValue({ + response: { + status: 409, + data: { guid: existingMemberGuid }, + }, + }), + loadMemberByGuid: vi.fn().mockResolvedValue(existingMember), + updateMember: vi.fn().mockResolvedValue(updatedMember), + }, + }) + + await user.type(await screen.findByLabelText('Username *'), 'testuser') + await user.type(await screen.findByLabelText('Password *'), 'testpass') + await user.click(screen.getByText('Continue')) + + await waitFor( + () => { + expect(mockApi.updateMember).toHaveBeenCalled() + }, + { timeout: 1000 }, + ) + }) + + it('calls onUpsertMember when updating existing member', async () => { + const existingMemberGuid = 'MBR-EXISTING' + const existingMember = { + guid: existingMemberGuid, + connection_status: ReadableStatuses.CONNECTED, + use_cases: ['verification'], + } + const updatedMember = { + ...existingMember, + connection_status: ReadableStatuses.CONNECTED, + } + + const { onUpsertMember, user } = renderCredentialsStep({ + members: [existingMember], + apiOverrides: { + addMember: vi.fn().mockRejectedValue({ + response: { + status: 409, + data: { guid: existingMemberGuid }, + }, + }), + loadMemberByGuid: vi.fn().mockResolvedValue(existingMember), + updateMember: vi.fn().mockResolvedValue(updatedMember), + }, + }) + + await user.type(await screen.findByLabelText('Username *'), 'testuser') + await user.type(await screen.findByLabelText('Password *'), 'testpass') + await user.click(screen.getByText('Continue')) + + await waitFor( + () => { + expect(onUpsertMember).toHaveBeenCalledWith(updatedMember) + }, + { timeout: 1000 }, + ) + }) + }) + + describe('Integration', () => { + it('disables the continue button while the member creation is in flight', async () => { + const { user } = renderCredentialsStep({ + apiOverrides: { + addMember: vi.fn().mockImplementation(() => new Promise(() => {})), // Never resolves + }, + }) + + await user.type(await screen.findByLabelText('Username *'), 'testuser') + await user.type(await screen.findByLabelText('Password *'), 'testpass') + + const button = screen.getByTestId('credentials-continue') + expect(button).not.toBeDisabled() + + await user.click(button) + + await waitFor( + () => { + const processingButton = screen.getByTestId('credentials-continue') + expect(processingButton).toBeDisabled() + }, + { timeout: 2000 }, + ) + }) + + it('displays error in Credentials when member creation fails', async () => { + const errorResponse = { + response: { + status: 500, + data: { message: 'Server error' }, + }, + } + const { user } = renderCredentialsStep({ + apiOverrides: { + addMember: vi.fn().mockRejectedValue(errorResponse), + }, + }) + + await user.type(await screen.findByLabelText('Username *'), 'testuser') + await user.type(await screen.findByLabelText('Password *'), 'testpass') + await user.click(screen.getByText('Continue')) + + await waitFor(() => { + expect(screen.getByText('Something went wrong')).toBeInTheDocument() + }) + }) + }) +}) diff --git a/src/views/credentials/UpdateMemberForm-test.tsx b/src/views/credentials/UpdateMemberForm-test.tsx new file mode 100644 index 0000000000..45ef62e4ab --- /dev/null +++ b/src/views/credentials/UpdateMemberForm-test.tsx @@ -0,0 +1,248 @@ +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, member } from 'src/services/mockedData' +import { apiValue as baseApiValue } from 'src/const/apiProviderMock' +import { PostMessageContext } from 'src/ConnectWidget' +import { STEPS } from 'src/const/Connect' + +type RenderUpdateStepOptions = { + apiOverrides?: Partial + onUpsertMember?: ReturnType +} + +const renderUpdateStep = ({ + apiOverrides = {}, + onUpsertMember = vi.fn(), +}: RenderUpdateStepOptions = {}) => { + const onPostMessage = vi.fn() + const navigationRef = React.createRef() + + const mockApi = { + ...baseApiValue, + getMemberCredentials: vi.fn(baseApiValue.getMemberCredentials), + updateMember: vi.fn(baseApiValue.updateMember), + ...apiOverrides, + } + + const preloadedState = { + ...initialState, + profiles: { + ...initialState.profiles, + clientProfile: { ...initialState.profiles.clientProfile, uses_oauth: false }, + }, + connect: { + ...initialState.connect, + location: [{ step: STEPS.ENTER_CREDENTIALS }], + selectedInstitution: institutionData.institution, + currentMemberGuid: member.member.guid, + members: [member.member], + updateCredentials: true, + }, + app: { humanEvent: true }, + } as unknown as typeof initialState + + return { + ...render( + + {}} + handleCredentialsGoBack={() => {}} + navigationRef={navigationRef} + onManualAccountAdded={() => {}} + onUpsertMember={onUpsertMember} + setConnectLocalState={() => {}} + /> + , + { + apiValue: mockApi, + preloadedState, + }, + ), + mockApi, + onPostMessage, + onUpsertMember, + navigationRef, + } +} + +describe('', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('Loading State', () => { + it('displays loading spinner while fetching credentials', () => { + renderUpdateStep({ + apiOverrides: { + getMemberCredentials: vi.fn().mockImplementation(() => new Promise(() => {})), + }, + }) + + expect(screen.queryByText('Continue')).not.toBeInTheDocument() + }) + + it('calls getMemberCredentials on mount', () => { + const { mockApi } = renderUpdateStep() + + expect(mockApi.getMemberCredentials).toHaveBeenCalledWith(member.member.guid) + }) + }) + + describe('Credentials Display', () => { + it('renders Credentials component after loading credentials', async () => { + renderUpdateStep() + + expect(await screen.findByText('Continue')).toBeInTheDocument() + }) + + it('passes credentials to Credentials component', async () => { + renderUpdateStep() + + expect(await screen.findByLabelText('Username *')).toBeInTheDocument() + expect(await screen.findByLabelText('Password *')).toBeInTheDocument() + }) + + it('renders institution header', async () => { + renderUpdateStep() + + await screen.findByText('Continue') + + expect(screen.getByTestId('institution-block')).toBeInTheDocument() + }) + }) + + describe('Error Handling', () => { + it('handles error when fetching credentials fails', async () => { + const error = new Error('Failed to fetch credentials') + renderUpdateStep({ + apiOverrides: { + getMemberCredentials: vi.fn().mockRejectedValue(error), + }, + }) + + await waitFor(() => { + expect(screen.queryByText('Continue')).not.toBeInTheDocument() + }) + + expect(screen.getByTestId('institution-block')).toBeInTheDocument() + }) + }) + + describe('Member Update', () => { + it('submits credentials and updates member', async () => { + const { mockApi, user } = renderUpdateStep() + + await user.type(await screen.findByLabelText('Username *'), 'newuser') + await user.type(await screen.findByLabelText('Password *'), 'newpass') + await user.click(screen.getByText('Continue')) + + await waitFor(() => { + expect(mockApi.updateMember).toHaveBeenCalled() + }) + }) + + it('posts connect/updateCredentials message when updating member', async () => { + const { onPostMessage, user } = renderUpdateStep() + + await user.type(await screen.findByLabelText('Username *'), 'newuser') + await user.type(await screen.findByLabelText('Password *'), 'newpass') + await user.click(screen.getByText('Continue')) + + await waitFor(() => { + expect(onPostMessage).toHaveBeenCalledWith('connect/updateCredentials', { + institution: { + guid: institutionData.institution.guid, + code: institutionData.institution.code, + }, + member_guid: member.member.guid, + }) + }) + }) + + it('calls onUpsertMember callback when member is updated', async () => { + const { onUpsertMember, user } = renderUpdateStep() + + await user.type(await screen.findByLabelText('Username *'), 'newuser') + await user.type(await screen.findByLabelText('Password *'), 'newpass') + await user.click(screen.getByText('Continue')) + + await waitFor( + () => { + expect(onUpsertMember).toHaveBeenCalledWith(member.member) + }, + { timeout: 1000 }, + ) + }) + + it('includes member data in update request', async () => { + const { mockApi, user } = renderUpdateStep() + + await user.type(await screen.findByLabelText('Username *'), 'newuser') + await user.type(await screen.findByLabelText('Password *'), 'newpass') + await user.click(screen.getByText('Continue')) + + await waitFor(() => { + expect(mockApi.updateMember).toHaveBeenCalledWith( + expect.objectContaining({ + guid: member.member.guid, + }), + expect.any(Object), + true, + ) + }) + }) + }) + + describe('Error in Update', () => { + it('displays error in Credentials when member update fails', async () => { + const errorResponse = { + response: { + status: 500, + data: { message: 'Server error' }, + }, + } + const { user } = renderUpdateStep({ + apiOverrides: { + updateMember: vi.fn().mockRejectedValue(errorResponse), + }, + }) + + await user.type(await screen.findByLabelText('Username *'), 'newuser') + await user.type(await screen.findByLabelText('Password *'), 'newpass') + await user.click(screen.getByText('Continue')) + + await waitFor(() => { + expect(screen.getByText('Something went wrong')).toBeInTheDocument() + }) + }) + }) + + describe('Integration', () => { + it('disables the continue button while the member update is in flight', async () => { + const { user } = renderUpdateStep({ + apiOverrides: { + updateMember: vi.fn().mockImplementation(() => new Promise(() => {})), + }, + }) + + await user.type(await screen.findByLabelText('Username *'), 'newuser') + await user.type(await screen.findByLabelText('Password *'), 'newpass') + + const button = screen.getByTestId('credentials-continue') + expect(button).not.toBeDisabled() + + await user.click(button) + + await waitFor( + () => { + const processingButton = screen.getByTestId('credentials-continue') + expect(processingButton).toBeDisabled() + }, + { timeout: 2000 }, + ) + }) + }) +}) From f1a958f617c3ef51b619e9acdb8726ff5a1e00c7 Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Mon, 29 Jun 2026 15:56:00 -0600 Subject: [PATCH 2/4] fix: add credential tests From cb0ab1f22c22ac9ebda84f111824c1c10ca1fdee Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Tue, 30 Jun 2026 16:05:55 -0600 Subject: [PATCH 3/4] combined tests, and used connect step for more realistic tests --- .../credentials/CreateMemberForm-test.tsx | 143 ++++++------------ .../credentials/UpdateMemberForm-test.tsx | 102 ++++++------- 2 files changed, 93 insertions(+), 152 deletions(-) diff --git a/src/views/credentials/CreateMemberForm-test.tsx b/src/views/credentials/CreateMemberForm-test.tsx index 85ba75b5ce..fff353a7f4 100644 --- a/src/views/credentials/CreateMemberForm-test.tsx +++ b/src/views/credentials/CreateMemberForm-test.tsx @@ -1,8 +1,9 @@ import React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { render, screen, waitFor } from 'src/utilities/testingLibrary' +import { createTestReduxStore, render, screen, waitFor } from 'src/utilities/testingLibrary' import RenderConnectStep from 'src/components/RenderConnectStep' -import { initialState, institutionData, member } from 'src/services/mockedData' +import { ConnectWidgetWithoutReduxProvider } from 'src/ConnectWidget' +import { initialState, institutionData, masterData, member } from 'src/services/mockedData' import { apiValue as baseApiValue } from 'src/const/apiProviderMock' import { PostMessageContext } from 'src/ConnectWidget' import { STEPS } from 'src/const/Connect' @@ -97,24 +98,12 @@ describe('', () => { }) describe('Credentials Display', () => { - it('renders Credentials component after loading credentials', async () => { + it('renders the credentials form with institution header after loading', async () => { renderCredentialsStep() expect(await screen.findByText('Continue')).toBeInTheDocument() - }) - - it('passes credentials to Credentials component', async () => { - renderCredentialsStep() - - expect(await screen.findByLabelText('Username *')).toBeInTheDocument() - expect(await screen.findByLabelText('Password *')).toBeInTheDocument() - }) - - it('renders institution header', async () => { - renderCredentialsStep() - - await screen.findByText('Continue') - + expect(screen.getByLabelText('Username *')).toBeInTheDocument() + expect(screen.getByLabelText('Password *')).toBeInTheDocument() expect(screen.getByTestId('institution-block')).toBeInTheDocument() }) }) @@ -137,37 +126,55 @@ describe('', () => { }) describe('Member Creation', () => { - it('submits credentials and creates member', async () => { - const { mockApi, user } = renderCredentialsStep() + it('submits credentials and creates a member with the institution data', async () => { + const { mockApi, onPostMessage, user } = renderCredentialsStep() await user.type(await screen.findByLabelText('Username *'), 'testuser') await user.type(await screen.findByLabelText('Password *'), 'testpass') await user.click(screen.getByText('Continue')) await waitFor(() => { - expect(mockApi.addMember).toHaveBeenCalled() + expect(mockApi.addMember).toHaveBeenCalledWith( + expect.objectContaining({ + institution_guid: institutionData.institution.guid, + rawInstitutionData: institutionData.institution, + }), + expect.any(Object), + true, + ) }) - }) - - it('posts connect/enterCredentials message when creating member', async () => { - const { onPostMessage, user } = renderCredentialsStep() - - await user.type(await screen.findByLabelText('Username *'), 'testuser') - await user.type(await screen.findByLabelText('Password *'), 'testpass') - await user.click(screen.getByText('Continue')) - await waitFor(() => { - expect(onPostMessage).toHaveBeenCalledWith('connect/enterCredentials', { - institution: { - guid: institutionData.institution.guid, - code: institutionData.institution.code, - }, - }) + expect(onPostMessage).toHaveBeenCalledWith('connect/enterCredentials', { + institution: { + guid: institutionData.institution.guid, + code: institutionData.institution.code, + }, }) }) - it('calls onUpsertMember callback when member is created', async () => { - const { onUpsertMember, user } = renderCredentialsStep() + it('calls the consumer onUpsertMember callback when a member is created', async () => { + const onUpsertMember = vi.fn() + + // Render the real widget from the very top so we exercise the same + // onUpsertMember wiring a consumer relies on (ConnectWidget -> Connect -> + // RenderConnectStep -> CreateMemberForm). + const { user } = render( + , + { apiValue: baseApiValue, store: createTestReduxStore() }, + ) await user.type(await screen.findByLabelText('Username *'), 'testuser') await user.type(await screen.findByLabelText('Password *'), 'testpass') @@ -180,25 +187,6 @@ describe('', () => { { timeout: 1000 }, ) }) - - it('includes institution data in member creation request', async () => { - const { mockApi, user } = renderCredentialsStep() - - await user.type(await screen.findByLabelText('Username *'), 'testuser') - await user.type(await screen.findByLabelText('Password *'), 'testpass') - await user.click(screen.getByText('Continue')) - - await waitFor(() => { - expect(mockApi.addMember).toHaveBeenCalledWith( - expect.objectContaining({ - institution_guid: institutionData.institution.guid, - rawInstitutionData: institutionData.institution, - }), - expect.any(Object), - true, - ) - }) - }) }) describe('409 Conflict Handling', () => { @@ -231,45 +219,7 @@ describe('', () => { }) }) - it('handles 409 error when member exists and needs update', async () => { - const existingMemberGuid = 'MBR-EXISTING' - const existingMember = { - guid: existingMemberGuid, - connection_status: ReadableStatuses.CONNECTED, - use_cases: ['verification'], - } - const updatedMember = { - ...existingMember, - connection_status: ReadableStatuses.CONNECTED, - } - - const { mockApi, user } = renderCredentialsStep({ - members: [existingMember], - apiOverrides: { - addMember: vi.fn().mockRejectedValue({ - response: { - status: 409, - data: { guid: existingMemberGuid }, - }, - }), - loadMemberByGuid: vi.fn().mockResolvedValue(existingMember), - updateMember: vi.fn().mockResolvedValue(updatedMember), - }, - }) - - await user.type(await screen.findByLabelText('Username *'), 'testuser') - await user.type(await screen.findByLabelText('Password *'), 'testpass') - await user.click(screen.getByText('Continue')) - - await waitFor( - () => { - expect(mockApi.updateMember).toHaveBeenCalled() - }, - { timeout: 1000 }, - ) - }) - - it('calls onUpsertMember when updating existing member', async () => { + it('updates the existing member and calls onUpsertMember on a 409 conflict', async () => { const existingMemberGuid = 'MBR-EXISTING' const existingMember = { guid: existingMemberGuid, @@ -281,7 +231,7 @@ describe('', () => { connection_status: ReadableStatuses.CONNECTED, } - const { onUpsertMember, user } = renderCredentialsStep({ + const { mockApi, onUpsertMember, user } = renderCredentialsStep({ members: [existingMember], apiOverrides: { addMember: vi.fn().mockRejectedValue({ @@ -305,6 +255,7 @@ describe('', () => { }, { timeout: 1000 }, ) + expect(mockApi.updateMember).toHaveBeenCalled() }) }) diff --git a/src/views/credentials/UpdateMemberForm-test.tsx b/src/views/credentials/UpdateMemberForm-test.tsx index 45ef62e4ab..542bab5464 100644 --- a/src/views/credentials/UpdateMemberForm-test.tsx +++ b/src/views/credentials/UpdateMemberForm-test.tsx @@ -1,8 +1,9 @@ import React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { render, screen, waitFor } from 'src/utilities/testingLibrary' +import { createTestReduxStore, render, screen, waitFor } from 'src/utilities/testingLibrary' import RenderConnectStep from 'src/components/RenderConnectStep' -import { initialState, institutionData, member } from 'src/services/mockedData' +import { ConnectWidgetWithoutReduxProvider } from 'src/ConnectWidget' +import { initialState, institutionData, masterData, member } from 'src/services/mockedData' import { apiValue as baseApiValue } from 'src/const/apiProviderMock' import { PostMessageContext } from 'src/ConnectWidget' import { STEPS } from 'src/const/Connect' @@ -92,24 +93,12 @@ describe('', () => { }) describe('Credentials Display', () => { - it('renders Credentials component after loading credentials', async () => { + it('renders the credentials form with institution header after loading', async () => { renderUpdateStep() expect(await screen.findByText('Continue')).toBeInTheDocument() - }) - - it('passes credentials to Credentials component', async () => { - renderUpdateStep() - - expect(await screen.findByLabelText('Username *')).toBeInTheDocument() - expect(await screen.findByLabelText('Password *')).toBeInTheDocument() - }) - - it('renders institution header', async () => { - renderUpdateStep() - - await screen.findByText('Continue') - + expect(screen.getByLabelText('Username *')).toBeInTheDocument() + expect(screen.getByLabelText('Password *')).toBeInTheDocument() expect(screen.getByTestId('institution-block')).toBeInTheDocument() }) }) @@ -132,38 +121,57 @@ describe('', () => { }) describe('Member Update', () => { - it('submits credentials and updates member', async () => { - const { mockApi, user } = renderUpdateStep() + it('submits credentials and updates the member with the member data', async () => { + const { mockApi, onPostMessage, user } = renderUpdateStep() await user.type(await screen.findByLabelText('Username *'), 'newuser') await user.type(await screen.findByLabelText('Password *'), 'newpass') await user.click(screen.getByText('Continue')) await waitFor(() => { - expect(mockApi.updateMember).toHaveBeenCalled() + expect(mockApi.updateMember).toHaveBeenCalledWith( + expect.objectContaining({ + guid: member.member.guid, + }), + expect.any(Object), + true, + ) }) - }) - - it('posts connect/updateCredentials message when updating member', async () => { - const { onPostMessage, user } = renderUpdateStep() - - await user.type(await screen.findByLabelText('Username *'), 'newuser') - await user.type(await screen.findByLabelText('Password *'), 'newpass') - await user.click(screen.getByText('Continue')) - await waitFor(() => { - expect(onPostMessage).toHaveBeenCalledWith('connect/updateCredentials', { - institution: { - guid: institutionData.institution.guid, - code: institutionData.institution.code, - }, - member_guid: member.member.guid, - }) + expect(onPostMessage).toHaveBeenCalledWith('connect/updateCredentials', { + institution: { + guid: institutionData.institution.guid, + code: institutionData.institution.code, + }, + member_guid: member.member.guid, }) }) - it('calls onUpsertMember callback when member is updated', async () => { - const { onUpsertMember, user } = renderUpdateStep() + it('calls the consumer onUpsertMember callback when a member is updated', async () => { + const onUpsertMember = vi.fn() + + // Render the real widget from the very top so we exercise the same + // onUpsertMember wiring a consumer relies on (ConnectWidget -> Connect -> + // RenderConnectStep -> UpdateMemberForm). update_credentials + a configured + // member lands the load flow on the update-credentials form. + const { user } = render( + , + { apiValue: baseApiValue, store: createTestReduxStore() }, + ) await user.type(await screen.findByLabelText('Username *'), 'newuser') await user.type(await screen.findByLabelText('Password *'), 'newpass') @@ -176,24 +184,6 @@ describe('', () => { { timeout: 1000 }, ) }) - - it('includes member data in update request', async () => { - const { mockApi, user } = renderUpdateStep() - - await user.type(await screen.findByLabelText('Username *'), 'newuser') - await user.type(await screen.findByLabelText('Password *'), 'newpass') - await user.click(screen.getByText('Continue')) - - await waitFor(() => { - expect(mockApi.updateMember).toHaveBeenCalledWith( - expect.objectContaining({ - guid: member.member.guid, - }), - expect.any(Object), - true, - ) - }) - }) }) describe('Error in Update', () => { From 68a153e6c0b9a1ff69067884c5906ba1f77f642a Mon Sep 17 00:00:00 2001 From: Ashley Wright Date: Wed, 1 Jul 2026 11:19:21 -0600 Subject: [PATCH 4/4] fixed PR comments --- .../credentials/CreateMemberForm-test.tsx | 31 +++++-------------- .../credentials/UpdateMemberForm-test.tsx | 21 +++---------- 2 files changed, 11 insertions(+), 41 deletions(-) diff --git a/src/views/credentials/CreateMemberForm-test.tsx b/src/views/credentials/CreateMemberForm-test.tsx index fff353a7f4..c3cb85247a 100644 --- a/src/views/credentials/CreateMemberForm-test.tsx +++ b/src/views/credentials/CreateMemberForm-test.tsx @@ -78,7 +78,7 @@ describe('', () => { }) describe('Loading State', () => { - it('displays loading spinner while fetching credentials', () => { + it('does not render the credentials form while fetching credentials', () => { renderCredentialsStep({ apiOverrides: { getInstitutionCredentials: vi.fn().mockImplementation(() => new Promise(() => {})), @@ -87,14 +87,6 @@ describe('', () => { expect(screen.queryByText('Continue')).not.toBeInTheDocument() }) - - it('calls getInstitutionCredentials on mount', () => { - const { mockApi } = renderCredentialsStep() - - expect(mockApi.getInstitutionCredentials).toHaveBeenCalledWith( - institutionData.institution.guid, - ) - }) }) describe('Credentials Display', () => { @@ -155,9 +147,6 @@ describe('', () => { it('calls the consumer onUpsertMember callback when a member is created', async () => { const onUpsertMember = vi.fn() - // Render the real widget from the very top so we exercise the same - // onUpsertMember wiring a consumer relies on (ConnectWidget -> Connect -> - // RenderConnectStep -> CreateMemberForm). const { user } = render( ', () => { await user.type(await screen.findByLabelText('Password *'), 'testpass') await user.click(screen.getByText('Continue')) - await waitFor( - () => { - expect(onUpsertMember).toHaveBeenCalledWith(member.member) - }, - { timeout: 1000 }, - ) + await waitFor(() => { + expect(onUpsertMember).toHaveBeenCalledWith(member.member) + }) }) }) @@ -249,12 +235,9 @@ describe('', () => { await user.type(await screen.findByLabelText('Password *'), 'testpass') await user.click(screen.getByText('Continue')) - await waitFor( - () => { - expect(onUpsertMember).toHaveBeenCalledWith(updatedMember) - }, - { timeout: 1000 }, - ) + await waitFor(() => { + expect(onUpsertMember).toHaveBeenCalledWith(updatedMember) + }) expect(mockApi.updateMember).toHaveBeenCalled() }) }) diff --git a/src/views/credentials/UpdateMemberForm-test.tsx b/src/views/credentials/UpdateMemberForm-test.tsx index 542bab5464..959e63bd79 100644 --- a/src/views/credentials/UpdateMemberForm-test.tsx +++ b/src/views/credentials/UpdateMemberForm-test.tsx @@ -75,7 +75,7 @@ describe('', () => { }) describe('Loading State', () => { - it('displays loading spinner while fetching credentials', () => { + it('does not render the credentials form while fetching credentials', () => { renderUpdateStep({ apiOverrides: { getMemberCredentials: vi.fn().mockImplementation(() => new Promise(() => {})), @@ -84,12 +84,6 @@ describe('', () => { expect(screen.queryByText('Continue')).not.toBeInTheDocument() }) - - it('calls getMemberCredentials on mount', () => { - const { mockApi } = renderUpdateStep() - - expect(mockApi.getMemberCredentials).toHaveBeenCalledWith(member.member.guid) - }) }) describe('Credentials Display', () => { @@ -150,10 +144,6 @@ describe('', () => { it('calls the consumer onUpsertMember callback when a member is updated', async () => { const onUpsertMember = vi.fn() - // Render the real widget from the very top so we exercise the same - // onUpsertMember wiring a consumer relies on (ConnectWidget -> Connect -> - // RenderConnectStep -> UpdateMemberForm). update_credentials + a configured - // member lands the load flow on the update-credentials form. const { user } = render( ', () => { await user.type(await screen.findByLabelText('Password *'), 'newpass') await user.click(screen.getByText('Continue')) - await waitFor( - () => { - expect(onUpsertMember).toHaveBeenCalledWith(member.member) - }, - { timeout: 1000 }, - ) + await waitFor(() => { + expect(onUpsertMember).toHaveBeenCalledWith(member.member) + }) }) })