Skip to content

Commit eaf5d99

Browse files
committed
Add test
1 parent 3d2450a commit eaf5d99

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import {renderHook} from '@testing-library/react-native';
2+
import Onyx from 'react-native-onyx';
3+
// eslint-disable-next-line import/order
4+
import useSidePanelContext from '@pages/inbox/report/ReportActionCompose/useSidePanelContext';
5+
import CONST from '@src/CONST';
6+
import ONYXKEYS from '@src/ONYXKEYS';
7+
import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates';
8+
9+
const REPORT_ID = 'report_123';
10+
11+
// Mutable state referenced by jest.mock factories below
12+
let mockIsInSidePanel = false;
13+
let mockCurrentReportIDState: {currentReportID: string | undefined; currentRHPReportID: string | undefined} = {
14+
currentReportID: undefined,
15+
currentRHPReportID: undefined,
16+
};
17+
let mockSearchState: {
18+
currentSearchQueryJSON: {type?: string} | undefined;
19+
selectedTransactionIDs: string[];
20+
selectedTransactions: Record<string, {isSelected: boolean; transaction?: Record<string, unknown>}>;
21+
selectedReports: Array<{reportID?: string}>;
22+
} = {
23+
currentSearchQueryJSON: undefined,
24+
selectedTransactionIDs: [],
25+
selectedTransactions: {},
26+
selectedReports: [],
27+
};
28+
29+
jest.mock('@hooks/useIsInSidePanel', () => ({
30+
// eslint-disable-next-line @typescript-eslint/naming-convention
31+
__esModule: true,
32+
default: () => mockIsInSidePanel,
33+
}));
34+
35+
jest.mock('@hooks/useCurrentReportID', () => ({
36+
useCurrentReportIDState: () => mockCurrentReportIDState,
37+
}));
38+
39+
jest.mock('@components/Search/SearchContext', () => ({
40+
useSearchStateContext: () => mockSearchState,
41+
}));
42+
43+
function resetMocks() {
44+
mockIsInSidePanel = false;
45+
mockCurrentReportIDState = {currentReportID: undefined, currentRHPReportID: undefined};
46+
mockSearchState = {
47+
currentSearchQueryJSON: undefined,
48+
selectedTransactionIDs: [],
49+
selectedTransactions: {},
50+
selectedReports: [],
51+
};
52+
}
53+
54+
describe('useSidePanelContext', () => {
55+
beforeAll(() => {
56+
Onyx.init({keys: ONYXKEYS});
57+
});
58+
59+
beforeEach(async () => {
60+
resetMocks();
61+
await Onyx.clear();
62+
await waitForBatchedUpdates();
63+
});
64+
65+
async function renderWithConciergeReport(reportID = REPORT_ID) {
66+
await Onyx.merge(ONYXKEYS.CONCIERGE_REPORT_ID, REPORT_ID);
67+
await waitForBatchedUpdates();
68+
return renderHook(() => useSidePanelContext(reportID));
69+
}
70+
71+
it('returns undefined when not in the side panel', async () => {
72+
mockIsInSidePanel = false;
73+
const {result} = await renderWithConciergeReport();
74+
await waitForBatchedUpdates();
75+
expect(result.current).toBeUndefined();
76+
});
77+
78+
it('returns undefined when reportID does not match conciergeReportID', async () => {
79+
mockIsInSidePanel = true;
80+
const {result} = await renderWithConciergeReport('different_report');
81+
await waitForBatchedUpdates();
82+
expect(result.current).toBeUndefined();
83+
});
84+
85+
it('returns undefined when no context is available', async () => {
86+
mockIsInSidePanel = true;
87+
const {result} = await renderWithConciergeReport();
88+
await waitForBatchedUpdates();
89+
expect(result.current).toBeUndefined();
90+
});
91+
92+
describe('EXPENSE_REPORT search type', () => {
93+
beforeEach(() => {
94+
mockIsInSidePanel = true;
95+
mockSearchState.currentSearchQueryJSON = {type: CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT};
96+
});
97+
98+
it('returns only selectedReportIDs when reports are selected', async () => {
99+
mockSearchState.selectedReports = [{reportID: 'report_1'}, {reportID: 'report_2'}];
100+
const {result} = await renderWithConciergeReport();
101+
await waitForBatchedUpdates();
102+
expect(result.current).toEqual({selectedReportIDs: 'report_1,report_2'});
103+
});
104+
105+
it('falls back to contextReportID and selectedTransactionIDs when no reports are selected', async () => {
106+
mockSearchState.selectedReports = [];
107+
mockCurrentReportIDState = {currentRHPReportID: 'rhp_report', currentReportID: undefined};
108+
mockSearchState.selectedTransactionIDs = ['txn_a'];
109+
const {result} = await renderWithConciergeReport();
110+
await waitForBatchedUpdates();
111+
expect(result.current).toEqual({reportID: 'rhp_report', selectedTransactionIDs: 'txn_a'});
112+
});
113+
114+
it('includes child transaction selections when drilling into a report', async () => {
115+
mockSearchState.selectedReports = [];
116+
mockSearchState.selectedTransactionIDs = ['txn_1', 'txn_2'];
117+
const {result} = await renderWithConciergeReport();
118+
await waitForBatchedUpdates();
119+
expect(result.current).toMatchObject({selectedTransactionIDs: 'txn_1,txn_2'});
120+
});
121+
});
122+
123+
describe('contextReportID resolution', () => {
124+
beforeEach(() => {
125+
mockIsInSidePanel = true;
126+
});
127+
128+
it('prefers currentRHPReportID over currentReportID', async () => {
129+
mockCurrentReportIDState = {currentRHPReportID: 'rhp_report', currentReportID: 'main_report'};
130+
const {result} = await renderWithConciergeReport();
131+
await waitForBatchedUpdates();
132+
expect(result.current).toMatchObject({reportID: 'rhp_report'});
133+
});
134+
135+
it('falls back to currentReportID when no RHP report is open', async () => {
136+
mockCurrentReportIDState = {currentRHPReportID: undefined, currentReportID: 'main_report'};
137+
const {result} = await renderWithConciergeReport();
138+
await waitForBatchedUpdates();
139+
expect(result.current).toMatchObject({reportID: 'main_report'});
140+
});
141+
});
142+
143+
describe('transaction ID derivation', () => {
144+
beforeEach(() => {
145+
mockIsInSidePanel = true;
146+
});
147+
148+
it('derives IDs from selectedTransactions map (Search list selections), filtering out unselected and non-transaction entries', async () => {
149+
mockSearchState.selectedTransactions = {
150+
txn1: {isSelected: true, transaction: {}},
151+
txn2: {isSelected: true, transaction: {}},
152+
txn3: {isSelected: false, transaction: {}}, // not selected
153+
reportKey: {isSelected: true, transaction: undefined}, // empty report row — no transaction
154+
};
155+
const {result} = await renderWithConciergeReport();
156+
await waitForBatchedUpdates();
157+
expect(result.current).toMatchObject({selectedTransactionIDs: 'txn1,txn2'});
158+
});
159+
160+
it('falls back to selectedTransactionIDs array when map is empty (report table view selections)', async () => {
161+
mockSearchState.selectedTransactions = {};
162+
mockSearchState.selectedTransactionIDs = ['txn_a', 'txn_b'];
163+
const {result} = await renderWithConciergeReport();
164+
await waitForBatchedUpdates();
165+
expect(result.current).toMatchObject({selectedTransactionIDs: 'txn_a,txn_b'});
166+
});
167+
168+
it('prefers the map over the array when both are non-empty', async () => {
169+
mockSearchState.selectedTransactions = {txnMap: {isSelected: true, transaction: {}}};
170+
mockSearchState.selectedTransactionIDs = ['txnArray'];
171+
const {result} = await renderWithConciergeReport();
172+
await waitForBatchedUpdates();
173+
expect(result.current).toMatchObject({selectedTransactionIDs: 'txnMap'});
174+
});
175+
});
176+
177+
it('returns all available context when every field is populated', async () => {
178+
mockIsInSidePanel = true;
179+
mockCurrentReportIDState = {currentRHPReportID: 'rhp_report', currentReportID: undefined};
180+
mockSearchState.selectedTransactions = {txn1: {isSelected: true, transaction: {}}};
181+
mockSearchState.selectedReports = [{reportID: 'report_1'}];
182+
const {result} = await renderWithConciergeReport();
183+
await waitForBatchedUpdates();
184+
expect(result.current).toEqual({
185+
reportID: 'rhp_report',
186+
selectedTransactionIDs: 'txn1',
187+
selectedReportIDs: 'report_1',
188+
});
189+
});
190+
});

0 commit comments

Comments
 (0)