Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions src/components/LHNOptionsList/OptionRowLHNData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@ import {deepEqual} from 'fast-equals';
import React, {useMemo, useRef} from 'react';
import useCurrentReportID from '@hooks/useCurrentReportID';
import useGetExpensifyCardFromReportAction from '@hooks/useGetExpensifyCardFromReportAction';
import useOnyx from '@hooks/useOnyx';
import {getSortedReportActions, shouldReportActionBeVisibleAsLastAction} from '@libs/ReportActionsUtils';
import {canUserPerformWriteAction as canUserPerformWriteActionUtil} from '@libs/ReportUtils';
import SidebarUtils from '@libs/SidebarUtils';
import CONST from '@src/CONST';
import type {OptionData} from '@src/libs/ReportUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import OptionRowLHN from './OptionRowLHN';
import type {OptionRowLHNDataProps} from './types';

Expand Down Expand Up @@ -45,22 +43,20 @@ function OptionRowLHNData({

const optionItemRef = useRef<OptionData | undefined>(undefined);

const [reportActionsData] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {canBeMissing: true});

const lastAction = useMemo(() => {
if (!reportActionsData || !fullReport) {
if (!reportActions || !fullReport) {
return undefined;
}

const canUserPerformWriteAction = canUserPerformWriteActionUtil(fullReport);
const actionsArray = getSortedReportActions(Object.values(reportActionsData));
const actionsArray = getSortedReportActions(Object.values(reportActions));

const reportActionsForDisplay = actionsArray.filter(
(reportAction) => shouldReportActionBeVisibleAsLastAction(reportAction, canUserPerformWriteAction) && reportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED,
);

return reportActionsForDisplay.at(-1);
}, [reportActionsData, fullReport]);
}, [reportActions, fullReport]);

