Skip to content

Commit 2d57f56

Browse files
committed
Merge branch 'SIW-2072' into SIW-2036
2 parents 8766f3b + 52d9e81 commit 2d57f56

File tree

7 files changed

+161
-20
lines changed

7 files changed

+161
-20
lines changed

ts/features/itwallet/common/saga/index.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,17 @@ import { SagaIterator } from "redux-saga";
22
import { call, fork } from "typed-redux-saga/macro";
33
import { watchItwCredentialsSaga } from "../../credentials/saga";
44
import { checkCredentialsStatusAttestation } from "../../credentials/saga/checkCredentialsStatusAttestation";
5-
import { handleWalletCredentialsRehydration } from "../../credentials/saga/handleWalletCredentialsRehydration";
65
import { watchItwLifecycleSaga } from "../../lifecycle/saga";
76
import { warmUpIntegrityServiceSaga } from "../../lifecycle/saga/checkIntegrityServiceReadySaga";
87
import { checkWalletInstanceStateSaga } from "../../lifecycle/saga/checkWalletInstanceStateSaga";
98

10-
function* checkWalletInstanceAndCredentialsValiditySaga() {
11-
// Status attestations of credentials are checked only in case of a valid wallet instance.
12-
// For this reason, these sagas must be called sequentially.
13-
yield* call(checkWalletInstanceStateSaga);
14-
yield* call(checkCredentialsStatusAttestation);
15-
}
16-
179
export function* watchItwSaga(): SagaIterator {
1810
yield* fork(warmUpIntegrityServiceSaga);
19-
yield* fork(checkWalletInstanceAndCredentialsValiditySaga);
20-
yield* fork(handleWalletCredentialsRehydration);
2111
yield* fork(watchItwCredentialsSaga);
2212
yield* fork(watchItwLifecycleSaga);
13+
14+
// Status attestations of credentials are checked only in case of a valid wallet instance.
15+
// For this reason, these sagas must be called sequentially.
16+
yield* call(checkWalletInstanceStateSaga);
17+
yield* call(checkCredentialsStatusAttestation);
2318
}

ts/features/itwallet/common/store/selectors/remoteConfig.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ const itwRemoteConfigSelector = (state: GlobalState) =>
1717
);
1818

