diff --git a/package.json b/package.json index 60019124658d..23584cec6ecc 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "test:debug": "TZ=utc NODE_OPTIONS='--inspect-brk --experimental-vm-modules' jest --runInBand", "perf-test": "NODE_OPTIONS=--experimental-vm-modules npx reassure", "typecheck": "NODE_OPTIONS=--max_old_space_size=8192 tsc", - "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=292 --cache --cache-location=node_modules/.cache/eslint", + "lint": "NODE_OPTIONS=--max_old_space_size=8192 eslint . --max-warnings=291 --cache --cache-location=node_modules/.cache/eslint", "lint-changed": "NODE_OPTIONS=--max_old_space_size=8192 ./scripts/lintChanged.sh", "lint-watch": "npx eslint-watch --watch --changed", "shellcheck": "./scripts/shellCheck.sh", diff --git a/src/components/EReceipt.tsx b/src/components/EReceipt.tsx index 11a415f8090d..e9fe5bf1ceef 100644 --- a/src/components/EReceipt.tsx +++ b/src/components/EReceipt.tsx @@ -54,7 +54,7 @@ function EReceipt({transactionID, transactionItem, isThumbnail = false}: EReceip const formattedAmount = convertToDisplayString(transactionAmount, transactionCurrency); const currency = getCurrencySymbol(transactionCurrency ?? ''); const amount = currency ? formattedAmount.replace(currency, '') : formattedAmount; - const cardDescription = getCompanyCardDescription(transactionCardName, transactionCardID, cardList) ?? (transactionCardID ? getCardDescription(transactionCardID) : ''); + const cardDescription = getCompanyCardDescription(transactionCardName, transactionCardID, cardList) ?? (transactionCardID ? getCardDescription(cardList?.[transactionCardID]) : ''); const secondaryBgcolorStyle = secondaryColor ? StyleUtils.getBackgroundColorStyle(secondaryColor) : undefined; const primaryTextColorStyle = primaryColor ? StyleUtils.getColorStyle(primaryColor) : undefined; diff --git a/src/components/Search/SearchAutocompleteList.tsx b/src/components/Search/SearchAutocompleteList.tsx index b0b05944c84d..d0783c999a1d 100644 --- a/src/components/Search/SearchAutocompleteList.tsx +++ b/src/components/Search/SearchAutocompleteList.tsx @@ -392,14 +392,14 @@ function SearchAutocompleteList( .filter( (card) => (card.bank.toLowerCase().includes(autocompleteValue.toLowerCase()) || card.lastFourPAN?.includes(autocompleteValue)) && - !alreadyAutocompletedKeys.includes(getCardDescription(card.cardID).toLowerCase()), + !alreadyAutocompletedKeys.includes(getCardDescription(card).toLowerCase()), ) .sort() .slice(0, 10); return filteredCards.map((card) => ({ filterKey: CONST.SEARCH.SEARCH_USER_FRIENDLY_KEYS.CARD_ID, - text: getCardDescription(card.cardID, allCards), + text: getCardDescription(card), autocompleteID: card.cardID.toString(), mapKey: CONST.SEARCH.SYNTAX_FILTER_KEYS.CARD_ID, })); @@ -462,7 +462,6 @@ function SearchAutocompleteList( expenseTypes, feedAutoCompleteList, cardAutocompleteList, - allCards, booleanTypes, workspaceList, ]); diff --git a/src/libs/CardUtils.ts b/src/libs/CardUtils.ts index 12d958d8b701..8f818ae66bb2 100644 --- a/src/libs/CardUtils.ts +++ b/src/libs/CardUtils.ts @@ -9,7 +9,6 @@ import type IllustrationsType from '@styles/theme/illustrations/types'; import * as Illustrations from '@src/components/Icon/Illustrations'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; -import type {OnyxValues} from '@src/ONYXKEYS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {BankAccountList, Card, CardFeeds, CardList, CompanyCardFeed, CurrencyList, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; import type {FilteredCardList} from '@src/types/onyx/Card'; @@ -21,18 +20,6 @@ import {filterObject} from './ObjectUtils'; import {getDisplayNameOrDefault} from './PersonalDetailsUtils'; import StringUtils from './StringUtils'; -let allCards: OnyxValues[typeof ONYXKEYS.CARD_LIST] = {}; -Onyx.connect({ - key: ONYXKEYS.CARD_LIST, - callback: (val) => { - if (!val || Object.keys(val).length === 0) { - return; - } - - allCards = val; - }, -}); - let allWorkspaceCards: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, @@ -50,14 +37,10 @@ function getMonthFromExpirationDateString(expirationDateString: string) { } /** - * @param cardID + * @param card * @returns boolean */ -function isExpensifyCard(cardID?: number) { - if (!cardID) { - return false; - } - const card = allCards[cardID]; +function isExpensifyCard(card?: Card) { if (!card) { return false; } @@ -65,14 +48,10 @@ function isExpensifyCard(cardID?: number) { } /** - * @param cardID + * @param card * @returns string in format % - %. */ -function getCardDescription(cardID?: number, cards: CardList = allCards) { - if (!cardID) { - return ''; - } - const card = cards[cardID]; +function getCardDescription(card?: Card) { if (!card) { return ''; } @@ -90,7 +69,7 @@ function getCardDescription(cardID?: number, cards: CardList = allCards) { * @returns company card name */ function getCompanyCardDescription(transactionCardName?: string, cardID?: number, cards?: CardList) { - if (!cardID || isExpensifyCard(cardID) || !cards?.[cardID]) { + if (!cardID || !cards?.[cardID] || isExpensifyCard(cards[cardID])) { return transactionCardName; } const card = cards[cardID]; @@ -110,9 +89,9 @@ function isCardClosed(card: Card) { return card?.state === CONST.EXPENSIFY_CARD.STATE.CLOSED; } -function mergeCardListWithWorkspaceFeeds(workspaceFeeds: Record, cardList = allCards, shouldExcludeCardHiddenFromSearch = false) { +function mergeCardListWithWorkspaceFeeds(workspaceFeeds: Record, cardList: CardList | undefined, shouldExcludeCardHiddenFromSearch = false) { const feedCards: CardList = {}; - Object.values(cardList).forEach((card) => { + Object.values(cardList ?? {}).forEach((card) => { if (!isCard(card) || (shouldExcludeCardHiddenFromSearch && isCardHiddenFromSearch(card))) { return; } diff --git a/src/libs/SearchQueryUtils.ts b/src/libs/SearchQueryUtils.ts index 611a187fdfbc..bcbc09de6995 100644 --- a/src/libs/SearchQueryUtils.ts +++ b/src/libs/SearchQueryUtils.ts @@ -722,7 +722,7 @@ function getFilterDisplayValue( if (Number.isNaN(cardID)) { return filterValue; } - return getCardDescription(cardID, cardList) || filterValue; + return getCardDescription(cardList?.[cardID]) || filterValue; } if (filterName === CONST.SEARCH.SYNTAX_FILTER_KEYS.IN) { return getReportName(reports?.[`${ONYXKEYS.COLLECTION.REPORT}${filterValue}`]) || filterValue; @@ -822,7 +822,7 @@ function buildUserReadableQueryString( if (Number.isNaN(cardID)) { acc.push({operator: filter.operator, value: cardID}); } else { - acc.push({operator: filter.operator, value: getCardDescription(cardID, cardList) || cardID}); + acc.push({operator: filter.operator, value: getCardDescription(cardList?.[cardID]) || cardID}); } } return acc; diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx index c6d72b220589..8ff02b3be250 100644 --- a/src/pages/Search/AdvancedSearchFilters.tsx +++ b/src/pages/Search/AdvancedSearchFilters.tsx @@ -358,7 +358,7 @@ function getFilterCardDisplayTitle(filters: Partial, const cardNames = Object.values(cards) .filter((card) => cardIdsFilter.includes(card.cardID.toString()) && !feedFilter.includes(createCardFeedKey(card.fundID, card.bank))) - .map((card) => getCardDescription(card.cardID, cards)); + .map((card) => getCardDescription(card)); const feedNames = Object.keys(cardFeedNamesWithType) .filter((workspaceCardFeedKey) => { diff --git a/src/pages/settings/Wallet/PaymentMethodList.tsx b/src/pages/settings/Wallet/PaymentMethodList.tsx index 96d07840bb89..ebd5502d2fac 100644 --- a/src/pages/settings/Wallet/PaymentMethodList.tsx +++ b/src/pages/settings/Wallet/PaymentMethodList.tsx @@ -219,14 +219,14 @@ function PaymentMethodList({ const assignedCards = Object.values(isLoadingCardList ? {} : (cardList ?? {})) // Filter by active cards associated with a domain .filter((card) => !!card.domainName && CONST.EXPENSIFY_CARD.ACTIVE_STATES.includes(card.state ?? 0)); - const assignedCardsSorted = lodashSortBy(assignedCards, (card) => !isExpensifyCard(card.cardID)); + const assignedCardsSorted = lodashSortBy(assignedCards, (card) => !isExpensifyCard(card)); const assignedCardsGrouped: PaymentMethodItem[] = []; assignedCardsSorted.forEach((card) => { const isDisabled = card.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || !!card.errors; const icon = getCardFeedIcon(card.bank as CompanyCardFeed, illustrations); - if (!isExpensifyCard(card.cardID)) { + if (!isExpensifyCard(card)) { const pressHandler = onPress as CardPressHandler; const lastFourPAN = lastFourNumbersFromCardName(card.cardName); const plaidUrl = getPlaidInstitutionIconUrl(card.bank); diff --git a/tests/unit/CardUtilsTest.ts b/tests/unit/CardUtilsTest.ts index f528e7ac30f3..546503348248 100644 --- a/tests/unit/CardUtilsTest.ts +++ b/tests/unit/CardUtilsTest.ts @@ -10,8 +10,10 @@ import { formatCardExpiration, getBankCardDetailsImage, getBankName, + getCardDescription, getCardFeedIcon, getCardsByCardholderName, + getCompanyCardDescription, getCompanyFeeds, getCustomOrFormattedFeedName, getFeedType, @@ -21,12 +23,13 @@ import { getYearFromExpirationDateString, hasIssuedExpensifyCard, isCustomFeed as isCustomFeedCardUtils, + isExpensifyCard, isExpensifyCardFullySetUp, lastFourNumbersFromCardName, maskCardNumber, sortCardsByCardholderName, } from '@src/libs/CardUtils'; -import type {CardFeeds, CardList, CompanyCardFeed, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; +import type {Card, CardFeeds, CardList, CompanyCardFeed, ExpensifyCardSettings, PersonalDetailsList, Policy, WorkspaceCardsList} from '@src/types/onyx'; import type {CompanyCardFeedWithNumber} from '@src/types/onyx/CardFeeds'; import {localeCompare} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; @@ -1024,4 +1027,115 @@ describe('CardUtils', () => { expect(sortedCards.at(0)?.cardID).toBe(1); }); }); + + describe('getCardDescription', () => { + it('should return the correct card description for company card', () => { + const card: Card = { + accountID: 18439984, + bank: CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, + cardID: 21310091, + cardName: '480801XXXXXX2554', + domainName: 'expensify-policy41314f4dc5ce25af.exfy', + fraud: 'none', + lastFourPAN: '2554', + lastUpdated: '', + lastScrape: '2024-11-27 11:00:53', + scrapeMinDate: '2024-10-17', + state: 3, + }; + const description = getCardDescription(card); + expect(description).toBe('Visa - 2554'); + }); + + it('should return the correct card description for Expensify card', () => { + const card: Card = { + accountID: 18439984, + bank: CONST.EXPENSIFY_CARD.BANK, + cardID: 21570657, + cardName: 'CREDIT CARD...5644', + domainName: 'expensify-policy17f617b9fe23d2f1.exfy', + fraud: 'none', + lastFourPAN: '', + lastScrape: '', + lastUpdated: '', + state: 2, + }; + const description = getCardDescription(card); + expect(description).toBe('Expensify Card'); + }); + }); + + describe('isExpensifyCard', () => { + it('should return true for Expensify Card', () => { + const card: Card = { + accountID: 18439984, + bank: CONST.EXPENSIFY_CARD.BANK, + cardID: 21570657, + cardName: 'CREDIT CARD...5644', + domainName: 'expensify-policy17f617b9fe23d2f1.exfy', + fraud: 'none', + lastFourPAN: '', + lastScrape: '', + lastUpdated: '', + state: 2, + }; + expect(isExpensifyCard(card)).toBe(true); + }); + + it('should return false for non-Expensify Card', () => { + const card: Card = { + accountID: 18439984, + bank: CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, + cardID: 21310091, + cardName: '480801XXXXXX2554', + domainName: 'expensify-policy41314f4dc5ce25af.exfy', + fraud: 'none', + lastFourPAN: '2554', + lastUpdated: '', + lastScrape: '2024-11-27 11:00:53', + scrapeMinDate: '2024-10-17', + state: 3, + }; + expect(isExpensifyCard(card)).toBe(false); + }); + }); + + describe('getCompanyCardDescription', () => { + const cardList: CardList = { + '21310091': { + accountID: 18439984, + bank: CONST.COMPANY_CARD.FEED_BANK_NAME.VISA, + cardID: 21310091, + cardName: '480801XXXXXX2554', + domainName: 'expensify-policy41314f4dc5ce25af.exfy', + fraud: 'none', + lastFourPAN: '2554', + lastUpdated: '', + lastScrape: '2024-11-27 11:00:53', + scrapeMinDate: '2024-10-17', + state: 3, + }, + '21570657': { + accountID: 18439984, + bank: CONST.EXPENSIFY_CARD.BANK, + cardID: 21570657, + cardName: 'CREDIT CARD...5644', + domainName: 'expensify-policy17f617b9fe23d2f1.exfy', + fraud: 'none', + lastFourPAN: '', + lastScrape: '', + lastUpdated: '', + state: 2, + }, + }; + it('should return the correct description for a company card', () => { + const description = getCompanyCardDescription('Test', 21310091, cardList); + expect(description).toBe('480801XXXXXX2554'); + }); + + it('should return the correct description for an Expensify card', () => { + const description = getCompanyCardDescription('Test', 21570657, cardList); + expect(description).toBe('Test'); + }); + }); });