From 8509aa23e9c8f467992713ae80b8ecb1a97d7c1e Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 9 Jun 2026 11:22:55 -0700 Subject: [PATCH 1/4] fix(eslint): remove unsafe type assertions in MoneyRequestConfirmationList selectors --- config/eslint/eslint.seatbelt.tsv | 2 +- .../sections/InvoiceSenderField.tsx | 4 ++-- .../sections/selectors.ts | 14 +++++++------- src/libs/TagsOptionsListUtils.ts | 2 +- src/libs/TransactionUtils/index.ts | 10 +++++----- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv index bd4f60383a83..009a68f64f76 100644 --- a/config/eslint/eslint.seatbelt.tsv +++ b/config/eslint/eslint.seatbelt.tsv @@ -271,7 +271,7 @@ "../../src/components/MoneyRequestConfirmationList/sections/AmountField.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/MoneyRequestConfirmationList/sections/AttendeeField.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/MoneyRequestConfirmationList/sections/TaxFields.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/components/MoneyRequestConfirmationList/sections/selectors.ts" "@typescript-eslint/no-unsafe-type-assertion" 7 +"../../src/components/MoneyRequestConfirmationList/sections/selectors.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../src/components/MoneyRequestConfirmationListFooter/ConfirmationReceiptThumbnail.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/MoneyRequestConfirmationListFooter/hooks/useReceiptThumbnailSource.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/components/MoneyRequestConfirmationListFooter/sections/DistanceMapSection.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 diff --git a/src/components/MoneyRequestConfirmationList/sections/InvoiceSenderField.tsx b/src/components/MoneyRequestConfirmationList/sections/InvoiceSenderField.tsx index ee822c9ad368..270bf2741cb8 100644 --- a/src/components/MoneyRequestConfirmationList/sections/InvoiceSenderField.tsx +++ b/src/components/MoneyRequestConfirmationList/sections/InvoiceSenderField.tsx @@ -31,8 +31,8 @@ type InvoiceSenderFieldProps = { /** The report ID */ reportID: string; - /** The transaction */ - transaction: OnyxEntry; + /** The transaction (only the fields this field reads) */ + transaction: OnyxEntry>; }; const senderWorkspaceSelector = (policy: OnyxEntry) => (policy ? {id: policy.id, name: policy.name, avatarURL: policy.avatarURL} : undefined); diff --git a/src/components/MoneyRequestConfirmationList/sections/selectors.ts b/src/components/MoneyRequestConfirmationList/sections/selectors.ts index 668ceb6495d1..74f4dee8bc44 100644 --- a/src/components/MoneyRequestConfirmationList/sections/selectors.ts +++ b/src/components/MoneyRequestConfirmationList/sections/selectors.ts @@ -89,7 +89,7 @@ const createTagDisplaySelector = (tagIndex: number) => (t: OnyxEntry, tagIndex); + return getTagForDisplay({tag: t.tag}, tagIndex); }; // --- CategoryField --- @@ -200,7 +200,7 @@ const taxSliceSelector = (t: OnyxEntry): TaxSlice | undefined => { type DerivedFlagsSlice = Pick; -const derivedFlagsSliceSelector = (t: OnyxEntry): OnyxEntry => { +const derivedFlagsSliceSelector = (t: OnyxEntry): OnyxEntry => { if (!t) { return undefined; } @@ -211,26 +211,26 @@ const derivedFlagsSliceSelector = (t: OnyxEntry): OnyxEntry; -const tagSliceSelector = (t: OnyxEntry): OnyxEntry => { +const tagSliceSelector = (t: OnyxEntry): OnyxEntry => { if (!t) { return undefined; } const slice: TagSlice = {tag: t.tag}; - return slice as Transaction; + return slice; }; // --- InvoiceSenderSection --- type InvoiceSenderSlice = Pick; -const invoiceSenderSliceSelector = (t: OnyxEntry): OnyxEntry => { +const invoiceSenderSliceSelector = (t: OnyxEntry): OnyxEntry => { if (!t) { return undefined; } @@ -238,7 +238,7 @@ const invoiceSenderSliceSelector = (t: OnyxEntry): OnyxEntry; - transaction: Transaction | undefined; + transaction: Pick | undefined; }): TagVisibility[] { const hasDependentTags = hasDependentTagsPolicyUtils(policy, policyTags); const isMultilevelTags = isMultiLevelTagsPolicyUtils(policyTags); diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 939fb5aac266..a83621273bc6 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -178,7 +178,7 @@ function isOdometerDistanceRequest(transaction: OnyxEntry): boolean return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER; } -function isScanRequest(transaction: OnyxEntry): boolean { +function isScanRequest(transaction: OnyxEntry>): boolean { return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.SCAN; } @@ -959,7 +959,7 @@ function getFormattedPostedDate(transaction: OnyxInputOrEntry, date /** * Return the currency field from the transaction, return the modifiedCurrency if present. */ -function getCurrency(transaction: OnyxInputOrEntry): string { +function getCurrency(transaction: OnyxInputOrEntry>): string { const currency = transaction?.modifiedCurrency ?? ''; if (currency) { return currency; @@ -1274,7 +1274,7 @@ function getExchangeRate(transaction: TransactionWithOptionalSearchFields, repor * Return the tag from the transaction. When the tagIndex is passed, return the tag based on the index. * This "tag" field has no "modified" complement. */ -function getTag(transaction: OnyxInputOrEntry, tagIndex?: number): string { +function getTag(transaction: OnyxInputOrEntry>, tagIndex?: number): string { if (tagIndex !== undefined) { const tagsArray = getTagArrayFromName(transaction?.tag ?? ''); return tagsArray.at(tagIndex) ?? ''; @@ -1283,7 +1283,7 @@ function getTag(transaction: OnyxInputOrEntry, tagIndex?: number): return transaction?.tag ?? ''; } -function getTagForDisplay(transaction: OnyxEntry, tagIndex?: number): string { +function getTagForDisplay(transaction: OnyxEntry>, tagIndex?: number): string { return getCommaSeparatedTagNameWithSanitizedColons(getTag(transaction, tagIndex)); } @@ -1310,7 +1310,7 @@ function isExpensifyCardTransaction(transaction: OnyxEntry): boolea /** * Determine whether a transaction is made with a centrally managed card (Expensify or Company Card). */ -function isManagedCardTransaction(transaction: OnyxEntry): boolean { +function isManagedCardTransaction(transaction: OnyxEntry>): boolean { return !!transaction?.managedCard; } From 043d2d6197f308228aa1127cbb6d99d8a8bca87c Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 9 Jun 2026 11:25:40 -0700 Subject: [PATCH 2/4] fix(eslint): remove unsafe type assertion in Navigation.ts --- config/eslint/eslint.seatbelt.tsv | 2 +- src/libs/Navigation/Navigation.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv index 009a68f64f76..783ce805da03 100644 --- a/config/eslint/eslint.seatbelt.tsv +++ b/config/eslint/eslint.seatbelt.tsv @@ -679,7 +679,7 @@ "../../src/libs/Navigation/AppNavigator/routerExtensions/addRootHistoryRouterExtension.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/AppNavigator/routerExtensions/addRootHistoryRouterExtensionUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Navigation/DebugTabNavigator.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../src/libs/Navigation/Navigation.ts" "@typescript-eslint/no-unsafe-type-assertion" 11 +"../../src/libs/Navigation/Navigation.ts" "@typescript-eslint/no-unsafe-type-assertion" 10 "../../src/libs/Navigation/Navigation.ts" "no-restricted-imports" 1 "../../src/libs/Navigation/OnyxTabNavigator.tsx" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/libs/Navigation/PlatformStackNavigation/ScreenLayout.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 84eb39be284a..0eda26ab907b 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -1158,7 +1158,7 @@ function removePreInsertedFullscreenIfNeeded() { const originalTabRoute = getPreInsertedOriginalTabRoute(); if (originalTabRoute) { clearPreInsertedOriginalTabRoute(); - const originalTabState = originalTabRoute.state as NavigationState | undefined; + const originalTabState = originalTabRoute.state; const originalFocusedTabIndex = originalTabState?.index ?? 0; const originalTabName = originalTabState?.routes?.[originalFocusedTabIndex]?.name; if (originalTabName) { From e24d30eded6c266db4cc46de750ce2b1cb2f2413 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 9 Jun 2026 11:30:56 -0700 Subject: [PATCH 3/4] fix(eslint): remove unsafe type assertions in SearchUIUtils --- config/eslint/eslint.seatbelt.tsv | 2 +- src/libs/SearchUIUtils.ts | 18 +++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv index 783ce805da03..04e7bfeabd85 100644 --- a/config/eslint/eslint.seatbelt.tsv +++ b/config/eslint/eslint.seatbelt.tsv @@ -774,7 +774,7 @@ "../../src/libs/SearchAutocompleteUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/libs/SearchQueryUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 73 "../../src/libs/SearchUIUtils.ts" "@typescript-eslint/no-deprecated/getSearchReportName" 1 -"../../src/libs/SearchUIUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 71 +"../../src/libs/SearchUIUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 69 "../../src/libs/SelectionScraper/index.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 "../../src/libs/SidebarUtils.ts" "@typescript-eslint/no-unsafe-type-assertion" 1 "../../src/libs/Sound/index.native.ts" "@typescript-eslint/no-unsafe-type-assertion" 4 diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index ebaa55c151cb..15f090d8bd22 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -5361,18 +5361,26 @@ function getWithdrawalStatusDisplayText(value: SearchWithdrawalStatus | undefine .join(', '); } +/** + * A `PolicyCategoriesLookup` can either be a single policy's categories or an Onyx collection of + * many policies' categories keyed by `policy_categories_`. A collection is the only + * variant whose keys start with that prefix, so we use that to discriminate the union. + */ +function isPolicyCategoriesCollection(policyCategories: NonNullable): policyCategories is NonNullable> { + return Object.keys(policyCategories).some((key) => key.startsWith(ONYXKEYS.COLLECTION.POLICY_CATEGORIES)); +} + function getPolicyCategoriesForPolicyID(policyCategories: PolicyCategoriesLookup | undefined, policyID?: string): OnyxEntry { if (!policyCategories) { return undefined; } - const policyCategoriesKey = policyID ? `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}` : undefined; - if (policyCategoriesKey && Object.prototype.hasOwnProperty.call(policyCategories, policyCategoriesKey)) { - return (policyCategories as OnyxCollection)?.[policyCategoriesKey]; + if (!isPolicyCategoriesCollection(policyCategories)) { + return policyCategories; } - const isPolicyCategoriesCollection = Object.keys(policyCategories).some((key) => key.startsWith(ONYXKEYS.COLLECTION.POLICY_CATEGORIES)); - return isPolicyCategoriesCollection ? undefined : (policyCategories as OnyxEntry); + const policyCategoriesKey = policyID ? `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}` : undefined; + return policyCategoriesKey ? policyCategories[policyCategoriesKey] : undefined; } /** From 0ed1008f3e124818e05e65a4bedae4c63c6455f4 Mon Sep 17 00:00:00 2001 From: Yuwen Memon Date: Tue, 9 Jun 2026 11:40:03 -0700 Subject: [PATCH 4/4] fix(eslint): remove unsafe type assertions in SearchUIUtilsTest --- config/eslint/eslint.seatbelt.tsv | 2 +- tests/unit/Search/SearchUIUtilsTest.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv index 04e7bfeabd85..a375a9055911 100644 --- a/config/eslint/eslint.seatbelt.tsv +++ b/config/eslint/eslint.seatbelt.tsv @@ -1846,7 +1846,7 @@ "../../tests/unit/Search/SearchListRenderCountTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../tests/unit/Search/SearchQueryUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 9 "../../tests/unit/Search/SearchSingleSelectionPickerTest.tsx" "@typescript-eslint/no-unsafe-type-assertion" 1 -"../../tests/unit/Search/SearchUIUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 135 +"../../tests/unit/Search/SearchUIUtilsTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 133 "../../tests/unit/Search/buildCardFilterDataTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 22 "../../tests/unit/Search/buildSubstitutionsMapTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 3 "../../tests/unit/Search/getSpendOverTimeStateTest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 123f913e8981..dd0028022f2a 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -5965,7 +5965,7 @@ describe('SearchUIUtils', () => { CONST.SEARCH.SORT_ORDER.ASC, undefined, {policyCategories: policyCategoriesForSort}, - ) as TransactionListItemType[]; + ); const descendingResult = SearchUIUtils.getSortedSections( CONST.SEARCH.DATA_TYPES.EXPENSE, '', @@ -5976,10 +5976,10 @@ describe('SearchUIUtils', () => { CONST.SEARCH.SORT_ORDER.DESC, undefined, {policyCategories: policyCategoriesForSort}, - ) as TransactionListItemType[]; + ); - expect(ascendingResult.map((transaction) => transaction.transactionID)).toEqual(['without-gl-code', 'gl-code-1010', 'gl-code-6100', 'gl-code-6200']); - expect(descendingResult.map((transaction) => transaction.transactionID)).toEqual(['gl-code-6200', 'gl-code-6100', 'gl-code-1010', 'without-gl-code']); + expect(ascendingResult.map((item) => ('transactionID' in item ? item.transactionID : undefined))).toEqual(['without-gl-code', 'gl-code-1010', 'gl-code-6100', 'gl-code-6200']); + expect(descendingResult.map((item) => ('transactionID' in item ? item.transactionID : undefined))).toEqual(['gl-code-6200', 'gl-code-6100', 'gl-code-1010', 'without-gl-code']); }); it('should return getSortedReportData result when type is expense-report', () => {