Skip to content

Commit

Permalink
Merge branch 'master' into IOPLT-810-set-allow-font-scaling-to-true-b…
Browse files Browse the repository at this point in the history
…y-default
  • Loading branch information
LeleDallas authored Dec 23, 2024
2 parents 498644c + 9321272 commit a8c80bc
Show file tree
Hide file tree
Showing 17 changed files with 469 additions and 199 deletions.
17 changes: 17 additions & 0 deletions ts/components/debug/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { truncateObjectStrings } from "../utils";

describe("truncateObjectStrings", () => {
it.each`
input | maxLength | expected
${"Long string"} | ${4} | ${"Long..."}
${{ outer: { inner: "Long string" }, bool: true }} | ${4} | ${{ outer: { inner: "Long..." }, bool: true }}
${["Long string", "Very long string"]} | ${4} | ${["Long...", "Very..."]}
${new Set(["Long string", "Very long string"])} | ${4} | ${["Long...", "Very..."]}
`(
"$input should be truncated to $expected",
({ input, maxLength, expected }) => {
const result = truncateObjectStrings(input, maxLength);
expect(result).toEqual(expected);
}
);
});
15 changes: 14 additions & 1 deletion ts/components/debug/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
type Primitive = string | number | boolean | null | undefined;

type TruncatableValue = Primitive | TruncatableObject | TruncatableArray;
type TruncatableValue =
| Primitive
| TruncatableObject
| TruncatableArray
| TruncatableSet;

interface TruncatableObject {
[key: string]: TruncatableValue;
}

type TruncatableArray = Array<TruncatableValue>;
type TruncatableSet = Set<TruncatableValue>;

/**
* Truncates all string values in an object or array structure to a specified maximum length.
Expand Down Expand Up @@ -37,6 +42,14 @@ export const truncateObjectStrings = <T extends TruncatableValue>(
}

if (typeof value === "object" && value !== null) {
if (value instanceof Set) {
// Set could not be serialized to JSON because values are not stored as properties
// For display purposes, we convert it to an array
return Array.from(value).map(item =>
truncateObjectStrings(item, maxLength)
) as T;
}

return Object.entries(value).reduce(
(acc, [key, val]) => ({
...acc,
Expand Down
6 changes: 4 additions & 2 deletions ts/components/debug/withDebugEnabled.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { isDebugModeEnabledSelector } from "../../store/reducers/debug";
* This HOC allows to render the wrapped component only if the debug mode is enabled, otherwise returns null (nothing)
*/
export const withDebugEnabled =
<P,>(WrappedComponent: React.ComponentType<P>) =>
<P extends Record<string, unknown>>(
WrappedComponent: React.ComponentType<P>
) =>
(props: P) => {
const isDebug = useIOSelector(isDebugModeEnabledSelector);
if (!isDebug) {
return null;
}
return <WrappedComponent {...(props as any)} />;
return <WrappedComponent {...props} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const ItwCredentialOnboardingSection = () => {
);

return (
<>
<View>
<ListItemHeader
label={I18n.t("features.wallet.onboarding.sections.itw")}
/>
Expand All @@ -134,7 +134,7 @@ const ItwCredentialOnboardingSection = () => {
/>
))}
</VStack>
</>
</View>
);
};

Expand Down Expand Up @@ -175,7 +175,7 @@ const OtherCardsOnboardingSection = (props: { showTitle?: boolean }) => {
);

