From e4253f523de23ae18f27d8e8bdae60f9720d6cc9 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Tue, 20 Jan 2026 01:28:38 +0100 Subject: [PATCH 01/12] fix: Update loading logic for company cards based on feed type --- .../companyCards/WorkspaceCompanyCardsTable/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index 7a6a488ec59a5..8cd8ca7ef903c 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -70,7 +70,10 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl const isFeedPending = !!selectedFeed?.pending; const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || policy?.id === undefined || isLoadingOnyxValue(lastSelectedFeedMetadata); - const isLoadingCards = cardFeedType === 'directFeed' ? selectedFeed?.accountList === undefined : isLoadingOnyxValue(cardListMetadata) || cardList === undefined; + const isDomainLevelFeed = selectedFeed?.preferredPolicy === policy?.id; + const isLoadingCards = isDomainLevelFeed ? isLoadingOnyxValue(cardListMetadata) : ( + cardFeedType === 'directFeed' ? selectedFeed?.accountList === undefined : isLoadingOnyxValue(cardListMetadata) || cardList === undefined + ); const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata)); const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed; From 4350f3ea6bd46c7b7bed22b53ff31d56dcca262b Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Tue, 20 Jan 2026 01:28:53 +0100 Subject: [PATCH 02/12] fix: Refactor loading logic for company cards to improve readability --- .../companyCards/WorkspaceCompanyCardsTable/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index 8cd8ca7ef903c..04b5726bbf4c6 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -71,9 +71,11 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || policy?.id === undefined || isLoadingOnyxValue(lastSelectedFeedMetadata); const isDomainLevelFeed = selectedFeed?.preferredPolicy === policy?.id; - const isLoadingCards = isDomainLevelFeed ? isLoadingOnyxValue(cardListMetadata) : ( - cardFeedType === 'directFeed' ? selectedFeed?.accountList === undefined : isLoadingOnyxValue(cardListMetadata) || cardList === undefined - ); + const isLoadingCards = isDomainLevelFeed + ? isLoadingOnyxValue(cardListMetadata) + : cardFeedType === 'directFeed' + ? selectedFeed?.accountList === undefined + : isLoadingOnyxValue(cardListMetadata) || cardList === undefined; const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata)); const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed; From f03a1ce9344b1e85d8c130bc0a275c79e492231c Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Tue, 20 Jan 2026 01:50:38 +0100 Subject: [PATCH 03/12] fix: Enhance loading logic for company cards to improve clarity --- .../WorkspaceCompanyCardsTable/index.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index 04b5726bbf4c6..5060dfca5c134 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -71,11 +71,14 @@ function WorkspaceCompanyCardsTable({policy, onAssignCard, isAssigningCardDisabl const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || policy?.id === undefined || isLoadingOnyxValue(lastSelectedFeedMetadata); const isDomainLevelFeed = selectedFeed?.preferredPolicy === policy?.id; - const isLoadingCards = isDomainLevelFeed - ? isLoadingOnyxValue(cardListMetadata) - : cardFeedType === 'directFeed' - ? selectedFeed?.accountList === undefined - : isLoadingOnyxValue(cardListMetadata) || cardList === undefined; + let isLoadingCards; + if (isDomainLevelFeed) { + isLoadingCards = isLoadingOnyxValue(cardListMetadata); + } else if (cardFeedType === 'directFeed') { + isLoadingCards = selectedFeed?.accountList === undefined; + } else { + isLoadingCards = isLoadingOnyxValue(cardListMetadata) || cardList === undefined; + } const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata)); const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed; From 9a0fe2656a8ee99fdb4904a3249c4066d3e32755 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Tue, 27 Jan 2026 18:17:37 +0100 Subject: [PATCH 04/12] fix: Update loading state logic for company cards table --- .../workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index ba734b633e167..09ce938f76ec8 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -117,7 +117,7 @@ function WorkspaceCompanyCardsTable({ } const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || !isPolicyLoaded || isLoadingOnyxValue(lastSelectedFeedMetadata) || !!selectedFeedStatus?.isLoading; - const isLoadingCards = cardFeedType === 'directFeed' ? selectedFeed?.accountList === undefined : isLoadingOnyxValue(cardListMetadata) || cardList === undefined; + const isLoadingCards = (selectedFeed?.accountList === undefined && cardList === undefined) || isLoadingOnyxValue(cardListMetadata); const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata) || areWorkspaceCardFeedsLoading); const isShowingLoadingState = isLoadingPage || isLoadingFeed; From 527c5b108a4ceed0e2212be44764c81b22b2c608 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Tue, 27 Jan 2026 21:11:00 +0100 Subject: [PATCH 05/12] Refactor loading state logic in WorkspaceCompanyCardsTable to simplify isLoadingCards condition --- .../workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index 27e62a90eab47..5273561965830 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -79,7 +79,7 @@ function WorkspaceCompanyCardsTable({policyID, isPolicyLoaded, domainOrWorkspace const hasNoAssignedCard = Object.keys(assignedCards ?? {}).length === 0; const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || !isPolicyLoaded || isLoadingOnyxValue(lastSelectedFeedMetadata); - const isLoadingCards = (selectedFeed?.accountList === undefined && cardList === undefined) || isLoadingOnyxValue(cardListMetadata); + const isLoadingCards = (selectedFeed?.accountList === undefined && cardList === undefined) ? isLoadingOnyxValue(cardListMetadata) : false; const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata)); const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed; From 21f4527fe6cb3776c7a9ede2e5f62e3409f4e0ee Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Wed, 28 Jan 2026 11:16:51 +0100 Subject: [PATCH 06/12] Refactor isLoadingCards condition in WorkspaceCompanyCardsTable for improved readability --- .../workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index 5273561965830..822ef57d37a2e 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -79,7 +79,7 @@ function WorkspaceCompanyCardsTable({policyID, isPolicyLoaded, domainOrWorkspace const hasNoAssignedCard = Object.keys(assignedCards ?? {}).length === 0; const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || !isPolicyLoaded || isLoadingOnyxValue(lastSelectedFeedMetadata); - const isLoadingCards = (selectedFeed?.accountList === undefined && cardList === undefined) ? isLoadingOnyxValue(cardListMetadata) : false; + const isLoadingCards = selectedFeed?.accountList === undefined && cardList === undefined ? isLoadingOnyxValue(cardListMetadata) : false; const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata)); const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed; From 308ee00a60b3f4a3d647bfb9fee8ce6d23fdef93 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Wed, 28 Jan 2026 17:45:56 +0100 Subject: [PATCH 07/12] Refactor useCompanyCards hook to replace cardNames with cardNamesToEncryptedCardNumber for better data handling and update WorkspaceCompanyCardsTable to accommodate this change. --- src/hooks/useCompanyCards.ts | 27 ++++++++----------- .../WorkspaceCompanyCardsTable/index.tsx | 14 ++++------ 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/hooks/useCompanyCards.ts b/src/hooks/useCompanyCards.ts index 7975b56f90ec8..ee077b734b693 100644 --- a/src/hooks/useCompanyCards.ts +++ b/src/hooks/useCompanyCards.ts @@ -1,6 +1,6 @@ import type {OnyxCollection, OnyxEntry, ResultMetadata} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import {getCompanyCardFeed, getCompanyFeeds, getPlaidInstitutionId, getSelectedFeed} from '@libs/CardUtils'; +import {getCompanyCardFeed, getCompanyFeeds, getSelectedFeed} from '@libs/CardUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {CardFeeds, CardList} from '@src/types/onyx'; @@ -12,20 +12,17 @@ import type {CombinedCardFeed} from './useCardFeeds'; import useCardsList from './useCardsList'; import useOnyx from './useOnyx'; -type CardFeedType = ValueOf; - type UseCompanyCardsProps = { policyID: string | undefined; feedName?: CompanyCardFeedWithDomainID; }; type UseCompanyCardsResult = Partial<{ - cardFeedType: CardFeedType; bankName: CompanyCardFeed; feedName: CompanyCardFeedWithDomainID; cardList: AssignableCardsList; assignedCards: CardList; - cardNames: string[]; + cardNamesToEncryptedCardNumber: Record; allCardFeeds: CombinedCardFeeds; companyCardFeeds: CompanyFeeds; selectedFeed: CombinedCardFeed; @@ -57,17 +54,16 @@ function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProp const companyCardFeeds = getCompanyFeeds(allCardFeeds); const selectedFeed = feedName && companyCardFeeds[feedName]; - const isPlaidCardFeed = !!getPlaidInstitutionId(feedName); - - // Direct feeds include Plaid feeds and OAuth feeds (like oauth.chase.com) that have accountList - const isDirectFeed = isPlaidCardFeed || !!selectedFeed?.accountList; - let cardFeedType: CardFeedType = 'customFeed'; - if (isDirectFeed) { - cardFeedType = 'directFeed'; - } const {cardList, ...assignedCards} = cardsList ?? {}; - const cardNames = cardFeedType === 'directFeed' ? (selectedFeed?.accountList ?? []) : Object.keys(cardList ?? {}); + const cardNamesToEncryptedCardNumber: Record = {}; + + for (const cardName of selectedFeed?.accountList ?? []) { + cardNamesToEncryptedCardNumber[cardName] = cardName; + } + for (const [cardName, encryptedCardNumber] of Object.entries(cardList ?? {})) { + cardNamesToEncryptedCardNumber[cardName] = encryptedCardNumber; + } const onyxMetadata = { cardListMetadata, @@ -90,10 +86,9 @@ function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProp companyCardFeeds, cardList, assignedCards, - cardNames, + cardNamesToEncryptedCardNumber, selectedFeed, bankName, - cardFeedType, onyxMetadata, isInitiallyLoadingFeeds, isNoFeed, diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index 822ef57d37a2e..b14d81f7c8686 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -14,7 +14,7 @@ import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import {resetFailedWorkspaceCompanyCardAssignment, resetFailedWorkspaceCompanyCardUnassignment} from '@libs/actions/CompanyCards'; -import {getDefaultCardName} from '@libs/CardUtils'; +import {getDefaultCardName, getPlaidInstitutionId} from '@libs/CardUtils'; import tokenizedSearch from '@libs/tokenizedSearch'; import WorkspaceCompanyCardPageEmptyState from '@pages/workspace/companyCards/WorkspaceCompanyCardPageEmptyState'; import WorkspaceCompanyCardsFeedAddedEmptyPage from '@pages/workspace/companyCards/WorkspaceCompanyCardsFeedAddedEmptyPage'; @@ -59,17 +59,14 @@ function WorkspaceCompanyCardsTable({policyID, isPolicyLoaded, domainOrWorkspace const { feedName, bankName, - cardList, assignedCards, - cardNames, - cardFeedType, + cardNamesToEncryptedCardNumber, selectedFeed, isInitiallyLoadingFeeds, isNoFeed, isFeedPending, onyxMetadata: {cardListMetadata, lastSelectedFeedMetadata}, } = companyCards; - const isDirectCardFeed = cardFeedType === 'directFeed'; const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); const [personalDetails, personalDetailsMetadata] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false}); @@ -79,7 +76,7 @@ function WorkspaceCompanyCardsTable({policyID, isPolicyLoaded, domainOrWorkspace const hasNoAssignedCard = Object.keys(assignedCards ?? {}).length === 0; const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || !isPolicyLoaded || isLoadingOnyxValue(lastSelectedFeedMetadata); - const isLoadingCards = selectedFeed?.accountList === undefined && cardList === undefined ? isLoadingOnyxValue(cardListMetadata) : false; + const isLoadingCards = Object.keys(cardNamesToEncryptedCardNumber ?? {}).length === 0 ? isLoadingOnyxValue(cardListMetadata) : false; const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata)); const showCards = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed && !isLoadingFeed; @@ -115,9 +112,8 @@ function WorkspaceCompanyCardsTable({policyID, isPolicyLoaded, domainOrWorkspace const cardsData: WorkspaceCompanyCardTableItemData[] = isLoadingCards ? [] - : (cardNames?.map((cardName) => { + : (Object.entries(cardNamesToEncryptedCardNumber ?? {}).map(([cardName, encryptedCardNumber]) => { // For direct feeds cardID equals cardName, for commercial feeds it's looked up from cardList - const encryptedCardNumber = isDirectCardFeed ? cardName : (cardList?.[cardName] ?? ''); const failedCompanyCardAssignment = failedCompanyCardAssignments?.[encryptedCardNumber]; if (failedCompanyCardAssignment) { @@ -251,7 +247,7 @@ function WorkspaceCompanyCardsTable({policyID, isPolicyLoaded, domainOrWorkspace item={item} policyID={policyID ?? String(CONST.DEFAULT_NUMBER_ID)} CardFeedIcon={cardFeedIcon} - isPlaidCardFeed={isDirectCardFeed} + isPlaidCardFeed={!!getPlaidInstitutionId(feedName)} onAssignCard={onAssignCard} isAssigningCardDisabled={isAssigningCardDisabled} shouldUseNarrowTableLayout={shouldUseNarrowTableLayout} From 77c25fef9bf18fd0b062a2428b4d2a9eb3a4241a Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Wed, 28 Jan 2026 17:52:38 +0100 Subject: [PATCH 08/12] Remove unused import from useCompanyCards hook and clean up commented code in WorkspaceCompanyCardsTable for improved clarity. --- src/hooks/useCompanyCards.ts | 1 - .../workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/src/hooks/useCompanyCards.ts b/src/hooks/useCompanyCards.ts index ee077b734b693..4774884a0b0a3 100644 --- a/src/hooks/useCompanyCards.ts +++ b/src/hooks/useCompanyCards.ts @@ -1,5 +1,4 @@ import type {OnyxCollection, OnyxEntry, ResultMetadata} from 'react-native-onyx'; -import type {ValueOf} from 'type-fest'; import {getCompanyCardFeed, getCompanyFeeds, getSelectedFeed} from '@libs/CardUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index b14d81f7c8686..692c94cf6c98a 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -113,7 +113,6 @@ function WorkspaceCompanyCardsTable({policyID, isPolicyLoaded, domainOrWorkspace const cardsData: WorkspaceCompanyCardTableItemData[] = isLoadingCards ? [] : (Object.entries(cardNamesToEncryptedCardNumber ?? {}).map(([cardName, encryptedCardNumber]) => { - // For direct feeds cardID equals cardName, for commercial feeds it's looked up from cardList const failedCompanyCardAssignment = failedCompanyCardAssignments?.[encryptedCardNumber]; if (failedCompanyCardAssignment) { From b1b80af1654c50e30ac713bdf34c9ea76aa57e35 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Wed, 28 Jan 2026 17:56:37 +0100 Subject: [PATCH 09/12] Refactor tests for useCompanyCards to replace cardFeedType checks with cardNamesToEncryptedCardNumber assertions, ensuring accurate mapping for custom, OAuth, and Plaid feeds. Enhance test coverage for card name derivation and merging logic. --- tests/unit/hooks/useCompanyCards.test.ts | 144 +++++++++++++---------- 1 file changed, 81 insertions(+), 63 deletions(-) diff --git a/tests/unit/hooks/useCompanyCards.test.ts b/tests/unit/hooks/useCompanyCards.test.ts index bcb619bef2216..fda3c6cd226a1 100644 --- a/tests/unit/hooks/useCompanyCards.test.ts +++ b/tests/unit/hooks/useCompanyCards.test.ts @@ -105,85 +105,93 @@ describe('useCompanyCards', () => { await Onyx.clear(); }); - describe('cardFeedType determination', () => { - it('should return cardFeedType as customFeed for VCF feeds without accountList', async () => { + describe('cardNamesToEncryptedCardNumber derivation', () => { + it('should derive cardNamesToEncryptedCardNumber from cardList for custom feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockCustomFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockCustomFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([mockCardsList, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - expect(result.current.cardFeedType).toBe('customFeed'); + // For custom feeds without accountList, cardNamesToEncryptedCardNumber comes from cardList + expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + card1: 'card1', + card2: 'card2', + }); expect(result.current.feedName).toBe(mockCustomFeed); }); - it('should return cardFeedType as directFeed for OAuth feeds with accountList', async () => { + it('should derive cardNamesToEncryptedCardNumber from accountList for OAuth feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockOAuthFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockOAuthFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - expect(result.current.cardFeedType).toBe('directFeed'); + // For OAuth feeds with accountList, card names map to themselves + expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + 'CREDIT CARD...6607': 'CREDIT CARD...6607', + 'CREDIT CARD...5501': 'CREDIT CARD...5501', + }); expect(result.current.feedName).toBe(mockOAuthFeed); expect(result.current.selectedFeed?.accountList).toEqual(['CREDIT CARD...6607', 'CREDIT CARD...5501']); }); - it('should return cardFeedType as directFeed for Plaid feeds', async () => { + it('should derive cardNamesToEncryptedCardNumber from accountList for Plaid feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockPlaidFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockPlaidFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - expect(result.current.cardFeedType).toBe('directFeed'); + // For Plaid feeds with accountList, card names map to themselves + expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + 'Plaid Checking 0000': 'Plaid Checking 0000', + 'Plaid Credit Card 3333': 'Plaid Credit Card 3333', + }); expect(result.current.feedName).toBe(mockPlaidFeed); }); - }); - describe('cardNames derivation', () => { - it('should derive cardNames from cardList for custom feeds', async () => { + it('should return empty cardNamesToEncryptedCardNumber when no cardList or accountList', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockCustomFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockCustomFeedData, {status: 'loaded'}, undefined]); - (useCardsList as jest.Mock).mockReturnValue([mockCardsList, {status: 'loaded'}]); - - const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - - expect(result.current.cardFeedType).toBe('customFeed'); - expect(result.current.cardNames).toEqual(['card1', 'card2']); - }); - - it('should derive cardNames from accountList for OAuth feeds (direct feeds)', async () => { - await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockOAuthFeed); - (useCardFeeds as jest.Mock).mockReturnValue([mockOAuthFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - expect(result.current.cardFeedType).toBe('directFeed'); - expect(result.current.cardNames).toEqual(['CREDIT CARD...6607', 'CREDIT CARD...5501']); + expect(result.current.cardNamesToEncryptedCardNumber).toEqual({}); }); - it('should derive cardNames from accountList for Plaid feeds (direct feeds)', async () => { - await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockPlaidFeed); - (useCardFeeds as jest.Mock).mockReturnValue([mockPlaidFeedData, {status: 'loaded'}, undefined]); - (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); - - const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - - expect(result.current.cardFeedType).toBe('directFeed'); - expect(result.current.cardNames).toEqual(['Plaid Checking 0000', 'Plaid Credit Card 3333']); - }); + it('should merge accountList and cardList entries, with cardList taking precedence', async () => { + // Create a feed that has both accountList AND cardList entries + const feedWithBoth: CompanyCardFeedWithDomainID = `${CONST.COMPANY_CARD.FEED_BANK_NAME.CHASE}#${domainID}` as CompanyCardFeedWithDomainID; + const feedDataWithAccountList = { + [feedWithBoth]: { + ...mockOAuthFeedData[mockOAuthFeed], + accountList: ['CARD A', 'CARD B'], + }, + }; + const cardsListWithEncrypted = { + cardList: { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'CARD A': 'encrypted_A', + // eslint-disable-next-line @typescript-eslint/naming-convention + 'CARD C': 'encrypted_C', + }, + }; - it('should return empty cardNames when cardList is undefined for custom feeds', async () => { - await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockCustomFeed); - (useCardFeeds as jest.Mock).mockReturnValue([mockCustomFeedData, {status: 'loaded'}, undefined]); - (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); + await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, feedWithBoth); + (useCardFeeds as jest.Mock).mockReturnValue([feedDataWithAccountList, {status: 'loaded'}, undefined]); + (useCardsList as jest.Mock).mockReturnValue([cardsListWithEncrypted, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - expect(result.current.cardFeedType).toBe('customFeed'); - expect(result.current.cardNames).toEqual([]); + // accountList entries map to themselves, but cardList entries override with encrypted values + expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + 'CARD A': 'encrypted_A', // cardList overrides accountList + 'CARD B': 'CARD B', // from accountList only + 'CARD C': 'encrypted_C', // from cardList only + }); }); }); @@ -195,7 +203,7 @@ describe('useCompanyCards', () => { const {result} = renderHook(() => useCompanyCards({policyID: undefined})); expect(result.current.feedName).toBeUndefined(); - expect(result.current.cardFeedType).toBeUndefined(); + expect(result.current.cardNamesToEncryptedCardNumber).toBeUndefined(); expect(result.current.onyxMetadata).toBeDefined(); }); }); @@ -212,7 +220,11 @@ describe('useCompanyCards', () => { // Should use provided feedName, not lastSelectedFeed expect(result.current.feedName).toBe(mockOAuthFeed); - expect(result.current.cardFeedType).toBe('directFeed'); + // OAuth feed has accountList, so cardNamesToEncryptedCardNumber should be populated + expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + 'CREDIT CARD...6607': 'CREDIT CARD...6607', + 'CREDIT CARD...5501': 'CREDIT CARD...5501', + }); }); }); @@ -253,20 +265,23 @@ describe('useCompanyCards', () => { // For commercial feeds, cardList contains {cardName: encryptedNumber} expect(result.current.cardList).toEqual(mockCardsListWithEncryptedNumbers.cardList); - // cardNames should be the keys (display names) - expect(result.current.cardNames).toEqual(['490901XXXXXX1234', '490901XXXXXX5678']); + // cardNamesToEncryptedCardNumber should map display names to encrypted values + expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + '490901XXXXXX1234': 'v12:74E3CA3C4C0FA02F4C754FEN4RYP3ED1', + '490901XXXXXX5678': 'v12:74E3CA3C4C0FA02F4C754FEN4RYP3ED2', + }); }); - it('should have cardList where keys differ from values for commercial feeds', async () => { + it('should have cardNamesToEncryptedCardNumber where keys differ from values for commercial feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockCustomFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockCustomFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([mockCardsListWithEncryptedNumbers, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - const cardList = result.current.cardList ?? {}; - const cardNames = Object.keys(cardList); - const encryptedNumbers = Object.values(cardList); + const cardNamesMap = result.current.cardNamesToEncryptedCardNumber ?? {}; + const cardNames = Object.keys(cardNamesMap); + const encryptedNumbers = Object.values(cardNamesMap); // In commercial feeds, the display name (key) should differ from encrypted value (value) for (const [index, name] of cardNames.entries()) { @@ -274,40 +289,43 @@ describe('useCompanyCards', () => { } }); - it('should not have cardList for direct feeds (accountList is used instead)', async () => { + it('should populate cardNamesToEncryptedCardNumber from accountList for direct feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockPlaidFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockPlaidFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - // Direct feeds use accountList, not cardList + // Direct feeds use accountList, cardList should be undefined expect(result.current.cardList).toBeUndefined(); expect(result.current.selectedFeed?.accountList).toBeDefined(); - expect(result.current.cardNames).toEqual(['Plaid Checking 0000', 'Plaid Credit Card 3333']); + // cardNamesToEncryptedCardNumber maps card names to themselves for direct feeds + expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + 'Plaid Checking 0000': 'Plaid Checking 0000', + 'Plaid Credit Card 3333': 'Plaid Credit Card 3333', + }); }); }); describe('card ID consistency', () => { - it('should ensure direct feed cardNames can be used as identifiers', async () => { + it('should ensure direct feed cardNamesToEncryptedCardNumber maps names to themselves', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockOAuthFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockOAuthFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - // For direct feeds, cardNames ARE the identifiers (no encryption) - const cardNames = result.current.cardNames ?? []; - expect(cardNames).toEqual(['CREDIT CARD...6607', 'CREDIT CARD...5501']); + // For direct feeds, card names map to themselves (no encryption) + const cardNamesMap = result.current.cardNamesToEncryptedCardNumber ?? {}; + expect(Object.keys(cardNamesMap)).toEqual(['CREDIT CARD...6607', 'CREDIT CARD...5501']); - // Each card name is both the display value AND the identifier - for (const name of cardNames) { - expect(typeof name).toBe('string'); - expect(name.length).toBeGreaterThan(0); + // Each card name maps to itself + for (const [name, encrypted] of Object.entries(cardNamesMap)) { + expect(name).toBe(encrypted); } }); - it('should ensure commercial feed cardList maps display names to encrypted identifiers', async () => { + it('should ensure commercial feed cardNamesToEncryptedCardNumber maps display names to encrypted identifiers', async () => { const commercialCardsList = { cardList: { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -323,16 +341,16 @@ describe('useCompanyCards', () => { const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - const cardList = result.current.cardList ?? {}; + const cardNamesMap = result.current.cardNamesToEncryptedCardNumber ?? {}; // Display names are keys - expect(Object.keys(cardList)).toEqual(['VISA - 1234', 'VISA - 5678']); + expect(Object.keys(cardNamesMap)).toEqual(['VISA - 1234', 'VISA - 5678']); // Encrypted identifiers are values - expect(Object.values(cardList)).toEqual(['enc_abc123', 'enc_def456']); + expect(Object.values(cardNamesMap)).toEqual(['enc_abc123', 'enc_def456']); // Lookup: given a display name, get the encrypted identifier - expect(cardList['VISA - 1234']).toBe('enc_abc123'); + expect(cardNamesMap['VISA - 1234']).toBe('enc_abc123'); }); }); }); From cbd2a2410ca1e91962b7623dbcfd6e2076dfb5aa Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Wed, 28 Jan 2026 18:01:31 +0100 Subject: [PATCH 10/12] Enable ESLint naming convention rule for useCompanyCards test file to ensure consistent code style. --- tests/unit/hooks/useCompanyCards.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/hooks/useCompanyCards.test.ts b/tests/unit/hooks/useCompanyCards.test.ts index fda3c6cd226a1..6b0f50938009a 100644 --- a/tests/unit/hooks/useCompanyCards.test.ts +++ b/tests/unit/hooks/useCompanyCards.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import {renderHook} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; import useCardFeeds from '@hooks/useCardFeeds'; From f1ae7ef2fa8d8793c0a2b1016aa3f12221af7060 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Wed, 28 Jan 2026 19:10:17 +0100 Subject: [PATCH 11/12] Refactor useCompanyCards hook to improve feed loading logic and update WorkspaceCompanyCardsPage to clarify API call dependencies, enhancing code readability and maintainability. --- src/hooks/useCompanyCards.ts | 2 +- src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hooks/useCompanyCards.ts b/src/hooks/useCompanyCards.ts index 4774884a0b0a3..bace2e91c0270 100644 --- a/src/hooks/useCompanyCards.ts +++ b/src/hooks/useCompanyCards.ts @@ -73,7 +73,7 @@ function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProp const isInitiallyLoadingFeeds = isLoadingOnyxValue(allCardFeedsMetadata); const isNoFeed = !selectedFeed && !isInitiallyLoadingFeeds; const isFeedPending = !!selectedFeed?.pending; - const isFeedAdded = !isLoadingOnyxValue(allCardFeedsMetadata) && !isFeedPending && !isNoFeed; + const isFeedAdded = !isInitiallyLoadingFeeds && !isFeedPending && !isNoFeed; if (!policyID) { return {onyxMetadata, isInitiallyLoadingFeeds, isNoFeed, isFeedPending, isFeedAdded}; diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx index bcffcee6ddf35..122c9ec9b74ca 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsPage.tsx @@ -50,7 +50,8 @@ function WorkspaceCompanyCardsPage({route}: WorkspaceCompanyCardsPageProps) { const isLoading = !isOffline && (!allCardFeeds || (isFeedAdded && isLoadingOnyxValue(cardListMetadata))); useEffect(() => { openPolicyCompanyCardsPage(policyID, domainOrWorkspaceAccountID); - }, [policyID, domainOrWorkspaceAccountID]); + // eslint-disable-next-line -- The API call is only needed when the policyID changes + }, [policyID]); useEffect(() => { if (isLoading || !bankName || isFeedPending) { From a43303cc63db12696a0668d1e60fd702eacd8c57 Mon Sep 17 00:00:00 2001 From: Fedi Rajhi Date: Thu, 29 Jan 2026 15:28:23 +0100 Subject: [PATCH 12/12] Refactor cardNamesToEncryptedCardNumber to cardNamesToEncryptedCardNumberMapping for clarity Updated the naming convention in the useCompanyCards hook and related components to improve clarity. The variable cardNamesToEncryptedCardNumber has been renamed to cardNamesToEncryptedCardNumberMapping across the codebase, including tests, to better reflect its purpose. This change enhances code readability and consistency. --- src/hooks/useCompanyCards.ts | 10 ++-- .../WorkspaceCompanyCardsTable/index.tsx | 7 ++- tests/unit/hooks/useCompanyCards.test.ts | 50 +++++++++---------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/hooks/useCompanyCards.ts b/src/hooks/useCompanyCards.ts index 84a813373802e..6f06b2086cc05 100644 --- a/src/hooks/useCompanyCards.ts +++ b/src/hooks/useCompanyCards.ts @@ -21,7 +21,7 @@ type UseCompanyCardsResult = Partial<{ feedName: CompanyCardFeedWithDomainID; cardList: AssignableCardsList; assignedCards: CardList; - cardNamesToEncryptedCardNumber: Record; + cardNamesToEncryptedCardNumberMapping: Record; workspaceCardFeedsStatus: CardFeedsStatusByDomainID; allCardFeeds: CombinedCardFeeds; companyCardFeeds: CompanyFeeds; @@ -56,13 +56,13 @@ function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProp const selectedFeed = feedName && companyCardFeeds[feedName]; const {cardList, ...assignedCards} = cardsList ?? {}; - const cardNamesToEncryptedCardNumber: Record = {}; + const cardNamesToEncryptedCardNumberMapping: Record = {}; for (const cardName of selectedFeed?.accountList ?? []) { - cardNamesToEncryptedCardNumber[cardName] = cardName; + cardNamesToEncryptedCardNumberMapping[cardName] = cardName; } for (const [cardName, encryptedCardNumber] of Object.entries(cardList ?? {})) { - cardNamesToEncryptedCardNumber[cardName] = encryptedCardNumber; + cardNamesToEncryptedCardNumberMapping[cardName] = encryptedCardNumber; } const onyxMetadata = { @@ -86,7 +86,7 @@ function useCompanyCards({policyID, feedName: feedNameProp}: UseCompanyCardsProp companyCardFeeds, cardList, assignedCards, - cardNamesToEncryptedCardNumber, + cardNamesToEncryptedCardNumberMapping, workspaceCardFeedsStatus, selectedFeed, bankName, diff --git a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx index fb58f2c403d99..7b2892170b43b 100644 --- a/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx +++ b/src/pages/workspace/companyCards/WorkspaceCompanyCardsTable/index.tsx @@ -78,9 +78,8 @@ function WorkspaceCompanyCardsTable({ feedName, bankName, assignedCards, - cardNamesToEncryptedCardNumber, + cardNamesToEncryptedCardNumberMapping, workspaceCardFeedsStatus, - cardFeedType, selectedFeed, isInitiallyLoadingFeeds, isNoFeed, @@ -115,7 +114,7 @@ function WorkspaceCompanyCardsTable({ } const isLoadingFeed = (!feedName && isInitiallyLoadingFeeds) || !isPolicyLoaded || isLoadingOnyxValue(lastSelectedFeedMetadata) || !!selectedFeedStatus?.isLoading; - const isLoadingCards = Object.keys(cardNamesToEncryptedCardNumber ?? {}).length === 0 ? isLoadingOnyxValue(cardListMetadata) : false; + const isLoadingCards = Object.keys(cardNamesToEncryptedCardNumberMapping ?? {}).length === 0 ? isLoadingOnyxValue(cardListMetadata) : false; const isLoadingPage = !isOffline && (isLoadingFeed || isLoadingOnyxValue(personalDetailsMetadata) || areWorkspaceCardFeedsLoading); const isLoading = isLoadingPage || isLoadingFeed; @@ -152,7 +151,7 @@ function WorkspaceCompanyCardsTable({ const cardsData: WorkspaceCompanyCardTableItemData[] = isLoadingCards ? [] - : (Object.entries(cardNamesToEncryptedCardNumber ?? {}).map(([cardName, encryptedCardNumber]) => { + : (Object.entries(cardNamesToEncryptedCardNumberMapping ?? {}).map(([cardName, encryptedCardNumber]) => { const failedCompanyCardAssignment = failedCompanyCardAssignments?.[encryptedCardNumber]; if (failedCompanyCardAssignment) { diff --git a/tests/unit/hooks/useCompanyCards.test.ts b/tests/unit/hooks/useCompanyCards.test.ts index 6b0f50938009a..d4e210ae9befe 100644 --- a/tests/unit/hooks/useCompanyCards.test.ts +++ b/tests/unit/hooks/useCompanyCards.test.ts @@ -106,23 +106,23 @@ describe('useCompanyCards', () => { await Onyx.clear(); }); - describe('cardNamesToEncryptedCardNumber derivation', () => { - it('should derive cardNamesToEncryptedCardNumber from cardList for custom feeds', async () => { + describe('cardNamesToEncryptedCardNumberMapping derivation', () => { + it('should derive cardNamesToEncryptedCardNumberMapping from cardList for custom feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockCustomFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockCustomFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([mockCardsList, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - // For custom feeds without accountList, cardNamesToEncryptedCardNumber comes from cardList - expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + // For custom feeds without accountList, cardNamesToEncryptedCardNumberMapping comes from cardList + expect(result.current.cardNamesToEncryptedCardNumberMapping).toEqual({ card1: 'card1', card2: 'card2', }); expect(result.current.feedName).toBe(mockCustomFeed); }); - it('should derive cardNamesToEncryptedCardNumber from accountList for OAuth feeds', async () => { + it('should derive cardNamesToEncryptedCardNumberMapping from accountList for OAuth feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockOAuthFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockOAuthFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); @@ -130,7 +130,7 @@ describe('useCompanyCards', () => { const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); // For OAuth feeds with accountList, card names map to themselves - expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + expect(result.current.cardNamesToEncryptedCardNumberMapping).toEqual({ 'CREDIT CARD...6607': 'CREDIT CARD...6607', 'CREDIT CARD...5501': 'CREDIT CARD...5501', }); @@ -138,7 +138,7 @@ describe('useCompanyCards', () => { expect(result.current.selectedFeed?.accountList).toEqual(['CREDIT CARD...6607', 'CREDIT CARD...5501']); }); - it('should derive cardNamesToEncryptedCardNumber from accountList for Plaid feeds', async () => { + it('should derive cardNamesToEncryptedCardNumberMapping from accountList for Plaid feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockPlaidFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockPlaidFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); @@ -146,21 +146,21 @@ describe('useCompanyCards', () => { const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); // For Plaid feeds with accountList, card names map to themselves - expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + expect(result.current.cardNamesToEncryptedCardNumberMapping).toEqual({ 'Plaid Checking 0000': 'Plaid Checking 0000', 'Plaid Credit Card 3333': 'Plaid Credit Card 3333', }); expect(result.current.feedName).toBe(mockPlaidFeed); }); - it('should return empty cardNamesToEncryptedCardNumber when no cardList or accountList', async () => { + it('should return empty cardNamesToEncryptedCardNumberMapping when no cardList or accountList', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockCustomFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockCustomFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - expect(result.current.cardNamesToEncryptedCardNumber).toEqual({}); + expect(result.current.cardNamesToEncryptedCardNumberMapping).toEqual({}); }); it('should merge accountList and cardList entries, with cardList taking precedence', async () => { @@ -188,7 +188,7 @@ describe('useCompanyCards', () => { const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); // accountList entries map to themselves, but cardList entries override with encrypted values - expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + expect(result.current.cardNamesToEncryptedCardNumberMapping).toEqual({ 'CARD A': 'encrypted_A', // cardList overrides accountList 'CARD B': 'CARD B', // from accountList only 'CARD C': 'encrypted_C', // from cardList only @@ -204,7 +204,7 @@ describe('useCompanyCards', () => { const {result} = renderHook(() => useCompanyCards({policyID: undefined})); expect(result.current.feedName).toBeUndefined(); - expect(result.current.cardNamesToEncryptedCardNumber).toBeUndefined(); + expect(result.current.cardNamesToEncryptedCardNumberMapping).toBeUndefined(); expect(result.current.onyxMetadata).toBeDefined(); }); }); @@ -221,8 +221,8 @@ describe('useCompanyCards', () => { // Should use provided feedName, not lastSelectedFeed expect(result.current.feedName).toBe(mockOAuthFeed); - // OAuth feed has accountList, so cardNamesToEncryptedCardNumber should be populated - expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + // OAuth feed has accountList, so cardNamesToEncryptedCardNumberMapping should be populated + expect(result.current.cardNamesToEncryptedCardNumberMapping).toEqual({ 'CREDIT CARD...6607': 'CREDIT CARD...6607', 'CREDIT CARD...5501': 'CREDIT CARD...5501', }); @@ -266,21 +266,21 @@ describe('useCompanyCards', () => { // For commercial feeds, cardList contains {cardName: encryptedNumber} expect(result.current.cardList).toEqual(mockCardsListWithEncryptedNumbers.cardList); - // cardNamesToEncryptedCardNumber should map display names to encrypted values - expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + // cardNamesToEncryptedCardNumberMapping should map display names to encrypted values + expect(result.current.cardNamesToEncryptedCardNumberMapping).toEqual({ '490901XXXXXX1234': 'v12:74E3CA3C4C0FA02F4C754FEN4RYP3ED1', '490901XXXXXX5678': 'v12:74E3CA3C4C0FA02F4C754FEN4RYP3ED2', }); }); - it('should have cardNamesToEncryptedCardNumber where keys differ from values for commercial feeds', async () => { + it('should have cardNamesToEncryptedCardNumberMapping where keys differ from values for commercial feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockCustomFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockCustomFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([mockCardsListWithEncryptedNumbers, {status: 'loaded'}]); const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - const cardNamesMap = result.current.cardNamesToEncryptedCardNumber ?? {}; + const cardNamesMap = result.current.cardNamesToEncryptedCardNumberMapping ?? {}; const cardNames = Object.keys(cardNamesMap); const encryptedNumbers = Object.values(cardNamesMap); @@ -290,7 +290,7 @@ describe('useCompanyCards', () => { } }); - it('should populate cardNamesToEncryptedCardNumber from accountList for direct feeds', async () => { + it('should populate cardNamesToEncryptedCardNumberMapping from accountList for direct feeds', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockPlaidFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockPlaidFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); @@ -300,8 +300,8 @@ describe('useCompanyCards', () => { // Direct feeds use accountList, cardList should be undefined expect(result.current.cardList).toBeUndefined(); expect(result.current.selectedFeed?.accountList).toBeDefined(); - // cardNamesToEncryptedCardNumber maps card names to themselves for direct feeds - expect(result.current.cardNamesToEncryptedCardNumber).toEqual({ + // cardNamesToEncryptedCardNumberMapping maps card names to themselves for direct feeds + expect(result.current.cardNamesToEncryptedCardNumberMapping).toEqual({ 'Plaid Checking 0000': 'Plaid Checking 0000', 'Plaid Credit Card 3333': 'Plaid Credit Card 3333', }); @@ -309,7 +309,7 @@ describe('useCompanyCards', () => { }); describe('card ID consistency', () => { - it('should ensure direct feed cardNamesToEncryptedCardNumber maps names to themselves', async () => { + it('should ensure direct feed cardNamesToEncryptedCardNumberMapping maps names to themselves', async () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${mockPolicyID}`, mockOAuthFeed); (useCardFeeds as jest.Mock).mockReturnValue([mockOAuthFeedData, {status: 'loaded'}, undefined]); (useCardsList as jest.Mock).mockReturnValue([undefined, {status: 'loaded'}]); @@ -317,7 +317,7 @@ describe('useCompanyCards', () => { const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); // For direct feeds, card names map to themselves (no encryption) - const cardNamesMap = result.current.cardNamesToEncryptedCardNumber ?? {}; + const cardNamesMap = result.current.cardNamesToEncryptedCardNumberMapping ?? {}; expect(Object.keys(cardNamesMap)).toEqual(['CREDIT CARD...6607', 'CREDIT CARD...5501']); // Each card name maps to itself @@ -326,7 +326,7 @@ describe('useCompanyCards', () => { } }); - it('should ensure commercial feed cardNamesToEncryptedCardNumber maps display names to encrypted identifiers', async () => { + it('should ensure commercial feed cardNamesToEncryptedCardNumberMapping maps display names to encrypted identifiers', async () => { const commercialCardsList = { cardList: { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -342,7 +342,7 @@ describe('useCompanyCards', () => { const {result} = renderHook(() => useCompanyCards({policyID: mockPolicyID})); - const cardNamesMap = result.current.cardNamesToEncryptedCardNumber ?? {}; + const cardNamesMap = result.current.cardNamesToEncryptedCardNumberMapping ?? {}; // Display names are keys expect(Object.keys(cardNamesMap)).toEqual(['VISA - 1234', 'VISA - 5678']);