wallet-stack 1.0.0-alpha.135 → 1.0.0-alpha.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/identity/actions.ts +10 -0
- package/src/identity/contactMapping.test.ts +5 -0
- package/src/identity/contactMapping.ts +5 -0
- package/src/identity/reducer.test.ts +29 -0
- package/src/identity/reducer.ts +15 -0
- package/src/identity/saga.ts +2 -2
- package/src/identity/selectors.ts +2 -0
- package/src/redux/migrations.ts +7 -0
- package/src/redux/store.test.ts +2 -1
- package/src/redux/store.ts +1 -1
- package/src/send/SendSelectRecipient.test.tsx +42 -2
- package/src/send/SendSelectRecipient.tsx +10 -11
- package/src/send/useFetchRecipientVerificationStatus.test.tsx +131 -0
- package/src/send/useFetchRecipientVerificationStatus.ts +9 -1
package/package.json
CHANGED
package/src/identity/actions.ts
CHANGED
|
@@ -15,6 +15,7 @@ export enum Actions {
|
|
|
15
15
|
CANCEL_IMPORT_CONTACTS = 'IDENTITY/CANCEL_IMPORT_CONTACTS',
|
|
16
16
|
END_IMPORT_CONTACTS = 'IDENTITY/END_IMPORT_CONTACTS',
|
|
17
17
|
FETCH_ADDRESS_VERIFICATION_STATUS = 'IDENTITY/FETCH_ADDRESS_VERIFICATION_STATUS',
|
|
18
|
+
RECIPIENT_LOOKUP_RESOLVED = 'IDENTITY/RECIPIENT_LOOKUP_RESOLVED',
|
|
18
19
|
CONTACTS_SAVED = 'IDENTITY/CONTACTS_SAVED',
|
|
19
20
|
STORED_PASSWORD_REFRESHED = 'IDENTITY/STORED_PASSWORD_REFRESHED',
|
|
20
21
|
}
|
|
@@ -57,6 +58,10 @@ export interface FetchAddressVerificationAction {
|
|
|
57
58
|
address: string
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
interface RecipientLookupResolvedAction {
|
|
62
|
+
type: Actions.RECIPIENT_LOOKUP_RESOLVED
|
|
63
|
+
}
|
|
64
|
+
|
|
60
65
|
interface ContactsSavedAction {
|
|
61
66
|
type: Actions.CONTACTS_SAVED
|
|
62
67
|
hash: string
|
|
@@ -74,6 +79,7 @@ export type ActionTypes =
|
|
|
74
79
|
| EndImportContactsAction
|
|
75
80
|
| FetchAddressesAndValidateAction
|
|
76
81
|
| FetchAddressVerificationAction
|
|
82
|
+
| RecipientLookupResolvedAction
|
|
77
83
|
| ContactsSavedAction
|
|
78
84
|
| StoredPasswordRefreshedAction
|
|
79
85
|
|
|
@@ -87,6 +93,10 @@ export const fetchAddressVerification = (address: string): FetchAddressVerificat
|
|
|
87
93
|
address,
|
|
88
94
|
})
|
|
89
95
|
|
|
96
|
+
export const recipientLookupResolved = (): RecipientLookupResolvedAction => ({
|
|
97
|
+
type: Actions.RECIPIENT_LOOKUP_RESOLVED,
|
|
98
|
+
})
|
|
99
|
+
|
|
90
100
|
export const updateE164PhoneNumberAddresses = (
|
|
91
101
|
e164NumberToAddress: E164NumberToAddressType,
|
|
92
102
|
addressToE164Number: AddressToE164NumberType,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
contactsSaved,
|
|
16
16
|
fetchAddressVerification,
|
|
17
17
|
fetchAddressesAndValidate,
|
|
18
|
+
recipientLookupResolved,
|
|
18
19
|
updateE164PhoneNumberAddresses,
|
|
19
20
|
} from 'src/identity/actions'
|
|
20
21
|
import {
|
|
@@ -118,6 +119,7 @@ describe('Fetch Addresses Saga', () => {
|
|
|
118
119
|
{}
|
|
119
120
|
)
|
|
120
121
|
)
|
|
122
|
+
.put(recipientLookupResolved())
|
|
121
123
|
.run()
|
|
122
124
|
|
|
123
125
|
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
@@ -228,6 +230,7 @@ describe('Fetch Addresses Saga', () => {
|
|
|
228
230
|
[call(retrieveSignedMessage), 'some signed message'],
|
|
229
231
|
])
|
|
230
232
|
.put(showErrorOrFallback(expect.anything(), ErrorMessages.ADDRESS_LOOKUP_FAILURE))
|
|
233
|
+
.put(recipientLookupResolved())
|
|
231
234
|
.run()
|
|
232
235
|
})
|
|
233
236
|
})
|
|
@@ -249,6 +252,7 @@ describe('Fetch Address Verification Saga', () => {
|
|
|
249
252
|
[call(retrieveSignedMessage), 'some signed message'],
|
|
250
253
|
])
|
|
251
254
|
.put(updateE164PhoneNumberAddresses({}, {}, { [mockAccount.toLowerCase()]: 'minipay' }))
|
|
255
|
+
.put(recipientLookupResolved())
|
|
252
256
|
.run()
|
|
253
257
|
|
|
254
258
|
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
@@ -296,6 +300,7 @@ describe('Fetch Address Verification Saga', () => {
|
|
|
296
300
|
])
|
|
297
301
|
.not.put.actionType(Actions.UPDATE_E164_PHONE_NUMBER_ADDRESSES)
|
|
298
302
|
.put(showErrorOrFallback(expect.anything(), ErrorMessages.ADDRESS_LOOKUP_FAILURE))
|
|
303
|
+
.put(recipientLookupResolved())
|
|
299
304
|
.run()
|
|
300
305
|
expect(AppAnalytics.track).toHaveBeenCalledWith(IdentityEvents.address_lookup_error, {
|
|
301
306
|
error: 'Unable to fetch verification status for this address',
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
FetchAddressesAndValidateAction,
|
|
14
14
|
contactsSaved,
|
|
15
15
|
endImportContacts,
|
|
16
|
+
recipientLookupResolved,
|
|
16
17
|
updateE164PhoneNumberAddresses,
|
|
17
18
|
updateImportContactsProgress,
|
|
18
19
|
} from 'src/identity/actions'
|
|
@@ -212,6 +213,8 @@ export function* fetchAddressesAndValidateSaga({ e164Number }: FetchAddressesAnd
|
|
|
212
213
|
AppAnalytics.track(IdentityEvents.phone_number_lookup_error, {
|
|
213
214
|
error: error.message,
|
|
214
215
|
})
|
|
216
|
+
} finally {
|
|
217
|
+
yield* put(recipientLookupResolved())
|
|
215
218
|
}
|
|
216
219
|
}
|
|
217
220
|
|
|
@@ -239,6 +242,8 @@ export function* fetchAddressVerificationSaga({ address }: FetchAddressVerificat
|
|
|
239
242
|
)
|
|
240
243
|
yield* put(showErrorOrFallback(error, ErrorMessages.ADDRESS_LOOKUP_FAILURE))
|
|
241
244
|
AppAnalytics.track(IdentityEvents.address_lookup_error, { error: error.message })
|
|
245
|
+
} finally {
|
|
246
|
+
yield* put(recipientLookupResolved())
|
|
242
247
|
}
|
|
243
248
|
}
|
|
244
249
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Actions,
|
|
3
|
+
fetchAddressVerification,
|
|
4
|
+
fetchAddressesAndValidate,
|
|
5
|
+
recipientLookupResolved,
|
|
6
|
+
} from 'src/identity/actions'
|
|
7
|
+
import { reducer } from 'src/identity/reducer'
|
|
8
|
+
|
|
9
|
+
const initialState = reducer(undefined, { type: 'INIT' } as any)
|
|
10
|
+
|
|
11
|
+
describe('identity reducer', () => {
|
|
12
|
+
describe('recipientLookupLoading', () => {
|
|
13
|
+
it(`is set true on ${Actions.FETCH_ADDRESSES_AND_VALIDATION_STATUS}`, () => {
|
|
14
|
+
const next = reducer(initialState, fetchAddressesAndValidate('+15551234567'))
|
|
15
|
+
expect(next.recipientLookupLoading).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it(`is set true on ${Actions.FETCH_ADDRESS_VERIFICATION_STATUS}`, () => {
|
|
19
|
+
const next = reducer(initialState, fetchAddressVerification('0xabc'))
|
|
20
|
+
expect(next.recipientLookupLoading).toBe(true)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it(`is cleared on ${Actions.RECIPIENT_LOOKUP_RESOLVED}`, () => {
|
|
24
|
+
const loadingState = { ...initialState, recipientLookupLoading: true }
|
|
25
|
+
const next = reducer(loadingState, recipientLookupResolved())
|
|
26
|
+
expect(next.recipientLookupLoading).toBe(false)
|
|
27
|
+
})
|
|
28
|
+
})
|
|
29
|
+
})
|
package/src/identity/reducer.ts
CHANGED
|
@@ -49,6 +49,8 @@ interface State {
|
|
|
49
49
|
importContactsProgress: ImportContactProgress
|
|
50
50
|
// Mapping of address to the entity that verified it (e.g. "valora", "minipay")
|
|
51
51
|
addressToVerifiedBy: AddressToVerifiedByType
|
|
52
|
+
// Single boolean is safe because both lookup sagas use `takeLatest` — at most one in flight.
|
|
53
|
+
recipientLookupLoading: boolean
|
|
52
54
|
lastSavedContactsHash: string | null
|
|
53
55
|
shouldRefreshStoredPasswordHash: boolean
|
|
54
56
|
}
|
|
@@ -64,6 +66,7 @@ const initialState: State = {
|
|
|
64
66
|
total: 0,
|
|
65
67
|
},
|
|
66
68
|
addressToVerifiedBy: {},
|
|
69
|
+
recipientLookupLoading: false,
|
|
67
70
|
lastSavedContactsHash: null,
|
|
68
71
|
shouldRefreshStoredPasswordHash: false,
|
|
69
72
|
}
|
|
@@ -85,6 +88,7 @@ export const reducer = (
|
|
|
85
88
|
current: 0,
|
|
86
89
|
total: 0,
|
|
87
90
|
},
|
|
91
|
+
recipientLookupLoading: false,
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
case Actions.UPDATE_E164_PHONE_NUMBER_ADDRESSES:
|
|
@@ -139,6 +143,11 @@ export const reducer = (
|
|
|
139
143
|
addressToE164Number: state.addressToE164Number,
|
|
140
144
|
e164NumberToAddress: state.e164NumberToAddress,
|
|
141
145
|
}
|
|
146
|
+
case Actions.FETCH_ADDRESSES_AND_VALIDATION_STATUS:
|
|
147
|
+
return {
|
|
148
|
+
...state,
|
|
149
|
+
recipientLookupLoading: true,
|
|
150
|
+
}
|
|
142
151
|
case Actions.FETCH_ADDRESS_VERIFICATION_STATUS:
|
|
143
152
|
return {
|
|
144
153
|
...state,
|
|
@@ -146,6 +155,12 @@ export const reducer = (
|
|
|
146
155
|
...state.addressToVerifiedBy,
|
|
147
156
|
[action.address]: undefined,
|
|
148
157
|
},
|
|
158
|
+
recipientLookupLoading: true,
|
|
159
|
+
}
|
|
160
|
+
case Actions.RECIPIENT_LOOKUP_RESOLVED:
|
|
161
|
+
return {
|
|
162
|
+
...state,
|
|
163
|
+
recipientLookupLoading: false,
|
|
149
164
|
}
|
|
150
165
|
case Actions.CONTACTS_SAVED:
|
|
151
166
|
return {
|
package/src/identity/saga.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { Actions } from 'src/identity/actions'
|
|
8
8
|
import Logger from 'src/utils/Logger'
|
|
9
9
|
import { safely } from 'src/utils/safely'
|
|
10
|
-
import { cancelled, spawn,
|
|
10
|
+
import { cancelled, spawn, takeLatest, takeLeading } from 'typed-redux-saga'
|
|
11
11
|
|
|
12
12
|
const TAG = 'identity/saga'
|
|
13
13
|
|
|
@@ -20,7 +20,7 @@ function* watchContactMapping() {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function* watchFetchAddressVerification() {
|
|
23
|
-
yield*
|
|
23
|
+
yield* takeLatest(Actions.FETCH_ADDRESS_VERIFICATION_STATUS, safely(fetchAddressVerificationSaga))
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function* identitySaga() {
|
|
@@ -3,6 +3,8 @@ import { RootState } from 'src/redux/reducers'
|
|
|
3
3
|
export const e164NumberToAddressSelector = (state: RootState) => state.identity.e164NumberToAddress
|
|
4
4
|
export const addressToE164NumberSelector = (state: RootState) => state.identity.addressToE164Number
|
|
5
5
|
export const addressToVerifiedBySelector = (state: RootState) => state.identity.addressToVerifiedBy
|
|
6
|
+
export const recipientLookupLoadingSelector = (state: RootState) =>
|
|
7
|
+
state.identity.recipientLookupLoading
|
|
6
8
|
export const importContactsProgressSelector = (state: RootState) =>
|
|
7
9
|
state.identity.importContactsProgress
|
|
8
10
|
export const addressToDisplayNameSelector = (state: RootState) =>
|
package/src/redux/migrations.ts
CHANGED
package/src/redux/store.test.ts
CHANGED
|
@@ -143,7 +143,7 @@ describe('store state', () => {
|
|
|
143
143
|
{
|
|
144
144
|
"_persist": {
|
|
145
145
|
"rehydrated": true,
|
|
146
|
-
"version":
|
|
146
|
+
"version": 258,
|
|
147
147
|
},
|
|
148
148
|
"account": {
|
|
149
149
|
"acceptedTerms": false,
|
|
@@ -253,6 +253,7 @@ describe('store state', () => {
|
|
|
253
253
|
"total": 0,
|
|
254
254
|
},
|
|
255
255
|
"lastSavedContactsHash": null,
|
|
256
|
+
"recipientLookupLoading": false,
|
|
256
257
|
"shouldRefreshStoredPasswordHash": true,
|
|
257
258
|
},
|
|
258
259
|
"imports": {
|
package/src/redux/store.ts
CHANGED
|
@@ -30,7 +30,7 @@ const persistConfig: PersistConfig<ReducersRootState> = {
|
|
|
30
30
|
key: 'root',
|
|
31
31
|
// default is -1, increment as we make migrations
|
|
32
32
|
// See https://github.com/valora-xyz/wallet/tree/main/WALLET.md#redux-state-migration
|
|
33
|
-
version:
|
|
33
|
+
version: 258,
|
|
34
34
|
keyPrefix: `reduxStore-`, // the redux-persist default is `persist:` which doesn't work with some file systems.
|
|
35
35
|
storage: FSStorage(),
|
|
36
36
|
blacklist: ['networkInfo', 'alert', 'imports', 'keylessBackup', transactionFeedV2Api.reducerPath],
|
|
@@ -6,14 +6,19 @@ import AppAnalytics from 'src/analytics/AppAnalytics'
|
|
|
6
6
|
import { SendEvents } from 'src/analytics/Events'
|
|
7
7
|
import { SendOrigin } from 'src/analytics/types'
|
|
8
8
|
import { getAppConfig } from 'src/appConfig'
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
fetchAddressVerification,
|
|
11
|
+
fetchAddressesAndValidate,
|
|
12
|
+
recipientLookupResolved,
|
|
13
|
+
} from 'src/identity/actions'
|
|
10
14
|
import { navigate } from 'src/navigator/NavigationService'
|
|
11
15
|
import { Screens } from 'src/navigator/Screens'
|
|
12
16
|
import { RecipientType } from 'src/recipients/recipient'
|
|
13
17
|
import SendSelectRecipient from 'src/send/SendSelectRecipient'
|
|
14
18
|
import { getDynamicConfigParams } from 'src/statsig'
|
|
15
19
|
import { StatsigDynamicConfigs } from 'src/statsig/types'
|
|
16
|
-
import {
|
|
20
|
+
import { setupStore } from 'src/redux/store'
|
|
21
|
+
import { createMockStore, getMockStackScreenProps, getMockStoreData } from 'test/utils'
|
|
17
22
|
import {
|
|
18
23
|
mockAccount,
|
|
19
24
|
mockAccount2,
|
|
@@ -33,6 +38,10 @@ jest.mock('src/recipients/resolve-id')
|
|
|
33
38
|
|
|
34
39
|
jest.mock('react-native-device-info', () => ({ getFontScaleSync: () => 1 }))
|
|
35
40
|
jest.mock('src/statsig')
|
|
41
|
+
jest.mock('src/redux/sagas', () => ({
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
43
|
+
rootSaga: jest.fn(function* () {}),
|
|
44
|
+
}))
|
|
36
45
|
|
|
37
46
|
const mockScreenProps = ({
|
|
38
47
|
defaultTokenIdOverride,
|
|
@@ -197,6 +206,37 @@ describe('SendSelectRecipient', () => {
|
|
|
197
206
|
})
|
|
198
207
|
expect(getByTestId('SelectRecipient/NoResults')).toBeTruthy()
|
|
199
208
|
})
|
|
209
|
+
describe('selection spinner', () => {
|
|
210
|
+
it('shows the spinner while the lookup is in flight and hides it once the saga resolves (error path)', async () => {
|
|
211
|
+
// Reducer-backed store: tapping a recipient dispatches `fetchAddressesAndValidate`
|
|
212
|
+
// which flips `recipientLookupLoading` to true; dispatching `recipientLookupResolved`
|
|
213
|
+
// (the saga's `finally` branch) flips it back to false. We assert the row spinner
|
|
214
|
+
// tracks the actual state transition, including the no-mapping (error) end state.
|
|
215
|
+
const { store } = setupStore(getMockStoreData(storeWithPhoneVerified))
|
|
216
|
+
|
|
217
|
+
const { getByTestId, queryByTestId } = render(
|
|
218
|
+
<Provider store={store}>
|
|
219
|
+
<SendSelectRecipient {...mockScreenProps({})} />
|
|
220
|
+
</Provider>
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
await act(() => {
|
|
224
|
+
fireEvent.changeText(getByTestId('SendSelectRecipientSearchInput'), 'George Bogart')
|
|
225
|
+
})
|
|
226
|
+
await act(() => {
|
|
227
|
+
fireEvent.press(getByTestId('RecipientItem'))
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
expect(queryByTestId('RecipientItem/ActivityIndicator')).toBeTruthy()
|
|
231
|
+
|
|
232
|
+
await act(() => {
|
|
233
|
+
store.dispatch(recipientLookupResolved())
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
expect(queryByTestId('RecipientItem/ActivityIndicator')).toBeFalsy()
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
|
|
200
240
|
it('navigates to send amount when a verified phone recipient is tapped in search results', async () => {
|
|
201
241
|
const store = createMockStore({
|
|
202
242
|
...storeWithPhoneVerified,
|
|
@@ -184,8 +184,13 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
184
184
|
const { contactRecipients, recentRecipients } = useSendRecipients()
|
|
185
185
|
const { mergedRecipients, searchQuery, setSearchQuery } = useMergedSearchRecipients(onSearch)
|
|
186
186
|
|
|
187
|
-
const {
|
|
188
|
-
|
|
187
|
+
const {
|
|
188
|
+
recipientVerificationStatus,
|
|
189
|
+
recipient,
|
|
190
|
+
setSelectedRecipient,
|
|
191
|
+
unsetSelectedRecipient,
|
|
192
|
+
isSelectedRecipientLoading,
|
|
193
|
+
} = useFetchRecipientVerificationStatus()
|
|
189
194
|
|
|
190
195
|
useEffect(() => {
|
|
191
196
|
// Auto-navigate once verification resolves. The picker stays mounted so the
|
|
@@ -280,9 +285,7 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
280
285
|
recipients={mergedRecipients}
|
|
281
286
|
onSelectRecipient={setSelectedRecipient}
|
|
282
287
|
selectedRecipient={recipient}
|
|
283
|
-
isSelectedRecipientLoading={
|
|
284
|
-
!!recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN
|
|
285
|
-
}
|
|
288
|
+
isSelectedRecipientLoading={isSelectedRecipientLoading}
|
|
286
289
|
/>
|
|
287
290
|
</>
|
|
288
291
|
)
|
|
@@ -327,9 +330,7 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
327
330
|
recipients={contactRecipients}
|
|
328
331
|
onSelectRecipient={setSelectedRecipient}
|
|
329
332
|
selectedRecipient={recipient}
|
|
330
|
-
isSelectedRecipientLoading={
|
|
331
|
-
!!recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN
|
|
332
|
-
}
|
|
333
|
+
isSelectedRecipientLoading={isSelectedRecipientLoading}
|
|
333
334
|
/>
|
|
334
335
|
) : (
|
|
335
336
|
<>
|
|
@@ -344,9 +345,7 @@ function SendSelectRecipient({ route }: Props) {
|
|
|
344
345
|
title={t('sendSelectRecipient.recents')}
|
|
345
346
|
onSelectRecipient={onSelectRecentRecipient}
|
|
346
347
|
selectedRecipient={recipient}
|
|
347
|
-
isSelectedRecipientLoading={
|
|
348
|
-
!!recipient && recipientVerificationStatus === RecipientVerificationStatus.UNKNOWN
|
|
349
|
-
}
|
|
348
|
+
isSelectedRecipientLoading={isSelectedRecipientLoading}
|
|
350
349
|
style={styles.recentRecipientPicker}
|
|
351
350
|
/>
|
|
352
351
|
) : (
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { act, renderHook } from '@testing-library/react-native'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { Provider } from 'react-redux'
|
|
4
|
+
import {
|
|
5
|
+
fetchAddressVerification,
|
|
6
|
+
fetchAddressesAndValidate,
|
|
7
|
+
recipientLookupResolved,
|
|
8
|
+
} from 'src/identity/actions'
|
|
9
|
+
import { RecipientVerificationStatus } from 'src/identity/types'
|
|
10
|
+
import { Recipient, RecipientType } from 'src/recipients/recipient'
|
|
11
|
+
import { setupStore } from 'src/redux/store'
|
|
12
|
+
import useFetchRecipientVerificationStatus from 'src/send/useFetchRecipientVerificationStatus'
|
|
13
|
+
import { getMockStoreData } from 'test/utils'
|
|
14
|
+
import { mockAccount, mockE164Number, mockName } from 'test/values'
|
|
15
|
+
|
|
16
|
+
jest.mock('src/redux/sagas', () => ({
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
18
|
+
rootSaga: jest.fn(function* () {}),
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
const phoneRecipient: Recipient = {
|
|
22
|
+
name: mockName,
|
|
23
|
+
contactId: 'contactId',
|
|
24
|
+
e164PhoneNumber: mockE164Number,
|
|
25
|
+
recipientType: RecipientType.PhoneNumber,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const addressRecipient: Recipient = {
|
|
29
|
+
address: mockAccount,
|
|
30
|
+
recipientType: RecipientType.Address,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function setupHook(storeOverrides: Parameters<typeof getMockStoreData>[0] = {}) {
|
|
34
|
+
const { store } = setupStore(getMockStoreData(storeOverrides))
|
|
35
|
+
const dispatchSpy = jest.spyOn(store, 'dispatch')
|
|
36
|
+
const { result, rerender } = renderHook(() => useFetchRecipientVerificationStatus(), {
|
|
37
|
+
wrapper: ({ children }: { children: React.ReactNode }) => (
|
|
38
|
+
<Provider store={store}>{children}</Provider>
|
|
39
|
+
),
|
|
40
|
+
})
|
|
41
|
+
return { result, rerender, store, dispatchSpy }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('useFetchRecipientVerificationStatus', () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
jest.clearAllMocks()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('starts with no recipient and not loading', () => {
|
|
50
|
+
const { result } = setupHook()
|
|
51
|
+
expect(result.current.recipient).toBeNull()
|
|
52
|
+
expect(result.current.recipientVerificationStatus).toBe(RecipientVerificationStatus.UNKNOWN)
|
|
53
|
+
expect(result.current.isSelectedRecipientLoading).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('phone-number recipient', () => {
|
|
57
|
+
it('dispatches fetchAddressesAndValidate when selected', () => {
|
|
58
|
+
const { result, dispatchSpy } = setupHook()
|
|
59
|
+
act(() => {
|
|
60
|
+
result.current.setSelectedRecipient(phoneRecipient)
|
|
61
|
+
})
|
|
62
|
+
expect(dispatchSpy).toHaveBeenCalledWith(fetchAddressesAndValidate(mockE164Number))
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('reports loading once the lookup is dispatched and stops once it resolves', () => {
|
|
66
|
+
const { result, store } = setupHook()
|
|
67
|
+
|
|
68
|
+
act(() => {
|
|
69
|
+
result.current.setSelectedRecipient(phoneRecipient)
|
|
70
|
+
})
|
|
71
|
+
expect(result.current.isSelectedRecipientLoading).toBe(true)
|
|
72
|
+
|
|
73
|
+
act(() => {
|
|
74
|
+
store.dispatch(recipientLookupResolved())
|
|
75
|
+
})
|
|
76
|
+
expect(result.current.isSelectedRecipientLoading).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('address recipient', () => {
|
|
81
|
+
it('dispatches fetchAddressVerification when phone is verified', () => {
|
|
82
|
+
const { result, dispatchSpy } = setupHook({
|
|
83
|
+
app: { phoneNumberVerified: true },
|
|
84
|
+
})
|
|
85
|
+
act(() => {
|
|
86
|
+
result.current.setSelectedRecipient(addressRecipient)
|
|
87
|
+
})
|
|
88
|
+
expect(dispatchSpy).toHaveBeenCalledWith(fetchAddressVerification(mockAccount))
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('marks address recipient as unverified when phone is not verified', () => {
|
|
92
|
+
const { result } = setupHook({ app: { phoneNumberVerified: false } })
|
|
93
|
+
act(() => {
|
|
94
|
+
result.current.setSelectedRecipient(addressRecipient)
|
|
95
|
+
})
|
|
96
|
+
expect(result.current.recipientVerificationStatus).toBe(
|
|
97
|
+
RecipientVerificationStatus.UNVERIFIED
|
|
98
|
+
)
|
|
99
|
+
expect(result.current.isSelectedRecipientLoading).toBe(false)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('reports loading once the lookup is dispatched and stops once it resolves', () => {
|
|
103
|
+
const { result, store } = setupHook({ app: { phoneNumberVerified: true } })
|
|
104
|
+
|
|
105
|
+
act(() => {
|
|
106
|
+
result.current.setSelectedRecipient(addressRecipient)
|
|
107
|
+
})
|
|
108
|
+
expect(result.current.isSelectedRecipientLoading).toBe(true)
|
|
109
|
+
|
|
110
|
+
act(() => {
|
|
111
|
+
store.dispatch(recipientLookupResolved())
|
|
112
|
+
})
|
|
113
|
+
expect(result.current.isSelectedRecipientLoading).toBe(false)
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('clears recipient and verification status on unset', () => {
|
|
118
|
+
const { result } = setupHook()
|
|
119
|
+
act(() => {
|
|
120
|
+
result.current.setSelectedRecipient(phoneRecipient)
|
|
121
|
+
})
|
|
122
|
+
expect(result.current.isSelectedRecipientLoading).toBe(true)
|
|
123
|
+
|
|
124
|
+
act(() => {
|
|
125
|
+
result.current.unsetSelectedRecipient()
|
|
126
|
+
})
|
|
127
|
+
expect(result.current.recipient).toBeNull()
|
|
128
|
+
expect(result.current.recipientVerificationStatus).toBe(RecipientVerificationStatus.UNKNOWN)
|
|
129
|
+
expect(result.current.isSelectedRecipientLoading).toBe(false)
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react'
|
|
2
2
|
import { phoneNumberVerifiedSelector } from 'src/app/selectors'
|
|
3
3
|
import { fetchAddressVerification, fetchAddressesAndValidate } from 'src/identity/actions'
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
addressToVerifiedBySelector,
|
|
6
|
+
e164NumberToAddressSelector,
|
|
7
|
+
recipientLookupLoadingSelector,
|
|
8
|
+
} from 'src/identity/selectors'
|
|
5
9
|
import { RecipientVerificationStatus } from 'src/identity/types'
|
|
6
10
|
import { Recipient, RecipientType, getRecipientVerificationStatus } from 'src/recipients/recipient'
|
|
7
11
|
import { useDispatch, useSelector } from 'src/redux/hooks'
|
|
@@ -14,6 +18,7 @@ const useFetchRecipientVerificationStatus = () => {
|
|
|
14
18
|
|
|
15
19
|
const e164NumberToAddress = useSelector(e164NumberToAddressSelector)
|
|
16
20
|
const addressToVerifiedBy = useSelector(addressToVerifiedBySelector)
|
|
21
|
+
const recipientLookupLoading = useSelector(recipientLookupLoadingSelector)
|
|
17
22
|
const phoneNumberVerified = useSelector(phoneNumberVerifiedSelector)
|
|
18
23
|
const dispatch = useDispatch()
|
|
19
24
|
|
|
@@ -51,11 +56,14 @@ const useFetchRecipientVerificationStatus = () => {
|
|
|
51
56
|
}
|
|
52
57
|
}, [e164NumberToAddress, addressToVerifiedBy, recipient, recipientVerificationStatus])
|
|
53
58
|
|
|
59
|
+
const isSelectedRecipientLoading = !!recipient && recipientLookupLoading
|
|
60
|
+
|
|
54
61
|
return {
|
|
55
62
|
recipient,
|
|
56
63
|
setSelectedRecipient,
|
|
57
64
|
unsetSelectedRecipient,
|
|
58
65
|
recipientVerificationStatus,
|
|
66
|
+
isSelectedRecipientLoading,
|
|
59
67
|
}
|
|
60
68
|
}
|
|
61
69
|
|