return (
<>
<View>
{props.showTitle && (
<ListItemHeader
label={I18n.t("features.wallet.onboarding.sections.other")}
Expand All @@ -190,7 +190,7 @@ const OtherCardsOnboardingSection = (props: { showTitle?: boolean }) => {
onPress={navigateToPaymentMethodOnboarding}
/>
</VStack>
</>
</View>
);
};

Expand Down
66 changes: 42 additions & 24 deletions ts/features/wallet/components/WalletCardsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,35 @@ import { useFocusEffect } from "@react-navigation/native";
import * as React from "react";
import { View } from "react-native";
import Animated, { LinearTransition } from "react-native-reanimated";
import { useDebugInfo } from "../../../hooks/useDebugInfo";
import I18n from "../../../i18n";
import { useIONavigation } from "../../../navigation/params/AppParamsList";
import { useIOSelector } from "../../../store/hooks";
import { isItwEnabledSelector } from "../../../store/reducers/backendStatus/remoteConfig";
import { useIOBottomSheetAutoresizableModal } from "../../../utils/hooks/bottomSheet";
import { ItwDiscoveryBannerStandalone } from "../../itwallet/common/components/discoveryBanner/ItwDiscoveryBannerStandalone";
import {
ItwEidInfoBottomSheetContent,
ItwEidInfoBottomSheetTitle
} from "../../itwallet/common/components/ItwEidInfoBottomSheetContent";
import { ItwEidLifecycleAlert } from "../../itwallet/common/components/ItwEidLifecycleAlert";
import { ItwFeedbackBanner } from "../../itwallet/common/components/ItwFeedbackBanner";
import { ItwWalletReadyBanner } from "../../itwallet/common/components/ItwWalletReadyBanner";
import { ItwDiscoveryBannerStandalone } from "../../itwallet/common/components/discoveryBanner/ItwDiscoveryBannerStandalone";
import { itwCredentialsEidStatusSelector } from "../../itwallet/credentials/store/selectors";
import { itwLifecycleIsValidSelector } from "../../itwallet/lifecycle/store/selectors";
import { useItwWalletInstanceRevocationAlert } from "../../itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert";
import {
isWalletEmptySelector,
selectIsWalletCardsLoading,
selectIsWalletLoading,
selectWalletCardsByCategory,
selectWalletCategories,
selectWalletCategoryFilter,
selectWalletItwCards,
selectWalletOtherCards,
shouldRenderWalletEmptyStateSelector
} from "../store/selectors";
import { WalletCardCategoryFilter } from "../types";
import { useItwWalletInstanceRevocationAlert } from "../../itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert";
import { withWalletCategoryFilter } from "../utils";
import { WalletCardSkeleton } from "./WalletCardSkeleton";
import { WalletCardsCategoryContainer } from "./WalletCardsCategoryContainer";
import { WalletCardsCategoryRetryErrorBanner } from "./WalletCardsCategoryRetryErrorBanner";
import { WalletCardSkeleton } from "./WalletCardSkeleton";
import { WalletEmptyScreenContent } from "./WalletEmptyScreenContent";

const EID_INFO_BOTTOM_PADDING = 128;
Expand All @@ -42,9 +42,8 @@ const EID_INFO_BOTTOM_PADDING = 128;
* and the empty state
*/
const WalletCardsContainer = () => {
const isLoading = useIOSelector(selectIsWalletCardsLoading);
const isLoading = useIOSelector(selectIsWalletLoading);
const isWalletEmpty = useIOSelector(isWalletEmptySelector);
const selectedCategory = useIOSelector(selectWalletCategoryFilter);
const shouldRenderEmptyState = useIOSelector(
shouldRenderWalletEmptyStateSelector
);
Expand All @@ -55,13 +54,6 @@ const WalletCardsContainer = () => {
// placeholders in the wallet
const shouldRenderLoadingState = isLoading && isWalletEmpty;

// Returns true if no category filter is selected or if the filter matches the given category
const shouldRenderCategory = React.useCallback(
(filter: WalletCardCategoryFilter): boolean =>
selectedCategory === undefined || selectedCategory === filter,
[selectedCategory]
);

// Content to render in the wallet screen, based on the current state
const walletContent = React.useMemo(() => {
if (shouldRenderLoadingState) {
Expand All @@ -72,11 +64,11 @@ const WalletCardsContainer = () => {
}
return (
<View testID="walletCardsContainerTestID" style={IOStyles.flex}>
{shouldRenderCategory("itw") && <ItwWalletCardsContainer />}
{shouldRenderCategory("other") && <OtherWalletCardsContainer />}
<ItwWalletCardsContainer />
<OtherWalletCardsContainer />
</View>
);
}, [shouldRenderEmptyState, shouldRenderCategory, shouldRenderLoadingState]);
}, [shouldRenderEmptyState, shouldRenderLoadingState]);

return (
<Animated.View
Expand All @@ -89,6 +81,9 @@ const WalletCardsContainer = () => {
);
};

/**
* Skeleton for the wallet cards container
*/
const WalletCardsContainerSkeleton = () => (
<>
<WalletCardSkeleton testID="walletCardSkeletonTestID_1" cardProps={{}} />
Expand All @@ -97,15 +92,29 @@ const WalletCardsContainerSkeleton = () => (
</>
);

const ItwWalletCardsContainer = () => {
/**
* Card container for the ITW credentials
*/
const ItwWalletCardsContainer = withWalletCategoryFilter("itw", () => {
const navigation = useIONavigation();
const cards = useIOSelector(selectWalletItwCards);
const cards = useIOSelector(state =>
selectWalletCardsByCategory(state, "itw")
);
const isItwValid = useIOSelector(itwLifecycleIsValidSelector);
const isItwEnabled = useIOSelector(isItwEnabledSelector);
const eidStatus = useIOSelector(itwCredentialsEidStatusSelector);

const isEidExpired = eidStatus === "jwtExpired";

useDebugInfo({
itw: {
isItwValid,
isItwEnabled,
eidStatus,
cards
}
});

const eidInfoBottomSheet = useIOBottomSheetAutoresizableModal(
{
title: <ItwEidInfoBottomSheetTitle isExpired={isEidExpired} />,
Expand Down Expand Up @@ -172,12 +181,21 @@ const ItwWalletCardsContainer = () => {
{isItwValid && eidInfoBottomSheet.bottomSheet}
</>
);
};
});

const OtherWalletCardsContainer = () => {
/**
* Card container for the other cards (payments, bonus, etc.)
*/
const OtherWalletCardsContainer = withWalletCategoryFilter("other", () => {
const cards = useIOSelector(selectWalletOtherCards);
const categories = useIOSelector(selectWalletCategories);

useDebugInfo({
other: {
cards
}
});

const sectionHeader = React.useMemo((): ListItemHeader | undefined => {
// The section header must be displayed only if there are more categories
if (categories.size <= 1) {
Expand All @@ -203,7 +221,7 @@ const OtherWalletCardsContainer = () => {
bottomElement={<WalletCardsCategoryRetryErrorBanner />}
/>
);
};
});

export {
ItwWalletCardsContainer,
Expand Down
24 changes: 17 additions & 7 deletions ts/features/wallet/components/WalletCategoryFilterTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import { useIODispatch, useIOSelector } from "../../../store/hooks";
import { trackWalletCategoryFilter } from "../../itwallet/analytics";
import { walletSetCategoryFilter } from "../store/actions/preferences";
import {
selectWalletCategories,
isWalletCategoryFilteringEnabledSelector,
selectWalletCategoryFilter
} from "../store/selectors";
import { walletCardCategoryFilters } from "../types";
import { useDebugInfo } from "../../../hooks/useDebugInfo";

/**
* Renders filter tabs to categorize cards on the wallet home screen.
Expand All @@ -23,18 +24,27 @@ import { walletCardCategoryFilters } from "../types";
const WalletCategoryFilterTabs = () => {
const dispatch = useIODispatch();

const selectedCategory = useIOSelector(selectWalletCategoryFilter);
const categories = useIOSelector(selectWalletCategories);
const categoryFilter = useIOSelector(selectWalletCategoryFilter);
const isFilteringEnabled = useIOSelector(
isWalletCategoryFilteringEnabledSelector
);

useDebugInfo({
wallet: {
isFilteringEnabled,
categoryFilter
}
});

const selectedIndex = React.useMemo(
() =>
selectedCategory
? walletCardCategoryFilters.indexOf(selectedCategory) + 1
categoryFilter
? walletCardCategoryFilters.indexOf(categoryFilter) + 1
: 0,
[selectedCategory]
[categoryFilter]
);

if (categories.size <= 1) {
if (!isFilteringEnabled) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ describe("WalletCardsContainer", () => {

it("should render the loading screen", () => {
jest
.spyOn(walletSelectors, "selectIsWalletCardsLoading")
.spyOn(walletSelectors, "selectIsWalletLoading")
.mockImplementation(() => true);
jest
.spyOn(walletSelectors, "selectWalletCategoryFilter")
Expand All @@ -117,7 +117,7 @@ describe("WalletCardsContainer", () => {

it("should render the empty screen", () => {
jest
.spyOn(walletSelectors, "selectIsWalletCardsLoading")
.spyOn(walletSelectors, "selectIsWalletLoading")
.mockImplementation(() => false);
jest
.spyOn(walletSelectors, "selectWalletCategoryFilter")
Expand Down Expand Up @@ -150,14 +150,14 @@ describe("WalletCardsContainer", () => {
.mockImplementation(() => [T_CARDS["1"], T_CARDS["2"], T_CARDS["3"]]);

jest
.spyOn(walletSelectors, "selectWalletItwCards")
.spyOn(walletSelectors, "selectWalletCardsByCategory")
.mockImplementation(() => [T_CARDS["4"], T_CARDS["5"]]);

jest
.spyOn(configSelectors, "isItwEnabledSelector")
.mockImplementation(() => true);
jest
.spyOn(walletSelectors, "selectIsWalletCardsLoading")
.spyOn(walletSelectors, "selectIsWalletLoading")
.mockImplementation(() => false);
jest
.spyOn(walletSelectors, "selectWalletCategoryFilter")
Expand Down Expand Up @@ -193,7 +193,7 @@ describe("WalletCardsContainer", () => {
.mockImplementation(() => true);

jest
.spyOn(walletSelectors, "selectIsWalletCardsLoading")
.spyOn(walletSelectors, "selectIsWalletLoading")
.mockImplementation(() => isLoading);
jest
.spyOn(walletSelectors, "shouldRenderWalletEmptyStateSelector")
Expand Down Expand Up @@ -244,7 +244,7 @@ describe("ItwWalletCardsContainer", () => {
.spyOn(configSelectors, "isItwEnabledSelector")
.mockImplementation(() => true);
jest
.spyOn(walletSelectors, "selectWalletItwCards")
.spyOn(walletSelectors, "selectWalletCardsByCategory")
.mockImplementation(() => [T_CARDS["4"], T_CARDS["5"]]);

const { queryByTestId } = renderComponent(ItwWalletCardsContainer);
Expand Down
Loading

0 comments on commit a8c80bc

Please sign in to comment.