diff --git a/src/hooks/usePolicyData/index.ts b/src/hooks/usePolicyData/index.ts index b67328916b7f2..7af00f30094f7 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,15 +35,16 @@ function usePolicyData(policyID?: string): PolicyData { [policyID, allReportsTransactionsAndViolations], ); - const [tags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}, [policyID]); + const policy = usePolicy(policyID); + const [tagLists] = 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]); const transactionsAndViolations = useMemo(() => { 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; @@ -53,10 +52,11 @@ function usePolicyData(policyID?: string): PolicyData { }, [reports, allReportsTransactionsAndViolations]); return { transactionsAndViolations, - tags: tags ?? {}, + tagLists: tagLists ?? {}, categories: categories ?? {}, - policy: policy as OnyxValueWithOfflineFeedback, - reports: Object.values(reports ?? {}) as Array>, + policyID, + policy, + reports: Object.values(reports ?? {}), }; } diff --git a/src/hooks/usePolicyData/types.ts b/src/hooks/usePolicyData/types.ts index df3367cd1fccc..509587cf6886c 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; - tags: PolicyTagLists; - categories: PolicyCategories; - reports: Array>; + policyID: string; + policy: OnyxEntry; + tagLists: OnyxEntry; + categories: OnyxEntry; + reports: Array>; transactionsAndViolations: ReportTransactionsAndViolationsDerivedValue; }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9de3de0196af5..3a043c20e863c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1998,9 +1998,13 @@ function pushTransactionViolationsOnyxData( categoriesUpdate: Record> = {}, tagListsUpdate: Record> = {}, ) { + const {policy, 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]; @@ -2026,53 +2030,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 b4c256377a6d7..10a2a2b790ad2 100644 --- a/src/libs/actions/Policy/Category.ts +++ b/src/libs/actions/Policy/Category.ts @@ -336,8 +336,12 @@ function setWorkspaceCategoryEnabled( hasOutstandingChildTask: boolean, parentReportAction: OnyxEntry | undefined, ) { - 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], @@ -357,7 +361,7 @@ function setWorkspaceCategoryEnabled( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, - value: policyCategoriesOptimisticData, + value: categoriesOptimisticData, }, ], successData: [ @@ -386,7 +390,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, @@ -401,16 +405,8 @@ function setWorkspaceCategoryEnabled( ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, {}, policyCategoriesOptimisticData); - appendSetupCategoriesOnboardingData( - onyxData, - setupCategoryTaskReport, - setupCategoryTaskParentReport, - isSetupCategoriesTaskParentReportArchived, - currentUserAccountID, - hasOutstandingChildTask, - parentReportAction, - ); + pushTransactionViolationsOnyxData(onyxData, policyData, {}, categoriesOptimisticData); + appendSetupCategoriesOnboardingData(onyxData, setupCategoryTaskReport, setupCategoryTaskParentReport, isSetupCategoriesTaskParentReportArchived, currentUserAccountID); const parameters = { policyID, @@ -552,9 +548,9 @@ function setPolicyCategoryAttendeesRequired(policyID: string, categoryName: stri } 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: { @@ -569,7 +565,7 @@ function setPolicyCategoryReceiptsRequired(policyData: PolicyData, categoryName: { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, - value: policyCategoriesOptimisticData, + value: categoriesOptimisticData, }, ], successData: [ @@ -605,7 +601,7 @@ function setPolicyCategoryReceiptsRequired(policyData: PolicyData, categoryName: ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, {}, policyCategoriesOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, {}, categoriesOptimisticData); const parameters: SetPolicyCategoryReceiptsRequiredParams = { policyID, @@ -617,9 +613,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: { @@ -634,7 +630,7 @@ function removePolicyCategoryReceiptsRequired(policyData: PolicyData, categoryNa { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, - value: policyCategoriesOptimisticData, + value: categoriesOptimisticData, }, ], successData: [ @@ -670,7 +666,7 @@ function removePolicyCategoryReceiptsRequired(policyData: PolicyData, categoryNa ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, {}, policyCategoriesOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, {}, categoriesOptimisticData); const parameters: RemovePolicyCategoryReceiptsRequiredParams = { policyID, @@ -729,9 +725,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); @@ -776,7 +771,7 @@ function renamePolicyCategory(policyData: PolicyData, policyCategory: {oldName: mccGroup: updatedMccGroup, }; - const policyCategoriesOptimisticData = { + const categoriesOptimisticData = { [policyCategory.newName]: { ...policyCategoryToUpdate, errors: null, @@ -797,7 +792,7 @@ function renamePolicyCategory(policyData: PolicyData, policyCategory: {oldName: key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, value: { [policyCategory.oldName]: null, - ...policyCategoriesOptimisticData, + ...categoriesOptimisticData, }, }, { @@ -860,7 +855,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, @@ -1007,7 +1002,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: { @@ -1096,14 +1091,14 @@ function deleteWorkspaceCategories( hasOutstandingChildTask: boolean, parentReportAction: OnyxEntry, ) { - 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 ? { @@ -1165,7 +1160,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, requiresCategory: enabled, @@ -1232,6 +1227,24 @@ function enablePolicyCategories(policyData: PolicyData, enabled: boolean, should ], }; + let policyCategoriesUpdate: Record> = {}; + + if (!enabled) { + policyCategoriesUpdate = Object.fromEntries( + Object.entries(categories ?? {}).map(([categoryName]) => [ + categoryName, + { + enabled: false, + }, + ]), + ); + onyxData.optimisticData?.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, + value: policyCategoriesUpdate, + }); + } + pushTransactionViolationsOnyxData(onyxData, policyData, policyUpdate, policyCategoriesUpdate); const parameters: EnablePolicyCategoriesParams = {policyID, enabled}; diff --git a/src/libs/actions/Policy/Tag.ts b/src/libs/actions/Policy/Tag.ts index bcb7edf7a2fba..2e64d6b151740 100644 --- a/src/libs/actions/Policy/Tag.ts +++ b/src/libs/actions/Policy/Tag.ts @@ -126,27 +126,29 @@ function updateImportSpreadsheetData(tagsLength: number): OnyxData { return onyxData; } -function createPolicyTag(policyID: string, tagName: string, policyTags: PolicyTagLists = {}) { - const policyTag = PolicyUtils.getTagLists(policyTags)?.at(0) ?? ({} as PolicyTagList); +function createPolicyTag(policyData: PolicyData, tagName: string) { + const {policyID, tagLists} = policyData; + const tagList = PolicyUtils.getTagLists(tagLists)?.at(0) ?? ({} as PolicyTagList); const newTagName = PolicyUtils.escapeTagName(tagName); + const tagListsOptimisticData = { + [tagList.name]: { + tags: { + [newTagName]: { + name: newTagName, + enabled: true, + errors: null, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + }, + }; const onyxData: OnyxData = { optimisticData: [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - value: { - [policyTag.name]: { - tags: { - [newTagName]: { - name: newTagName, - enabled: true, - errors: null, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, - }, - }, - }, + value: tagListsOptimisticData, }, ], successData: [ @@ -154,7 +156,7 @@ function createPolicyTag(policyID: string, tagName: string, policyTags: PolicyTa onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, value: { - [policyTag.name]: { + [tagList.name]: { tags: { [newTagName]: { errors: null, @@ -170,7 +172,7 @@ function createPolicyTag(policyID: string, tagName: string, policyTags: PolicyTa onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, value: { - [policyTag.name]: { + [tagList.name]: { tags: { [newTagName]: { errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.genericFailureMessage'), @@ -187,6 +189,8 @@ function createPolicyTag(policyID: string, tagName: string, policyTags: PolicyTa tags: JSON.stringify([{name: newTagName}]), }; + pushTransactionViolationsOnyxData(onyxData, policyData, {}, {}, tagListsOptimisticData); + API.write(WRITE_COMMANDS.CREATE_POLICY_TAG, parameters, onyxData); } @@ -203,39 +207,40 @@ function importPolicyTags(policyID: string, tags: PolicyTag[]) { } function setWorkspaceTagEnabled(policyData: PolicyData, tagsToUpdate: Record, tagListIndex: number) { - const policyTag = PolicyUtils.getTagLists(policyData.tags)?.at(tagListIndex); + if (tagListIndex === -1) { + return; + } + const {policyID, policy, tagLists} = policyData; + const tagList = PolicyUtils.getTagLists(tagLists)?.at(tagListIndex); - if (!policyTag || tagListIndex === -1 || !policyData.policy) { + if (!tagList || !policy) { return; } - const policyID = policyData.policy?.id; - const policyTagsOptimisticData = { - ...Object.keys(tagsToUpdate).reduce((acc, key) => { - acc[key] = { - ...policyTag.tags[key], - ...tagsToUpdate[key], - errors: null, - pendingFields: { - ...policyTag.tags[key]?.pendingFields, - enabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, - }, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, - }; + const tagListsOptimisticData = { + [tagList.name]: { + ...Object.keys(tagsToUpdate).reduce((acc, key) => { + acc[key] = { + ...tagList.tags[key], + ...tagsToUpdate[key], + errors: null, + pendingFields: { + ...tagList.tags[key]?.pendingFields, + enabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }; - return acc; - }, {}), + return acc; + }, {}), + }, }; const onyxData: OnyxData = { optimisticData: [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - value: { - [policyTag.name]: { - tags: policyTagsOptimisticData, - }, - }, + value: tagListsOptimisticData, }, ], successData: [ @@ -243,15 +248,15 @@ function setWorkspaceTagEnabled(policyData: PolicyData, tagsToUpdate: Record((acc, key) => { acc[key] = { - ...policyTag.tags[key], + ...tagList.tags[key], ...tagsToUpdate[key], errors: null, pendingFields: { - ...policyTag.tags[key].pendingFields, + ...tagList.tags[key].pendingFields, enabled: null, }, pendingAction: null, @@ -269,15 +274,15 @@ function setWorkspaceTagEnabled(policyData: PolicyData, tagsToUpdate: Record((acc, key) => { acc[key] = { - ...policyTag.tags[key], + ...tagList.tags[key], ...tagsToUpdate[key], errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.genericFailureMessage'), pendingFields: { - ...policyTag.tags[key].pendingFields, + ...tagList.tags[key].pendingFields, enabled: null, }, pendingAction: null, @@ -292,17 +297,7 @@ function setWorkspaceTagEnabled(policyData: PolicyData, tagsToUpdate: Record((acc, key) => { - if (tagListIndexes.includes(policyData.tags[key].orderWeight)) { + const tagListsOptimisticData = { + ...Object.entries(tagLists ?? {}).reduce((acc, [key, value]) => { + if (tagListIndexes.includes(value.orderWeight)) { acc[key] = { ...acc[key], required: isRequired, @@ -344,7 +336,7 @@ function setWorkspaceTagRequired(policyData: PolicyData, tagListIndexes: number[ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - value: policyTagsOptimisticData, + value: tagListsOptimisticData, }, ], successData: [ @@ -352,8 +344,8 @@ function setWorkspaceTagRequired(policyData: PolicyData, tagListIndexes: number[ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, value: { - ...Object.keys(policyData.tags).reduce((acc, key) => { - if (tagListIndexes.includes(policyData.tags[key].orderWeight)) { + ...Object.entries(tagLists ?? {}).reduce((acc, [key, value]) => { + if (tagListIndexes.includes(value.orderWeight)) { acc[key] = { ...acc[key], errors: undefined, @@ -375,7 +367,7 @@ function setWorkspaceTagRequired(policyData: PolicyData, tagListIndexes: number[ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, value: { - ...Object.keys(policyData.tags).reduce((acc, key) => { + ...Object.keys(tagLists ?? {}).reduce((acc, key) => { acc[key] = { ...acc[key], errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.genericFailureMessage'), @@ -391,7 +383,7 @@ function setWorkspaceTagRequired(policyData: PolicyData, tagListIndexes: number[ ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, {}, {}, policyTagsOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, {}, {}, tagListsOptimisticData); const parameters: SetPolicyTagListsRequired = { policyID, @@ -403,15 +395,15 @@ function setWorkspaceTagRequired(policyData: PolicyData, tagListIndexes: number[ } function deletePolicyTags(policyData: PolicyData, tagsToDelete: string[]) { - const policyID = policyData.policy?.id; - const policyTag = PolicyUtils.getTagLists(policyData.tags)?.at(0); + const {policyID, tagLists} = policyData; + const tagList = PolicyUtils.getTagLists(tagLists)?.at(0); - if (!policyTag) { + if (!tagList) { return; } - const policyTagsOptimisticData: Record>>> = { - [policyTag.name]: { + const tagListsOptimisticData: Record>>> = { + [tagList.name]: { tags: { ...tagsToDelete.reduce>>>((acc, tagName) => { acc[tagName] = {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, enabled: false}; @@ -426,7 +418,7 @@ function deletePolicyTags(policyData: PolicyData, tagsToDelete: string[]) { { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - value: policyTagsOptimisticData, + value: tagListsOptimisticData, }, ], successData: [ @@ -434,7 +426,7 @@ function deletePolicyTags(policyData: PolicyData, tagsToDelete: string[]) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, value: { - [policyTag.name]: { + [tagList.name]: { tags: { ...tagsToDelete.reduce>>>((acc, tagName) => { acc[tagName] = null; @@ -450,13 +442,13 @@ function deletePolicyTags(policyData: PolicyData, tagsToDelete: string[]) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, value: { - [policyTag.name]: { + [tagList.name]: { tags: { ...tagsToDelete.reduce>>>((acc, tagName) => { acc[tagName] = { pendingAction: null, errors: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.deleteFailureMessage'), - enabled: !!policyTag?.tags[tagName]?.enabled, + enabled: !!tagList?.tags[tagName]?.enabled, }; return acc; }, {}), @@ -467,7 +459,7 @@ function deletePolicyTags(policyData: PolicyData, tagsToDelete: string[]) { ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, {}, {}, policyTagsOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, {}, {}, tagListsOptimisticData); const parameters = { policyID, @@ -566,8 +558,8 @@ function clearPolicyTagListErrors({policyID, tagListIndex, policyTags}: ClearPol } function renamePolicyTag(policyData: PolicyData, policyTag: {oldName: string; newName: string}, tagListIndex: number) { - const policyID = policyData.policy?.id; - const tagList = PolicyUtils.getTagLists(policyData.tags)?.at(tagListIndex); + const {policyID, policy, tagLists} = policyData; + const tagList = PolicyUtils.getTagLists(tagLists)?.at(tagListIndex); if (!tagList) { return; } @@ -575,8 +567,8 @@ function renamePolicyTag(policyData: PolicyData, policyTag: {oldName: string; ne const oldTagName = policyTag.oldName; const newTagName = PolicyUtils.escapeTagName(policyTag.newName); - const policyTagRule = PolicyUtils.getTagApproverRule(policyData.policy, oldTagName); - const approvalRules = policyData.policy?.rules?.approvalRules ?? []; + const policyTagRule = PolicyUtils.getTagApproverRule(policyID, oldTagName); + const approvalRules = policy?.rules?.approvalRules ?? []; const updatedApprovalRules: ApprovalRule[] = lodashCloneDeep(approvalRules); // Its related by name, so the corresponding rule has to be updated to handle offline scenario @@ -600,7 +592,7 @@ function renamePolicyTag(policyData: PolicyData, policyTag: {oldName: string; ne }, }; - const policyTagsOptimisticData: Record>>> = { + const tagListsOptimisticData: Record>>> = { [tagList?.name]: { tags: { [oldTagName]: null, @@ -624,7 +616,7 @@ function renamePolicyTag(policyData: PolicyData, policyTag: {oldName: string; ne { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - value: policyTagsOptimisticData, + value: tagListsOptimisticData, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -675,7 +667,7 @@ function renamePolicyTag(policyData: PolicyData, policyTag: {oldName: string; ne ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, policyOptimisticData, {}, policyTagsOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, policyOptimisticData, {}, tagListsOptimisticData); const parameters: RenamePolicyTagsParams = { policyID, @@ -688,7 +680,7 @@ function renamePolicyTag(policyData: PolicyData, policyTag: {oldName: string; ne } function enablePolicyTags(policyData: PolicyData, enabled: boolean) { - const policyID = policyData.policy?.id; + const {policyID, tagLists} = policyData; const policyOptimisticData = { areTagsEnabled: enabled, pendingFields: { @@ -729,15 +721,7 @@ function enablePolicyTags(policyData: PolicyData, enabled: boolean) { ], }; - if (Object.keys(policyData.tags).length === 0) { - const defaultTagList: PolicyTagLists = { - Tag: { - name: 'Tag', - orderWeight: 0, - required: false, - tags: {}, - }, - }; + if (!tagLists || Object.keys(tagLists).length === 0) { onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, @@ -748,18 +732,18 @@ function enablePolicyTags(policyData: PolicyData, enabled: boolean) { key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, value: null, }); - pushTransactionViolationsOnyxData(onyxData, policyData, policyOptimisticData, {}, defaultTagList); + pushTransactionViolationsOnyxData(onyxData, policyData, policyOptimisticData, {}, CONST.POLICY.DEFAULT_TAG_LIST); } else if (!enabled) { - const policyTag = PolicyUtils.getTagLists(policyData.tags).at(0); + const tagList = PolicyUtils.getTagLists(tagLists).at(0); - if (!policyTag) { + if (!tagList) { return; } - const policyTagsOptimisticData: Record> = { - [policyTag.name]: { + const tagListsOptimisticData: Record> = { + [tagList.name]: { tags: Object.fromEntries( - Object.keys(policyTag.tags).map((tagName) => [ + Object.keys(tagLists ?? {}).map((tagName) => [ tagName, { enabled: false, @@ -773,7 +757,7 @@ function enablePolicyTags(policyData: PolicyData, enabled: boolean) { { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - value: policyTagsOptimisticData, + value: tagListsOptimisticData, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -784,7 +768,7 @@ function enablePolicyTags(policyData: PolicyData, enabled: boolean) { }, ); - pushTransactionViolationsOnyxData(onyxData, policyData, {...policyOptimisticData, requiresTag: false}, {}, policyTagsOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, {...policyOptimisticData, requiresTag: false}, {}, tagListsOptimisticData); } else { pushTransactionViolationsOnyxData(onyxData, policyData, policyOptimisticData); } @@ -891,9 +875,9 @@ function importMultiLevelTags(policyID: string, spreadsheet: ImportedSpreadsheet ); } -function renamePolicyTagList(policyID: string, policyTagListName: {oldName: string; newName: string}, policyTags: OnyxEntry, tagListIndex: number) { - const newName = policyTagListName.newName; - const oldName = policyTagListName.oldName; +function renamePolicyTagList(policyID: string, tagListName: {oldName: string; newName: string}, policyTags: OnyxEntry, tagListIndex: number) { + const newName = tagListName.newName; + const oldName = tagListName.oldName; const oldPolicyTags = policyTags?.[oldName] ?? {}; const onyxData: OnyxData = { optimisticData: [ @@ -943,7 +927,7 @@ function renamePolicyTagList(policyID: string, policyTagListName: {oldName: stri } function setPolicyRequiresTag(policyData: PolicyData, requiresTag: boolean) { - const policyID = policyData.policy?.id; + const {policyID, tagLists} = policyData; const policyOptimisticData: Partial = { requiresTag, errors: {requiresTag: null}, @@ -990,7 +974,7 @@ function setPolicyRequiresTag(policyData: PolicyData, requiresTag: boolean) { }; const getUpdatedTagsData = (required: boolean): PolicyTagLists => ({ - ...Object.keys(policyData.tags).reduce((acc, key) => { + ...Object.keys(tagLists ?? {}).reduce((acc, key) => { acc[key] = { ...acc[key], required, @@ -1020,14 +1004,13 @@ function setPolicyRequiresTag(policyData: PolicyData, requiresTag: boolean) { } function setPolicyTagsRequired(policyData: PolicyData, requiresTag: boolean, tagListIndex: number) { - const policyTag = PolicyUtils.getTagLists(policyData.tags)?.at(tagListIndex); - if (!policyTag || !policyTag.name) { + const {policyID, tagLists} = policyData; + const tagList = PolicyUtils.getTagLists(tagLists)?.at(tagListIndex); + if (!tagList || !tagList.name) { return; } - - const policyID = policyData.policy?.id; - const policyTagsOptimisticData = { - [policyTag.name]: { + const tagListsOptimisticData = { + [tagList.name]: { required: requiresTag, pendingFields: {required: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}, errorFields: {required: null}, @@ -1039,7 +1022,7 @@ function setPolicyTagsRequired(policyData: PolicyData, requiresTag: boolean, tag { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, - value: policyTagsOptimisticData, + value: tagListsOptimisticData, }, ], successData: [ @@ -1047,7 +1030,7 @@ function setPolicyTagsRequired(policyData: PolicyData, requiresTag: boolean, tag onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, value: { - [policyTag.name]: { + [tagList.name]: { pendingFields: {required: null}, }, }, @@ -1058,8 +1041,8 @@ function setPolicyTagsRequired(policyData: PolicyData, requiresTag: boolean, tag onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, value: { - [policyTag.name]: { - required: policyTag.required, + [tagList.name]: { + required: tagList.required, pendingFields: {required: null}, errorFields: { required: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.tags.genericFailureMessage'), @@ -1070,7 +1053,7 @@ function setPolicyTagsRequired(policyData: PolicyData, requiresTag: boolean, tag ], }; - pushTransactionViolationsOnyxData(onyxData, policyData, {}, {}, policyTagsOptimisticData); + pushTransactionViolationsOnyxData(onyxData, policyData, {}, {}, tagListsOptimisticData); const parameters: SetPolicyTagsRequired = { policyID, diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index 5f2afd160d18e..7e6f976a9f08b 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -73,7 +73,7 @@ function IOURequestStepCategory({ const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`, {canBeMissing: true}); 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/WorkspaceCategoriesSettingsPage.tsx b/src/pages/workspace/categories/WorkspaceCategoriesSettingsPage.tsx index 327cc620831f6..f979c44e6e772 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/src/pages/workspace/tags/EditTagPage.tsx b/src/pages/workspace/tags/EditTagPage.tsx index 33527e374dff1..31f9f01df5f12 100644 --- a/src/pages/workspace/tags/EditTagPage.tsx +++ b/src/pages/workspace/tags/EditTagPage.tsx @@ -30,7 +30,7 @@ type EditTagPageProps = function EditTagPage({route}: EditTagPageProps) { const {backTo, policyID} = route.params; const policyData = usePolicyData(policyID); - const {tags: policyTags} = policyData; + const {tagLists: policyTags} = policyData; const styles = useThemeStyles(); const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); diff --git a/src/pages/workspace/tags/TagSettingsPage.tsx b/src/pages/workspace/tags/TagSettingsPage.tsx index dda57b6a02531..e8aa0a2da9fe0 100644 --- a/src/pages/workspace/tags/TagSettingsPage.tsx +++ b/src/pages/workspace/tags/TagSettingsPage.tsx @@ -47,7 +47,7 @@ function TagSettingsPage({route, navigation}: TagSettingsPageProps) { const expensifyIcons = useMemoizedLazyExpensifyIcons(['Lock']); const {translate} = useLocalize(); const policyData = usePolicyData(policyID); - const {policy, tags: policyTags} = policyData; + const {policy, tagLists: policyTags} = policyData; const policyTag = useMemo(() => getTagListByOrderWeight(policyTags, orderWeight), [policyTags, orderWeight]); const {environmentURL} = useEnvironment(); const hasAccountingConnections = hasAccountingConnectionsPolicyUtils(policy); diff --git a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx index a3b912b678a91..de7e4c7913563 100644 --- a/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx +++ b/src/pages/workspace/tags/WorkspaceCreateTagPage.tsx @@ -8,7 +8,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import TextInput from '@components/TextInput'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; +import usePolicyData from '@hooks/usePolicyData'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; import Navigation from '@libs/Navigation/Navigation'; @@ -29,19 +29,20 @@ type WorkspaceCreateTagPageProps = | PlatformStackScreenProps; function WorkspaceCreateTagPage({route}: WorkspaceCreateTagPageProps) { - const policyID = route.params.policyID; - const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`, {canBeMissing: true}); + const {policyID, backTo} = route.params; + const isQuickSettingsFlow = route.name === SCREENS.SETTINGS_TAGS.SETTINGS_TAG_CREATE; + const styles = useThemeStyles(); const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); - const backTo = route.params.backTo; - const isQuickSettingsFlow = route.name === SCREENS.SETTINGS_TAGS.SETTINGS_TAG_CREATE; + const policyData = usePolicyData(policyID); + const {tagLists} = policyData; const validate = useCallback( (values: FormOnyxValues) => { const errors: FormInputErrors = {}; const tagName = escapeTagName(values.tagName.trim()); - const {tags} = getTagList(policyTags, 0); + const {tags} = getTagList(tagLists, 0); if (!isRequiredFulfilled(tagName)) { errors.tagName = translate('workspace.tags.tagRequiredError'); @@ -56,16 +57,16 @@ function WorkspaceCreateTagPage({route}: WorkspaceCreateTagPageProps) { return errors; }, - [policyTags, translate], + [tagLists, translate], ); const createTag = useCallback( (values: FormOnyxValues) => { - createPolicyTag(policyID, values.tagName.trim(), policyTags); + createPolicyTag(policyData, values.tagName.trim()); Keyboard.dismiss(); Navigation.goBack(isQuickSettingsFlow ? ROUTES.SETTINGS_TAGS_ROOT.getRoute(policyID, backTo) : undefined); }, - [policyID, policyTags, isQuickSettingsFlow, backTo], + [policyID, policyData, isQuickSettingsFlow, backTo], ); return ( diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 50f57e24f8641..e7117d3db9a3f 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -88,7 +88,7 @@ function WorkspaceTagsPage({route}: WorkspaceTagsPageProps) { const [isCannotMakeLastTagOptionalModalVisible, setIsCannotMakeLastTagOptionalModalVisible] = useState(false); const {backTo, policyID} = route.params; const policyData = usePolicyData(policyID); - const {policy, tags: policyTags} = policyData; + const {policy, tagLists: policyTags} = policyData; const isMobileSelectionModeEnabled = useMobileSelectionMode(); const {environmentURL} = useEnvironment(); const [connectionSyncProgress] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS}${policy?.id}`, {canBeMissing: true}); diff --git a/src/pages/workspace/tags/WorkspaceTagsSettingsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsSettingsPage.tsx index 6ed1e4b5d9f4e..58e73df382329 100644 --- a/src/pages/workspace/tags/WorkspaceTagsSettingsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsSettingsPage.tsx @@ -54,7 +54,7 @@ function WorkspaceTagsSettingsPage({route}: WorkspaceTagsSettingsPageProps) { const backTo = route.params.backTo; const styles = useThemeStyles(); const policyData = usePolicyData(policyID); - const {tags: policyTags} = policyData; + const {tagLists: policyTags} = policyData; const {translate} = useLocalize(); const [policyTagLists, isMultiLevelTags] = useMemo(() => [getTagListsUtil(policyTags), isMultiLevelTagsUtil(policyTags)], [policyTags]); const isLoading = !getTagListsUtil(policyTags)?.at(0) || Object.keys(policyTags ?? {}).at(0) === 'undefined'; diff --git a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx index ec1abffc952c1..ddc8400c73881 100644 --- a/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceViewTagsPage.tsx @@ -68,8 +68,7 @@ function WorkspaceViewTagsPage({route}: WorkspaceViewTagsProps) { const [isDeleteTagsConfirmModalVisible, setIsDeleteTagsConfirmModalVisible] = useState(false); const isFocused = useIsFocused(); const policyData = usePolicyData(policyID); - const {policy, tags: policyTags} = policyData; - + const {policy, tagLists: policyTags} = policyData; const isMobileSelectionModeEnabled = useMobileSelectionMode(); const currentTagListName = useMemo(() => getTagListName(policyTags, orderWeight), [policyTags, orderWeight]); const hasDependentTags = useMemo(() => hasDependentTagsPolicyUtils(policy, policyTags), [policy, policyTags]); diff --git a/tests/actions/PolicyTagTest.ts b/tests/actions/PolicyTagTest.ts index 73f4574cb5d7a..e874233cd5a6c 100644 --- a/tests/actions/PolicyTagTest.ts +++ b/tests/actions/PolicyTagTest.ts @@ -288,11 +288,15 @@ describe('actions/Policy', () => { const newTagName = 'new tag'; const fakePolicyTags = createRandomPolicyTags(tagListName); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); + mockFetch.pause(); await waitForBatchedUpdates(); + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); // When creating a new tag - createPolicyTag(fakePolicy.id, newTagName, fakePolicyTags); + createPolicyTag(policyData.current, newTagName); await waitForBatchedUpdates(); // Then the tag should appear optimistically with pending state so the user sees immediate feedback @@ -322,12 +326,17 @@ describe('actions/Policy', () => { const newTagName = 'new tag'; const fakePolicyTags = createRandomPolicyTags(tagListName); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); + await waitForBatchedUpdates(); + mockFetch.pause(); await waitForBatchedUpdates(); mockFetch.fail(); + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); // When the API fails - createPolicyTag(fakePolicy.id, newTagName, fakePolicyTags); + createPolicyTag(policyData.current, newTagName); await waitForBatchedUpdates(); mockFetch.resume(); await waitForBatchedUpdates(); @@ -344,12 +353,17 @@ describe('actions/Policy', () => { fakePolicy.areTagsEnabled = true; const newTagName = 'new tag'; + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, {}); + await waitForBatchedUpdates(); + + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); mockFetch.pause(); await waitForBatchedUpdates(); // When adding the first tag - createPolicyTag(fakePolicy.id, newTagName, {}); + createPolicyTag(policyData.current, newTagName); await waitForBatchedUpdates(); // Then the tag should be created in a new list with pending state so the user sees immediate feedback @@ -388,18 +402,17 @@ describe('actions/Policy', () => { const fakePolicyTags = createRandomPolicyTags(tagListName); mockFetch.pause(); - + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); await waitForBatchedUpdates(); - const {result} = renderHook(() => useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`)); - + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); await waitFor(() => { - expect(result.current[0]).toBeDefined(); + expect(policyData.current.tagLists).toBeDefined(); }); // When using data from useOnyx hook - createPolicyTag(fakePolicy.id, newTagName, result.current[0] ?? {}); + createPolicyTag(policyData.current, newTagName); await waitForBatchedUpdates(); // Then the tag should appear optimistically with pending state so the user sees immediate feedback @@ -440,9 +453,10 @@ 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}); - await waitForBatchedUpdates(); + setWorkspaceTagEnabled(policyData.current, tagsToUpdate, 0); await waitForBatchedUpdates(); @@ -497,8 +511,10 @@ describe('actions/Policy', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); - + mockFetch?.fail?.(); + await waitForBatchedUpdates(); + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); setWorkspaceTagEnabled(policyData.current, tagsToUpdate, 0); await waitForBatchedUpdates(); @@ -530,15 +546,8 @@ describe('actions/Policy', () => { mockFetch?.pause?.(); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${fakePolicy.id}`, fakePolicy); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); - - const {result} = renderHook(() => useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`)); - - const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); await waitForBatchedUpdates(); - - await waitFor(() => { - expect(result.current[0]).toBeDefined(); - }); + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); setWorkspaceTagEnabled(policyData.current, {[tagName]: {name: tagName, enabled: false}}, 0); @@ -588,15 +597,9 @@ describe('actions/Policy', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, fakePolicyTags); // When the tag is renamed - const policyData = { - policy: fakePolicy, - tags: fakePolicyTags, - categories: {}, - reports: [], - transactionsAndViolations: {}, - }; + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); renamePolicyTag( - policyData, + policyData.current, { oldName: oldTagName, newName: newTagName, @@ -645,15 +648,9 @@ describe('actions/Policy', () => { mockFetch.fail(); // When the tag rename fails - const policyData = { - policy: fakePolicy, - tags: fakePolicyTags, - categories: {}, - reports: [], - transactionsAndViolations: {}, - }; + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); renamePolicyTag( - policyData, + policyData.current, { oldName: oldTagName, newName: newTagName, @@ -685,17 +682,11 @@ describe('actions/Policy', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${fakePolicy.id}`, existingPolicyTags); // When trying to rename a tag with invalid index - const policyData = { - policy: fakePolicy, - tags: {}, - categories: {}, - reports: [], - transactionsAndViolations: {}, - }; + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); expect(() => { renamePolicyTag( - policyData, + policyData.current, { oldName: 'oldTag', newName: 'newTag', @@ -743,17 +734,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(); // When the tag is renamed - const policyData = { - policy: fakePolicy, - tags: fakePolicyTags, - categories: {}, - reports: [], - transactionsAndViolations: {}, - }; + const {result: policyData} = renderHook(() => usePolicyData(fakePolicy.id), {wrapper: OnyxListItemProvider}); renamePolicyTag( - policyData, + policyData.current, { oldName: oldTagName, newName: newTagName, @@ -1791,7 +1777,7 @@ describe('actions/Policy', () => { 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); + const defaultTag = Object.values(policyData.current?.tagLists ?? {}).at(0); expect(defaultTag?.name).toBe('Tag'); expect(defaultTag?.orderWeight).toBe(0); expect(defaultTag?.required).toBe(false); @@ -1834,7 +1820,7 @@ describe('actions/Policy', () => { // And all tags should be disabled for (const tagName of Object.keys(existingTags)) { - expect(policyData.current?.tags?.[tagListName]?.tags[tagName]?.enabled).toBe(false); + expect(policyData.current?.tagLists?.[tagListName]?.tags[tagName]?.enabled).toBe(false); } await mockFetch.resume(); @@ -1868,9 +1854,9 @@ 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.tags).toMatchObject({}); + expect(policyData?.current?.policy?.areTagsEnabled).toBe(false); + expect(policyData?.current?.policy?.pendingFields?.areTagsEnabled).toBeUndefined(); + expect(policyData?.current?.tagLists).toMatchObject({}); }); it('should work with data from useOnyx hook', async () => { @@ -1892,8 +1878,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); await mockFetch.resume(); await waitForBatchedUpdates(); @@ -1901,11 +1887,11 @@ 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(); + expect(policyData?.current?.tagLists?.Tag).toBeDefined(); }); }); @@ -1936,7 +1922,7 @@ describe('actions/Policy', () => { rerender(fakePolicy.id); // Then the tag list should be marked as required with pending fields - let updatedPolicyTags = policyData.current.tags; + let updatedPolicyTags = policyData?.current?.tagLists; expect(updatedPolicyTags?.[tagListName]?.required).toBe(true); // Check optimistic data - pendingFields should be set @@ -1948,7 +1934,7 @@ describe('actions/Policy', () => { await waitForBatchedUpdates(); rerender(fakePolicy.id); // Then after API success, pending fields should be cleared - updatedPolicyTags = policyData.current.tags; + updatedPolicyTags = policyData?.current?.tagLists; expect(updatedPolicyTags?.[tagListName]?.required).toBe(true); expect(updatedPolicyTags?.[tagListName]?.pendingFields?.required).toBeUndefined(); @@ -1979,7 +1965,7 @@ describe('actions/Policy', () => { rerender(fakePolicy.id); // Then the tag list should be marked as not required with pending fields - let updatedPolicyTags = policyData.current.tags; + let updatedPolicyTags = policyData?.current?.tagLists; expect(updatedPolicyTags?.[tagListName]?.required).toBe(false); // Check optimistic data - pendingFields should be set @@ -1992,7 +1978,7 @@ describe('actions/Policy', () => { rerender(fakePolicy.id); // Then after API success, pending fields should be cleared - updatedPolicyTags = policyData.current.tags; + updatedPolicyTags = policyData?.current?.tagLists; expect(updatedPolicyTags?.[tagListName]?.required).toBe(false); expect(updatedPolicyTags?.[tagListName]?.pendingFields?.required).toBeUndefined(); @@ -2027,7 +2013,7 @@ describe('actions/Policy', () => { rerender(fakePolicy.id); // Then the tag list should be restored to original state with error - const updatedPolicyTags = policyData.current.tags; + const updatedPolicyTags = policyData?.current?.tagLists; expect(updatedPolicyTags?.[tagListName]?.required).toBe(false); expect(updatedPolicyTags?.[tagListName]?.pendingFields?.required).toBeUndefined(); expect(updatedPolicyTags?.[tagListName]?.errorFields?.required).toBeTruthy(); @@ -2065,7 +2051,7 @@ describe('actions/Policy', () => { rerender(fakePolicy.id); // Then the tag list should be marked as required - const updatedPolicyTags = policyData.current.tags; + const updatedPolicyTags = policyData?.current?.tagLists; expect(updatedPolicyTags?.[tagListName]?.required).toBe(true); // Check optimistic data - pendingFields should be set 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..05f31ca881a70 100644 --- a/tests/unit/usePolicyData.test.ts +++ b/tests/unit/usePolicyData.test.ts @@ -91,35 +91,29 @@ 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?.policyID).toEqual(mockPolicy.id) + expect(result.current?.policy).toEqual(mockPolicy); + expect(result.current?.tagLists).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?.reports).toHaveLength(1); + expect(result.current?.reports.at(0)).toEqual(mockIOUReport); expect(result.current.transactionsAndViolations).toEqual(expectedTransactionsAndViolations); }); test('returns default empty 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).toEqual(policyID); + expect(result.current?.policy).toBeUndefined(); + + expect(result.current?.reports).toBeUndefined(); + expect(result.current?.tagLists).toBeUndefined(); + expect(result.current?.categories).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?.transactionsAndViolations).toEqual({}); }); });