diff --git a/src/hooks/usePolicyData/index.ts b/src/hooks/usePolicyData/index.ts index b67328916b7f2..f95b5a22f7cad 100644 --- a/src/hooks/usePolicyData/index.ts +++ b/src/hooks/usePolicyData/index.ts @@ -4,9 +4,8 @@ import {useAllReportsTransactionsAndViolations} from '@components/OnyxListItemPr import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Policy, Report} from '@src/types/onyx'; +import type {Report} from '@src/types/onyx'; import type {ReportTransactionsAndViolationsDerivedValue} from '@src/types/onyx/DerivedValues'; -import type {OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon'; import type PolicyData from './types'; /** @@ -14,8 +13,7 @@ import type PolicyData from './types'; * @param policyID The ID of the policy to retrieve data for. * @returns An object containing policy data */ -function usePolicyData(policyID?: string): PolicyData { - const policy = usePolicy(policyID); +function usePolicyData(policyID: string): PolicyData { const allReportsTransactionsAndViolations = useAllReportsTransactionsAndViolations(); // Stable selector for useOnyx to avoid defining the selector inline @@ -37,6 +35,7 @@ function usePolicyData(policyID?: string): PolicyData { [policyID, allReportsTransactionsAndViolations], ); + const policy = usePolicy(policyID); const [tags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}, [policyID]); const [categories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, {canBeMissing: true}, [policyID]); const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true, selector: reportsSelectorCallback}, [policyID, allReportsTransactionsAndViolations]); @@ -44,19 +43,21 @@ function usePolicyData(policyID?: string): PolicyData { if (!reports || !allReportsTransactionsAndViolations) { return {}; } - return Object.keys(reports).reduce((acc, reportID) => { - if (allReportsTransactionsAndViolations[reportID]) { + return Object.entries(reports).reduce((acc, [reportID]) => { + if (Object.keys(allReportsTransactionsAndViolations[reportID].transactions).length > 0) { acc[reportID] = allReportsTransactionsAndViolations[reportID]; } return acc; }, {}); }, [reports, allReportsTransactionsAndViolations]); + return { transactionsAndViolations, tags: tags ?? {}, - categories: categories ?? {}, - policy: policy as OnyxValueWithOfflineFeedback, - reports: Object.values(reports ?? {}) as Array>, + categories, + policyID, + policy, + reports: Object.values(reports ?? {}), }; } diff --git a/src/hooks/usePolicyData/types.ts b/src/hooks/usePolicyData/types.ts index df3367cd1fccc..4199e0ad36f08 100644 --- a/src/hooks/usePolicyData/types.ts +++ b/src/hooks/usePolicyData/types.ts @@ -1,12 +1,13 @@ +import type {OnyxEntry} from 'react-native-onyx'; import type {Policy, PolicyCategories, PolicyTagLists, Report} from '@src/types/onyx'; import type {ReportTransactionsAndViolationsDerivedValue} from '@src/types/onyx/DerivedValues'; -import type {OnyxValueWithOfflineFeedback} from '@src/types/onyx/OnyxCommon'; type PolicyData = { - policy: OnyxValueWithOfflineFeedback; + policyID: string; + policy: OnyxEntry; tags: PolicyTagLists; - categories: PolicyCategories; - reports: Array>; + categories: OnyxEntry; + reports: Array>; transactionsAndViolations: ReportTransactionsAndViolationsDerivedValue; }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d25756fc39b98..818d954416d36 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1987,9 +1987,13 @@ function pushTransactionViolationsOnyxData( categoriesUpdate: Record> = {}, tagListsUpdate: Record> = {}, ) { + const {policy, tags: tagLists, categories} = policyData; + if (policy === undefined) { + return; + } const nonInvoiceReportTransactionsAndViolations = policyData.reports.reduce((acc, report) => { // Skipping invoice reports since they should not have any category or tag violations - if (isInvoiceReport(report)) { + if (report === undefined || isInvoiceReport(report)) { return acc; } const reportTransactionsAndViolations = policyData.transactionsAndViolations[report.reportID]; @@ -2015,53 +2019,72 @@ function pushTransactionViolationsOnyxData( } // Merge the existing policy with the optimistic updates - const optimisticPolicy = isPolicyUpdateEmpty ? policyData.policy : {...policyData.policy, ...policyUpdate}; + const optimisticPolicy = isPolicyUpdateEmpty ? policy : {...policy, ...policyUpdate}; // Merge the existing categories with the optimistic updates const optimisticCategories = isCategoriesUpdateEmpty - ? policyData.categories + ? (categories ?? {}) : { - ...Object.fromEntries(Object.entries(policyData.categories).filter(([categoryName]) => !(categoryName in categoriesUpdate) || !!categoriesUpdate[categoryName])), + ...Object.fromEntries(Object.entries(categories ?? {}).filter(([categoryName]) => !(categoryName in categoriesUpdate) || !!categoriesUpdate[categoryName])), ...Object.entries(categoriesUpdate).reduce((acc, [categoryName, categoryUpdate]) => { if (!categoryUpdate) { return acc; } + const category = categories?.[categoryName]; + if (category === undefined) { + acc[categoryName] = categoryUpdate as PolicyCategory; + return acc; + } + acc[categoryName] = { - ...(policyData.categories?.[categoryName] ?? {}), + ...category, ...categoryUpdate, - }; + } as PolicyCategory; return acc; }, {}), }; // Merge the existing tag lists with the optimistic updates const optimisticTagLists = isTagListsUpdateEmpty - ? policyData.tags + ? (tagLists ?? {}) : { - ...Object.fromEntries(Object.entries(policyData.tags ?? {}).filter(([tagListName]) => !(tagListName in tagListsUpdate) || !!tagListsUpdate[tagListName])), + ...Object.fromEntries(Object.entries(tagLists ?? {}).filter(([tagListName]) => !(tagListName in tagListsUpdate) || !!tagListsUpdate[tagListName])), ...Object.entries(tagListsUpdate).reduce((acc, [tagListName, tagListUpdate]) => { - if (!tagListUpdate) { + if (!tagListName || !tagListUpdate) { return acc; } - const tagList = policyData.tags?.[tagListName]; - const tags = tagList.tags ?? {}; + const tagList = tagLists?.[tagListName]; + if (!tagList) { + acc[tagListName] = tagListUpdate as PolicyTagList; + return acc; + } + const tags = tagList?.tags; + if (!tags) { + acc[tagListName] = {...tagList, ...tagListUpdate} as PolicyTagList; + return acc; + } const tagsUpdate = tagListUpdate?.tags ?? {}; - acc[tagListName] = { ...tagList, ...tagListUpdate, tags: { ...((): PolicyTags => { - const optimisticTags: PolicyTags = Object.fromEntries(Object.entries(tags).filter(([tagName]) => !(tagName in tagsUpdate) || !!tagsUpdate[tagName])); + const optimisticTags: PolicyTags = Object.fromEntries( + Object.entries(tags ?? {}).filter(([tagName, tag]) => !tag || !(tagName in tagsUpdate) || !!tagsUpdate[tagName]), + ); for (const [tagName, tagUpdate] of Object.entries(tagsUpdate)) { if (!tagUpdate) { continue; } - optimisticTags[tagName] = { - ...(tags[tagName] ?? {}), - ...tagUpdate, - }; + const tag = tags?.[tagName]; + optimisticTags[tagName] = + tag !== undefined + ? { + ...tag, + ...tagUpdate, + } + : tagUpdate; } return optimisticTags; })(), diff --git a/src/libs/actions/Policy/Category.ts b/src/libs/actions/Policy/Category.ts index 3a0f980474dd9..6b42a960e4481 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -323,8 +323,12 @@ function setWorkspaceCategoryEnabled( setupCategoryTaskParentReport: OnyxEntry, currentUserAccountID: number, ) { - const policyID = policyData.policy?.id; - const policyCategoriesOptimisticData = { + const {policyID, categories} = policyData; + if (!categories) { + Log.warn('setWorkspaceCategoryEnabled invalid params', {categories}); + return; + } + const categoriesOptimisticData = { ...Object.keys(categoriesToUpdate).reduce((acc, key) => { acc[key] = { ...categoriesToUpdate[key], @@ -344,7 +348,7 @@ function setWorkspaceCategoryEnabled( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, - value: policyCategoriesOptimisticData, + value: categoriesOptimisticData, }, ], successData: [ @@ -373,7 +377,7 @@ function setWorkspaceCategoryEnabled( value: { ...Object.keys(categoriesToUpdate).reduce((acc, key) => { acc[key] = { - ...policyData.categories[key], + ...categories[key], errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.categories.updateFailureMessage'), pendingFields: { enabled: null, @@ -388,7 +392,7 @@ function setWorkspaceCategoryEnabled( ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, {}, policyCategoriesOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, {}, categoriesOptimisticData); appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived, currentUserAccountID); const parameters = { @@ -469,9 +473,9 @@ function setPolicyCategoryDescriptionRequired(policyID: string, categoryName: st } function setPolicyCategoryReceiptsRequired(policyData: PolicyData, categoryName: string, maxAmountNoReceipt: number) { - const policyID = policyData.policy?.id; - const originalMaxAmountNoReceipt = policyData.categories[categoryName]?.maxAmountNoReceipt; - const policyCategoriesOptimisticData = { + const {policyID, categories} = policyData; + const originalMaxAmountNoReceipt = categories?.[categoryName]?.maxAmountNoReceipt; + const categoriesOptimisticData = { [categoryName]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, pendingFields: { @@ -486,7 +490,7 @@ function setPolicyCategoryReceiptsRequired(policyData: PolicyData, categoryName: { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, - value: policyCategoriesOptimisticData, + value: categoriesOptimisticData, }, ], successData: [ @@ -522,7 +526,7 @@ function setPolicyCategoryReceiptsRequired(policyData: PolicyData, categoryName: ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, {}, policyCategoriesOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, {}, categoriesOptimisticData); const parameters: SetPolicyCategoryReceiptsRequiredParams = { policyID, @@ -534,9 +538,9 @@ function setPolicyCategoryReceiptsRequired(policyData: PolicyData, categoryName: } function removePolicyCategoryReceiptsRequired(policyData: PolicyData, categoryName: string) { - const policyID = policyData.policy?.id; - const originalMaxAmountNoReceipt = policyData.categories[categoryName]?.maxAmountNoReceipt; - const policyCategoriesOptimisticData = { + const {policyID, categories} = policyData; + const originalMaxAmountNoReceipt = categories?.[categoryName]?.maxAmountNoReceipt; + const categoriesOptimisticData = { [categoryName]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, pendingFields: { @@ -551,7 +555,7 @@ function removePolicyCategoryReceiptsRequired(policyData: PolicyData, categoryNa { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, - value: policyCategoriesOptimisticData, + value: categoriesOptimisticData, }, ], successData: [ @@ -587,7 +591,7 @@ function removePolicyCategoryReceiptsRequired(policyData: PolicyData, categoryNa ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, {}, policyCategoriesOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, {}, categoriesOptimisticData); const parameters: RemovePolicyCategoryReceiptsRequiredParams = { policyID, @@ -598,17 +602,18 @@ function removePolicyCategoryReceiptsRequired(policyData: PolicyData, categoryNa } function createPolicyCategory( - policyID: string, + policyData: PolicyData, categoryName: string, isSetupCategoriesTaskParentReportArchived: boolean, setupCategoryTaskReport: OnyxEntry, setupCategoryTaskParentReport: OnyxEntry, currentUserAccountID: number, ) { - const onyxData = buildOptimisticPolicyCategories(policyID, [categoryName]); + const onyxData = buildOptimisticPolicyCategories(policyData.policyID, [categoryName]); appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived, currentUserAccountID); + const parameters = { - policyID, + policyID: policyData.policyID, categories: JSON.stringify([{name: categoryName}]), }; @@ -636,9 +641,8 @@ function importPolicyCategories(policyID: string, categories: PolicyCategory[]) } function renamePolicyCategory(policyData: PolicyData, policyCategory: {oldName: string; newName: string}) { - const policy = policyData.policy; - const policyID = policy.id; - const policyCategoryToUpdate = policyData.categories?.[policyCategory.oldName]; + const {policyID, policy, categories} = policyData; + const policyCategoryToUpdate = categories?.[policyCategory.oldName]; const policyCategoryApproverRule = CategoryUtils.getCategoryApproverRule(policy?.rules?.approvalRules ?? [], policyCategory.oldName); const policyCategoryExpenseRule = CategoryUtils.getCategoryExpenseRule(policy?.rules?.expenseRules ?? [], policyCategory.oldName); @@ -683,7 +687,7 @@ function renamePolicyCategory(policyData: PolicyData, policyCategory: {oldName: mccGroup: updatedMccGroup, }; - const policyCategoriesOptimisticData = { + const categoriesOptimisticData = { [policyCategory.newName]: { ...policyCategoryToUpdate, errors: null, @@ -704,7 +708,7 @@ function renamePolicyCategory(policyData: PolicyData, policyCategory: {oldName: key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, value: { [policyCategory.oldName]: null, - ...policyCategoriesOptimisticData, + ...categoriesOptimisticData, }, }, { @@ -767,7 +771,7 @@ function renamePolicyCategory(policyData: PolicyData, policyCategory: {oldName: return acc; }, {}); - pushTransactionViolationsOnyxData(onyxData, {...policyData, categories: policyCategories}, policyOptimisticData, policyCategoriesOptimisticData); + pushTransactionViolationsOnyxData(onyxData, {...policyData, categories: policyCategories}, policyOptimisticData, categoriesOptimisticData); const parameters = { policyID, @@ -914,7 +918,7 @@ function setPolicyCategoryGLCode(policyID: string, categoryName: string, glCode: } function setWorkspaceRequiresCategory(policyData: PolicyData, requiresCategory: boolean) { - const policyID = policyData.policy?.id; + const {policyID} = policyData; const policyOptimisticData: Partial = { requiresCategory, errors: { @@ -1001,14 +1005,14 @@ function deleteWorkspaceCategories( setupCategoryTaskParentReport: OnyxEntry, currentUserAccountID: number, ) { - const policyID = policyData.policy?.id; + const {policyID, categories} = policyData; const optimisticPolicyCategoriesData = categoryNamesToDelete.reduce>>((acc, categoryName) => { acc[categoryName] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, enabled: false}; return acc; }, {}); const shouldDisableRequiresCategory = !hasEnabledOptions( - Object.values(policyData.categories).filter((category) => !categoryNamesToDelete.includes(category.name) && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE), + Object.values(categories ?? {}).filter((category) => !categoryNamesToDelete.includes(category.name) && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE), ); const optimisticPolicyData: Partial = shouldDisableRequiresCategory ? { @@ -1062,7 +1066,7 @@ function deleteWorkspaceCategories( } function enablePolicyCategories(policyData: PolicyData, enabled: boolean, shouldGoBack = true) { - const policyID = policyData.policy?.id; + const {policyID, categories} = policyData; const policyUpdate: Partial = { areCategoriesEnabled: enabled, pendingFields: { @@ -1106,7 +1110,7 @@ function enablePolicyCategories(policyData: PolicyData, enabled: boolean, should if (!enabled) { policyCategoriesUpdate = Object.fromEntries( - Object.entries(policyData.categories).map(([categoryName]) => [ + Object.entries(categories ?? {}).map(([categoryName]) => [ categoryName, { enabled: false, diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index 39932c5cace67..33c036b0d0fcf 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -71,7 +71,7 @@ function IOURequestStepCategory({ const report = reportReal ?? reportDraft; const policyCategories = policyCategoriesReal ?? policyCategoriesDraft; - const policyData = usePolicyData(policy?.id); + const policyData = usePolicyData(policyID ?? CONST.DEFAULT_NUMBER_ID.toString()); const {currentSearchHash} = useSearchContext(); const isEditing = action === CONST.IOU.ACTION.EDIT; const isEditingSplit = (iouType === CONST.IOU.TYPE.SPLIT || iouType === CONST.IOU.TYPE.SPLIT_EXPENSE) && isEditing; diff --git a/src/pages/workspace/categories/CreateCategoryPage.tsx b/src/pages/workspace/categories/CreateCategoryPage.tsx index d28f49213676b..d26acca4476bf 100644 --- a/src/pages/workspace/categories/CreateCategoryPage.tsx +++ b/src/pages/workspace/categories/CreateCategoryPage.tsx @@ -5,7 +5,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnboardingTaskInformation from '@hooks/useOnboardingTaskInformation'; -import useOnyx from '@hooks/useOnyx'; +import usePolicyData from '@hooks/usePolicyData'; import useThemeStyles from '@hooks/useThemeStyles'; import {createPolicyCategory} from '@libs/actions/Policy/Category'; import Navigation from '@libs/Navigation/Navigation'; @@ -23,11 +23,11 @@ type CreateCategoryPageProps = | PlatformStackScreenProps; function CreateCategoryPage({route}: CreateCategoryPageProps) { - const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${route.params.policyID}`, {canBeMissing: true}); const styles = useThemeStyles(); const {translate} = useLocalize(); const backTo = route.params?.backTo; const isQuickSettingsFlow = route.name === SCREENS.SETTINGS_CATEGORIES.SETTINGS_CATEGORY_CREATE; + const policyData = usePolicyData(route.params.policyID); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const { taskReport: setupCategoryTaskReport, @@ -38,24 +38,16 @@ function CreateCategoryPage({route}: CreateCategoryPageProps) { const createCategory = useCallback( (values: FormOnyxValues) => { createPolicyCategory( - route.params.policyID, + policyData, values.categoryName.trim(), isSetupCategoryTaskParentReportArchived, setupCategoryTaskReport, setupCategoryTaskParentReport, currentUserPersonalDetails.accountID, ); - Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_CATEGORIES_ROOT.getRoute(route.params.policyID, backTo) : undefined); + Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_CATEGORIES_ROOT.getRoute(policyData.policyID, backTo) : undefined); }, - [ - route.params.policyID, - isSetupCategoryTaskParentReportArchived, - setupCategoryTaskReport, - setupCategoryTaskParentReport, - currentUserPersonalDetails.accountID, - isQuickSettingsFlow, - backTo, - ], + [policyData, isSetupCategoryTaskParentReportArchived, setupCategoryTaskReport, setupCategoryTaskParentReport, currentUserPersonalDetails.accountID, isQuickSettingsFlow, backTo], ); return ( @@ -76,7 +68,7 @@ function CreateCategoryPage({route}: CreateCategoryPageProps) { /> diff --git a/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx index e13707d180e61..ccd6a94753ac7 100644 --- a/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx +++ b/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx @@ -70,7 +70,7 @@ function WorkspaceCategoriesSettingsPage({policy, route}: WorkspaceCategoriesSet ); }, [policyData.policy]); - const hasEnabledCategories = hasEnabledOptions(policyData.categories); + const hasEnabledCategories = hasEnabledOptions(policyData?.categories ?? {}); const isToggleDisabled = !policy?.areCategoriesEnabled || !hasEnabledCategories || isConnectedToAccounting; const setNewCategory = (selectedCategory: SelectionListWithSectionsListItem, currentGroupID: string) => { diff --git a/tests/actions/PolicyCategoryTest.ts b/tests/actions/PolicyCategoryTest.ts index c5a45ebb55068..485e987e9a1a4 100644 --- a/tests/actions/PolicyCategoryTest.ts +++ b/tests/actions/PolicyCategoryTest.ts @@ -76,7 +76,9 @@ describe('actions/PolicyCategory', () => { mockFetch?.pause?.(); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); - Category.createPolicyCategory(fakePolicy.id, newCategoryName, false, undefined, undefined, CONST.DEFAULT_NUMBER_ID); + await waitForBatchedUpdates(); + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); + Category.createPolicyCategory(policyData.current, newCategoryName, false, undefined, undefined, CONST.DEFAULT_NUMBER_ID); await waitForBatchedUpdates(); await new Promise((resolve) => { const connection = Onyx.connect({ @@ -121,7 +123,7 @@ describe('actions/PolicyCategory', () => { mockFetch?.pause?.(); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); - + await waitForBatchedUpdates(); const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); Category.renamePolicyCategory(policyData.current, { oldName: oldCategoryName ?? '', @@ -176,7 +178,7 @@ describe('actions/PolicyCategory', () => { mockFetch?.pause?.(); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); - + await waitForBatchedUpdates(); const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); Category.setWorkspaceCategoryEnabled(policyData.current, categoriesToUpdate, false, undefined, undefined, CONST.DEFAULT_NUMBER_ID); await waitForBatchedUpdates(); @@ -224,6 +226,7 @@ describe('actions/PolicyCategory', () => { Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${fakePolicy.id}`, fakeCategories); + await waitForBatchedUpdates(); const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); Category.deleteWorkspaceCategories(policyData.current, categoriesToDelete, false, undefined, undefined, CONST.DEFAULT_NUMBER_ID); await waitForBatchedUpdates(); diff --git a/tests/actions/PolicyTagTest.ts b/tests/actions/PolicyTagTest.ts index 195e23ad1b27d..24f8888b313c3 100644 --- a/tests/actions/PolicyTagTest.ts +++ b/tests/actions/PolicyTagTest.ts @@ -585,17 +585,13 @@ describe('actions/Policy', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); + await waitForBatchedUpdates(); + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); // When the tag is renamed - const policyData = { - policy: fakePolicy, - tags: fakePolicyTags, - categories: {}, - reports: [], - transactionsAndViolations: {}, - }; + renamePolicyTag( - policyData, + policyData.current, { oldName: oldTagName, newName: newTagName, @@ -640,19 +636,14 @@ describe('actions/Policy', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); + await waitForBatchedUpdates(); + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); mockFetch.fail(); // When the tag rename fails - const policyData = { - policy: fakePolicy, - tags: fakePolicyTags, - categories: {}, - reports: [], - transactionsAndViolations: {}, - }; renamePolicyTag( - policyData, + policyData.current, { oldName: oldTagName, newName: newTagName, @@ -682,19 +673,14 @@ describe('actions/Policy', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, existingPolicyTags); + await waitForBatchedUpdates(); + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); // When trying to rename a tag with invalid index - const policyData = { - policy: fakePolicy, - tags: {}, - categories: {}, - reports: [], - transactionsAndViolations: {}, - }; expect(() => { renamePolicyTag( - policyData, + policyData.current, { oldName: 'oldTag', newName: 'newTag', @@ -742,17 +728,12 @@ describe('actions/Policy', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); + await waitForBatchedUpdates(); + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); // When the tag is renamed - const policyData = { - policy: fakePolicy, - tags: fakePolicyTags, - categories: {}, - reports: [], - transactionsAndViolations: {}, - }; renamePolicyTag( - policyData, + policyData.current, { oldName: oldTagName, newName: newTagName, @@ -789,7 +770,7 @@ describe('actions/Policy', () => { const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); await waitFor(() => { - expect(policyData.current.policy).toBeDefined(); + expect(policyData.current?.policy).toBeDefined(); }); // When renaming tag with data from usePolicyData @@ -1708,8 +1689,8 @@ describe('actions/Policy', () => { rerender(fakePolicy.id); // Then the policy should be updated optimistically - expect(policyData.current.policy?.areTagsEnabled).toBe(true); - expect(policyData.current.policy?.pendingFields?.areTagsEnabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + expect(policyData.current?.policy?.areTagsEnabled).toBe(true); + expect(policyData.current?.policy?.pendingFields?.areTagsEnabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); // And a default tag list should be created const defaultTag = Object.values(policyData.current?.tags ?? {}).at(0); @@ -1723,7 +1704,7 @@ describe('actions/Policy', () => { rerender(fakePolicy.id); // And after API success, pending fields should be cleared - expect(policyData.current.policy?.pendingFields?.areTagsEnabled).toBeFalsy(); + expect(policyData.current?.policy?.pendingFields?.areTagsEnabled).toBeFalsy(); }); it('should disable tags and update existing tag list', async () => { @@ -1749,9 +1730,9 @@ describe('actions/Policy', () => { // Then the policy should be updated optimistically rerender(fakePolicy.id); - expect(policyData.current.policy?.areTagsEnabled).toBe(false); - expect(policyData.current.policy?.requiresTag).toBe(false); - expect(policyData.current.policy?.pendingFields?.areTagsEnabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + expect(policyData.current?.policy?.areTagsEnabled).toBe(false); + expect(policyData.current?.policy?.requiresTag).toBe(false); + expect(policyData.current?.policy?.pendingFields?.areTagsEnabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); // And all tags should be disabled for (const tagName of Object.keys(existingTags)) { @@ -1764,7 +1745,7 @@ describe('actions/Policy', () => { // And after API success, pending fields should be cleared rerender(fakePolicy.id); - expect(policyData.current.policy?.pendingFields).toBeDefined(); + expect(policyData.current?.policy?.pendingFields).toBeDefined(); }); it('should reset changes when API returns error', async () => { @@ -1789,8 +1770,8 @@ describe('actions/Policy', () => { rerender(fakePolicy.id); // After the API request failure, the policy should be reset to original state - expect(policyData.current.policy.areTagsEnabled).toBe(false); - expect(policyData.current.policy.pendingFields?.areTagsEnabled).toBeUndefined(); + expect(policyData.current?.policy?.areTagsEnabled).toBe(false); + expect(policyData.current?.policy?.pendingFields?.areTagsEnabled).toBeUndefined(); expect(policyData.current.tags).toMatchObject({}); }); @@ -1806,15 +1787,15 @@ describe('actions/Policy', () => { const {result: policyData, rerender} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); - expect(policyData.current.policy).toBeDefined(); + expect(policyData.current?.policy).toBeDefined(); enablePolicyTags(policyData.current, true); await waitForBatchedUpdates(); rerender(fakePolicy.id); // Then the policy should be updated optimistically - expect(policyData.current.policy.areTagsEnabled).toBe(true); - expect(policyData.current.policy.pendingFields?.areTagsEnabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); + expect(policyData.current?.policy?.areTagsEnabled).toBe(true); + expect(policyData.current?.policy?.pendingFields?.areTagsEnabled).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); await mockFetch.resume(); await waitForBatchedUpdates(); @@ -1822,8 +1803,8 @@ describe('actions/Policy', () => { rerender(fakePolicy.id); // And after API success, policy should be enabled - expect(policyData.current.policy.areTagsEnabled).toBe(true); - expect(policyData.current.policy.pendingFields?.areTagsEnabled).toBeUndefined(); + expect(policyData.current?.policy?.areTagsEnabled).toBe(true); + expect(policyData.current?.policy?.pendingFields?.areTagsEnabled).toBeUndefined(); // And default tag list should be created expect(policyData.current.tags.Tag).toBeDefined(); diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts index ac8df3a728b6c..b4c17550f0bb9 100644 --- a/tests/perf-test/ReportUtils.perf-test.ts +++ b/tests/perf-test/ReportUtils.perf-test.ts @@ -229,7 +229,8 @@ describe('ReportUtils', () => { const policyData: PolicyData = { reports, - tags: createRandomPolicyTags('Tags', 8), + policyID, + tagLists: createRandomPolicyTags('Tags', 8), categories: createRandomPolicyCategories(8), // Current policy with categories and tags enabled but does not require them policy: { diff --git a/tests/unit/usePolicyData.test.ts b/tests/unit/usePolicyData.test.ts index b946077d1a5a9..f974fa1847e69 100644 --- a/tests/unit/usePolicyData.test.ts +++ b/tests/unit/usePolicyData.test.ts @@ -30,6 +30,7 @@ const mockTransaction = { tag: Object.values(mockPolicyTagLists).at(0)?.name, }; +const expectedReports = [mockIOUReport]; const expectedTransactionsAndViolations = { [mockIOUReport.reportID]: { transactions: { @@ -77,7 +78,7 @@ describe('usePolicyData', () => { return waitForBatchedUpdates(); }); - test('returns data given a policy ID that exists in the onyx', async () => { + it('should return the correct data given a policy ID that exists in the onyx', async () => { await Onyx.multiSet({ ...reportsCollection, ...reportActionsCollection, @@ -91,35 +92,24 @@ describe('usePolicyData', () => { const {result} = renderHook(() => usePolicyData(mockPolicy.id), {wrapper: OnyxListItemProvider}); - await waitForBatchedUpdates(); - - expect(result.current.policy).toEqual(mockPolicy); - expect(result.current.tags).toEqual(mockPolicyTagLists); - expect(result.current.categories).toEqual(mockPolicyCategories); - - expect(result.current.reports).toHaveLength(1); - expect(result.current.reports.at(0)).toEqual(mockIOUReport); - - expect(result.current.transactionsAndViolations).toEqual(expectedTransactionsAndViolations); + expect(result.current?.policyID).toMatchObject(mockPolicy.id); + expect(result.current?.policy).toMatchObject(mockPolicy); + expect(result.current?.tags).toMatchObject(mockPolicyTagLists); + expect(result.current?.categories).toMatchObject(mockPolicyCategories); + expect(result.current?.reports).toMatchObject(expectedReports); + expect(result.current?.transactionsAndViolations).toMatchObject(expectedTransactionsAndViolations); }); - test('returns default empty values when policy ID does not exist in the onyx', () => { + it('should return the default values when policy ID does not exist in the onyx', () => { + const policyID = 'non_existent_policy_id'; const {result} = renderHook(() => usePolicyData('non_existent_policy_id'), {wrapper: OnyxListItemProvider}); - expect(result.current.reports).toEqual([]); - expect(result.current.tags).toEqual({}); - expect(result.current.categories).toEqual({}); - expect(result.current.policy).toBeUndefined(); - expect(result.current.transactionsAndViolations).toEqual({}); - }); - - test('returns default empty values when policyID is undefined', () => { - const {result} = renderHook(() => usePolicyData(undefined), {wrapper: OnyxListItemProvider}); + expect(result.current?.policyID).toMatchObject(policyID); + expect(result.current?.policy).toBeUndefined(); + expect(result.current?.categories).toBeUndefined(); + expect(result.current?.tags).toBeUndefined(); - expect(result.current.reports).toEqual([]); - expect(result.current.tags).toEqual({}); - expect(result.current.categories).toEqual({}); - expect(result.current.policy).toBeUndefined(); - expect(result.current.transactionsAndViolations).toEqual({}); + expect(result.current?.reports).toMatchObject([]); + expect(result.current?.transactionsAndViolations).toMatchObject({}); }); });