From 5b86a18d6633d994a04b22346ba075e900eefe0f Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Mon, 17 Nov 2025 20:35:31 +0500 Subject: [PATCH 01/16] Migrated icons to lazy loading --- __mocks__/Illustrations.ts | 10 ++++++++ src/components/ApproverSelectionList.tsx | 4 +++- src/components/CardPreview.tsx | 5 ++-- src/components/Icon/Illustrations.ts | 12 ---------- .../Icon/chunks/illustrations.chunk.ts | 24 +++++++++++++++++++ .../home/report/ReportDetailsExportPage.tsx | 4 +++- src/pages/iou/ReceiptDropUI.tsx | 5 ++-- .../step/IOURequestStepScan/index.native.tsx | 9 ++++--- .../request/step/IOURequestStepScan/index.tsx | 13 +++++----- .../MergeAccounts/MergeResultPage.tsx | 4 +++- .../workspace/expensifyCard/EmptyCardView.tsx | 4 +++- 11 files changed, 62 insertions(+), 32 deletions(-) diff --git a/__mocks__/Illustrations.ts b/__mocks__/Illustrations.ts index 0170c75167c7..300f7529830a 100644 --- a/__mocks__/Illustrations.ts +++ b/__mocks__/Illustrations.ts @@ -21,6 +21,7 @@ const CreditCardsNew = 'CreditCardsNew'; const CreditCardsNewGreen = 'CreditCardsNewGreen'; const InvoiceBlue = 'InvoiceBlue'; const LaptopWithSecondScreenAndHourglass = 'LaptopWithSecondScreenAndHourglass'; +const RunningTurtle = 'RunningTurtle'; const LockOpen = 'LockOpen'; const Luggage = 'Luggage'; const MoneyIntoWallet = 'MoneyIntoWallet'; @@ -29,6 +30,7 @@ const ConciergeBubble = 'ConciergeBubble'; const TreasureChest = 'TreasureChest'; const ThumbsUpStars = 'ThumbsUpStars'; const Hands = 'Hands'; +const Hand = 'Hand'; const SmartScan = 'SmartScan'; const Hourglass = 'Hourglass'; const CommentBubbles = 'CommentBubbles'; @@ -56,6 +58,9 @@ const Binoculars = 'Binoculars'; const CompanyCard = 'CompanyCard'; const ReceiptUpload = 'ReceiptUpload'; const ExpensifyCardIllustration = 'ExpensifyCardIllustration'; +const ExpensifyCardImage = 'ExpensifyCardImage'; +const MultiScan = 'MultiScan'; +const Shutter = 'Shutter'; const SplitBill = 'SplitBill'; const PiggyBank = 'PiggyBank'; const Pillow = 'Pillow'; @@ -134,6 +139,7 @@ export { CreditCardsNewGreen, InvoiceBlue, LaptopWithSecondScreenAndHourglass, + RunningTurtle, LockOpen, Luggage, MoneyIntoWallet, @@ -142,6 +148,7 @@ export { TreasureChest, ThumbsUpStars, Hands, + Hand, SmartScan, Hourglass, CommentBubbles, @@ -169,6 +176,9 @@ export { CompanyCard, ReceiptUpload, ExpensifyCardIllustration, + ExpensifyCardImage, + MultiScan, + Shutter, SplitBill, PiggyBank, Pillow, diff --git a/src/components/ApproverSelectionList.tsx b/src/components/ApproverSelectionList.tsx index ceab0014cab3..3f1bb3f7da67 100644 --- a/src/components/ApproverSelectionList.tsx +++ b/src/components/ApproverSelectionList.tsx @@ -1,6 +1,7 @@ import React, {useMemo, useState} from 'react'; import type {SectionListData} from 'react-native'; import useDebouncedState from '@hooks/useDebouncedState'; +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -82,6 +83,7 @@ function ApproverSelectionList({ const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); const shouldShowTextInput = shouldShowTextInputProp ?? allApprovers?.length >= CONST.STANDARD_LIST_ITEM_LIMIT; + const lazyIllustrations = useMemoizedLazyIllustrations(['TurtleInShell'] as const); const [selectedMembers, setSelectedMembers] = useState([]); @@ -127,7 +129,7 @@ function ApproverSelectionList({ const listEmptyContent = useMemo( () => ( (null); const styles = useThemeStyles(); + const lazyIllustrations = useMemoizedLazyIllustrations(['LaptopWithSecondScreenAndHourglass'] as const); const iconToDisplay = getIntegrationIcon(connectionName); const canBeExported = canBeExportedUtil(report); @@ -89,7 +91,7 @@ function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) { onBackButtonPress={() => Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(reportID, backTo))} /> >>()] = useOnyx(ONYXKEYS.NVP_MUTED_PLATFORMS, {canBeMissing: true}); const isPlatformMuted = mutedPlatforms[platform]; @@ -871,7 +870,7 @@ function IOURequestStepScan({ {shouldShowMultiScanEducationalPopup && ( diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 5ea5a902b070..41336a888910 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -8,11 +8,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import {RESULTS} from 'react-native-permissions'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import type Webcam from 'react-webcam'; -import MultiScan from '@assets/images/educational-illustration__multi-scan.svg'; import TestReceipt from '@assets/images/fake-receipt.png'; -import Hand from '@assets/images/hand.svg'; -import ReceiptUpload from '@assets/images/receipt-upload.svg'; -import Shutter from '@assets/images/shutter.svg'; import ActivityIndicator from '@components/ActivityIndicator'; import AttachmentPicker from '@components/AttachmentPicker'; import Button from '@components/Button'; @@ -31,6 +27,7 @@ import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalD import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useFilesValidation from '@hooks/useFilesValidation'; import useIOUUtils from '@hooks/useIOUUtils'; +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; @@ -129,6 +126,7 @@ function IOURequestStepScan({ const defaultExpensePolicy = useDefaultExpensePolicy(); const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); + const lazyIllustrations = useMemoizedLazyIllustrations(['MultiScan', 'Hand', 'ReceiptUpload', 'Shutter'] as const); const isEditing = action === CONST.IOU.ACTION.EDIT; const canUseMultiScan = isStartingScan && iouType !== CONST.IOU.TYPE.SPLIT; const isReplacingReceipt = (isEditing && hasReceipt(initialTransaction)) || (!!initialTransaction?.receipt && !!backTo); @@ -850,7 +848,7 @@ function IOURequestStepScan({ {cameraPermissionState !== 'granted' && isQueriedPermissionState && ( - @@ -990,7 +989,7 @@ function IOURequestStepScan({ {canUseMultiScan && isMobile() && shouldShowMultiScanEducationalPopup && ( >(); const {environmentURL} = useEnvironment(); const {result, login, backTo} = params; + const lazyIllustrations = useMemoizedLazyIllustrations(['RunningTurtle'] as const); const defaultResult = { heading: translate('mergeAccountsPage.mergeFailureGenericHeading'), @@ -124,7 +126,7 @@ function MergeResultPage() { shouldShowSecondaryButton: true, buttonText: translate('common.buttonConfirm'), onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), - illustration: Illustrations.RunningTurtle, + illustration: lazyIllustrations.RunningTurtle, illustrationStyle: {width: 132, height: 150}, }, [CONST.MERGE_ACCOUNT_RESULTS.ERR_SAML_PRIMARY_LOGIN]: { diff --git a/src/pages/workspace/expensifyCard/EmptyCardView.tsx b/src/pages/workspace/expensifyCard/EmptyCardView.tsx index 1e43f3fac700..df008367d996 100644 --- a/src/pages/workspace/expensifyCard/EmptyCardView.tsx +++ b/src/pages/workspace/expensifyCard/EmptyCardView.tsx @@ -8,6 +8,7 @@ import CardRowSkeleton from '@components/Skeletons/CardRowSkeleton'; import Text from '@components/Text'; import useEmptyViewHeaderHeight from '@hooks/useEmptyViewHeaderHeight'; import useExpensifyCardUkEuSupported from '@hooks/useExpensifyCardUkEuSupported'; +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -31,6 +32,7 @@ function EmptyCardView({isBankAccountVerified, policyID, buttons}: EmptyCardView const {windowHeight} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const isUkEuCurrencySupported = useExpensifyCardUkEuSupported(policyID); + const lazyIllustrations = useMemoizedLazyIllustrations(['EmptyCardState'] as const); const headerHeight = useEmptyViewHeaderHeight(shouldUseNarrowLayout, isBankAccountVerified); @@ -43,7 +45,7 @@ function EmptyCardView({isBankAccountVerified, policyID, buttons}: EmptyCardView Date: Mon, 17 Nov 2025 22:24:54 +0500 Subject: [PATCH 02/16] Added lazyIllustrations in dependency array --- src/components/ApproverSelectionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ApproverSelectionList.tsx b/src/components/ApproverSelectionList.tsx index 3f1bb3f7da67..2355a20580da 100644 --- a/src/components/ApproverSelectionList.tsx +++ b/src/components/ApproverSelectionList.tsx @@ -139,7 +139,7 @@ function ApproverSelectionList({ contentFitImage="contain" /> ), - [translate, listEmptyContentSubtitle, styles.textSupporting, styles.pb10], + [translate, listEmptyContentSubtitle, styles.textSupporting, styles.pb10, lazyIllustrations], ); return ( From cef1107d838e2a936f3c3f27b56c2fcc3389c768 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Tue, 18 Nov 2025 12:23:28 +0500 Subject: [PATCH 03/16] Updated with main branch --- src/components/Icon/chunks/illustrations.chunk.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts index 43531c550129..f844e882bba2 100644 --- a/src/components/Icon/chunks/illustrations.chunk.ts +++ b/src/components/Icon/chunks/illustrations.chunk.ts @@ -13,7 +13,6 @@ import LaptopWithSecondScreenSync from '@assets/images/laptop-with-second-screen import LaptopWithSecondScreenX from '@assets/images/laptop-with-second-screen-x.svg'; // Product Illustrations import TeleScope from '@assets/images/product-illustrations/telescope.svg'; -<<<<<<< HEAD // Educational Illustrations import MultiScan from '@assets/images/educational-illustration__multi-scan.svg'; // Other Images @@ -23,9 +22,7 @@ import RunningTurtle from '@assets/images/running-turtle.svg'; import Shutter from '@assets/images/shutter.svg'; import ExpensifyApprovedLogo from '@assets/images/subscription-details__approvedlogo.svg'; import TurtleInShell from '@assets/images/turtle-in-shell.svg'; -======= import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg'; ->>>>>>> main // Simple Illustrations - Core ones that are actually used import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.svg'; import BlueShield from '@assets/images/simple-illustrations/simple-illustration__blueshield.svg'; From c9461ade67c301aceb7608005bff403d25a5e858 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Tue, 18 Nov 2025 14:12:42 +0500 Subject: [PATCH 04/16] Fixed lint issues --- src/components/ApproverSelectionList.tsx | 1 - src/components/CardPreview.tsx | 2 +- src/components/Icon/Illustrations.ts | 2 ++ .../Icon/chunks/illustrations.chunk.ts | 26 +++++++++++++----- src/libs/CardUtils.ts | 5 ++-- .../BaseOnboardingPurpose.tsx | 19 ++++++------- .../home/report/ReportDetailsExportPage.tsx | 5 ++-- .../step/IOURequestStepScan/index.native.tsx | 14 +++++----- .../request/step/IOURequestStepScan/index.tsx | 17 ++++++------ .../MergeAccounts/MergeResultPage.tsx | 27 +++++++++---------- .../workspace/expensifyCard/EmptyCardView.tsx | 5 ++-- 11 files changed, 67 insertions(+), 56 deletions(-) diff --git a/src/components/ApproverSelectionList.tsx b/src/components/ApproverSelectionList.tsx index 2355a20580da..41557347dc95 100644 --- a/src/components/ApproverSelectionList.tsx +++ b/src/components/ApproverSelectionList.tsx @@ -18,7 +18,6 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import BlockingView from './BlockingViews/BlockingView'; import FullPageNotFoundView from './BlockingViews/FullPageNotFoundView'; import HeaderWithBackButton from './HeaderWithBackButton'; -import * as Illustrations from './Icon/Illustrations'; import ScreenWrapper from './ScreenWrapper'; import SelectionList from './SelectionListWithSections'; import InviteMemberListItem from './SelectionListWithSections/InviteMemberListItem'; diff --git a/src/components/CardPreview.tsx b/src/components/CardPreview.tsx index 588e850e0f4f..62f0b9d3938f 100644 --- a/src/components/CardPreview.tsx +++ b/src/components/CardPreview.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {View} from 'react-native'; -import useOnyx from '@hooks/useOnyx'; import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 4bb9caa343a1..5ffcbc2af7a5 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -23,6 +23,7 @@ import WellsFargoCompanyCardDetailLarge from '@assets/images/companyCards/large/ import PendingBank from '@assets/images/companyCards/pending-bank.svg'; import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg'; import Computer from '@assets/images/computer.svg'; +import ExpensifyCardImage from '@assets/images/expensify-card.svg'; import LaptopOnDeskWithCoffeeAndKey from '@assets/images/laptop-on-desk-with-coffee-and-key.svg'; import LaptopWithSecondScreenSync from '@assets/images/laptop-with-second-screen-sync.svg'; import LaptopWithSecondScreenX from '@assets/images/laptop-with-second-screen-x.svg'; @@ -118,6 +119,7 @@ export { EmailAddress, EmptyStateExpenses, EnvelopeReceipt, + ExpensifyCardImage, PaymentHands, MagicCode, Mailbox, diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts index f844e882bba2..31f950410844 100644 --- a/src/components/Icon/chunks/illustrations.chunk.ts +++ b/src/components/Icon/chunks/illustrations.chunk.ts @@ -2,29 +2,30 @@ // Company Cards import type {SvgProps} from 'react-native-svg'; import CompanyCardsEmptyState from '@assets/images/companyCards/emptystate__card-pos.svg'; +import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg'; // Other assets import Computer from '@assets/images/computer.svg'; +// Educational Illustrations +import MultiScan from '@assets/images/educational-illustration__multi-scan.svg'; import EmptyCardState from '@assets/images/emptystate__expensifycard.svg'; import ExpensifyCardImage from '@assets/images/expensify-card.svg'; // Expensify Card import ExpensifyCardIllustration from '@assets/images/expensifyCard/cardIllustration.svg'; +// Other Images +import Hand from '@assets/images/hand.svg'; import LaptopWithSecondScreenAndHourglass from '@assets/images/laptop-with-second-screen-and-hourglass.svg'; import LaptopWithSecondScreenSync from '@assets/images/laptop-with-second-screen-sync.svg'; import LaptopWithSecondScreenX from '@assets/images/laptop-with-second-screen-x.svg'; // Product Illustrations import TeleScope from '@assets/images/product-illustrations/telescope.svg'; -// Educational Illustrations -import MultiScan from '@assets/images/educational-illustration__multi-scan.svg'; -// Other Images -import Hand from '@assets/images/hand.svg'; +import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg'; import ReceiptUpload from '@assets/images/receipt-upload.svg'; import RunningTurtle from '@assets/images/running-turtle.svg'; import Shutter from '@assets/images/shutter.svg'; -import ExpensifyApprovedLogo from '@assets/images/subscription-details__approvedlogo.svg'; -import TurtleInShell from '@assets/images/turtle-in-shell.svg'; -import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cloud.svg'; // Simple Illustrations - Core ones that are actually used +import Abacus from '@assets/images/simple-illustrations/simple-illustration__abacus.svg'; import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.svg'; +import Binoculars from '@assets/images/simple-illustrations/simple-illustration__binoculars.svg'; import BlueShield from '@assets/images/simple-illustrations/simple-illustration__blueshield.svg'; import Building from '@assets/images/simple-illustrations/simple-illustration__building.svg'; import CarIce from '@assets/images/simple-illustrations/simple-illustration__car-ice.svg'; @@ -33,23 +34,29 @@ import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustrat import FolderOpen from '@assets/images/simple-illustrations/simple-illustration__folder-open.svg'; import HandCard from '@assets/images/simple-illustrations/simple-illustration__handcard.svg'; import InvoiceBlue from '@assets/images/simple-illustrations/simple-illustration__invoice.svg'; +import LockClosedOrange from '@assets/images/simple-illustrations/simple-illustration__lockclosed_orange.svg'; import Luggage from '@assets/images/simple-illustrations/simple-illustration__luggage.svg'; import MagnifyingGlassMoney from '@assets/images/simple-illustrations/simple-illustration__magnifyingglass-money.svg'; import MoneyReceipts from '@assets/images/simple-illustrations/simple-illustration__money-receipts.svg'; import MoneyWings from '@assets/images/simple-illustrations/simple-illustration__moneywings.svg'; import Pencil from '@assets/images/simple-illustrations/simple-illustration__pencil.svg'; import PerDiem from '@assets/images/simple-illustrations/simple-illustration__perdiem.svg'; +import PiggyBank from '@assets/images/simple-illustrations/simple-illustration__piggybank.svg'; import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustration__receipt-wrangler.svg'; import ReportReceipt from '@assets/images/simple-illustrations/simple-illustration__report-receipt.svg'; import Rules from '@assets/images/simple-illustrations/simple-illustration__rules.svg'; +import SplitBill from '@assets/images/simple-illustrations/simple-illustration__splitbill.svg'; import Tag from '@assets/images/simple-illustrations/simple-illustration__tag.svg'; import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg'; import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg'; +import ExpensifyApprovedLogo from '@assets/images/subscription-details__approvedlogo.svg'; +import TurtleInShell from '@assets/images/turtle-in-shell.svg'; // Create the illustrations object with all imported illustrations const Illustrations = { // Company Cards CompanyCardsEmptyState, + CompanyCardsPendingState, // Other assets Computer, @@ -79,20 +86,25 @@ const Illustrations = { TurtleInShell, // Simple Illustrations + Abacus, Accounting, + Binoculars, Building, Coins, CreditCardsNew, FolderOpen, HandCard, InvoiceBlue, + LockClosedOrange, MagnifyingGlassMoney, MoneyReceipts, MoneyWings, PerDiem, + PiggyBank, ReceiptWrangler, ReportReceipt, Rules, + SplitBill, Tag, CompanyCard, Workflows, diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 5b30207565bf..a8e7918562ea 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -2,7 +2,6 @@ import {fromUnixTime, isBefore} from 'date-fns'; import groupBy from 'lodash/groupBy'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import ExpensifyCardImage from '@assets/images/expensify-card.svg'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type IllustrationsType from '@styles/theme/illustrations/types'; import * as Illustrations from '@src/components/Icon/Illustrations'; @@ -312,11 +311,11 @@ function getCardFeedIcon(cardFeed: CompanyCardFeed | typeof CONST.EXPENSIFY_CARD [CONST.COMPANY_CARD.FEED_BANK_NAME.STRIPE]: Illustrations.StripeCompanyCardDetailLarge, [CONST.COMPANY_CARD.FEED_BANK_NAME.CSV]: illustrations.GenericCSVCompanyCardLarge, [CONST.COMPANY_CARD.FEED_BANK_NAME.PEX]: illustrations.GenericCompanyCardLarge, - [CONST.EXPENSIFY_CARD.BANK]: ExpensifyCardImage, + [CONST.EXPENSIFY_CARD.BANK]: Illustrations.ExpensifyCardImage, }; if (cardFeed.startsWith(CONST.EXPENSIFY_CARD.BANK)) { - return ExpensifyCardImage; + return Illustrations.ExpensifyCardImage; } if (feedIcons[cardFeed]) { diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index 8ee9ab3f4047..11b3d6176359 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -4,11 +4,11 @@ import {InteractionManager, View} from 'react-native'; import {ScrollView} from 'react-native-gesture-handler'; import FormHelpMessage from '@components/FormHelpMessage'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import * as Illustrations from '@components/Icon/Illustrations'; import type {MenuItemProps} from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnboardingMessages from '@hooks/useOnboardingMessages'; import useOnyx from '@hooks/useOnyx'; @@ -39,15 +39,16 @@ function getOnboardingChoices(customChoices: OnboardingPurpose[]) { return selectableOnboardingChoices.filter((choice) => customChoices.includes(choice)); } -const menuIcons = { - [CONST.ONBOARDING_CHOICES.EMPLOYER]: Illustrations.ReceiptUpload, - [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: Illustrations.Abacus, - [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: Illustrations.PiggyBank, - [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: Illustrations.SplitBill, - [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: Illustrations.Binoculars, -}; - function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, route}: BaseOnboardingPurposeProps) { + const lazyIllustrations = useMemoizedLazyIllustrations(['ReceiptUpload', 'Abacus', 'PiggyBank', 'SplitBill', 'Binoculars'] as const); + + const menuIcons = { + [CONST.ONBOARDING_CHOICES.EMPLOYER]: lazyIllustrations.ReceiptUpload, + [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: lazyIllustrations.Abacus, + [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: lazyIllustrations.PiggyBank, + [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: lazyIllustrations.SplitBill, + [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: lazyIllustrations.Binoculars, + }; const styles = useThemeStyles(); const {translate} = useLocalize(); const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); diff --git a/src/pages/home/report/ReportDetailsExportPage.tsx b/src/pages/home/report/ReportDetailsExportPage.tsx index 797aa09d1cf5..e36ca19d91c0 100644 --- a/src/pages/home/report/ReportDetailsExportPage.tsx +++ b/src/pages/home/report/ReportDetailsExportPage.tsx @@ -3,7 +3,6 @@ import type {ValueOf} from 'type-fest'; import ConfirmationPage from '@components/ConfirmationPage'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import * as Illustrations from '@components/Icon/Illustrations'; import ScreenWrapper from '@components/ScreenWrapper'; import UserListItem from '@components/SelectionListWithSections/UserListItem'; import type {SelectorType} from '@components/SelectionScreen'; @@ -32,8 +31,8 @@ function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) { const connectionName = route?.params?.connectionName; const reportID = route.params.reportID; const backTo = route.params.backTo; - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); - const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {canBeMissing: true}); + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {canBeMissing: true}); const policyID = report?.policyID; const {translate} = useLocalize(); diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 373fd8edb35d..ce553b2ebab6 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -19,7 +19,6 @@ import Button from '@components/Button'; import FeatureTrainingModal from '@components/FeatureTrainingModal'; import {useFullScreenLoader} from '@components/FullScreenLoaderContext'; import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import ImageSVG from '@components/ImageSVG'; import LocationPermissionModal from '@components/LocationPermissionModal'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; @@ -28,7 +27,7 @@ import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalD import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useFilesValidation from '@hooks/useFilesValidation'; import useIOUUtils from '@hooks/useIOUUtils'; -import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; @@ -125,6 +124,7 @@ function IOURequestStepScan({ const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const lazyIllustrations = useMemoizedLazyIllustrations(['MultiScan', 'Hand', 'Shutter'] as const); + const lazyIcons = useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash'] as const); const platform = getPlatform(true); const [mutedPlatforms = getEmptyObject>>()] = useOnyx(ONYXKEYS.NVP_MUTED_PLATFORMS, {canBeMissing: true}); const isPlatformMuted = mutedPlatforms[platform]; @@ -801,7 +801,7 @@ function IOURequestStepScan({ @@ -908,7 +908,7 @@ function IOURequestStepScan({ @@ -938,7 +938,7 @@ function IOURequestStepScan({ @@ -953,7 +953,7 @@ function IOURequestStepScan({ diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 026f3e188168..682e91ed0229 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -17,7 +17,6 @@ import {DragAndDropContext} from '@components/DragAndDrop/Provider'; import DropZoneUI from '@components/DropZone/DropZoneUI'; import FeatureTrainingModal from '@components/FeatureTrainingModal'; import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import LocationPermissionModal from '@components/LocationPermissionModal'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import ReceiptAlternativeMethods from '@components/ReceiptAlternativeMethods'; @@ -27,7 +26,7 @@ import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalD import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useFilesValidation from '@hooks/useFilesValidation'; import useIOUUtils from '@hooks/useIOUUtils'; -import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; +import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; @@ -127,6 +126,7 @@ function IOURequestStepScan({ const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); const lazyIllustrations = useMemoizedLazyIllustrations(['MultiScan', 'Hand', 'ReceiptUpload', 'Shutter'] as const); + const lazyIcons = useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash', 'ReplaceReceipt', 'SmartScan'] as const); const isEditing = action === CONST.IOU.ACTION.EDIT; const canUseMultiScan = isStartingScan && iouType !== CONST.IOU.TYPE.SPLIT; const isReplacingReceipt = (isEditing && hasReceipt(initialTransaction)) || (!!initialTransaction?.receipt && !!backTo); @@ -905,7 +905,7 @@ function IOURequestStepScan({ @@ -938,7 +938,7 @@ function IOURequestStepScan({ @@ -967,7 +967,7 @@ function IOURequestStepScan({ @@ -982,7 +982,7 @@ function IOURequestStepScan({ @@ -1030,7 +1030,8 @@ function IOURequestStepScan({ }} > {PDFValidationComponent} - @@ -1086,7 +1087,7 @@ function IOURequestStepScan({ >(); const {environmentURL} = useEnvironment(); const {result, login, backTo} = params; - const lazyIllustrations = useMemoizedLazyIllustrations(['RunningTurtle'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['RunningTurtle', 'LockClosedOrange'] as const); const defaultResult = { heading: translate('mergeAccountsPage.mergeFailureGenericHeading'), buttonText: translate('common.buttonConfirm'), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }; const results: Record, ConfirmationPageProps> = useMemo(() => { @@ -70,7 +69,7 @@ function MergeResultPage() { ), onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), buttonText: translate('common.buttonConfirm'), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }, [CONST.MERGE_ACCOUNT_RESULTS.ERR_2FA]: { heading: translate('mergeAccountsPage.mergeFailureGenericHeading'), @@ -83,7 +82,7 @@ function MergeResultPage() { ctaStyle: {...styles.mt2, ...styles.textSupporting}, onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), buttonText: translate('common.buttonConfirm'), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }, [CONST.MERGE_ACCOUNT_RESULTS.ERR_SMART_SCANNER]: { heading: translate('mergeAccountsPage.mergeFailureGenericHeading'), @@ -93,7 +92,7 @@ function MergeResultPage() { ), buttonText: translate('common.buttonConfirm'), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), }, [CONST.MERGE_ACCOUNT_RESULTS.ERR_SAML_DOMAIN_CONTROL]: { @@ -105,7 +104,7 @@ function MergeResultPage() { ), buttonText: translate('common.buttonConfirm'), onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }, [CONST.MERGE_ACCOUNT_RESULTS.ERR_SAML_NOT_SUPPORTED]: { heading: translate('mergeAccountsPage.mergePendingSAML.weAreWorkingOnIt'), @@ -138,7 +137,7 @@ function MergeResultPage() { ), buttonText: translate('common.buttonConfirm'), onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }, [CONST.MERGE_ACCOUNT_RESULTS.ERR_ACCOUNT_LOCKED]: { heading: translate('mergeAccountsPage.mergeFailureGenericHeading'), @@ -149,7 +148,7 @@ function MergeResultPage() { ), buttonText: translate('common.buttonConfirm'), onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }, [CONST.MERGE_ACCOUNT_RESULTS.ERR_INVOICING]: { heading: translate('mergeAccountsPage.mergeFailureGenericHeading'), @@ -160,31 +159,31 @@ function MergeResultPage() { ), buttonText: translate('common.buttonConfirm'), onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }, [CONST.MERGE_ACCOUNT_RESULTS.TOO_MANY_ATTEMPTS]: { heading: translate('mergeAccountsPage.mergeFailureTooManyAttempts.heading'), description: translate('mergeAccountsPage.mergeFailureTooManyAttempts.description'), buttonText: translate('common.buttonConfirm'), onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }, [CONST.MERGE_ACCOUNT_RESULTS.ACCOUNT_UNVALIDATED]: { heading: translate('mergeAccountsPage.mergeFailureGenericHeading'), description: translate('mergeAccountsPage.mergeFailureUnvalidatedAccount.description'), buttonText: translate('common.buttonConfirm'), onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }, [CONST.MERGE_ACCOUNT_RESULTS.ERR_MERGE_SELF]: { heading: translate('mergeAccountsPage.mergeFailureGenericHeading'), description: translate('mergeAccountsPage.mergeFailureSelfMerge.description'), buttonText: translate('common.buttonConfirm'), onButtonPress: () => Navigation.goBack(ROUTES.SETTINGS_SECURITY), - illustration: Illustrations.LockClosedOrange, + illustration: lazyIllustrations.LockClosedOrange, }, }; - }, [login, translate, userEmailOrPhone, styles, environmentURL]); + }, [login, translate, userEmailOrPhone, styles, environmentURL, lazyIllustrations.LockClosedOrange, lazyIllustrations.RunningTurtle]); useEffect(() => { /** diff --git a/src/pages/workspace/expensifyCard/EmptyCardView.tsx b/src/pages/workspace/expensifyCard/EmptyCardView.tsx index df008367d996..9d2e6ffec371 100644 --- a/src/pages/workspace/expensifyCard/EmptyCardView.tsx +++ b/src/pages/workspace/expensifyCard/EmptyCardView.tsx @@ -2,7 +2,6 @@ import React from 'react'; import {View} from 'react-native'; import EmptyStateComponent from '@components/EmptyStateComponent'; import type {EmptyStateButton} from '@components/EmptyStateComponent/types'; -import * as Illustrations from '@components/Icon/Illustrations'; import ScrollView from '@components/ScrollView'; import CardRowSkeleton from '@components/Skeletons/CardRowSkeleton'; import Text from '@components/Text'; @@ -32,7 +31,7 @@ function EmptyCardView({isBankAccountVerified, policyID, buttons}: EmptyCardView const {windowHeight} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const isUkEuCurrencySupported = useExpensifyCardUkEuSupported(policyID); - const lazyIllustrations = useMemoizedLazyIllustrations(['EmptyCardState'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['EmptyCardState', 'CompanyCardsPendingState'] as const); const headerHeight = useEmptyViewHeaderHeight(shouldUseNarrowLayout, isBankAccountVerified); @@ -45,7 +44,7 @@ function EmptyCardView({isBankAccountVerified, policyID, buttons}: EmptyCardView Date: Tue, 18 Nov 2025 16:35:27 +0500 Subject: [PATCH 05/16] test: add lazy loading tests for migrated icons --- tests/ui/IOURequestStartPageTest.tsx | 23 ++++++++++++++++++++++- tests/ui/WalletExpensifyCardPageTest.tsx | 11 ++++++++++- tests/unit/CardUtilsTest.ts | 21 +++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/tests/ui/IOURequestStartPageTest.tsx b/tests/ui/IOURequestStartPageTest.tsx index e6dec5ce0861..2f1deb1476d9 100644 --- a/tests/ui/IOURequestStartPageTest.tsx +++ b/tests/ui/IOURequestStartPageTest.tsx @@ -1,10 +1,11 @@ import {NavigationContainer} from '@react-navigation/native'; -import {act, render} from '@testing-library/react-native'; +import {act, render, renderHook} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; +import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import type {IOURequestType} from '@libs/actions/IOU'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; @@ -90,4 +91,24 @@ describe('IOURequestStartPage', () => { }); expect(iouRequestType).toBe(CONST.IOU.REQUEST_TYPE.MANUAL); }); + + it('should load IOURequestStepScan illustrations via lazy loading', () => { + // Test that IOURequestStepScan's lazy-loaded illustrations are available + const {result} = renderHook(() => useMemoizedLazyIllustrations(['Hand', 'MultiScan', 'Shutter', 'ReceiptUpload'] as const)); + + expect(result.current.Hand).toBeDefined(); + expect(result.current.MultiScan).toBeDefined(); + expect(result.current.Shutter).toBeDefined(); + expect(result.current.ReceiptUpload).toBeDefined(); + }); + + it('should load IOURequestStepScan icons via lazy loading', () => { + // Test that IOURequestStepScan's lazy-loaded Expensify icons are available + const {result} = renderHook(() => useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash'] as const)); + + expect(result.current.Bolt).toBeDefined(); + expect(result.current.Gallery).toBeDefined(); + expect(result.current.ReceiptMultiple).toBeDefined(); + expect(result.current.boltSlash).toBeDefined(); + }); }); diff --git a/tests/ui/WalletExpensifyCardPageTest.tsx b/tests/ui/WalletExpensifyCardPageTest.tsx index a1b573117c3e..40f5881af5e0 100644 --- a/tests/ui/WalletExpensifyCardPageTest.tsx +++ b/tests/ui/WalletExpensifyCardPageTest.tsx @@ -1,12 +1,13 @@ import {PortalProvider} from '@gorhom/portal'; import {NavigationContainer} from '@react-navigation/native'; -import {act, render, screen, waitFor} from '@testing-library/react-native'; +import {act, render, renderHook, screen, waitFor} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; import ComposeProviders from '@components/ComposeProviders'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; import {CurrentReportIDContextProvider} from '@hooks/useCurrentReportID'; +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import * as useResponsiveLayoutModule from '@hooks/useResponsiveLayout'; import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator'; @@ -235,4 +236,12 @@ describe('ExpensifyCardPage', () => { unmount(); await waitForBatchedUpdatesWithAct(); }); + + it('should load ExpensifyCardImage via lazy loading in CardPreview', () => { + // Test that CardPreview's lazy-loaded ExpensifyCardImage icon is available + const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage'] as const)); + + // Verify the lazy-loaded icon is defined + expect(result.current.ExpensifyCardImage).toBeDefined(); + }); }); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 170015cd9d32..96dedf0e9e96 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -336,6 +336,7 @@ const mockIllustrations = { ExampleCheckEN: 'ExampleCheckEN', WorkspaceProfile: 'WorkspaceProfile', ExpensifyApprovedLogo: 'ExpensifyApprovedLogo', + ExpensifyCardImage: 'ExpensifyCardImage', GenericCompanyCard: 'GenericCompanyCard', GenericCSVCompanyCardLarge: 'GenericCSVCompanyCardLarge', GenericCompanyCardLarge: 'GenericCompanyCardLarge', @@ -1268,4 +1269,24 @@ describe('CardUtils', () => { expect(sorted.map((r: Card) => r.cardID)).toEqual([11, 10, 99]); }); }); + + describe('getCardFeedIcon - Lazy Loaded ExpensifyCardImage', () => { + it('should return ExpensifyCardImage for Expensify card feed', () => { + // Test that getCardFeedIcon correctly returns ExpensifyCardImage for Expensify cards + const feed = CONST.EXPENSIFY_CARD.BANK; + const illustration = getCardFeedIcon(feed, mockIllustrations as unknown as IllustrationsType); + + // Verify it returns the ExpensifyCardImage (now sourced from Illustrations.ts via chunk) + expect(illustration).toBe('ExpensifyCardImage'); + }); + + it('should return ExpensifyCardImage for Expensify card feed variations', () => { + // Test with feed name that starts with EXPENSIFY_CARD.BANK + const feedVariation = `${CONST.EXPENSIFY_CARD.BANK}_variant` as typeof CONST.EXPENSIFY_CARD.BANK; + const illustration = getCardFeedIcon(feedVariation, mockIllustrations as unknown as IllustrationsType); + + // Should still return ExpensifyCardImage for any feed starting with expensify + expect(illustration).toBe('ExpensifyCardImage'); + }); + }); }); From 29d6b8673e3fe127c856c75900a467579a5091c0 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Tue, 18 Nov 2025 16:55:33 +0500 Subject: [PATCH 06/16] fixed link issue --- tests/unit/CardUtilsTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 96dedf0e9e96..2c5b3cf894cb 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -1,6 +1,7 @@ import lodashSortBy from 'lodash/sortBy'; import type {OnyxCollection} from 'react-native-onyx'; import type IllustrationsType from '@styles/theme/illustrations/types'; +// eslint-disable-next-line no-restricted-imports import type * as Illustrations from '@src/components/Icon/Illustrations'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; From b626039ad51e78a96a40257f0040fc0d6c04056b Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Tue, 18 Nov 2025 17:05:08 +0500 Subject: [PATCH 07/16] fixed lint issue --- tests/unit/CardUtilsTest.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 2c5b3cf894cb..21efec62ff1f 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -1,8 +1,6 @@ import lodashSortBy from 'lodash/sortBy'; import type {OnyxCollection} from 'react-native-onyx'; import type IllustrationsType from '@styles/theme/illustrations/types'; -// eslint-disable-next-line no-restricted-imports -import type * as Illustrations from '@src/components/Icon/Illustrations'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; import { @@ -343,7 +341,8 @@ const mockIllustrations = { GenericCompanyCardLarge: 'GenericCompanyCardLarge', }; -jest.mock('@src/components/Icon/Illustrations', () => require('../../__mocks__/Illustrations') as typeof Illustrations); +// eslint-disable-next-line @typescript-eslint/no-unsafe-return +jest.mock('@src/components/Icon/Illustrations', () => require('../../__mocks__/Illustrations')); describe('CardUtils', () => { describe('Expiration date formatting', () => { From 79a2067299b9685d66b37f82b3f2c83f03787803 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Wed, 19 Nov 2025 19:16:27 +0500 Subject: [PATCH 08/16] Resolved feedbacks --- src/components/ApproverSelectionList.tsx | 2 +- src/components/CardPreview.tsx | 2 +- src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx | 2 +- src/pages/home/report/ReportDetailsExportPage.tsx | 2 +- src/pages/iou/ReceiptDropUI.tsx | 2 +- .../iou/request/step/IOURequestStepScan/index.native.tsx | 4 ++-- src/pages/iou/request/step/IOURequestStepScan/index.tsx | 4 ++-- .../settings/Security/MergeAccounts/MergeResultPage.tsx | 2 +- src/pages/workspace/expensifyCard/EmptyCardView.tsx | 2 +- tests/ui/IOURequestStartPageTest.tsx | 6 ++---- tests/ui/WalletExpensifyCardPageTest.tsx | 2 +- tests/unit/CardUtilsTest.ts | 6 ------ 12 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/components/ApproverSelectionList.tsx b/src/components/ApproverSelectionList.tsx index 41557347dc95..53d267de8499 100644 --- a/src/components/ApproverSelectionList.tsx +++ b/src/components/ApproverSelectionList.tsx @@ -82,7 +82,7 @@ function ApproverSelectionList({ const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); const shouldShowTextInput = shouldShowTextInputProp ?? allApprovers?.length >= CONST.STANDARD_LIST_ITEM_LIMIT; - const lazyIllustrations = useMemoizedLazyIllustrations(['TurtleInShell'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['TurtleInShell']); const [selectedMembers, setSelectedMembers] = useState([]); diff --git a/src/components/CardPreview.tsx b/src/components/CardPreview.tsx index 62f0b9d3938f..f9826a2ecd71 100644 --- a/src/components/CardPreview.tsx +++ b/src/components/CardPreview.tsx @@ -10,7 +10,7 @@ import Text from './Text'; function CardPreview() { const styles = useThemeStyles(); - const lazyIllustrations = useMemoizedLazyIllustrations(['ExpensifyCardImage'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['ExpensifyCardImage']); const [privatePersonalDetails] = useOnyx(ONYXKEYS.PRIVATE_PERSONAL_DETAILS, {canBeMissing: true}); const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true}); diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index 11b3d6176359..98b358fc4a72 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -40,7 +40,7 @@ function getOnboardingChoices(customChoices: OnboardingPurpose[]) { } function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, route}: BaseOnboardingPurposeProps) { - const lazyIllustrations = useMemoizedLazyIllustrations(['ReceiptUpload', 'Abacus', 'PiggyBank', 'SplitBill', 'Binoculars'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['ReceiptUpload', 'Abacus', 'PiggyBank', 'SplitBill', 'Binoculars']); const menuIcons = { [CONST.ONBOARDING_CHOICES.EMPLOYER]: lazyIllustrations.ReceiptUpload, diff --git a/src/pages/home/report/ReportDetailsExportPage.tsx b/src/pages/home/report/ReportDetailsExportPage.tsx index e36ca19d91c0..c3411d2379bc 100644 --- a/src/pages/home/report/ReportDetailsExportPage.tsx +++ b/src/pages/home/report/ReportDetailsExportPage.tsx @@ -38,7 +38,7 @@ function ReportDetailsExportPage({route}: ReportDetailsExportPageProps) { const {translate} = useLocalize(); const [modalStatus, setModalStatus] = useState(null); const styles = useThemeStyles(); - const lazyIllustrations = useMemoizedLazyIllustrations(['LaptopWithSecondScreenAndHourglass'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['LaptopWithSecondScreenAndHourglass']); const iconToDisplay = getIntegrationIcon(connectionName); const canBeExported = canBeExportedUtil(report); diff --git a/src/pages/iou/ReceiptDropUI.tsx b/src/pages/iou/ReceiptDropUI.tsx index 04ce74d8c5b8..a88a2208d985 100644 --- a/src/pages/iou/ReceiptDropUI.tsx +++ b/src/pages/iou/ReceiptDropUI.tsx @@ -19,7 +19,7 @@ type ReceiptDropUIProps = { function ReceiptDropUI({onDrop, receiptImageTopPosition}: ReceiptDropUIProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const lazyIllustrations = useMemoizedLazyIllustrations(['ReceiptUpload'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['ReceiptUpload']); return ( diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index ce553b2ebab6..594d979782d2 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -123,8 +123,8 @@ function IOURequestStepScan({ const defaultExpensePolicy = useDefaultExpensePolicy(); const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); - const lazyIllustrations = useMemoizedLazyIllustrations(['MultiScan', 'Hand', 'Shutter'] as const); - const lazyIcons = useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['MultiScan', 'Hand', 'Shutter']); + const lazyIcons = useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash']); const platform = getPlatform(true); const [mutedPlatforms = getEmptyObject>>()] = useOnyx(ONYXKEYS.NVP_MUTED_PLATFORMS, {canBeMissing: true}); const isPlatformMuted = mutedPlatforms[platform]; diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 682e91ed0229..67d14be8dd4e 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -125,8 +125,8 @@ function IOURequestStepScan({ const defaultExpensePolicy = useDefaultExpensePolicy(); const [dismissedProductTraining] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING, {canBeMissing: true}); const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); - const lazyIllustrations = useMemoizedLazyIllustrations(['MultiScan', 'Hand', 'ReceiptUpload', 'Shutter'] as const); - const lazyIcons = useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash', 'ReplaceReceipt', 'SmartScan'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['MultiScan', 'Hand', 'ReceiptUpload', 'Shutter']); + const lazyIcons = useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash', 'ReplaceReceipt', 'SmartScan']); const isEditing = action === CONST.IOU.ACTION.EDIT; const canUseMultiScan = isStartingScan && iouType !== CONST.IOU.TYPE.SPLIT; const isReplacingReceipt = (isEditing && hasReceipt(initialTransaction)) || (!!initialTransaction?.receipt && !!backTo); diff --git a/src/pages/settings/Security/MergeAccounts/MergeResultPage.tsx b/src/pages/settings/Security/MergeAccounts/MergeResultPage.tsx index 9ca1400d8851..e8f62e479f83 100644 --- a/src/pages/settings/Security/MergeAccounts/MergeResultPage.tsx +++ b/src/pages/settings/Security/MergeAccounts/MergeResultPage.tsx @@ -33,7 +33,7 @@ function MergeResultPage() { const {params} = useRoute>(); const {environmentURL} = useEnvironment(); const {result, login, backTo} = params; - const lazyIllustrations = useMemoizedLazyIllustrations(['RunningTurtle', 'LockClosedOrange'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['RunningTurtle', 'LockClosedOrange']); const defaultResult = { heading: translate('mergeAccountsPage.mergeFailureGenericHeading'), diff --git a/src/pages/workspace/expensifyCard/EmptyCardView.tsx b/src/pages/workspace/expensifyCard/EmptyCardView.tsx index 9d2e6ffec371..a3412a59aeb3 100644 --- a/src/pages/workspace/expensifyCard/EmptyCardView.tsx +++ b/src/pages/workspace/expensifyCard/EmptyCardView.tsx @@ -31,7 +31,7 @@ function EmptyCardView({isBankAccountVerified, policyID, buttons}: EmptyCardView const {windowHeight} = useWindowDimensions(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const isUkEuCurrencySupported = useExpensifyCardUkEuSupported(policyID); - const lazyIllustrations = useMemoizedLazyIllustrations(['EmptyCardState', 'CompanyCardsPendingState'] as const); + const lazyIllustrations = useMemoizedLazyIllustrations(['EmptyCardState', 'CompanyCardsPendingState']); const headerHeight = useEmptyViewHeaderHeight(shouldUseNarrowLayout, isBankAccountVerified); diff --git a/tests/ui/IOURequestStartPageTest.tsx b/tests/ui/IOURequestStartPageTest.tsx index 2f1deb1476d9..96472b6d3b2a 100644 --- a/tests/ui/IOURequestStartPageTest.tsx +++ b/tests/ui/IOURequestStartPageTest.tsx @@ -93,8 +93,7 @@ describe('IOURequestStartPage', () => { }); it('should load IOURequestStepScan illustrations via lazy loading', () => { - // Test that IOURequestStepScan's lazy-loaded illustrations are available - const {result} = renderHook(() => useMemoizedLazyIllustrations(['Hand', 'MultiScan', 'Shutter', 'ReceiptUpload'] as const)); + const {result} = renderHook(() => useMemoizedLazyIllustrations(['Hand', 'MultiScan', 'Shutter', 'ReceiptUpload'])); expect(result.current.Hand).toBeDefined(); expect(result.current.MultiScan).toBeDefined(); @@ -103,8 +102,7 @@ describe('IOURequestStartPage', () => { }); it('should load IOURequestStepScan icons via lazy loading', () => { - // Test that IOURequestStepScan's lazy-loaded Expensify icons are available - const {result} = renderHook(() => useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash'] as const)); + const {result} = renderHook(() => useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash'])); expect(result.current.Bolt).toBeDefined(); expect(result.current.Gallery).toBeDefined(); diff --git a/tests/ui/WalletExpensifyCardPageTest.tsx b/tests/ui/WalletExpensifyCardPageTest.tsx index 40f5881af5e0..06f9082c16fb 100644 --- a/tests/ui/WalletExpensifyCardPageTest.tsx +++ b/tests/ui/WalletExpensifyCardPageTest.tsx @@ -239,7 +239,7 @@ describe('ExpensifyCardPage', () => { it('should load ExpensifyCardImage via lazy loading in CardPreview', () => { // Test that CardPreview's lazy-loaded ExpensifyCardImage icon is available - const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage'] as const)); + const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage'])); // Verify the lazy-loaded icon is defined expect(result.current.ExpensifyCardImage).toBeDefined(); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 21efec62ff1f..b2dc4f7f9f3e 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -1272,20 +1272,14 @@ describe('CardUtils', () => { describe('getCardFeedIcon - Lazy Loaded ExpensifyCardImage', () => { it('should return ExpensifyCardImage for Expensify card feed', () => { - // Test that getCardFeedIcon correctly returns ExpensifyCardImage for Expensify cards const feed = CONST.EXPENSIFY_CARD.BANK; const illustration = getCardFeedIcon(feed, mockIllustrations as unknown as IllustrationsType); - - // Verify it returns the ExpensifyCardImage (now sourced from Illustrations.ts via chunk) expect(illustration).toBe('ExpensifyCardImage'); }); it('should return ExpensifyCardImage for Expensify card feed variations', () => { - // Test with feed name that starts with EXPENSIFY_CARD.BANK const feedVariation = `${CONST.EXPENSIFY_CARD.BANK}_variant` as typeof CONST.EXPENSIFY_CARD.BANK; const illustration = getCardFeedIcon(feedVariation, mockIllustrations as unknown as IllustrationsType); - - // Should still return ExpensifyCardImage for any feed starting with expensify expect(illustration).toBe('ExpensifyCardImage'); }); }); From 0c112abe7d4edadcc7803d26ace81e3cfd8801cd Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Wed, 19 Nov 2025 19:53:47 +0500 Subject: [PATCH 09/16] Added a sample test case to verify that the Icons/Illustrations display correctly after the migration. --- .../LazyIconsIllustrationsMigrationTest.tsx | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 tests/ui/LazyIconsIllustrationsMigrationTest.tsx diff --git a/tests/ui/LazyIconsIllustrationsMigrationTest.tsx b/tests/ui/LazyIconsIllustrationsMigrationTest.tsx new file mode 100644 index 000000000000..39d6778e4907 --- /dev/null +++ b/tests/ui/LazyIconsIllustrationsMigrationTest.tsx @@ -0,0 +1,145 @@ +import {renderHook} from '@testing-library/react-native'; +import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; + +/** + * Test suite to verify that migrated icons and illustrations + * display correctly after lazy loading migration. + * Migrated illustrations tested: + * - Computer + * - EmptyCardState + * - ExpensifyCardImage + * - LaptopWithSecondScreenAndHourglass + * - LaptopWithSecondScreenSync + * - LaptopWithSecondScreenX + * - RunningTurtle + * - ExpensifyApprovedLogo + * - TurtleInShell + * - MultiScan + * - Hand + * - ReceiptUpload + * - Shutter + */ +describe('Lazy Icons and Illustrations Migration', () => { + describe('Loading Multiple Assets', () => { + it('should load all migrated illustrations via useMemoizedLazyIllustrations', () => { + // Load multiple illustrations at once + // The chunk is loaded once and all assets are available + const {result} = renderHook(() => + useMemoizedLazyIllustrations([ + 'Computer', + 'EmptyCardState', + 'ExpensifyCardImage', + 'LaptopWithSecondScreenAndHourglass', + 'LaptopWithSecondScreenSync', + 'LaptopWithSecondScreenX', + 'RunningTurtle', + 'ExpensifyApprovedLogo', + 'TurtleInShell', + 'MultiScan', + 'Hand', + 'ReceiptUpload', + 'Shutter', + ]), + ); + + // Verify all illustrations are loaded + expect(result.current.Computer).toBeDefined(); + expect(result.current.EmptyCardState).toBeDefined(); + expect(result.current.ExpensifyCardImage).toBeDefined(); + expect(result.current.LaptopWithSecondScreenAndHourglass).toBeDefined(); + expect(result.current.LaptopWithSecondScreenSync).toBeDefined(); + expect(result.current.LaptopWithSecondScreenX).toBeDefined(); + expect(result.current.RunningTurtle).toBeDefined(); + expect(result.current.ExpensifyApprovedLogo).toBeDefined(); + expect(result.current.TurtleInShell).toBeDefined(); + expect(result.current.MultiScan).toBeDefined(); + expect(result.current.Hand).toBeDefined(); + expect(result.current.ReceiptUpload).toBeDefined(); + expect(result.current.Shutter).toBeDefined(); + }); + + it('should load ExpensifyCardImage illustration', () => { + // Test ExpensifyCardImage specifically as it's used in CardUtils + const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage'])); + + // Verify the lazy-loaded icon is defined + expect(result.current.ExpensifyCardImage).toBeDefined(); + }); + + it('should load ReceiptUpload illustration', () => { + // Test ReceiptUpload as it's used in BaseOnboardingPurpose + const {result} = renderHook(() => useMemoizedLazyIllustrations(['ReceiptUpload'])); + + expect(result.current.ReceiptUpload).toBeDefined(); + }); + + it('should load IOURequestStepScan illustrations (Hand, MultiScan, Shutter)', () => { + // Test illustrations used in IOURequestStepScan components + const {result} = renderHook(() => useMemoizedLazyIllustrations(['Hand', 'MultiScan', 'Shutter'])); + + expect(result.current.Hand).toBeDefined(); + expect(result.current.MultiScan).toBeDefined(); + expect(result.current.Shutter).toBeDefined(); + }); + + it('should load EmptyCardState illustration', () => { + // Test EmptyCardState used in EmptyCardView + const {result} = renderHook(() => useMemoizedLazyIllustrations(['EmptyCardState'])); + + expect(result.current.EmptyCardState).toBeDefined(); + }); + + it('should load LaptopWithSecondScreen illustrations', () => { + // Test LaptopWithSecondScreen variants used in ReportDetailsExportPage + const {result} = renderHook(() => useMemoizedLazyIllustrations(['LaptopWithSecondScreenAndHourglass', 'LaptopWithSecondScreenSync', 'LaptopWithSecondScreenX'])); + + expect(result.current.LaptopWithSecondScreenAndHourglass).toBeDefined(); + expect(result.current.LaptopWithSecondScreenSync).toBeDefined(); + expect(result.current.LaptopWithSecondScreenX).toBeDefined(); + }); + + it('should load RunningTurtle and TurtleInShell illustrations', () => { + // Test turtle illustrations used in MergeResultPage and ApproverSelectionList + const {result} = renderHook(() => useMemoizedLazyIllustrations(['RunningTurtle', 'TurtleInShell'])); + + expect(result.current.RunningTurtle).toBeDefined(); + expect(result.current.TurtleInShell).toBeDefined(); + }); + + it('should load ExpensifyApprovedLogo illustration', () => { + // Test ExpensifyApprovedLogo + const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyApprovedLogo'])); + + expect(result.current.ExpensifyApprovedLogo).toBeDefined(); + }); + + it('should load Computer illustration', () => { + // Test Computer illustration + const {result} = renderHook(() => useMemoizedLazyIllustrations(['Computer'])); + + expect(result.current.Computer).toBeDefined(); + }); + }); + + describe('Illustration Loading Behavior', () => { + it('should handle loading multiple illustrations in a single hook call', () => { + // Load multiple illustrations at once + // The chunk is loaded once and all assets are available + const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage', 'EmptyCardState', 'ReceiptUpload'])); + + expect(result.current.ExpensifyCardImage).toBeDefined(); + expect(result.current.EmptyCardState).toBeDefined(); + expect(result.current.ReceiptUpload).toBeDefined(); + }); + + it('should return valid IconAsset components that can be used in Icon src prop', () => { + // Verify that loaded illustrations are valid IconAsset components + const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage', 'Hand', 'MultiScan'])); + + // All illustrations should be defined (valid React components) + expect(result.current.ExpensifyCardImage).toBeDefined(); + expect(result.current.Hand).toBeDefined(); + expect(result.current.MultiScan).toBeDefined(); + }); + }); +}); From b991d44fe02a26ceb563aa399d0132383ca68a46 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Thu, 20 Nov 2025 02:49:06 +0500 Subject: [PATCH 10/16] removed irrelevent icons change --- .../Icon/chunks/illustrations.chunk.ts | 10 +--------- .../BaseOnboardingPurpose.tsx | 19 +++++++++---------- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts index 9e2ddad65a69..e6fae2d561d5 100644 --- a/src/components/Icon/chunks/illustrations.chunk.ts +++ b/src/components/Icon/chunks/illustrations.chunk.ts @@ -2,8 +2,8 @@ // Company Cards import type {SvgProps} from 'react-native-svg'; import CompanyCardsEmptyState from '@assets/images/companyCards/emptystate__card-pos.svg'; -import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg'; import PendingBank from '@assets/images/companyCards/pending-bank.svg'; +import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_laptop-with-hourglass-and-cards.svg'; // Other assets import Computer from '@assets/images/computer.svg'; // Educational Illustrations @@ -24,9 +24,7 @@ import ReceiptUpload from '@assets/images/receipt-upload.svg'; import RunningTurtle from '@assets/images/running-turtle.svg'; import Shutter from '@assets/images/shutter.svg'; // Simple Illustrations - Core ones that are actually used -import Abacus from '@assets/images/simple-illustrations/simple-illustration__abacus.svg'; import Accounting from '@assets/images/simple-illustrations/simple-illustration__accounting.svg'; -import Binoculars from '@assets/images/simple-illustrations/simple-illustration__binoculars.svg'; import BlueShield from '@assets/images/simple-illustrations/simple-illustration__blueshield.svg'; import Building from '@assets/images/simple-illustrations/simple-illustration__building.svg'; import CarIce from '@assets/images/simple-illustrations/simple-illustration__car-ice.svg'; @@ -42,11 +40,9 @@ import MoneyReceipts from '@assets/images/simple-illustrations/simple-illustrati import MoneyWings from '@assets/images/simple-illustrations/simple-illustration__moneywings.svg'; import Pencil from '@assets/images/simple-illustrations/simple-illustration__pencil.svg'; import PerDiem from '@assets/images/simple-illustrations/simple-illustration__perdiem.svg'; -import PiggyBank from '@assets/images/simple-illustrations/simple-illustration__piggybank.svg'; import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustration__receipt-wrangler.svg'; import ReportReceipt from '@assets/images/simple-illustrations/simple-illustration__report-receipt.svg'; import Rules from '@assets/images/simple-illustrations/simple-illustration__rules.svg'; -import SplitBill from '@assets/images/simple-illustrations/simple-illustration__splitbill.svg'; import Tag from '@assets/images/simple-illustrations/simple-illustration__tag.svg'; import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg'; import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg'; @@ -88,9 +84,7 @@ const Illustrations = { TurtleInShell, // Simple Illustrations - Abacus, Accounting, - Binoculars, Building, Coins, CreditCardsNew, @@ -102,11 +96,9 @@ const Illustrations = { MoneyReceipts, MoneyWings, PerDiem, - PiggyBank, ReceiptWrangler, ReportReceipt, Rules, - SplitBill, Tag, CompanyCard, Workflows, diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index 98b358fc4a72..8ee9ab3f4047 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -4,11 +4,11 @@ import {InteractionManager, View} from 'react-native'; import {ScrollView} from 'react-native-gesture-handler'; import FormHelpMessage from '@components/FormHelpMessage'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Illustrations from '@components/Icon/Illustrations'; import type {MenuItemProps} from '@components/MenuItem'; import MenuItemList from '@components/MenuItemList'; import ScreenWrapper from '@components/ScreenWrapper'; import Text from '@components/Text'; -import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnboardingMessages from '@hooks/useOnboardingMessages'; import useOnyx from '@hooks/useOnyx'; @@ -39,16 +39,15 @@ function getOnboardingChoices(customChoices: OnboardingPurpose[]) { return selectableOnboardingChoices.filter((choice) => customChoices.includes(choice)); } +const menuIcons = { + [CONST.ONBOARDING_CHOICES.EMPLOYER]: Illustrations.ReceiptUpload, + [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: Illustrations.Abacus, + [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: Illustrations.PiggyBank, + [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: Illustrations.SplitBill, + [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: Illustrations.Binoculars, +}; + function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, route}: BaseOnboardingPurposeProps) { - const lazyIllustrations = useMemoizedLazyIllustrations(['ReceiptUpload', 'Abacus', 'PiggyBank', 'SplitBill', 'Binoculars']); - - const menuIcons = { - [CONST.ONBOARDING_CHOICES.EMPLOYER]: lazyIllustrations.ReceiptUpload, - [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: lazyIllustrations.Abacus, - [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: lazyIllustrations.PiggyBank, - [CONST.ONBOARDING_CHOICES.CHAT_SPLIT]: lazyIllustrations.SplitBill, - [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: lazyIllustrations.Binoculars, - }; const styles = useThemeStyles(); const {translate} = useLocalize(); const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); From 58c09bdcb712fd148acb987f0e7dd9d2818379ea Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Thu, 20 Nov 2025 12:26:36 +0500 Subject: [PATCH 11/16] removed UI test cases --- tests/ui/IOURequestStartPageTest.tsx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tests/ui/IOURequestStartPageTest.tsx b/tests/ui/IOURequestStartPageTest.tsx index 96472b6d3b2a..29f0dd52309d 100644 --- a/tests/ui/IOURequestStartPageTest.tsx +++ b/tests/ui/IOURequestStartPageTest.tsx @@ -91,22 +91,4 @@ describe('IOURequestStartPage', () => { }); expect(iouRequestType).toBe(CONST.IOU.REQUEST_TYPE.MANUAL); }); - - it('should load IOURequestStepScan illustrations via lazy loading', () => { - const {result} = renderHook(() => useMemoizedLazyIllustrations(['Hand', 'MultiScan', 'Shutter', 'ReceiptUpload'])); - - expect(result.current.Hand).toBeDefined(); - expect(result.current.MultiScan).toBeDefined(); - expect(result.current.Shutter).toBeDefined(); - expect(result.current.ReceiptUpload).toBeDefined(); - }); - - it('should load IOURequestStepScan icons via lazy loading', () => { - const {result} = renderHook(() => useMemoizedLazyExpensifyIcons(['Bolt', 'Gallery', 'ReceiptMultiple', 'boltSlash'])); - - expect(result.current.Bolt).toBeDefined(); - expect(result.current.Gallery).toBeDefined(); - expect(result.current.ReceiptMultiple).toBeDefined(); - expect(result.current.boltSlash).toBeDefined(); - }); }); From 719c554735b637046d9c23c559ca3ef65276a9ac Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Thu, 20 Nov 2025 12:28:46 +0500 Subject: [PATCH 12/16] removed UI test cases --- tests/ui/IOURequestStartPageTest.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/ui/IOURequestStartPageTest.tsx b/tests/ui/IOURequestStartPageTest.tsx index 29f0dd52309d..e6dec5ce0861 100644 --- a/tests/ui/IOURequestStartPageTest.tsx +++ b/tests/ui/IOURequestStartPageTest.tsx @@ -1,11 +1,10 @@ import {NavigationContainer} from '@react-navigation/native'; -import {act, render, renderHook} from '@testing-library/react-native'; +import {act, render} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; -import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import type {IOURequestType} from '@libs/actions/IOU'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; From 6c84be81a6a267bdbaa0ab1c656dfc0b645952b0 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Thu, 20 Nov 2025 13:15:07 +0500 Subject: [PATCH 13/16] resolve feedbacks --- .../LazyIconsIllustrationsMigrationTest.tsx | 145 ------------------ tests/ui/WalletExpensifyCardPageTest.tsx | 11 +- tests/unit/CardUtilsTest.ts | 14 -- 3 files changed, 1 insertion(+), 169 deletions(-) delete mode 100644 tests/ui/LazyIconsIllustrationsMigrationTest.tsx diff --git a/tests/ui/LazyIconsIllustrationsMigrationTest.tsx b/tests/ui/LazyIconsIllustrationsMigrationTest.tsx deleted file mode 100644 index 39d6778e4907..000000000000 --- a/tests/ui/LazyIconsIllustrationsMigrationTest.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import {renderHook} from '@testing-library/react-native'; -import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; - -/** - * Test suite to verify that migrated icons and illustrations - * display correctly after lazy loading migration. - * Migrated illustrations tested: - * - Computer - * - EmptyCardState - * - ExpensifyCardImage - * - LaptopWithSecondScreenAndHourglass - * - LaptopWithSecondScreenSync - * - LaptopWithSecondScreenX - * - RunningTurtle - * - ExpensifyApprovedLogo - * - TurtleInShell - * - MultiScan - * - Hand - * - ReceiptUpload - * - Shutter - */ -describe('Lazy Icons and Illustrations Migration', () => { - describe('Loading Multiple Assets', () => { - it('should load all migrated illustrations via useMemoizedLazyIllustrations', () => { - // Load multiple illustrations at once - // The chunk is loaded once and all assets are available - const {result} = renderHook(() => - useMemoizedLazyIllustrations([ - 'Computer', - 'EmptyCardState', - 'ExpensifyCardImage', - 'LaptopWithSecondScreenAndHourglass', - 'LaptopWithSecondScreenSync', - 'LaptopWithSecondScreenX', - 'RunningTurtle', - 'ExpensifyApprovedLogo', - 'TurtleInShell', - 'MultiScan', - 'Hand', - 'ReceiptUpload', - 'Shutter', - ]), - ); - - // Verify all illustrations are loaded - expect(result.current.Computer).toBeDefined(); - expect(result.current.EmptyCardState).toBeDefined(); - expect(result.current.ExpensifyCardImage).toBeDefined(); - expect(result.current.LaptopWithSecondScreenAndHourglass).toBeDefined(); - expect(result.current.LaptopWithSecondScreenSync).toBeDefined(); - expect(result.current.LaptopWithSecondScreenX).toBeDefined(); - expect(result.current.RunningTurtle).toBeDefined(); - expect(result.current.ExpensifyApprovedLogo).toBeDefined(); - expect(result.current.TurtleInShell).toBeDefined(); - expect(result.current.MultiScan).toBeDefined(); - expect(result.current.Hand).toBeDefined(); - expect(result.current.ReceiptUpload).toBeDefined(); - expect(result.current.Shutter).toBeDefined(); - }); - - it('should load ExpensifyCardImage illustration', () => { - // Test ExpensifyCardImage specifically as it's used in CardUtils - const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage'])); - - // Verify the lazy-loaded icon is defined - expect(result.current.ExpensifyCardImage).toBeDefined(); - }); - - it('should load ReceiptUpload illustration', () => { - // Test ReceiptUpload as it's used in BaseOnboardingPurpose - const {result} = renderHook(() => useMemoizedLazyIllustrations(['ReceiptUpload'])); - - expect(result.current.ReceiptUpload).toBeDefined(); - }); - - it('should load IOURequestStepScan illustrations (Hand, MultiScan, Shutter)', () => { - // Test illustrations used in IOURequestStepScan components - const {result} = renderHook(() => useMemoizedLazyIllustrations(['Hand', 'MultiScan', 'Shutter'])); - - expect(result.current.Hand).toBeDefined(); - expect(result.current.MultiScan).toBeDefined(); - expect(result.current.Shutter).toBeDefined(); - }); - - it('should load EmptyCardState illustration', () => { - // Test EmptyCardState used in EmptyCardView - const {result} = renderHook(() => useMemoizedLazyIllustrations(['EmptyCardState'])); - - expect(result.current.EmptyCardState).toBeDefined(); - }); - - it('should load LaptopWithSecondScreen illustrations', () => { - // Test LaptopWithSecondScreen variants used in ReportDetailsExportPage - const {result} = renderHook(() => useMemoizedLazyIllustrations(['LaptopWithSecondScreenAndHourglass', 'LaptopWithSecondScreenSync', 'LaptopWithSecondScreenX'])); - - expect(result.current.LaptopWithSecondScreenAndHourglass).toBeDefined(); - expect(result.current.LaptopWithSecondScreenSync).toBeDefined(); - expect(result.current.LaptopWithSecondScreenX).toBeDefined(); - }); - - it('should load RunningTurtle and TurtleInShell illustrations', () => { - // Test turtle illustrations used in MergeResultPage and ApproverSelectionList - const {result} = renderHook(() => useMemoizedLazyIllustrations(['RunningTurtle', 'TurtleInShell'])); - - expect(result.current.RunningTurtle).toBeDefined(); - expect(result.current.TurtleInShell).toBeDefined(); - }); - - it('should load ExpensifyApprovedLogo illustration', () => { - // Test ExpensifyApprovedLogo - const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyApprovedLogo'])); - - expect(result.current.ExpensifyApprovedLogo).toBeDefined(); - }); - - it('should load Computer illustration', () => { - // Test Computer illustration - const {result} = renderHook(() => useMemoizedLazyIllustrations(['Computer'])); - - expect(result.current.Computer).toBeDefined(); - }); - }); - - describe('Illustration Loading Behavior', () => { - it('should handle loading multiple illustrations in a single hook call', () => { - // Load multiple illustrations at once - // The chunk is loaded once and all assets are available - const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage', 'EmptyCardState', 'ReceiptUpload'])); - - expect(result.current.ExpensifyCardImage).toBeDefined(); - expect(result.current.EmptyCardState).toBeDefined(); - expect(result.current.ReceiptUpload).toBeDefined(); - }); - - it('should return valid IconAsset components that can be used in Icon src prop', () => { - // Verify that loaded illustrations are valid IconAsset components - const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage', 'Hand', 'MultiScan'])); - - // All illustrations should be defined (valid React components) - expect(result.current.ExpensifyCardImage).toBeDefined(); - expect(result.current.Hand).toBeDefined(); - expect(result.current.MultiScan).toBeDefined(); - }); - }); -}); diff --git a/tests/ui/WalletExpensifyCardPageTest.tsx b/tests/ui/WalletExpensifyCardPageTest.tsx index 06f9082c16fb..a1b573117c3e 100644 --- a/tests/ui/WalletExpensifyCardPageTest.tsx +++ b/tests/ui/WalletExpensifyCardPageTest.tsx @@ -1,13 +1,12 @@ import {PortalProvider} from '@gorhom/portal'; import {NavigationContainer} from '@react-navigation/native'; -import {act, render, renderHook, screen, waitFor} from '@testing-library/react-native'; +import {act, render, screen, waitFor} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; import ComposeProviders from '@components/ComposeProviders'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; import {CurrentReportIDContextProvider} from '@hooks/useCurrentReportID'; -import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import * as useResponsiveLayoutModule from '@hooks/useResponsiveLayout'; import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator'; @@ -236,12 +235,4 @@ describe('ExpensifyCardPage', () => { unmount(); await waitForBatchedUpdatesWithAct(); }); - - it('should load ExpensifyCardImage via lazy loading in CardPreview', () => { - // Test that CardPreview's lazy-loaded ExpensifyCardImage icon is available - const {result} = renderHook(() => useMemoizedLazyIllustrations(['ExpensifyCardImage'])); - - // Verify the lazy-loaded icon is defined - expect(result.current.ExpensifyCardImage).toBeDefined(); - }); }); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index b2dc4f7f9f3e..8650683bc4c8 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -1269,18 +1269,4 @@ describe('CardUtils', () => { expect(sorted.map((r: Card) => r.cardID)).toEqual([11, 10, 99]); }); }); - - describe('getCardFeedIcon - Lazy Loaded ExpensifyCardImage', () => { - it('should return ExpensifyCardImage for Expensify card feed', () => { - const feed = CONST.EXPENSIFY_CARD.BANK; - const illustration = getCardFeedIcon(feed, mockIllustrations as unknown as IllustrationsType); - expect(illustration).toBe('ExpensifyCardImage'); - }); - - it('should return ExpensifyCardImage for Expensify card feed variations', () => { - const feedVariation = `${CONST.EXPENSIFY_CARD.BANK}_variant` as typeof CONST.EXPENSIFY_CARD.BANK; - const illustration = getCardFeedIcon(feedVariation, mockIllustrations as unknown as IllustrationsType); - expect(illustration).toBe('ExpensifyCardImage'); - }); - }); }); From c9ac4bb90d3f4b556a8fb61e717fc19e864b0df2 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Thu, 20 Nov 2025 13:50:46 +0500 Subject: [PATCH 14/16] resolved feedbacks --- src/components/Icon/chunks/illustrations.chunk.ts | 3 +-- tests/unit/CardUtilsTest.ts | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts index 1a5bedb7ee6f..000e468644d6 100644 --- a/src/components/Icon/chunks/illustrations.chunk.ts +++ b/src/components/Icon/chunks/illustrations.chunk.ts @@ -8,9 +8,9 @@ import CompanyCardsPendingState from '@assets/images/companyCards/pendingstate_l import Computer from '@assets/images/computer.svg'; // Educational Illustrations import MultiScan from '@assets/images/educational-illustration__multi-scan.svg'; +// Expensify Card import EmptyCardState from '@assets/images/emptystate__expensifycard.svg'; import ExpensifyCardImage from '@assets/images/expensify-card.svg'; -// Expensify Card import ExpensifyCardIllustration from '@assets/images/expensifyCard/cardIllustration.svg'; // Other Images import Hand from '@assets/images/hand.svg'; @@ -24,7 +24,6 @@ import ToddBehindCloud from '@assets/images/product-illustrations/todd-behind-cl import ReceiptUpload from '@assets/images/receipt-upload.svg'; import RunningTurtle from '@assets/images/running-turtle.svg'; import Shutter from '@assets/images/shutter.svg'; -// Simple Illustrations - Core ones that are actually used // Simple Illustrations - Bucket 3 (Common) import Puzzle from '@assets/images/simple-illustrations/emptystate__puzzlepieces.svg'; import Abacus from '@assets/images/simple-illustrations/simple-illustration__abacus.svg'; diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index 8650683bc4c8..170015cd9d32 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -1,6 +1,7 @@ import lodashSortBy from 'lodash/sortBy'; import type {OnyxCollection} from 'react-native-onyx'; import type IllustrationsType from '@styles/theme/illustrations/types'; +import type * as Illustrations from '@src/components/Icon/Illustrations'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; import { @@ -335,14 +336,12 @@ const mockIllustrations = { ExampleCheckEN: 'ExampleCheckEN', WorkspaceProfile: 'WorkspaceProfile', ExpensifyApprovedLogo: 'ExpensifyApprovedLogo', - ExpensifyCardImage: 'ExpensifyCardImage', GenericCompanyCard: 'GenericCompanyCard', GenericCSVCompanyCardLarge: 'GenericCSVCompanyCardLarge', GenericCompanyCardLarge: 'GenericCompanyCardLarge', }; -// eslint-disable-next-line @typescript-eslint/no-unsafe-return -jest.mock('@src/components/Icon/Illustrations', () => require('../../__mocks__/Illustrations')); +jest.mock('@src/components/Icon/Illustrations', () => require('../../__mocks__/Illustrations') as typeof Illustrations); describe('CardUtils', () => { describe('Expiration date formatting', () => { From 118d51f0db8475aa3af3b30a867c339354a04416 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Thu, 20 Nov 2025 13:53:07 +0500 Subject: [PATCH 15/16] resolved feedbacks --- src/components/Icon/chunks/illustrations.chunk.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts index 000e468644d6..68b1e7ce804d 100644 --- a/src/components/Icon/chunks/illustrations.chunk.ts +++ b/src/components/Icon/chunks/illustrations.chunk.ts @@ -119,7 +119,6 @@ const Illustrations = { TurtleInShell, // Simple Illustrations - // Simple Illustrations - Original Accounting, Building, Coins, From 8e3f9a735ed603ddbd458b81979746469b511332 Mon Sep 17 00:00:00 2001 From: Faizan Shoukat Abbasi Date: Thu, 20 Nov 2025 13:57:34 +0500 Subject: [PATCH 16/16] resolved feedbacks --- src/components/Icon/chunks/illustrations.chunk.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/Icon/chunks/illustrations.chunk.ts b/src/components/Icon/chunks/illustrations.chunk.ts index 68b1e7ce804d..b1f566acf446 100644 --- a/src/components/Icon/chunks/illustrations.chunk.ts +++ b/src/components/Icon/chunks/illustrations.chunk.ts @@ -143,8 +143,6 @@ const Illustrations = { BlueShield, Pencil, Luggage, - - // Simple Illustrations - Bucket 3 (Common) Puzzle, Abacus, Alert,