Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 113 additions & 1 deletion src/Connect-test.tsx → src/Connect.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import React from 'react'
import { beforeEach, describe, it, expect, vi, afterEach } from 'vitest'
import { render, screen, waitFor } from 'src/utilities/testingLibrary'
import { Connect } from './Connect'
import { initialState, masterData } from 'src/services/mockedData'
import { apiValue as apiValueMock } from 'src/const/apiProviderMock'
import { initialState, masterData, institutionData } from 'src/services/mockedData'
import { STEPS } from 'src/const/Connect'
import { createRenderConnectStepInitialState } from 'src/utilities/test/createRenderConnectStepInitialState'

describe('<Connect />', () => {
const mockPostMessage = vi.fn()
Expand Down Expand Up @@ -261,4 +263,114 @@ describe('<Connect />', () => {
})
})
})

describe('Connect - Demo Connect Guard', () => {
const defaultProps = {
clientConfig: { current_institution_guid: 'INS-123' } as ClientConfigType,
onShowConnectSuccessSurvey: () => undefined,
onSubmitConnectSuccessSurvey: () => {},
profiles: { ...masterData, loading: false },
}

const nonDemoInstitution = { ...institutionData.institution, is_demo: false }
const demoInstitution = { ...institutionData.institution, is_demo: true }
const demoUser = { ...masterData.user, is_demo: true }
const regularUser = { ...masterData.user, is_demo: false }

it('blocks demo user from accessing non-demo institution', async () => {
const mockApiValue = {
...apiValueMock,
loadInstitutionByGuid: vi.fn().mockResolvedValue(nonDemoInstitution),
loadMembers: vi.fn().mockResolvedValue([]),
}

render(
<Connect {...defaultProps} profiles={{ ...masterData, user: demoUser, loading: false }} />,
{ apiValue: mockApiValue },
)

expect(await screen.findByText(/Demo mode active/i)).toBeInTheDocument()
})

it('allows demo user to access demo institution', async () => {
const mockApiValue = {
...apiValueMock,
loadInstitutionByGuid: vi.fn().mockResolvedValue(demoInstitution),
loadMembers: vi.fn().mockResolvedValue([]),
}

render(
<Connect {...defaultProps} profiles={{ ...masterData, user: demoUser, loading: false }} />,
{ apiValue: mockApiValue },
)

expect(await screen.findByText(/Log in at Test Bank/i)).toBeInTheDocument()
expect(screen.queryByText(/Demo mode active/i)).not.toBeInTheDocument()
})

it('allows regular user to access non-demo institution', async () => {
const mockApiValue = {
...apiValueMock,
loadInstitutionByGuid: vi.fn().mockResolvedValue(nonDemoInstitution),
loadMembers: vi.fn().mockResolvedValue([]),
}

render(
<Connect
{...defaultProps}
profiles={{ ...masterData, user: regularUser, loading: false }}
/>,
{ apiValue: mockApiValue },
)

expect(await screen.findByText(/Log in at Test Bank/i)).toBeInTheDocument()
expect(screen.queryByText(/Demo mode active/i)).not.toBeInTheDocument()
})

describe('back button', () => {
const props = {
...defaultProps,
clientConfig: {} as ClientConfigType,
profiles: { ...masterData, user: demoUser, loading: false },
}
const guardState = (steps: string[]) => {
const base = createRenderConnectStepInitialState(
steps[steps.length - 1],
nonDemoInstitution,
)
return {
...base,
connect: {
...base.connect,
isComponentLoading: false,
location: steps.map((s) => ({ step: s })),
},
}
}

it('is hidden when guard fires as the first screen', async () => {
render(<Connect {...props} />, { preloadedState: guardState([STEPS.ENTER_CREDENTIALS]) })
await waitFor(() => expect(screen.getByText(/Demo mode active/i)).toBeInTheDocument())
expect(screen.queryByTestId('back-button')).not.toBeInTheDocument()
})

it('navigates back to search when the previous step is search', async () => {
const { user } = render(<Connect {...props} />, {
preloadedState: guardState([STEPS.SEARCH, STEPS.ENTER_CREDENTIALS]),
})
await waitFor(() => expect(screen.getByTestId('back-button')).toBeInTheDocument())
await user.click(screen.getByTestId('back-button'))
expect(screen.queryByText('Select your institution')).toBeInTheDocument()
})

it('pops one step back rather than resetting to search', async () => {
const { user } = render(<Connect {...props} />, {
preloadedState: guardState([STEPS.SEARCH, STEPS.CONSENT, STEPS.ENTER_CREDENTIALS]),
})
await waitFor(() => expect(screen.getByTestId('back-button')).toBeInTheDocument())
await user.click(screen.getByTestId('back-button'))
await waitFor(() => expect(screen.getByText(/Demo mode active/i)).toBeInTheDocument())
})
})
})
})
78 changes: 74 additions & 4 deletions src/components/RenderConnectStep-test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,15 @@ describe('RenderConnectStep', () => {
})

