From 8186c83aea1a2d968934893287f12729061d42b4 Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Tue, 24 Feb 2026 08:01:57 -0700 Subject: [PATCH 1/9] dedupe members --- .../approvals/WorkspaceWorkflowsApprovalsEditPage.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 5a353813b27a..ab190945576f 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -115,9 +115,13 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true return clearApprovalWorkflow(); } + // Deduplicate by email: current workflow members first, then defaultWorkflowMembers not already in workflow. + // Prevents duplicate keyForList (email) in Expenses From list which causes blank row when deselecting (#83251). + const memberEmails = new Set(currentApprovalWorkflow.members.map((m) => m.email)); + const additionalMembers = defaultWorkflowMembers.filter((m) => !memberEmails.has(m.email)); setApprovalWorkflow({ ...currentApprovalWorkflow, - availableMembers: [...currentApprovalWorkflow.members, ...defaultWorkflowMembers], + availableMembers: [...currentApprovalWorkflow.members, ...additionalMembers], usedApproverEmails, action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, errors: null, From 0d53924b82cfef861ffea85262dce060f9893e43 Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Tue, 24 Feb 2026 08:09:56 -0700 Subject: [PATCH 2/9] Move merge function to libs and add test --- src/libs/WorkflowUtils.ts | 12 ++++++ .../WorkspaceWorkflowsApprovalsEditPage.tsx | 8 +--- tests/unit/WorkflowUtilsTest.ts | 39 +++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/libs/WorkflowUtils.ts b/src/libs/WorkflowUtils.ts index a6109820873e..a5eed5a31ed4 100644 --- a/src/libs/WorkflowUtils.ts +++ b/src/libs/WorkflowUtils.ts @@ -574,6 +574,17 @@ function getOpenConnectedToPolicyBusinessBankAccounts(bankAccountList: BankAccou }); } +/** + * Merge workflow members with all available members, deduplicating by email. + * Used when setting up the Edit page's availableMembers to prevent duplicate keyForList + * in the Expenses From list (which causes a blank row when deselecting - see #83251). + */ +function mergeWorkflowMembersWithAvailableMembers(workflowMembers: Member[], allAvailableMembers: Member[]): Member[] { + const memberEmails = new Set(workflowMembers.map((m) => m.email)); + const additionalMembers = allAvailableMembers.filter((m) => !memberEmails.has(m.email)); + return [...workflowMembers, ...additionalMembers]; +} + export { calculateApprovers, convertPolicyEmployeesToApprovalWorkflows, @@ -582,5 +593,6 @@ export { getEligibleExistingBusinessBankAccounts, getOpenConnectedToPolicyBusinessBankAccounts, INITIAL_APPROVAL_WORKFLOW, + mergeWorkflowMembersWithAvailableMembers, updateWorkflowDataOnApproverRemoval, }; diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index ab190945576f..78a0c2a4cce1 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -17,7 +17,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types'; import {goBackFromInvalidPolicy, isPendingDeletePolicy, isPolicyAdmin} from '@libs/PolicyUtils'; -import {convertPolicyEmployeesToApprovalWorkflows} from '@libs/WorkflowUtils'; +import {convertPolicyEmployeesToApprovalWorkflows, mergeWorkflowMembersWithAvailableMembers} from '@libs/WorkflowUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -115,13 +115,9 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true return clearApprovalWorkflow(); } - // Deduplicate by email: current workflow members first, then defaultWorkflowMembers not already in workflow. - // Prevents duplicate keyForList (email) in Expenses From list which causes blank row when deselecting (#83251). - const memberEmails = new Set(currentApprovalWorkflow.members.map((m) => m.email)); - const additionalMembers = defaultWorkflowMembers.filter((m) => !memberEmails.has(m.email)); setApprovalWorkflow({ ...currentApprovalWorkflow, - availableMembers: [...currentApprovalWorkflow.members, ...additionalMembers], + availableMembers: mergeWorkflowMembersWithAvailableMembers(currentApprovalWorkflow.members, defaultWorkflowMembers), usedApproverEmails, action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, errors: null, diff --git a/tests/unit/WorkflowUtilsTest.ts b/tests/unit/WorkflowUtilsTest.ts index f2b665593545..a1847510d375 100644 --- a/tests/unit/WorkflowUtilsTest.ts +++ b/tests/unit/WorkflowUtilsTest.ts @@ -6,6 +6,7 @@ import { convertPolicyEmployeesToApprovalWorkflows, getApprovalLimitDescription, getOpenConnectedToPolicyBusinessBankAccounts, + mergeWorkflowMembersWithAvailableMembers, updateWorkflowDataOnApproverRemoval, } from '@src/libs/WorkflowUtils'; import type {Policy} from '@src/types/onyx'; @@ -543,6 +544,44 @@ describe('WorkflowUtils', () => { }); }); + describe('mergeWorkflowMembersWithAvailableMembers', () => { + it('Should deduplicate members when workflow members overlap with available members', () => { + const workflowMembers = [buildMember(1)]; + const allAvailableMembers = [buildMember(1), buildMember(2), buildMember(3)]; + const result = mergeWorkflowMembersWithAvailableMembers(workflowMembers, allAvailableMembers); + + expect(result).toHaveLength(3); + expect(result.map((m) => m.email)).toEqual(['1@example.com', '2@example.com', '3@example.com']); + }); + + it('Should not duplicate when editing self-approval workflow (user A submits to user A)', () => { + const userA = buildMember(1); + const workflowMembers = [userA]; + const allAvailableMembers = [userA]; + const result = mergeWorkflowMembersWithAvailableMembers(workflowMembers, allAvailableMembers); + + expect(result).toHaveLength(1); + expect(result[0].email).toBe('1@example.com'); + }); + + it('Should preserve workflow member order and append additional members', () => { + const workflowMembers = [buildMember(2), buildMember(3)]; + const allAvailableMembers = [buildMember(1), buildMember(2), buildMember(3), buildMember(4)]; + const result = mergeWorkflowMembersWithAvailableMembers(workflowMembers, allAvailableMembers); + + expect(result.map((m) => m.email)).toEqual(['2@example.com', '3@example.com', '1@example.com', '4@example.com']); + }); + + it('Should return workflow members only when all available are already in workflow', () => { + const workflowMembers = [buildMember(1), buildMember(2)]; + const allAvailableMembers = [buildMember(1), buildMember(2)]; + const result = mergeWorkflowMembersWithAvailableMembers(workflowMembers, allAvailableMembers); + + expect(result).toHaveLength(2); + expect(result.map((m) => m.email)).toEqual(['1@example.com', '2@example.com']); + }); + }); + describe('convertApprovalWorkflowToPolicyEmployees', () => { it('Should return an updated employee list for a simple default workflow', () => { const approvalWorkflow: ApprovalWorkflow = { From 52ca376c0b5f4422523d5ff07b88cc96bbe3911e Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Tue, 24 Feb 2026 08:32:57 -0700 Subject: [PATCH 3/9] add page component test --- ...orkspaceWorkflowsApprovalsEditPageTest.tsx | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx diff --git a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx new file mode 100644 index 000000000000..399779d35049 --- /dev/null +++ b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx @@ -0,0 +1,140 @@ +/** + * Component test for WorkspaceWorkflowsApprovalsEditPage. + * + * Regresses on issue #83251: when editing a self-approval workflow (user A submits to user A), + * availableMembers must not contain duplicate entries. Raw concatenation + * [...members, ...defaultWorkflowMembers] produces duplicates because defaultWorkflowMembers + * (from convertPolicyEmployeesToApprovalWorkflows) now includes all workspace members. + * This test verifies the page uses mergeWorkflowMembersWithAvailableMembers. + */ + +import {act, render} from '@testing-library/react-native'; +import React from 'react'; +import Onyx from 'react-native-onyx'; +import ComposeProviders from '@components/ComposeProviders'; +import {LocaleContextProvider} from '@components/LocaleContextProvider'; +import OnyxListItemProvider from '@components/OnyxListItemProvider'; +import * as Workflow from '@userActions/Workflow'; +import WorkspaceWorkflowsApprovalsEditPage from '@pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Policy} from '@src/types/onyx'; +import type {PersonalDetailsList} from '@src/types/onyx/PersonalDetails'; +import type {PolicyEmployeeList} from '@src/types/onyx/PolicyEmployee'; +import {buildPersonalDetails} from '../utils/TestHelper'; +import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; + +const POLICY_ID = 'workflow-approvals-edit-test-policy'; +const ALICE_EMAIL = 'alice@example.com'; + +jest.mock('@react-navigation/native', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment + const actualNav = jest.requireActual('@react-navigation/native'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return { + ...actualNav, + useIsFocused: () => true, + usePreventRemove: jest.fn(), + }; +}); + +jest.mock('@libs/Navigation/Navigation', () => ({ + goBack: jest.fn(), + dismissModal: jest.fn(), +})); + +function buildPolicy(employeeList: PolicyEmployeeList): Policy { + return { + id: POLICY_ID, + name: 'Test Workspace', + type: CONST.POLICY.TYPE.CORPORATE, + role: CONST.POLICY.ROLE.ADMIN, + owner: ALICE_EMAIL, + employeeList, + approver: ALICE_EMAIL, + areWorkflowsEnabled: true, + isPolicyExpenseChatEnabled: true, + outputCurrency: 'USD', + avatarURL: '', + lastModified: new Date().toISOString(), + pendingAction: null, + errors: {}, + } as Policy; +} + +function buildPersonalDetailsList(): PersonalDetailsList { + return { + 1: buildPersonalDetails(ALICE_EMAIL, 1, 'alice'), + }; +} + +const mockRoute = { + key: 'test-route', + name: 'Workspace_Approvals_Edit', + params: { + policyID: POLICY_ID, + firstApproverEmail: ALICE_EMAIL, + }, +}; + +const renderEditPage = () => + render( + + + , + ); + +describe('WorkspaceWorkflowsApprovalsEditPage', () => { + beforeAll(async () => { + Onyx.init({keys: ONYXKEYS}); + }); + + beforeEach(async () => { + jest.spyOn(Workflow, 'setApprovalWorkflow'); + await act(async () => { + await Onyx.clear(); + await Onyx.set(ONYXKEYS.HAS_LOADED_APP, true); + await Onyx.set(ONYXKEYS.IS_LOADING_REPORT_DATA, false); + + const employeeList: PolicyEmployeeList = { + [ALICE_EMAIL]: { + email: ALICE_EMAIL, + submitsTo: ALICE_EMAIL, + forwardsTo: undefined, + }, + }; + const policy = buildPolicy(employeeList); + const personalDetails = buildPersonalDetailsList(); + + await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); + await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails); + await Onyx.merge(ONYXKEYS.SESSION, {email: ALICE_EMAIL, accountID: 1}); + await waitForBatchedUpdatesWithAct(); + }); + }); + + afterEach(async () => { + jest.restoreAllMocks(); + await act(async () => { + await Onyx.clear(); + await waitForBatchedUpdatesWithAct(); + }); + }); + + it('should pass deduplicated availableMembers to setApprovalWorkflow for self-approval workflow', async () => { + renderEditPage(); + await waitForBatchedUpdatesWithAct(); + + expect(Workflow.setApprovalWorkflow).toHaveBeenCalled(); + const callArg = (Workflow.setApprovalWorkflow as jest.Mock).mock.calls[0][0]; + const availableMembers = callArg.availableMembers ?? []; + const emails = availableMembers.map((m: {email: string}) => m.email); + const uniqueEmails = [...new Set(emails)]; + + expect(emails.length).toBe(uniqueEmails.length); + expect(emails).toContain(ALICE_EMAIL); + }); +}); From de160bb93fb2087bde41fb6f2ae5e917c69a9e1e Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Tue, 24 Feb 2026 08:34:36 -0700 Subject: [PATCH 4/9] Slight cleanup --- ...WorkspaceWorkflowsApprovalsEditPageTest.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx index 399779d35049..2a479894502e 100644 --- a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx +++ b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx @@ -43,7 +43,14 @@ jest.mock('@libs/Navigation/Navigation', () => ({ dismissModal: jest.fn(), })); -function buildPolicy(employeeList: PolicyEmployeeList): Policy { +function buildPolicy(): Policy { + const employeeList: PolicyEmployeeList = { + [ALICE_EMAIL]: { + email: ALICE_EMAIL, + submitsTo: ALICE_EMAIL, + forwardsTo: undefined, + }, + }; return { id: POLICY_ID, name: 'Test Workspace', @@ -99,14 +106,7 @@ describe('WorkspaceWorkflowsApprovalsEditPage', () => { await Onyx.set(ONYXKEYS.HAS_LOADED_APP, true); await Onyx.set(ONYXKEYS.IS_LOADING_REPORT_DATA, false); - const employeeList: PolicyEmployeeList = { - [ALICE_EMAIL]: { - email: ALICE_EMAIL, - submitsTo: ALICE_EMAIL, - forwardsTo: undefined, - }, - }; - const policy = buildPolicy(employeeList); + const policy = buildPolicy(); const personalDetails = buildPersonalDetailsList(); await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); From 82be1ef0dfaeefe029130dbe6c314152cb1ccd9a Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Tue, 24 Feb 2026 08:43:37 -0700 Subject: [PATCH 5/9] fix prettier and lint --- .../approvals/WorkspaceWorkflowsApprovalsEditPage.tsx | 1 + tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 78a0c2a4cce1..cebf7460dc00 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -118,6 +118,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true setApprovalWorkflow({ ...currentApprovalWorkflow, availableMembers: mergeWorkflowMembersWithAvailableMembers(currentApprovalWorkflow.members, defaultWorkflowMembers), + // availableMembers: [...currentApprovalWorkflow.members, ...defaultWorkflowMembers], usedApproverEmails, action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, errors: null, diff --git a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx index 2a479894502e..0d40ecaa9413 100644 --- a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx +++ b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx @@ -7,15 +7,14 @@ * (from convertPolicyEmployeesToApprovalWorkflows) now includes all workspace members. * This test verifies the page uses mergeWorkflowMembersWithAvailableMembers. */ - import {act, render} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; import ComposeProviders from '@components/ComposeProviders'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; -import * as Workflow from '@userActions/Workflow'; import WorkspaceWorkflowsApprovalsEditPage from '@pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage'; +import * as Workflow from '@userActions/Workflow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy} from '@src/types/onyx'; From 6ad05b336d4d867b0d710786130df428e4bda99c Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Tue, 24 Feb 2026 09:00:00 -0700 Subject: [PATCH 6/9] clean up comments & fix eslint --- .../WorkspaceWorkflowsApprovalsEditPage.tsx | 1 - .../WorkspaceWorkflowsApprovalsEditPageTest.tsx | 17 ++++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index cebf7460dc00..78a0c2a4cce1 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -118,7 +118,6 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true setApprovalWorkflow({ ...currentApprovalWorkflow, availableMembers: mergeWorkflowMembersWithAvailableMembers(currentApprovalWorkflow.members, defaultWorkflowMembers), - // availableMembers: [...currentApprovalWorkflow.members, ...defaultWorkflowMembers], usedApproverEmails, action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, errors: null, diff --git a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx index 0d40ecaa9413..f426d1de71c8 100644 --- a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx +++ b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx @@ -1,12 +1,3 @@ -/** - * Component test for WorkspaceWorkflowsApprovalsEditPage. - * - * Regresses on issue #83251: when editing a self-approval workflow (user A submits to user A), - * availableMembers must not contain duplicate entries. Raw concatenation - * [...members, ...defaultWorkflowMembers] produces duplicates because defaultWorkflowMembers - * (from convertPolicyEmployeesToApprovalWorkflows) now includes all workspace members. - * This test verifies the page uses mergeWorkflowMembersWithAvailableMembers. - */ import {act, render} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; @@ -14,7 +5,7 @@ import ComposeProviders from '@components/ComposeProviders'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; import WorkspaceWorkflowsApprovalsEditPage from '@pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage'; -import * as Workflow from '@userActions/Workflow'; +import {setApprovalWorkflow} from '@userActions/Workflow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy} from '@src/types/onyx'; @@ -99,7 +90,7 @@ describe('WorkspaceWorkflowsApprovalsEditPage', () => { }); beforeEach(async () => { - jest.spyOn(Workflow, 'setApprovalWorkflow'); + jest.spyOn(require('@userActions/Workflow'), 'setApprovalWorkflow'); await act(async () => { await Onyx.clear(); await Onyx.set(ONYXKEYS.HAS_LOADED_APP, true); @@ -127,8 +118,8 @@ describe('WorkspaceWorkflowsApprovalsEditPage', () => { renderEditPage(); await waitForBatchedUpdatesWithAct(); - expect(Workflow.setApprovalWorkflow).toHaveBeenCalled(); - const callArg = (Workflow.setApprovalWorkflow as jest.Mock).mock.calls[0][0]; + expect(setApprovalWorkflow).toHaveBeenCalled(); + const callArg = (setApprovalWorkflow as jest.Mock).mock.calls[0][0]; const availableMembers = callArg.availableMembers ?? []; const emails = availableMembers.map((m: {email: string}) => m.email); const uniqueEmails = [...new Set(emails)]; From 44b266aed25ab2a4eac82dc9bdac5b085383b24c Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Tue, 24 Feb 2026 09:02:52 -0700 Subject: [PATCH 7/9] simplify function description --- src/libs/WorkflowUtils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/WorkflowUtils.ts b/src/libs/WorkflowUtils.ts index a5eed5a31ed4..61f784d8f228 100644 --- a/src/libs/WorkflowUtils.ts +++ b/src/libs/WorkflowUtils.ts @@ -575,9 +575,7 @@ function getOpenConnectedToPolicyBusinessBankAccounts(bankAccountList: BankAccou } /** - * Merge workflow members with all available members, deduplicating by email. - * Used when setting up the Edit page's availableMembers to prevent duplicate keyForList - * in the Expenses From list (which causes a blank row when deselecting - see #83251). + * Combine workflow members with available members, deduplicating by email. */ function mergeWorkflowMembersWithAvailableMembers(workflowMembers: Member[], allAvailableMembers: Member[]): Member[] { const memberEmails = new Set(workflowMembers.map((m) => m.email)); From e80c0a483681d186d90cfff79db8a260f66ffbd7 Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Tue, 24 Feb 2026 09:10:27 -0700 Subject: [PATCH 8/9] fix eslint --- .../WorkspaceWorkflowsApprovalsEditPageTest.tsx | 16 ++++++++++------ tests/unit/WorkflowUtilsTest.ts | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx index f426d1de71c8..9089ed257ec9 100644 --- a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx +++ b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx @@ -9,6 +9,7 @@ import {setApprovalWorkflow} from '@userActions/Workflow'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy} from '@src/types/onyx'; +import type {Member} from '@src/types/onyx/ApprovalWorkflow'; import type {PersonalDetailsList} from '@src/types/onyx/PersonalDetails'; import type {PolicyEmployeeList} from '@src/types/onyx/PolicyEmployee'; import {buildPersonalDetails} from '../utils/TestHelper'; @@ -16,6 +17,7 @@ import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct' const POLICY_ID = 'workflow-approvals-edit-test-policy'; const ALICE_EMAIL = 'alice@example.com'; +const ALICE_ACCOUNT_ID = 1; jest.mock('@react-navigation/native', () => { // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment @@ -61,7 +63,7 @@ function buildPolicy(): Policy { function buildPersonalDetailsList(): PersonalDetailsList { return { - 1: buildPersonalDetails(ALICE_EMAIL, 1, 'alice'), + [ALICE_ACCOUNT_ID]: buildPersonalDetails(ALICE_EMAIL, ALICE_ACCOUNT_ID, 'alice'), }; } @@ -101,7 +103,7 @@ describe('WorkspaceWorkflowsApprovalsEditPage', () => { await Onyx.set(`${ONYXKEYS.COLLECTION.POLICY}${POLICY_ID}`, policy); await Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, personalDetails); - await Onyx.merge(ONYXKEYS.SESSION, {email: ALICE_EMAIL, accountID: 1}); + await Onyx.merge(ONYXKEYS.SESSION, {email: ALICE_EMAIL, accountID: ALICE_ACCOUNT_ID}); await waitForBatchedUpdatesWithAct(); }); }); @@ -119,12 +121,14 @@ describe('WorkspaceWorkflowsApprovalsEditPage', () => { await waitForBatchedUpdatesWithAct(); expect(setApprovalWorkflow).toHaveBeenCalled(); - const callArg = (setApprovalWorkflow as jest.Mock).mock.calls[0][0]; - const availableMembers = callArg.availableMembers ?? []; - const emails = availableMembers.map((m: {email: string}) => m.email); + const mockCalls = (setApprovalWorkflow as jest.Mock).mock.calls; + const firstCall = mockCalls.at(0); + const callArg = firstCall?.at(0) as {availableMembers?: Member[]} | undefined; + const availableMembers: Member[] = callArg?.availableMembers ?? []; + const emails = availableMembers.map((m) => m.email); const uniqueEmails = [...new Set(emails)]; - expect(emails.length).toBe(uniqueEmails.length); + expect(emails).toHaveLength(uniqueEmails.length); expect(emails).toContain(ALICE_EMAIL); }); }); diff --git a/tests/unit/WorkflowUtilsTest.ts b/tests/unit/WorkflowUtilsTest.ts index a1847510d375..8c32b0cc0a30 100644 --- a/tests/unit/WorkflowUtilsTest.ts +++ b/tests/unit/WorkflowUtilsTest.ts @@ -561,7 +561,7 @@ describe('WorkflowUtils', () => { const result = mergeWorkflowMembersWithAvailableMembers(workflowMembers, allAvailableMembers); expect(result).toHaveLength(1); - expect(result[0].email).toBe('1@example.com'); + expect(result.at(0)?.email).toBe('1@example.com'); }); it('Should preserve workflow member order and append additional members', () => { From bfb5c496a2c0984e390ea891b78858fec7189a3f Mon Sep 17 00:00:00 2001 From: Alex Beaman Date: Tue, 24 Feb 2026 09:15:12 -0700 Subject: [PATCH 9/9] fix remaining eslint issues --- tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx index 9089ed257ec9..ef462e550190 100644 --- a/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx +++ b/tests/ui/WorkspaceWorkflowsApprovalsEditPageTest.tsx @@ -121,9 +121,9 @@ describe('WorkspaceWorkflowsApprovalsEditPage', () => { await waitForBatchedUpdatesWithAct(); expect(setApprovalWorkflow).toHaveBeenCalled(); - const mockCalls = (setApprovalWorkflow as jest.Mock).mock.calls; + const mockCalls = (setApprovalWorkflow as jest.Mock).mock.calls; const firstCall = mockCalls.at(0); - const callArg = firstCall?.at(0) as {availableMembers?: Member[]} | undefined; + const callArg = firstCall?.at(0); const availableMembers: Member[] = callArg?.availableMembers ?? []; const emails = availableMembers.map((m) => m.email); const uniqueEmails = [...new Set(emails)];