Skip to content

Commit d7741aa

Browse files
codingLoganCopilot
andauthored
feat(unavailable): handle unavailable status institutions (#313)
* test(red): add tests for when institution.status is unavailable * test(green): Implement all code to satisfy the tests that were written * fix(load): go to the ins status details page when mx has set the status to unavailable Co-authored-by: Copilot <copilot@github.com> * fix(institution): add fields to the types Co-authored-by: Copilot <copilot@github.com> * fix(institution): make the unavailable tag red by using the colors that mxui/mui provide * fix(i18n): add translations * test(tile): remove the usage of act in tests Co-authored-by: Copilot <copilot@github.com> * refactor(unavailable): use the includes strategy for consistency Co-authored-by: Copilot <copilot@github.com> * refactor(test): remove mocks where possible from the institutionStatus tests * refactor: use the unavailable status list pattern Co-authored-by: Copilot <copilot@github.com> * refactor(institution): create a reusable function for checking if an institution is unavailable Co-authored-by: Copilot <copilot@github.com> * refactor(institution): add concise naming to the status function --------- Co-authored-by: Copilot <copilot@github.com>
1 parent 4385b0d commit d7741aa

14 files changed

Lines changed: 318 additions & 74 deletions

File tree

src/components/InstitutionTile.js

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import { ChevronRight } from '@kyper/icon/ChevronRight'
1010
import { InstitutionLogo } from '@mxenabled/mxui'
1111

1212
import { formatUrl } from 'src/utilities/FormatUrl'
13-
import { InstitutionStatus, useInstitutionStatus } from 'src/utilities/institutionStatus'
13+
import {
14+
institutionStatusIsUnavailable,
15+
useInstitutionStatus,
16+
} from 'src/utilities/institutionStatus'
1417

1518
export const InstitutionTile = (props) => {
1619
const { institution, selectInstitution, size } = props
@@ -19,6 +22,13 @@ export const InstitutionTile = (props) => {
1922
const tokens = useTokens()
2023
const styles = getStyles(tokens)
2124

25+
let statusChip = null
26+
if (institution.is_disabled_by_client) {
27+
statusChip = <Chip color="secondary" label={__('DISABLED')} size="small" sx={styles.chip} />
28+
} else if (institutionStatusIsUnavailable(status)) {
29+
statusChip = <Chip color="error" label={__('UNAVAILABLE')} size="small" sx={styles.chip} />
30+
}
31+
2232
return (
2333
<Button
2434
aria-label={__('Add account with %1', institution.name)}
@@ -73,12 +83,7 @@ export const InstitutionTile = (props) => {
7383
<div style={styles.name}>{institution.name}</div>
7484
<div style={styles.url}>{formatUrl(institution.url)}</div>
7585
</div>
76-
{institution.is_disabled_by_client && (
77-
<Chip color="secondary" label={__('DISABLED')} size="small" sx={styles.chip} />
78-
)}
79-
{!institution.is_disabled_by_client && status === InstitutionStatus.UNAVAILABLE && (
80-
<Chip color="secondary" label={__('UNAVAILABLE')} size="small" sx={styles.chip} />
81-
)}
86+
{statusChip}
8287
</Button>
8388
)
8489
}
@@ -138,8 +143,6 @@ const getStyles = (tokens) => {
138143
},
139144
chip: {
140145
padding: `${tokens.Spacing.XTiny}px 0`,
141-
background: '#ECECEC',
142-
color: '#494949',
143146
height: tokens.Spacing.Medium,
144147
fontSize: '9px',
145148
},
Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,84 @@
11
import React from 'react'
22
import { render, screen } from 'src/utilities/testingLibrary'
3-
import { act } from 'react'
43
import { InstitutionTile } from '../InstitutionTile'
4+
import { InstitutionStatusField } from 'src/utilities/institutionStatus'
55

66
describe('<InstitutionTile />', () => {
7-
it('renders the logoUrl in the src if there is one', async () => {
7+
it('renders the logoUrl in the src if there is one', () => {
88
const institution = {
99
name: 'testName',
1010
logo_url: 'testLogoUrl',
1111
}
1212

13-
await act(async () => {
14-
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
15-
})
13+
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
1614

1715
expect(screen.getByAltText(`${institution.name} logo`)).toHaveAttribute(
1816
'src',
1917
institution.logo_url,
2018
)
2119
})
2220

23-
it('renders a generated url with the guid if there is no logoUrl', async () => {
21+
it('renders a generated url with the guid if there is no logoUrl', () => {
2422
const institution = {
2523
guid: 'testGuid',
2624
name: 'testName',
2725
}
2826

29-
await act(async () => {
30-
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
31-
})
27+
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
3228

3329
expect(screen.getByAltText(`${institution.name} logo`).src.includes(institution.guid)).toBe(
3430
true,
3531
)
3632
})
3733

38-
it('renders a disabled Chip if the institution is disabled', async () => {
34+
it('renders a disabled Chip if the institution is disabled', () => {
3935
const institution = {
4036
guid: 'testGuid',
4137
name: 'testName',
4238
is_disabled_by_client: true,
4339
}
4440

45-
await act(async () => {
46-
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
47-
})
41+
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
4842

4943
expect(screen.getByText('DISABLED')).toBeInTheDocument()
5044
})
5145

52-
it('does not render a disabled Chip if the institution is not disabled', async () => {
46+
it('does not render a disabled Chip if the institution is not disabled', () => {
5347
const institution = {
5448
guid: 'testGuid',
5549
name: 'testName',
5650
is_disabled_by_client: false,
5751
}
5852

59-
await act(async () => {
60-
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
61-
})
53+
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
6254

6355
expect(screen.queryByText('DISABLED')).not.toBeInTheDocument()
6456
})
57+
58+
it('renders an UNAVAILABLE Chip if the institution is unavailable by experiment values', () => {
59+
const institution = { guid: 'testGuid', name: 'testName' }
60+
const preloadedState = {
61+
experimentalFeatures: {
62+
unavailableInstitutions: [institution],
63+
},
64+
}
65+
66+
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />, {
67+
preloadedState,
68+
})
69+
70+
expect(screen.getByText('UNAVAILABLE')).toBeInTheDocument()
71+
})
72+
73+
it('renders an UNAVAILABLE Chip if the institution is unavailable by API', () => {
74+
const institution = {
75+
guid: 'testGuid',
76+
name: 'testName',
77+
status: InstitutionStatusField.UNAVAILABLE,
78+
}
79+
80+
render(<InstitutionTile institution={institution} selectInstitution={() => {}} />)
81+
82+
expect(screen.getByText('UNAVAILABLE')).toBeInTheDocument()
83+
})
6584
})

src/const/language/es.po

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,7 +1881,6 @@ msgstr ""
18811881
msgid "Log in again"
18821882
msgstr "Inicie sesión nuevamente"
18831883

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

20732072
#: src/views/demoConnectGuard/DemoConnectGuard.tsx
2074-
msgid "Live institutions are not available in the demo environment. Please select "
2073+
msgid ""
2074+
"Live institutions are not available in the demo environment. Please select "
20752075
"*MX Bank* to test the connection process."
2076-
msgstr "Las instituciones en vivo no están disponibles en el entorno de "
2076+
msgstr ""
2077+
"Las instituciones en vivo no están disponibles en el entorno de "
20772078
"demostración. Seleccione *MX Bank* para probar el proceso de conexión."
2079+
2080+
#: src/utilities/institutionStatus.ts
2081+
msgid "Connection unavailable"
2082+
msgstr "Conexión no disponible"
2083+
2084+
#: src/utilities/institutionStatus.ts
2085+
msgid ""
2086+
"This institution is experiencing issues that prevent successful "
2087+
"connections. It's unclear when this will be resolved."
2088+
msgstr ""
2089+
"Esta institución está experimentando problemas que impiden establecer "
2090+
"conexiones exitosas. No está claro cuándo se resolverá esta situación."
2091+
2092+
#: src/views/institutionStatusDetails/InstitutionStatusDetails.tsx
2093+
msgid "Back"
2094+
msgstr "Retroceder"

src/const/language/frCa.po

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1957,7 +1957,6 @@ msgstr ""
19571957
msgid "Log in again"
19581958
msgstr "Connectez-vous à nouveau"
19591959

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

21512150
#: src/views/demoConnectGuard/DemoConnectGuard.tsx
2152-
msgid "Live institutions are not available in the demo environment. Please select "
2151+
msgid ""
2152+
"Live institutions are not available in the demo environment. Please select "
21532153
"*MX Bank* to test the connection process."
2154-
msgstr "Les établissements réels ne sont pas disponibles dans l'environnement de "
2154+
msgstr ""
2155+
"Les établissements réels ne sont pas disponibles dans l'environnement de "
21552156
"démonstration. Veuillez sélectionner *MX Bank* pour tester la procédure de "
21562157
"connexion."
2158+
2159+
#: src/utilities/institutionStatus.ts
2160+
msgid "Connection unavailable"
2161+
msgstr "Connexion indisponible"
2162+
2163+
#: src/utilities/institutionStatus.ts
2164+
msgid ""
2165+
"This institution is experiencing issues that prevent successful "
2166+
"connections. It's unclear when this will be resolved."
2167+
msgstr ""
2168+
"Cet établissement rencontre des problèmes qui empêchent d'établir des "
2169+
"connexions. Il est difficile de déterminer quand la situation sera résolue."
2170+
2171+
#: src/views/institutionStatusDetails/InstitutionStatusDetails.tsx
2172+
msgid "Back"
2173+
msgstr "Reculer"

src/hooks/__tests__/useLoadConnect-test.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { apiValue } from 'src/const/apiProviderMock'
1010
import { ConfigError } from 'src/components/ConfigError'
1111
import { COMBO_JOB_DATA_TYPES } from 'src/const/comboJobDataTypes'
1212
import { loadExperimentalFeatures } from 'src/redux/reducers/experimentalFeaturesSlice'
13+
import { InstitutionStatusField } from 'src/utilities/institutionStatus'
1314

1415
const TestLoadConnectComponent: React.FC<{
1516
clientConfig: ClientConfigType
@@ -309,7 +310,7 @@ describe('useLoadConnect', () => {
309310
).toBeInTheDocument()
310311
})
311312

312-
it('will return the INSTITUTION_STATUS_DETAILS step if the state contains a configured unavailable institution', async () => {
313+
it('will return the INSTITUTION_STATUS_DETAILS step if the state contains a configured unavailable institution, via the experimental props', async () => {
313314
const mockApi = {
314315
...apiValue,
315316
loadInstitutionByGuid: vi.fn().mockResolvedValue(
@@ -337,4 +338,29 @@ describe('useLoadConnect', () => {
337338
)
338339
expect(await screen.findByText(/Institution status details/i)).toBeInTheDocument()
339340
})
341+
342+
it('will return the INSTITUTION_STATUS_DETAILS step if the state contains a configured unavailable institution, via the api', async () => {
343+
const mockApi = {
344+
...apiValue,
345+
loadInstitutionByGuid: vi.fn().mockResolvedValue(
346+
Promise.resolve({
347+
...institutionData.institution,
348+
guid: 'INS-unavailable',
349+
name: 'Unavailable Bank',
350+
status: InstitutionStatusField.UNAVAILABLE, // This status triggers the UNAVAILABLE_PER_MX status
351+
}),
352+
),
353+
}
354+
render(
355+
<ApiProvider apiValue={mockApi}>
356+
<TestLoadConnectComponent
357+
clientConfig={{
358+
...initialState.config,
359+
current_institution_guid: 'INS-unavailable',
360+
}}
361+
/>
362+
</ApiProvider>,
363+
)
364+
expect(await screen.findByText(/Institution status details/i)).toBeInTheDocument()
365+
})
340366
})

src/redux/reducers/Connect.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import {
2020
institutionIsBlockedForCostReasons,
2121
memberIsBlockedForCostReasons,
2222
} from 'src/utilities/institutionBlocks'
23-
import { InstitutionStatus } from 'src/utilities/institutionStatus'
23+
import {
24+
getInstitutionStatus,
25+
institutionStatusIsUnavailable,
26+
} from 'src/utilities/institutionStatus'
2427

2528
export const defaultState = {
2629
error: null, // The most recent job request error, if any
@@ -290,7 +293,7 @@ const selectInstitutionSuccess = (state, action) => {
290293
if (
291294
action.payload.institution &&
292295
(institutionIsBlockedForCostReasons(action.payload.institution) ||
293-
action.payload.institutionStatus === InstitutionStatus.UNAVAILABLE)
296+
institutionStatusIsUnavailable(action.payload.institutionStatus))
294297
) {
295298
nextStep = STEPS.INSTITUTION_STATUS_DETAILS
296299
} else if (action.payload.user?.is_demo && !action.payload.institution?.is_demo) {
@@ -544,11 +547,8 @@ function getStartingStep(
544547
// Unavailable institutions experimental feature: Make sure we don't load a user
545548
// directly to an institution that should be unavailable.
546549
const unavailableInstitutions = experimentalFeatures?.unavailableInstitutions || []
547-
const institutionIsAvailable =
548-
institution &&
549-
unavailableInstitutions.find(
550-
(ins) => ins.guid === institution?.guid || ins.name === institution?.name,
551-
) === undefined
550+
const institutionStatus = getInstitutionStatus(institution, unavailableInstitutions)
551+
const institutionIsAvailable = institution && !institutionStatusIsUnavailable(institutionStatus)
552552

553553
const shouldStepToMFA =
554554
member && config.update_credentials && member.connection_status === ReadableStatuses.CHALLENGED

0 commit comments

Comments
 (0)