describe('Step Rendering', () => {
it('should render DemoConnectGuard when step is DEMO_CONNECT_GUARD', () => {
const state = createRenderConnectStepInitialState(STEPS.DEMO_CONNECT_GUARD, mockInstitution)
it('should render DemoConnectGuard when user is demo and institution is not demo', () => {
const nonDemoInstitution = { ...mockInstitution, is_demo: false }
const state = {
...createRenderConnectStepInitialState(STEPS.ENTER_CREDENTIALS, nonDemoInstitution),
profiles: {
...initialState.profiles,
user: { ...initialState.profiles.user, is_demo: true },
},
}

const { container } = render(<RenderConnectStep {...defaultProps} />, {
preloadedState: state,
Expand All @@ -56,10 +63,73 @@ describe('RenderConnectStep', () => {
const errorIcon = container.querySelector('svg.MuiSvgIcon-colorError')
expect(errorIcon).toBeInTheDocument()

const button = screen.getByRole('button', { name: /return to institution selection/i })
const button = screen.getByRole('button', { name: /go back/i })
expect(button).toBeInTheDocument()
})

it('should render DemoConnectGuard on ACTIONABLE_ERROR when user is demo and institution is not demo', () => {
const nonDemoInstitution = { ...mockInstitution, is_demo: false }
const state = {
...createRenderConnectStepInitialState(STEPS.ACTIONABLE_ERROR, nonDemoInstitution),
profiles: {
...initialState.profiles,
user: { ...initialState.profiles.user, is_demo: true },
},
}

render(<RenderConnectStep {...defaultProps} />, { preloadedState: state })

expect(screen.getByText('Demo mode active')).toBeInTheDocument()
})

it('should NOT render DemoConnectGuard on INSTITUTION_STATUS_DETAILS even when user is demo and institution is not demo', () => {
const nonDemoInstitution = { ...mockInstitution, is_demo: false }
const state = {
...createRenderConnectStepInitialState(
STEPS.INSTITUTION_STATUS_DETAILS,
nonDemoInstitution,
),
profiles: {
...initialState.profiles,
user: { ...initialState.profiles.user, is_demo: true },
},
}

render(<RenderConnectStep {...defaultProps} />, { preloadedState: state })

expect(screen.queryByText('Demo mode active')).not.toBeInTheDocument()
})

it('should NOT render DemoConnectGuard when user is demo but institution is also demo', () => {
const demoInstitution = { ...mockInstitution, is_demo: true }
const state = {
...createRenderConnectStepInitialState(STEPS.ENTER_CREDENTIALS, demoInstitution),
profiles: {
...initialState.profiles,
user: { ...initialState.profiles.user, is_demo: true },
},
}

render(<RenderConnectStep {...defaultProps} />, { preloadedState: state })

expect(screen.queryByText('Demo mode active')).not.toBeInTheDocument()
})

it('should NOT render DemoConnectGuard when user is not demo', () => {
const nonDemoInstitution = { ...mockInstitution, is_demo: false }
const state = {
...createRenderConnectStepInitialState(STEPS.ENTER_CREDENTIALS, nonDemoInstitution),
profiles: {
...initialState.profiles,
user: { ...initialState.profiles.user, is_demo: false },
},
}

render(<RenderConnectStep {...defaultProps} />, { preloadedState: state })

expect(screen.queryByText('Demo mode active')).not.toBeInTheDocument()
})

it('should render Search view for SEARCH step', async () => {
const state = createRenderConnectStepInitialState(STEPS.SEARCH)

Expand Down Expand Up @@ -244,7 +314,7 @@ describe('RenderConnectStep', () => {
})

it('should not apply maxHeight for non-SEARCH steps', () => {
const state = createRenderConnectStepInitialState(STEPS.DEMO_CONNECT_GUARD, mockInstitution)
const state = createRenderConnectStepInitialState(STEPS.CONNECTING, mockInstitution)

const { container } = render(<RenderConnectStep {...defaultProps} />, {
preloadedState: state,
Expand Down
23 changes: 21 additions & 2 deletions src/components/RenderConnectStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,29 @@ const RenderConnectStep = (props) => {
const hasAtriumAPI = client.has_atrium_api

const showSupport = widgetProfile.enable_support_requests && mode === AGG_MODE
const user = useSelector((state) => state.profiles.user)
const isDeleteInstitutionOptionEnabled = widgetProfile?.display_delete_option_in_connect ?? true

const STEPS_TO_DEMO_GUARD = [
STEPS.ADDITIONAL_PRODUCT,
STEPS.CONSENT,
STEPS.ENTER_CREDENTIALS,
STEPS.CONNECTING,
STEPS.MFA,
STEPS.CONNECTED,
STEPS.ACTIONABLE_ERROR,
STEPS.OAUTH_ERROR,
]
const shouldShowDemoConnectGuard =
STEPS_TO_DEMO_GUARD.includes(step) &&
user?.is_demo &&
selectedInstitution?.guid &&
!selectedInstitution?.is_demo

if (shouldShowDemoConnectGuard) {
return <DemoConnectGuard ref={props.navigationRef} />
}

let connectStepView = null

if (step === STEPS.DISCLOSURE) {
Expand All @@ -111,8 +132,6 @@ const RenderConnectStep = (props) => {
throw new Error('invalid product offer')

connectStepView = <AdditionalProductStep ref={props.navigationRef} />
} else if (step === STEPS.DEMO_CONNECT_GUARD) {
connectStepView = <DemoConnectGuard />
} else if (step === STEPS.ADD_MANUAL_ACCOUNT) {
connectStepView = (
<ManualAccountConnect
Expand Down
1 change: 0 additions & 1 deletion src/const/Connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export const STEPS = {
CONNECTING: 'connecting',
CONSENT: 'consent',
DELETE_MEMBER_SUCCESS: 'deleteMemberSuccess',
DEMO_CONNECT_GUARD: 'demoConnectGuard',
DISCLOSURE: 'disclosure',
ENTER_CREDENTIALS: 'enterCreds',
EXISTING_MEMBER: 'existingMember',
Expand Down
3 changes: 0 additions & 3 deletions src/redux/reducers/Connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,6 @@ const selectInstitutionSuccess = (state, action) => {
institutionStatusIsUnavailable(action.payload.institutionStatus))
) {
nextStep = STEPS.INSTITUTION_STATUS_DETAILS
} else if (action.payload.user?.is_demo && !action.payload.institution?.is_demo) {
nextStep = STEPS.DEMO_CONNECT_GUARD
} else if (canOfferVerification || canOfferAggregation) {
nextStep = STEPS.ADDITIONAL_PRODUCT
} else if (action.payload.consentIsEnabled) {
Expand Down Expand Up @@ -574,7 +572,6 @@ function getStartingStep(
(institution && institutionIsBlockedForCostReasons(institution)) ||
(member && memberIsBlockedForCostReasons(member)) ||
!institutionIsAvailable

if (shouldStepToInstitutionStatusDetails) {
return STEPS.INSTITUTION_STATUS_DETAILS
} else if (shouldStepToMFA)
Expand Down
43 changes: 6 additions & 37 deletions src/redux/reducers/__tests__/Connect-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,10 +455,7 @@ describe('Connect redux store', () => {
const config = { mode: VERIFY_MODE }
const afterState = reducer(
{ ...defaultState, isComponentLoading: true },
{
type: ActionTypes.LOAD_CONNECT_SUCCESS,
payload: { config, members: [], widgetProfile },
},
loadConnectSuccess({ config, members: [], widgetProfile }),
)
expect(afterState.location[afterState.location.length - 1].step).toEqual(STEPS.SEARCH)
})
Expand All @@ -473,10 +470,7 @@ describe('Connect redux store', () => {
const members = [member]
const afterState = reducer(
{ ...defaultState, isComponentLoading: true },
{
type: ActionTypes.LOAD_CONNECT_SUCCESS,
payload: { config, member, members, widgetProfile },
},
loadConnectSuccess({ config, member, members, widgetProfile }),
)
expect(afterState.location[afterState.location.length - 1].step).toEqual(
STEPS.ACTIONABLE_ERROR,
Expand All @@ -500,10 +494,7 @@ describe('Connect redux store', () => {
const members = [member]
const afterState = reducer(
{ ...defaultState, isComponentLoading: true },
{
type: ActionTypes.LOAD_CONNECT_SUCCESS,
payload: { config, member, members, widgetProfile },
},
loadConnectSuccess({ config, member, members, widgetProfile }),
)
expect(afterState.location[afterState.location.length - 1].step).toEqual(
STEPS.ACTIONABLE_ERROR,
Expand All @@ -527,10 +518,7 @@ describe('Connect redux store', () => {
const members = [member]
const afterState = reducer(
{ ...defaultState, isComponentLoading: true },
{
type: ActionTypes.LOAD_CONNECT_SUCCESS,
payload: { config, member, members, widgetProfile },
},
loadConnectSuccess({ config, member, members, widgetProfile }),
)
expect(afterState.location[afterState.location.length - 1].step).toEqual(
STEPS.ENTER_CREDENTIALS,
Expand All @@ -554,10 +542,7 @@ describe('Connect redux store', () => {
const members = [member]
const afterState = reducer(
{ ...defaultState, isComponentLoading: true },
{
type: ActionTypes.LOAD_CONNECT_SUCCESS,
payload: { config, member, members, widgetProfile },
},
loadConnectSuccess({ config, member, members, widgetProfile }),
)
expect(afterState.location[afterState.location.length - 1].step).toEqual(STEPS.MFA)
})
Expand All @@ -578,10 +563,7 @@ describe('Connect redux store', () => {
const members = [member]
const afterState = reducer(
{ ...defaultState, isComponentLoading: true },
{
type: ActionTypes.LOAD_CONNECT_SUCCESS,
payload: { config, member, members, accounts: [], widgetProfile },
},
loadConnectSuccess({ config, member, members, accounts: [], widgetProfile }),
)
expect(afterState.location[afterState.location.length - 1].step).toEqual(
STEPS.ACTIONABLE_ERROR,
Expand Down Expand Up @@ -641,19 +623,6 @@ describe('Connect redux store', () => {
STEPS.INSTITUTION_STATUS_DETAILS,
)
})

it('should set the step to DEMO_CONNECT_GUARD when the user is a demo user but the institution is not a demo institution', () => {
const institution = { guid: 'INST-1', is_demo: false, credentials }
const user = { guid: 'USR-1', is_demo: true }
const afterState = reducer(defaultState, {
type: ActionTypes.SELECT_INSTITUTION_SUCCESS,
payload: { institution, user },
})

expect(afterState.location[afterState.location.length - 1].step).toEqual(
STEPS.DEMO_CONNECT_GUARD,
)
})
})

describe('LOAD_CONNECT', () => {
Expand Down
Loading
Loading