const card = useGetExpensifyCardFromReportAction({reportAction: lastAction, policyID: fullReport?.policyID});
const optionItem = useMemo(() => {
Expand Down
18 changes: 17 additions & 1 deletion src/components/OnyxListItemProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const [SessionProvider, , useSession] = createOnyxContext(ONYXKEYS.SESSION);
const [PolicyCategoriesProvider, , usePolicyCategories] = createOnyxContext(ONYXKEYS.COLLECTION.POLICY_CATEGORIES);
const [PolicyTagsProvider, , usePolicyTags] = createOnyxContext(ONYXKEYS.COLLECTION.POLICY_TAGS);
const [ReportTransactionsAndViolationsProvider, , useAllReportsTransactionsAndViolations] = createOnyxContext(ONYXKEYS.DERIVED.REPORT_TRANSACTIONS_AND_VIOLATIONS);
const [CardListProvider, , useCardList] = createOnyxContext(ONYXKEYS.CARD_LIST);
const [WorkspaceCardListProvider, , useWorkspaceCardList] = createOnyxContext(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST);

type OnyxListItemProviderProps = {
/** Rendered child component */
Expand All @@ -32,6 +34,8 @@ function OnyxListItemProvider(props: OnyxListItemProviderProps) {
PolicyCategoriesProvider,
PolicyTagsProvider,
ReportTransactionsAndViolationsProvider,
CardListProvider,
WorkspaceCardListProvider,
]}
>
{props.children}
Expand All @@ -43,4 +47,16 @@ OnyxListItemProvider.displayName = 'OnyxListItemProvider';

export default OnyxListItemProvider;

export {usePersonalDetails, BetasContext, useBetas, PersonalDetailsContext, useBlockedFromConcierge, useSession, usePolicyCategories, usePolicyTags, useAllReportsTransactionsAndViolations};
export {
usePersonalDetails,
BetasContext,
useBetas,
PersonalDetailsContext,
useBlockedFromConcierge,
useSession,
usePolicyCategories,
usePolicyTags,
useAllReportsTransactionsAndViolations,
useCardList,
useWorkspaceCardList,
};
17 changes: 7 additions & 10 deletions src/hooks/useGetExpensifyCardFromReportAction.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import {useCardList, useWorkspaceCardList} from '@components/OnyxListItemProvider';
import {getPolicy, getWorkspaceAccountID, isPolicyAdmin} from '@libs/PolicyUtils';
import {getOriginalMessage, isCardIssuedAction} from '@libs/ReportActionsUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Card, ReportAction} from '@src/types/onyx';
import useOnyx from './useOnyx';

function useGetExpensifyCardFromReportAction({reportAction, policyID}: {reportAction?: ReportAction; policyID?: string}): Card | undefined {
const [allUserCards] = useOnyx(ONYXKEYS.CARD_LIST, {canBeMissing: true});
const [allExpensifyCards] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {
selector: (val) => {
const workspaceAccountID = getWorkspaceAccountID(policyID);
return val?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`] ?? {};
},
canBeMissing: true,
});
const allUserCards = useCardList();
const workspaceAccountID = getWorkspaceAccountID(policyID);
const allExpensifyCards = useWorkspaceCardList();
const expensifyCards = allExpensifyCards?.[`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID}_${CONST.EXPENSIFY_CARD.BANK}`] ?? {};

const cardIssuedActionOriginalMessage = isCardIssuedAction(reportAction) ? getOriginalMessage(reportAction) : undefined;

const cardID = cardIssuedActionOriginalMessage?.cardID ?? CONST.DEFAULT_NUMBER_ID;
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line deprecation/deprecation
return isPolicyAdmin(getPolicy(policyID)) ? allExpensifyCards?.[cardID] : allUserCards?.[cardID];
return isPolicyAdmin(getPolicy(policyID)) ? expensifyCards?.[cardID] : allUserCards?.[cardID];
}

export default useGetExpensifyCardFromReportAction;
48 changes: 31 additions & 17 deletions tests/unit/useGetExpensifyCardFromReportActionTest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {renderHook} from '@testing-library/react-native';
import Onyx from 'react-native-onyx';
import {useCardList, useWorkspaceCardList} from '@components/OnyxListItemProvider';
import {getPolicy, getWorkspaceAccountID, isPolicyAdmin} from '@libs/PolicyUtils';
import {getOriginalMessage, isCardIssuedAction} from '@libs/ReportActionsUtils';
import CONST from '@src/CONST';
Expand All @@ -12,6 +13,10 @@ import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'
// Mock the dependencies
jest.mock('@libs/PolicyUtils');
jest.mock('@libs/ReportActionsUtils');
jest.mock('@components/OnyxListItemProvider', () => ({
useCardList: jest.fn(),
useWorkspaceCardList: jest.fn(),
}));

// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
// eslint-disable-next-line deprecation/deprecation
Expand All @@ -20,6 +25,8 @@ const mockGetWorkspaceAccountID = getWorkspaceAccountID as jest.MockedFunction<t
const mockIsPolicyAdmin = isPolicyAdmin as jest.MockedFunction<typeof isPolicyAdmin>;
const mockGetOriginalMessage = getOriginalMessage as jest.MockedFunction<typeof getOriginalMessage>;
const mockIsCardIssuedAction = isCardIssuedAction as jest.MockedFunction<typeof isCardIssuedAction>;
const mockUseCardList = useCardList as jest.MockedFunction<typeof useCardList>;
const mockUseWorkspaceCardList = useWorkspaceCardList as jest.MockedFunction<typeof useWorkspaceCardList>;

describe('useGetExpensifyCardFromReportAction', () => {
const mockCard: Card = {
Expand Down Expand Up @@ -76,6 +83,8 @@ describe('useGetExpensifyCardFromReportAction', () => {
mockIsPolicyAdmin.mockReturnValue(false);
mockGetOriginalMessage.mockReturnValue({cardID: 123, assigneeAccountID: 1});
mockIsCardIssuedAction.mockReturnValue(true);
mockUseCardList.mockReturnValue({});
mockUseWorkspaceCardList.mockReturnValue({});
});

describe('when reportAction is not a card issued action', () => {
Expand All @@ -102,8 +111,7 @@ describe('useGetExpensifyCardFromReportAction', () => {

it('returns card from allUserCards when card exists', async () => {
// eslint-disable-next-line @typescript-eslint/naming-convention
Onyx.set(ONYXKEYS.CARD_LIST, {'123': mockCard});
await waitForBatchedUpdatesWithAct();
mockUseCardList.mockReturnValue({'123': mockCard});

const {result} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'}));
await waitForBatchedUpdatesWithAct();
Expand All @@ -112,8 +120,7 @@ describe('useGetExpensifyCardFromReportAction', () => {
});

it('returns undefined when card does not exist in allUserCards', async () => {
Onyx.set(ONYXKEYS.CARD_LIST, {});
await waitForBatchedUpdatesWithAct();
mockUseCardList.mockReturnValue({});

const {result} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'}));
await waitForBatchedUpdatesWithAct();
Expand Down Expand Up @@ -143,8 +150,7 @@ describe('useGetExpensifyCardFromReportAction', () => {
it('returns card from allExpensifyCards when card exists', async () => {
const workspaceCardsKey = `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}123_${CONST.EXPENSIFY_CARD.BANK}` as OnyxKey;
// eslint-disable-next-line @typescript-eslint/naming-convention
Onyx.set(workspaceCardsKey, {123: mockCard});
await waitForBatchedUpdatesWithAct();
mockUseWorkspaceCardList.mockReturnValue({[workspaceCardsKey]: {123: mockCard}});

const {result} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'}));
await waitForBatchedUpdatesWithAct();
Expand All @@ -155,8 +161,7 @@ describe('useGetExpensifyCardFromReportAction', () => {
it('returns undefined when card does not exist in allExpensifyCards', async () => {
const workspaceCardsKey = `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}123_${CONST.EXPENSIFY_CARD.BANK}` as OnyxKey;
// eslint-disable-next-line @typescript-eslint/naming-convention
Onyx.set(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {[workspaceCardsKey]: {}});
await waitForBatchedUpdatesWithAct();
mockUseWorkspaceCardList.mockReturnValue({[workspaceCardsKey]: {}});

const {result} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'}));
await waitForBatchedUpdatesWithAct();
Expand All @@ -168,16 +173,22 @@ describe('useGetExpensifyCardFromReportAction', () => {

describe('reactivity to Onyx changes', () => {
it('updates when allUserCards changes', async () => {
mockUseCardList.mockReturnValue({});
mockUseWorkspaceCardList.mockReturnValue({});

const {result} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'}));
await waitForBatchedUpdatesWithAct();

expect(result.current).toBeUndefined();

// eslint-disable-next-line @typescript-eslint/naming-convention
Onyx.set(ONYXKEYS.CARD_LIST, {'123': mockCard});
mockUseCardList.mockReturnValue({'123': mockCard});

// Re-render the hook to get the updated result
const {result: updatedResult} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'}));
await waitForBatchedUpdatesWithAct();

expect(result.current).toEqual(mockCard);
expect(updatedResult.current).toEqual(mockCard);
});

it('updates when allExpensifyCards changes for policy admin', async () => {
Expand All @@ -193,26 +204,30 @@ describe('useGetExpensifyCardFromReportAction', () => {
isPolicyExpenseChatEnabled: false,
workspaceAccountID: 123,
});

// Set initial state
mockUseCardList.mockReturnValue({});
mockUseWorkspaceCardList.mockReturnValue({});

const {result} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'}));
await waitForBatchedUpdatesWithAct();

expect(result.current).toBeUndefined();

// eslint-disable-next-line @typescript-eslint/naming-convention
const workspaceCardsKey = `${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}123_${CONST.EXPENSIFY_CARD.BANK}` as OnyxKey;
// eslint-disable-next-line @typescript-eslint/naming-convention
Onyx.set(workspaceCardsKey, {123: mockCard});
mockUseWorkspaceCardList.mockReturnValue({[workspaceCardsKey]: {123: mockCard}});
const {result: updatedResult} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'}));
await waitForBatchedUpdatesWithAct();

expect(result.current).toEqual(mockCard);
expect(updatedResult.current).toEqual(mockCard);
});
});

describe('workspace account ID generation', () => {
it('calls getWorkspaceAccountID with correct policyID', async () => {
// eslint-disable-next-line @typescript-eslint/naming-convention
Onyx.set(ONYXKEYS.CARD_LIST, {'123': mockCard});
await waitForBatchedUpdatesWithAct();
mockUseCardList.mockReturnValue({'123': mockCard});

const {result} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'test-policy-123'}));
await waitForBatchedUpdatesWithAct();
Expand All @@ -238,8 +253,7 @@ describe('useGetExpensifyCardFromReportAction', () => {
mockGetPolicy.mockReturnValue(testPolicy);

// eslint-disable-next-line @typescript-eslint/naming-convention
Onyx.set(ONYXKEYS.CARD_LIST, {'123': mockCard});
await waitForBatchedUpdatesWithAct();
mockUseCardList.mockReturnValue({'123': mockCard});

const {result} = renderHook(() => useGetExpensifyCardFromReportAction({reportAction: createMockReportAction(), policyID: 'policy123'}));
await waitForBatchedUpdatesWithAct();
Expand Down
Loading