From 36117941d4e318fe4c2a1a18d219972e0cf5dd6e Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Sat, 9 May 2026 16:00:56 +0700 Subject: [PATCH 1/2] update createTaskAndNavigate to use currentPersonalDetails from useOnyx --- src/libs/actions/Task.ts | 13 +- .../ReportActionCompose/useComposerSubmit.ts | 2 + src/pages/tasks/NewTaskDetailsPage.tsx | 2 + src/pages/tasks/NewTaskPage.tsx | 2 + tests/actions/TaskTest.ts | 140 ++++++++++++++++++ 5 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 271667b89115..551bd1b8cc69 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -19,6 +19,7 @@ import {getReportName} from '@libs/ReportNameUtils'; import * as ReportUtils from '@libs/ReportUtils'; import {buildOptimisticSnapshotData} from '@libs/SearchQueryUtils'; import playSound, {SOUNDS} from '@libs/Sound'; +import {AvatarSource} from '@libs/UserAvatarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -74,6 +75,8 @@ type CreateTaskAndNavigateParams = { isCreatedUsingMarkdown?: boolean; quickAction?: OnyxEntry; ancestors?: ReportUtils.Ancestor[]; + currentUserDisplayName: string | undefined; + currentUserAvatar: AvatarSource | undefined; }; /** @@ -110,6 +113,8 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { assigneeEmail, currentUserAccountID, currentUserEmail, + currentUserDisplayName, + currentUserAvatar, assigneeAccountID = 0, assigneeChatReport, policyID = CONST.POLICY.OWNER_EMAIL_FAKE, @@ -138,7 +143,13 @@ function createTaskAndNavigate(params: CreateTaskAndNavigateParams) { let assigneeChatReportOnyxData; // Parent ReportAction indicating that a task has been created - const optimisticTaskCreatedAction = ReportUtils.buildOptimisticCreatedReportAction({emailCreatingAction: currentUserEmail}); + const optimisticTaskCreatedAction = ReportUtils.buildOptimisticCreatedReportAction({ + emailCreatingAction: currentUserEmail, + currentUserAccountID, + currentUserDisplayName, + currentUserEmail, + currentUserAvatar, + }); const optimisticAddCommentReport = ReportUtils.buildOptimisticTaskCommentReportAction(taskReportID, title, assigneeAccountID, `task for ${title}`, parentReportID); optimisticTaskReport.parentReportActionID = optimisticAddCommentReport.reportAction.reportActionID; diff --git a/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts b/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts index 92d491a85916..8749065188fb 100644 --- a/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts +++ b/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts @@ -113,6 +113,8 @@ function useComposerSubmit(reportID: string): (comment: string) => void { assigneeEmail: assignee?.login ?? '', currentUserAccountID: currentUserPersonalDetails.accountID, currentUserEmail, + currentUserDisplayName: currentUserPersonalDetails.displayName, + currentUserAvatar: currentUserPersonalDetails.avatar, assigneeAccountID: assignee?.accountID, assigneeChatReport, policyID: report?.policyID, diff --git a/src/pages/tasks/NewTaskDetailsPage.tsx b/src/pages/tasks/NewTaskDetailsPage.tsx index 490d00927fc6..b626d60dcbd6 100644 --- a/src/pages/tasks/NewTaskDetailsPage.tsx +++ b/src/pages/tasks/NewTaskDetailsPage.tsx @@ -80,6 +80,8 @@ function NewTaskDetailsPage({route}: NewTaskDetailsPageProps) { assigneeEmail: task?.assignee ?? '', currentUserAccountID: currentUserPersonalDetails.accountID, currentUserEmail: currentUserPersonalDetails.email ?? '', + currentUserDisplayName: currentUserPersonalDetails.displayName, + currentUserAvatar: currentUserPersonalDetails.avatar, assigneeAccountID: task.assigneeAccountID, assigneeChatReport: task.assigneeChatReport, policyID: CONST.POLICY.OWNER_EMAIL_FAKE, diff --git a/src/pages/tasks/NewTaskPage.tsx b/src/pages/tasks/NewTaskPage.tsx index 922f5d77ba76..39f944296474 100644 --- a/src/pages/tasks/NewTaskPage.tsx +++ b/src/pages/tasks/NewTaskPage.tsx @@ -104,6 +104,8 @@ function NewTaskPage({route}: NewTaskPageProps) { assigneeEmail: task?.assignee ?? '', currentUserAccountID: currentUserPersonalDetails.accountID, currentUserEmail: currentUserPersonalDetails.email ?? '', + currentUserDisplayName: currentUserPersonalDetails.displayName, + currentUserAvatar: currentUserPersonalDetails.avatar, assigneeAccountID: task.assigneeAccountID, assigneeChatReport: task.assigneeChatReport, policyID: parentReport?.policyID, diff --git a/tests/actions/TaskTest.ts b/tests/actions/TaskTest.ts index 7773e883b32b..fa6eade5bea5 100644 --- a/tests/actions/TaskTest.ts +++ b/tests/actions/TaskTest.ts @@ -313,6 +313,8 @@ describe('actions/Task', () => { const mockPolicyID = 'policy_123'; const mockCurrentUserAccountID = 123; const mockCurrentUserEmail = 'creator@example.com'; + const mockCurrentUserDisplayName = 'Creator User'; + const mockCurrentUserAvatar = 'https://example.com/avatar.png'; beforeEach(async () => { jest.clearAllMocks(); @@ -412,6 +414,8 @@ describe('actions/Task', () => { assigneeEmail: mockAssigneeEmail, currentUserAccountID: mockCurrentUserAccountID, currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, assigneeAccountID: mockAssigneeAccountID, assigneeChatReport: mockAssigneeChatReport, policyID: mockPolicyID, @@ -454,6 +458,8 @@ describe('actions/Task', () => { assigneeEmail: mockAssigneeEmail, currentUserAccountID: mockCurrentUserAccountID, currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, assigneeAccountID: mockAssigneeAccountID, policyID: mockPolicyID, isCreatedUsingMarkdown: false, @@ -481,6 +487,8 @@ describe('actions/Task', () => { assigneeEmail: mockAssigneeEmail, currentUserAccountID: mockCurrentUserAccountID, currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, assigneeAccountID: mockAssigneeAccountID, policyID: mockPolicyID, isCreatedUsingMarkdown: false, @@ -512,6 +520,8 @@ describe('actions/Task', () => { assigneeEmail: mockAssigneeEmail, currentUserAccountID: mockCurrentUserAccountID, currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, assigneeAccountID: mockAssigneeAccountID, assigneeChatReport: undefined, policyID: mockPolicyID, @@ -572,6 +582,8 @@ describe('actions/Task', () => { assigneeEmail: mockAssigneeEmail, currentUserAccountID: mockCurrentUserAccountID, currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, assigneeAccountID: mockAssigneeAccountID, assigneeChatReport: mockAssigneeChatReport, policyID: mockPolicyID, @@ -610,6 +622,8 @@ describe('actions/Task', () => { assigneeEmail: mockAssigneeEmail, currentUserAccountID: mockCurrentUserAccountID, currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, assigneeAccountID: mockAssigneeAccountID, assigneeChatReport: mockAssigneeChatReport, policyID: CONST.POLICY.OWNER_EMAIL_FAKE, @@ -655,6 +669,8 @@ describe('actions/Task', () => { assigneeEmail: mockCurrentUserEmail, currentUserAccountID: mockCurrentUserAccountID, currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, assigneeAccountID: mockCurrentUserAccountID, // assignee is current user assigneeChatReport: mockAssigneeChatReport, policyID: mockPolicyID, @@ -710,6 +726,8 @@ describe('actions/Task', () => { assigneeEmail: mockAssigneeEmail, currentUserAccountID: mockCurrentUserAccountID, currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, assigneeAccountID: mockAssigneeAccountID, assigneeChatReport: mockAssigneeChatReport, policyID: mockPolicyID, @@ -745,6 +763,8 @@ describe('actions/Task', () => { assigneeEmail: mockAssigneeEmail, currentUserAccountID: mockCurrentUserAccountID, currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, assigneeAccountID: mockAssigneeAccountID, assigneeChatReport: mockAssigneeChatReport, policyID: mockPolicyID, @@ -773,6 +793,126 @@ describe('actions/Task', () => { }), ); }); + + it('should forward currentUserAccountID, currentUserEmail, currentUserDisplayName and currentUserAvatar to buildOptimisticCreatedReportAction when all are provided', () => { + // Given: All current user identity fields are provided as defined values + // When: createTaskAndNavigate is called + createTaskAndNavigate({ + parentReport: {reportID: mockParentReportID}, + title: mockTitle, + description: mockDescription, + assigneeEmail: mockAssigneeEmail, + currentUserAccountID: mockCurrentUserAccountID, + currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, + assigneeAccountID: mockAssigneeAccountID, + policyID: mockPolicyID, + isCreatedUsingMarkdown: false, + quickAction: {}, + }); + + // Then: buildOptimisticCreatedReportAction receives the exact identity values that were passed in + expect(mockBuildOptimisticCreatedReportAction).toHaveBeenCalledWith({ + emailCreatingAction: mockCurrentUserEmail, + currentUserAccountID: mockCurrentUserAccountID, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserEmail: mockCurrentUserEmail, + currentUserAvatar: mockCurrentUserAvatar, + }); + }); + + it('should forward undefined currentUserDisplayName and currentUserAvatar to buildOptimisticCreatedReportAction without substituting fallbacks', () => { + // Given: currentUserDisplayName and currentUserAvatar are explicitly undefined + // When: createTaskAndNavigate is called + createTaskAndNavigate({ + parentReport: {reportID: mockParentReportID}, + title: mockTitle, + description: mockDescription, + assigneeEmail: mockAssigneeEmail, + currentUserAccountID: mockCurrentUserAccountID, + currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: undefined, + currentUserAvatar: undefined, + assigneeAccountID: mockAssigneeAccountID, + policyID: mockPolicyID, + isCreatedUsingMarkdown: false, + quickAction: {}, + }); + + // Then: buildOptimisticCreatedReportAction is invoked with the same undefined values + expect(mockBuildOptimisticCreatedReportAction).toHaveBeenCalledWith({ + emailCreatingAction: mockCurrentUserEmail, + currentUserAccountID: mockCurrentUserAccountID, + currentUserDisplayName: undefined, + currentUserEmail: mockCurrentUserEmail, + currentUserAvatar: undefined, + }); + }); + + it('should forward an empty currentUserEmail to buildOptimisticCreatedReportAction so emailCreatingAction is also empty', () => { + // Given: currentUserEmail is provided as an empty string (caller bypassing missing data) + // When: createTaskAndNavigate is called + createTaskAndNavigate({ + parentReport: {reportID: mockParentReportID}, + title: mockTitle, + description: mockDescription, + assigneeEmail: mockAssigneeEmail, + currentUserAccountID: mockCurrentUserAccountID, + currentUserEmail: '', + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, + assigneeAccountID: mockAssigneeAccountID, + policyID: mockPolicyID, + isCreatedUsingMarkdown: false, + quickAction: {}, + }); + + // Then: emailCreatingAction is the empty string we provided (no fallback to a session value) + expect(mockBuildOptimisticCreatedReportAction).toHaveBeenCalledWith({ + emailCreatingAction: '', + currentUserAccountID: mockCurrentUserAccountID, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserEmail: '', + currentUserAvatar: mockCurrentUserAvatar, + }); + }); + + it('should use the provided currentUserAccountID as actorAccountID and lastActorAccountID instead of reading from session', async () => { + // Given: a currentUserAccountID different from the session account that overrides what session says + const overrideUserAccountID = 999; + + // When: createTaskAndNavigate is called with that override + createTaskAndNavigate({ + parentReport: {reportID: mockParentReportID}, + title: mockTitle, + description: mockDescription, + assigneeEmail: mockAssigneeEmail, + currentUserAccountID: overrideUserAccountID, + currentUserEmail: mockCurrentUserEmail, + currentUserDisplayName: mockCurrentUserDisplayName, + currentUserAvatar: mockCurrentUserAvatar, + assigneeAccountID: mockAssigneeAccountID, + policyID: mockPolicyID, + isCreatedUsingMarkdown: false, + quickAction: {}, + }); + + await waitForBatchedUpdatesWithAct(); + + // Then: the optimistic parent report update uses the provided account ID, proving the function does not read the session + // eslint-disable-next-line rulesdir/no-multiple-api-calls + const [, , onyx] = (API.write as jest.Mock).mock.calls.at(0) as [unknown, unknown, OnyxData]; + const parentReportUpdate = onyx.optimisticData?.find( + (update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${mockParentReportID}` && (update.value as Report | undefined)?.lastActorAccountID !== undefined, + ); + expect((parentReportUpdate?.value as Report | undefined)?.lastActorAccountID).toBe(overrideUserAccountID); + expect(mockBuildOptimisticCreatedReportAction).toHaveBeenCalledWith( + expect.objectContaining({ + currentUserAccountID: overrideUserAccountID, + }), + ); + }); }); describe('completeTask', () => { From c2986667deb443fc825550c41223477bf2182c1b Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Sat, 9 May 2026 16:15:33 +0700 Subject: [PATCH 2/2] lint fix --- src/libs/actions/Task.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 551bd1b8cc69..81b8dde020f1 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -19,7 +19,7 @@ import {getReportName} from '@libs/ReportNameUtils'; import * as ReportUtils from '@libs/ReportUtils'; import {buildOptimisticSnapshotData} from '@libs/SearchQueryUtils'; import playSound, {SOUNDS} from '@libs/Sound'; -import {AvatarSource} from '@libs/UserAvatarUtils'; +import type {AvatarSource} from '@libs/UserAvatarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES';