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
19 changes: 11 additions & 8 deletions src/components/InstitutionTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ export const InstitutionTile = (props) => {
const tokens = useTokens()
const styles = getStyles(tokens)

let statusChip = null
if (institution.is_disabled_by_client) {
statusChip = <Chip color="secondary" label={__('DISABLED')} size="small" sx={styles.chip} />
} else if (
status === InstitutionStatus.UNAVAILABLE ||
status === InstitutionStatus.UNAVAILABLE_PER_MX
) {
statusChip = <Chip color="error" label={__('UNAVAILABLE')} size="small" sx={styles.chip} />
}

return (
<Button
aria-label={__('Add account with %1', institution.name)}
Expand Down Expand Up @@ -73,12 +83,7 @@ export const InstitutionTile = (props) => {
<div style={styles.name}>{institution.name}</div>
<div style={styles.url}>{formatUrl(institution.url)}</div>
</div>
{institution.is_disabled_by_client && (
<Chip color="secondary" label={__('DISABLED')} size="small" sx={styles.chip} />
)}
{!institution.is_disabled_by_client && status === InstitutionStatus.UNAVAILABLE && (
<Chip color="secondary" label={__('UNAVAILABLE')} size="small" sx={styles.chip} />
)}
{statusChip}
</Button>
)
}
Expand Down Expand Up @@ -138,8 +143,6 @@ const getStyles = (tokens) => {
},
chip: {
padding: `${tokens.Spacing.XTiny}px 0`,
background: '#ECECEC',
color: '#494949',
height: tokens.Spacing.Medium,
fontSize: '9px',
},
Expand Down
53 changes: 36 additions & 17 deletions src/components/__tests__/InstitutionTile-test.jsx
Original file line number Diff line number Diff line change
@@ -1,65 +1,84 @@
import React from 'react'
import { render, screen } from 'src/utilities/testingLibrary'
import { act } from 'react'
import { InstitutionTile } from '../InstitutionTile'
import { InstitutionStatusField } from 'src/utilities/institutionStatus'

describe('<InstitutionTile />', () => {
it('renders the logoUrl in the src if there is one', async () => {
it('renders the logoUrl in the src if there is one', () => {
const institution = {
name: 'testName',
logo_url: 'testLogoUrl',
}

await act(async () => {
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
})
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)

expect(screen.getByAltText(`${institution.name} logo`)).toHaveAttribute(
'src',
institution.logo_url,
)
})

it('renders a generated url with the guid if there is no logoUrl', async () => {
it('renders a generated url with the guid if there is no logoUrl', () => {
const institution = {
guid: 'testGuid',
name: 'testName',
}

await act(async () => {
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
})
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)

expect(screen.getByAltText(`${institution.name} logo`).src.includes(institution.guid)).toBe(
true,
)
})

it('renders a disabled Chip if the institution is disabled', async () => {
it('renders a disabled Chip if the institution is disabled', () => {
const institution = {
guid: 'testGuid',
name: 'testName',
is_disabled_by_client: true,
}

await act(async () => {
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
})
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)

expect(screen.getByText('DISABLED')).toBeInTheDocument()
})

it('does not render a disabled Chip if the institution is not disabled', async () => {
it('does not render a disabled Chip if the institution is not disabled', () => {
const institution = {
guid: 'testGuid',
name: 'testName',
is_disabled_by_client: false,
}

await act(async () => {
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
})
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)

expect(screen.queryByText('DISABLED')).not.toBeInTheDocument()
})

it('renders an UNAVAILABLE Chip if the institution is unavailable by experiment values', () => {
const institution = { guid: 'testGuid', name: 'testName' }
const preloadedState = {
experimentalFeatures: {
unavailableInstitutions: [institution],
},
}

render(<InstitutionTile institution={institution} selectInstitution={() => {}} />, {
preloadedState,
})

expect(screen.getByText('UNAVAILABLE')).toBeInTheDocument()
})

it('renders an UNAVAILABLE Chip if the institution is unavailable by API', () => {
const institution = {
guid: 'testGuid',
name: 'testName',
status: InstitutionStatusField.UNAVAILABLE,
}

render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)

expect(screen.getByText('UNAVAILABLE')).toBeInTheDocument()
})
})
23 changes: 20 additions & 3 deletions src/const/language/es.po
Original file line number Diff line number Diff line change
Expand Up @@ -1881,7 +1881,6 @@ msgstr ""
msgid "Log in again"
msgstr "Inicie sesión nuevamente"

#: src/views/institutionStatusDetails/InstitutionStatusDetails.tsx
#: src/views/actionableError/useActionableErrorMap.tsx
msgid "Connect a different institution"
msgstr "Conecte una institución diferente"
Expand Down Expand Up @@ -2071,7 +2070,25 @@ msgid "Demo mode active"
msgstr "Modo de demostración activo"

#: src/views/demoConnectGuard/DemoConnectGuard.tsx
msgid "Live institutions are not available in the demo environment. Please select "
msgid ""
"Live institutions are not available in the demo environment. Please select "
"*MX Bank* to test the connection process."
msgstr "Las instituciones en vivo no están disponibles en el entorno de "
msgstr ""
"Las instituciones en vivo no están disponibles en el entorno de "
"demostración. Seleccione *MX Bank* para probar el proceso de conexión."

#: src/utilities/institutionStatus.ts
msgid "Connection unavailable"
msgstr "Conexión no disponible"

#: src/utilities/institutionStatus.ts
msgid ""
"This institution is experiencing issues that prevent successful "
"connections. It's unclear when this will be resolved."
msgstr ""
"Esta institución está experimentando problemas que impiden establecer "
"conexiones exitosas. No está claro cuándo se resolverá esta situación."

#: src/views/institutionStatusDetails/InstitutionStatusDetails.tsx
msgid "Back"
msgstr "Retroceder"
23 changes: 20 additions & 3 deletions src/const/language/frCa.po
Original file line number Diff line number Diff line change
Expand Up @@ -1957,7 +1957,6 @@ msgstr ""
msgid "Log in again"
msgstr "Connectez-vous à nouveau"

#: src/views/institutionStatusDetails/InstitutionStatusDetails.tsx
#: src/views/actionableError/useActionableErrorMap.tsx
msgid "Connect a different institution"
msgstr "Mettre en relation un autre établissement"
Expand Down Expand Up @@ -2149,8 +2148,26 @@ msgid "Demo mode active"
msgstr "Mode démo actif"

#: src/views/demoConnectGuard/DemoConnectGuard.tsx
msgid "Live institutions are not available in the demo environment. Please select "
msgid ""
"Live institutions are not available in the demo environment. Please select "
"*MX Bank* to test the connection process."
msgstr "Les établissements réels ne sont pas disponibles dans l'environnement de "
msgstr ""
"Les établissements réels ne sont pas disponibles dans l'environnement de "
"démonstration. Veuillez sélectionner *MX Bank* pour tester la procédure de "
"connexion."

#: src/utilities/institutionStatus.ts
msgid "Connection unavailable"
msgstr "Connexion indisponible"

#: src/utilities/institutionStatus.ts
msgid ""
"This institution is experiencing issues that prevent successful "
"connections. It's unclear when this will be resolved."
msgstr ""
"Cet établissement rencontre des problèmes qui empêchent d'établir des "
"connexions. Il est difficile de déterminer quand la situation sera résolue."

#: src/views/institutionStatusDetails/InstitutionStatusDetails.tsx
msgid "Back"
msgstr "Reculer"
28 changes: 27 additions & 1 deletion src/hooks/__tests__/useLoadConnect-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { apiValue } from 'src/const/apiProviderMock'
import { ConfigError } from 'src/components/ConfigError'
import { COMBO_JOB_DATA_TYPES } from 'src/const/comboJobDataTypes'
import { loadExperimentalFeatures } from 'src/redux/reducers/experimentalFeaturesSlice'
import { InstitutionStatusField } from 'src/utilities/institutionStatus'

const TestLoadConnectComponent: React.FC<{
clientConfig: ClientConfigType
Expand Down Expand Up @@ -309,7 +310,7 @@ describe('useLoadConnect', () => {
).toBeInTheDocument()
})

it('will return the INSTITUTION_STATUS_DETAILS step if the state contains a configured unavailable institution', async () => {
it('will return the INSTITUTION_STATUS_DETAILS step if the state contains a configured unavailable institution, via the experimental props', async () => {
const mockApi = {
...apiValue,
loadInstitutionByGuid: vi.fn().mockResolvedValue(
Expand Down Expand Up @@ -337,4 +338,29 @@ describe('useLoadConnect', () => {
)
expect(await screen.findByText(/Institution status details/i)).toBeInTheDocument()
})

it('will return the INSTITUTION_STATUS_DETAILS step if the state contains a configured unavailable institution, via the api', async () => {
const mockApi = {
...apiValue,
loadInstitutionByGuid: vi.fn().mockResolvedValue(
Promise.resolve({
...institutionData.institution,
guid: 'INS-unavailable',
name: 'Unavailable Bank',
status: InstitutionStatusField.UNAVAILABLE, // This status triggers the UNAVAILABLE_PER_MX status
}),
),
}
render(
<ApiProvider apiValue={mockApi}>
<TestLoadConnectComponent
clientConfig={{
...initialState.config,
current_institution_guid: 'INS-unavailable',
}}
/>
</ApiProvider>,
)
expect(await screen.findByText(/Institution status details/i)).toBeInTheDocument()
})
})
13 changes: 6 additions & 7 deletions src/redux/reducers/Connect.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
institutionIsBlockedForCostReasons,
memberIsBlockedForCostReasons,
} from 'src/utilities/institutionBlocks'
import { InstitutionStatus } from 'src/utilities/institutionStatus'
import { getInstitutionStatus, InstitutionStatus } from 'src/utilities/institutionStatus'

export const defaultState = {
error: null, // The most recent job request error, if any
Expand Down Expand Up @@ -290,7 +290,8 @@ const selectInstitutionSuccess = (state, action) => {
if (
action.payload.institution &&
(institutionIsBlockedForCostReasons(action.payload.institution) ||
action.payload.institutionStatus === InstitutionStatus.UNAVAILABLE)
action.payload.institutionStatus === InstitutionStatus.UNAVAILABLE ||
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's probably worth using the same pattern you used on line 550 with the includes here

action.payload.institutionStatus === InstitutionStatus.UNAVAILABLE_PER_MX)
) {
nextStep = STEPS.INSTITUTION_STATUS_DETAILS
} else if (action.payload.user?.is_demo && !action.payload.institution?.is_demo) {
Expand Down Expand Up @@ -544,11 +545,9 @@ function getStartingStep(
// Unavailable institutions experimental feature: Make sure we don't load a user
// directly to an institution that should be unavailable.
const unavailableInstitutions = experimentalFeatures?.unavailableInstitutions || []
const institutionIsAvailable =
institution &&
unavailableInstitutions.find(
(ins) => ins.guid === institution?.guid || ins.name === institution?.name,
) === undefined
const institutionStatus = getInstitutionStatus(institution, unavailableInstitutions)
const unavailableStatuses = [InstitutionStatus.UNAVAILABLE, InstitutionStatus.UNAVAILABLE_PER_MX]
const institutionIsAvailable = institution && !unavailableStatuses.includes(institutionStatus)

const shouldStepToMFA =
member && config.update_credentials && member.connection_status === ReadableStatuses.CHALLENGED
Expand Down
49 changes: 49 additions & 0 deletions src/utilities/__tests__/institutionStatus-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
useInstitutionStatusMessage,
useInstitutionStatus,
getInstitutionStatus,
InstitutionStatusField,
} from '../institutionStatus'
import * as institutionBlocks from '../institutionBlocks'
import { Provider } from 'react-redux'
Expand Down Expand Up @@ -93,6 +94,20 @@ describe('institutionStatus', () => {
const result = getInstitutionStatus(institution, unavailableInstitutions)
expect(result).toBe(InstitutionStatus.OPERATIONAL)
})

// API response for institution.status
it('returns UNAVAILABLE_PER_MX when institution.status is set to UNAVAILABLE', () => {
const institution = {
guid: 'test-guid',
name: 'Test Bank',
status: InstitutionStatusField.UNAVAILABLE,
}
const unavailableInstitutions: { guid: string; name: string }[] = []
vi.mocked(institutionBlocks.institutionIsBlockedForCostReasons).mockReturnValue(false)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to make this fail in a real way than mock it


const result = getInstitutionStatus(institution, unavailableInstitutions)
expect(result).toBe(InstitutionStatus.UNAVAILABLE_PER_MX)
})
})

describe('useInstitutionStatus', () => {
Expand All @@ -108,6 +123,21 @@ describe('institutionStatus', () => {
expect(result.current).toBe(InstitutionStatus.UNAVAILABLE)
})

it('returns UNAVAILABLE_PER_MX when institution.status is set to UNAVAILABLE in API response', () => {
const institution = {
guid: 'test-guid',
name: 'Test Bank',
status: InstitutionStatusField.UNAVAILABLE,
}
const store = createMockStore([])

const { result } = renderHook(() => useInstitutionStatus(institution), {
wrapper: ({ children }) => wrapper({ children, store }),
})

expect(result.current).toBe(InstitutionStatus.UNAVAILABLE_PER_MX)
})

it('handles null institution', () => {
const store = createMockStore([])

Expand Down Expand Up @@ -173,6 +203,25 @@ describe('institutionStatus', () => {
})
})

it('returns a unique unavailable message when institution.status is set to UNAVAILABLE in API response', () => {
const institution = {
guid: 'test-guid',
name: 'Test Bank',
status: InstitutionStatusField.UNAVAILABLE,
}
const store = createMockStore([])
vi.mocked(institutionBlocks.institutionIsBlockedForCostReasons).mockReturnValue(false)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here


const { result } = renderHook(() => useInstitutionStatusMessage(institution), {
wrapper: ({ children }) => wrapper({ children, store }),
})

expect(result.current).toEqual({
title: 'Connection unavailable',
body: "This institution is experiencing issues that prevent successful connections. It's unclear when this will be resolved.",
})
})

it('returns empty message for OPERATIONAL status', () => {
const institution = { guid: 'test-guid', name: 'Test Bank' }
const store = createMockStore([])
Expand Down
Loading
Loading