1919
/**
20-
* Return the remote config about IT-WALLET enabled/disabled
21-
* if there is no data or the local Feature Flag is disabled,
22-
* false is the default value -> (IT-WALLET disabled)
20+
* Returns the remote config for IT-WALLET
2321
*/
2422
export const isItwEnabledSelector = createSelector(
2523
itwRemoteConfigSelector,

ts/features/itwallet/navigation/ItwStackNavigator.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { ItwOfflineWalletScreen } from "../wallet/screens/ItwOfflineWalletScreen
4545
import { isItwEnabledSelector } from "../common/store/selectors/remoteConfig";
4646
import { ItwGenericErrorContent } from "../common/components/ItwGenericErrorContent";
4747
import { useIOSelector } from "../../../store/hooks";
48+
import { isConnectedSelector } from "../../connectivity/store/selectors";
4849
import { ItwParamsList } from "./ItwParamsList";
4950
import { ITW_ROUTES } from "./routes";
5051

@@ -259,10 +260,10 @@ const withItwEnabled =
259260
<P extends Record<string, unknown>>(Screen: ComponentType<P>) =>
260261
(props: P) => {
261262
const isItwEnabled = useIOSelector(isItwEnabledSelector);
263+
const isConnected = useIOSelector(isConnectedSelector);
262264

263-
if (!isItwEnabled) {
264-
// In case the user lands in this screen and IT Wallet is not enabled,
265-
// we should render an error screen.
265+
// Show error content only if connected and IT Wallet is not enabled
266+
if (isConnected && !isItwEnabled) {
266267
return <ItwGenericErrorContent />;
267268
}
268269
return <Screen {...props} />;

ts/features/wallet/store/selectors/__tests__/index.test.ts

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import {
99
selectWalletCardsByType,
1010
selectWalletCategories,
1111
shouldRenderWalletCategorySelector,
12-
shouldRenderWalletEmptyStateSelector
12+
shouldRenderWalletEmptyStateSelector,
13+
shouldRenderItwCardsContainerSelector
1314
} from "..";
1415
import { applicationChangeState } from "../../../../../store/actions/application";
1516
import { appReducer } from "../../../../../store/reducers";
@@ -19,6 +20,9 @@ import {
1920
} from "../../../../itwallet/common/utils/itwMocksUtils";
2021
import { ItwLifecycleState } from "../../../../itwallet/lifecycle/store/reducers";
2122
import * as itwLifecycleSelectors from "../../../../itwallet/lifecycle/store/selectors";
23+
import * as itwWalletInstanceSelectors from "../../../../itwallet/walletInstance/store/selectors";
24+
import * as itwSelectors from "../../../../itwallet/common/store/selectors/remoteConfig";
25+
import * as connectivitySelectors from "../../../../connectivity/store/selectors";
2226
import { walletCardCategoryFilters } from "../../../types";
2327
import { WalletCardsState } from "../../reducers/cards";
2428

@@ -366,3 +370,128 @@ describe("shouldRenderWalletCategorySelector", () => {
366370
}
367371
);
368372
});
373+
374+
describe("shouldRenderItwCardsContainerSelector", () => {
375+
it("should return true when ITW is enabled, lifecycle is valid, and no WI error", () => {
376+
const globalState = appReducer(undefined, applicationChangeState("active"));
377+
378+
jest
379+
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
380+
.mockImplementation(() => true);
381+
382+
jest
383+
.spyOn(
384+
itwWalletInstanceSelectors,
385+
"itwIsWalletInstanceStatusFailureSelector"
386+
)
387+
.mockImplementation(() => false);
388+
389+
jest
390+
.spyOn(itwSelectors, "isItwEnabledSelector")
391+
.mockImplementation(() => true);
392+
393+
const shouldRenderItwCardsContainer =
394+
shouldRenderItwCardsContainerSelector(globalState);
395+
expect(shouldRenderItwCardsContainer).toBe(true);
396+
});
397+
398+
it("should return true when offline, lifecycle is valid, and no WI error, even if ITW is disabled", () => {
399+
const globalState = appReducer(undefined, applicationChangeState("active"));
400+
401+
jest
402+
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
403+
.mockImplementation(() => true);
404+
405+
jest
406+
.spyOn(
407+
itwWalletInstanceSelectors,
408+
"itwIsWalletInstanceStatusFailureSelector"
409+
)
410+
.mockImplementation(() => false);
411+
412+
jest
413+
.spyOn(itwSelectors, "isItwEnabledSelector")
414+
.mockImplementation(() => false);
415+
416+
jest
417+
.spyOn(connectivitySelectors, "isConnectedSelector")
418+
.mockImplementation(() => false);
419+
420+
const shouldRenderItwCardsContainer =
421+
shouldRenderItwCardsContainerSelector(globalState);
422+
expect(shouldRenderItwCardsContainer).toBe(true);
423+
});
424+
425+
it("should return false when ITW is disabled and online", () => {
426+
const globalState = appReducer(undefined, applicationChangeState("active"));
427+
428+
jest
429+
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
430+
.mockImplementation(() => true);
431+
432+
jest
433+
.spyOn(
434+
itwWalletInstanceSelectors,
435+
"itwIsWalletInstanceStatusFailureSelector"
436+
)
437+
.mockImplementation(() => false);
438+
439+
jest
440+
.spyOn(itwSelectors, "isItwEnabledSelector")
441+
.mockImplementation(() => false);
442+
443+
jest
444+
.spyOn(connectivitySelectors, "isConnectedSelector")
445+
.mockImplementation(() => true);
446+
447+
const shouldRenderItwCardsContainer =
448+
shouldRenderItwCardsContainerSelector(globalState);
449+
expect(shouldRenderItwCardsContainer).toBe(false);
450+
});
451+
452+
it("should return false when lifecycle is not valid", () => {
453+
const globalState = appReducer(undefined, applicationChangeState("active"));
454+
455+
jest
456+
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
457+
.mockImplementation(() => false);
458+
459+
jest
460+
.spyOn(
461+
itwWalletInstanceSelectors,
462+
"itwIsWalletInstanceStatusFailureSelector"
463+
)
464+
.mockImplementation(() => false);
465+
466+
jest
467+
.spyOn(itwSelectors, "isItwEnabledSelector")
468+
.mockImplementation(() => true);
469+
470+
const shouldRenderItwCardsContainer =
471+
shouldRenderItwCardsContainerSelector(globalState);
472+
expect(shouldRenderItwCardsContainer).toBe(false);
473+
});
474+
475+
it("should return false when there is a WI error", () => {
476+
const globalState = appReducer(undefined, applicationChangeState("active"));
477+
478+
jest
479+
.spyOn(itwLifecycleSelectors, "itwLifecycleIsValidSelector")
480+
.mockImplementation(() => true);
481+
482+
jest
483+
.spyOn(
484+
itwWalletInstanceSelectors,
485+
"itwIsWalletInstanceStatusFailureSelector"
486+
)
487+
.mockImplementation(() => true);
488+
489+
jest
490+
.spyOn(itwSelectors, "isItwEnabledSelector")
491+
.mockImplementation(() => true);
492+
493+
const shouldRenderItwCardsContainer =
494+
shouldRenderItwCardsContainerSelector(globalState);
495+
expect(shouldRenderItwCardsContainer).toBe(false);
496+
});
497+
});

ts/features/wallet/store/selectors/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from "../../types";
1616
import { WalletCardCategoryFilter } from "../../types/index";
1717
import { isItwEnabledSelector } from "../../../itwallet/common/store/selectors/remoteConfig";
18+
import { isConnectedSelector } from "../../../connectivity/store/selectors";
1819

1920
/**
2021
* Returns the list of cards excluding hidden cards
@@ -169,11 +170,11 @@ export const shouldRenderWalletCategorySelector = createSelector(
169170
/**
170171
* Determines whether the IT Wallet cards section is rendered in the wallet screen.
171172
* The section is rendered if:
172-
* - the IT Wallet feature flag is enabled
173+
* - the IT Wallet feature flag is enabled OR the app is in offline mode
173174
* - the IT Wallet is in a valid lifecycle state
174175
* - the IT Wallet WI does not have an error
175176
*/
176177
export const shouldRenderItwCardsContainerSelector = (state: GlobalState) =>
177-
isItwEnabledSelector(state) &&
178+
(isItwEnabledSelector(state) || !isConnectedSelector(state)) &&
178179
itwLifecycleIsValidSelector(state) &&
179180
!itwIsWalletInstanceStatusFailureSelector(state);

ts/sagas/__tests__/initializeApplicationSaga.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { checkSession } from "../startup/watchCheckSessionSaga";
5353
import { formatRequestedTokenString } from "../../features/zendesk/utils";
5454
import { checkPublicKeyAndBlockIfNeeded } from "../../features/lollipop/navigation";
5555
import { userFromSuccessLoginSelector } from "../../features/login/info/store/selectors";
56+
import { handleWalletCredentialsRehydration } from "../../features/itwallet/credentials/saga/handleWalletCredentialsRehydration";
5657

5758
const aSessionToken = "a_session_token" as SessionToken;
5859
const aSessionInfo = O.some({
@@ -119,6 +120,8 @@ describe("initializeApplicationSaga", () => {
119120
.next(generateLollipopKeySaga)
120121
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
121122
.next(false) // the device is supported
123+
.fork(handleWalletCredentialsRehydration)
124+
.next()
122125
.select(offlineAccessReasonSelector)
123126
.next()
124127
.select(remoteConfigSelector)
@@ -176,6 +179,8 @@ describe("initializeApplicationSaga", () => {
176179
.next(generateLollipopKeySaga)
177180
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
178181
.next(false) // the device is supported
182+
.fork(handleWalletCredentialsRehydration)
183+
.next()
179184
.select(offlineAccessReasonSelector)
180185
.next()
181186
.select(remoteConfigSelector)
@@ -227,6 +232,8 @@ describe("initializeApplicationSaga", () => {
227232
.next(generateLollipopKeySaga)
228233
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
229234
.next(false) // the device is supported
235+
.fork(handleWalletCredentialsRehydration)
236+
.next()
230237
.select(offlineAccessReasonSelector)
231238
.next()
232239
.select(remoteConfigSelector)
@@ -283,6 +290,8 @@ describe("initializeApplicationSaga", () => {
283290
.next(generateLollipopKeySaga)
284291
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
285292
.next(false) // the device is supported
293+
.fork(handleWalletCredentialsRehydration)
294+
.next()
286295
.select(offlineAccessReasonSelector)
287296
.next()
288297
.select(remoteConfigSelector)
@@ -352,6 +361,8 @@ describe("initializeApplicationSaga", () => {
352361
.next(generateLollipopKeySaga)
353362
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
354363
.next(false) // the device is supported
364+
.fork(handleWalletCredentialsRehydration)
365+
.next()
355366
.select(offlineAccessReasonSelector)
356367
.next()
357368
.select(remoteConfigSelector)
@@ -408,6 +419,8 @@ describe("initializeApplicationSaga", () => {
408419
.next(generateLollipopKeySaga)
409420
.call(checkPublicKeyAndBlockIfNeeded) // is device unsupported?
410421
.next(false) // the device is supported
422+
.fork(handleWalletCredentialsRehydration)
423+
.next()
411424
.select(offlineAccessReasonSelector)
412425
.next()
413426
.select(remoteConfigSelector)

ts/sagas/startup.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
offlineAccessReasonSelector
3838
} from "../features/ingress/store/selectors";
3939
import { watchItwSaga } from "../features/itwallet/common/saga";
40+
import { handleWalletCredentialsRehydration } from "../features/itwallet/credentials/saga/handleWalletCredentialsRehydration";
4041
import { userFromSuccessLoginSelector } from "../features/login/info/store/selectors";
4142
import { checkPublicKeyAndBlockIfNeeded } from "../features/lollipop/navigation";
4243
import {
@@ -228,6 +229,9 @@ export function* initializeApplicationSaga(
228229
}
229230
// #LOLLIPOP_CHECK_BLOCK1_END
230231

232+
// Rehydrate wallet with ITW credentials
233+
yield* fork(handleWalletCredentialsRehydration);
234+
231235
// `isConnectedSelector` was **discarded** as it might be unclear.
232236
// `isStartupLoaded` was **not used** because it required dispatching the action
233237
// that initializes the new navigator before user authentication,
@@ -528,7 +532,7 @@ export function* initializeApplicationSaga(
528532
yield* call(checkAcknowledgedEmailSaga, userProfile);
529533
}
530534

531-
userProfile = (yield* call(checkEmailSaga)) ?? userProfile;
535+
userProfile = yield* call(checkEmailSaga) ?? userProfile;
532536

533537
// Check for both profile notifications permissions (anonymous
534538
// content && reminder) and system notifications permissions.

0 commit comments

Comments
 (0)