Skip to content

Commit 03cb66c

Browse files
committed
test: address perps integration harness feedback
1 parent af7f065 commit 03cb66c

5 files changed

Lines changed: 303 additions & 212 deletions

File tree

app/components/UI/Perps/integration/orderLifecycle.integration.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
* Reference: tests/integration/AGENTS.md · .agents/skills/integration-test/
1010
*/
1111

12+
import { type Position } from '@metamask/perps-controller';
13+
1214
import { buildPerpsIntegrationHarness } from '../../../../../tests/integration/harnesses/perps';
1315

1416
describe('Perps order lifecycle — integration', () => {
@@ -107,8 +109,7 @@ describe('Perps order lifecycle — integration', () => {
107109

108110
describe('closing a position', () => {
109111
/** Long BTC position seeded into the subscription cache. */
110-
const openLongBTC = {
111-
coin: 'BTC',
112+
const openLongBTC: Position = {
112113
symbol: 'BTC',
113114
size: '0.1', // positive = long
114115
entryPrice: '50000',
@@ -120,6 +121,8 @@ describe('Perps order lifecycle — integration', () => {
120121
maxLeverage: 50,
121122
returnOnEquity: '0',
122123
cumulativeFunding: { allTime: '0', sinceOpen: '0', sinceChange: '0' },
124+
takeProfitCount: 0,
125+
stopLossCount: 0,
123126
};
124127

125128
it('closes a long position fully when size is omitted (cache-lookup path)', async () => {
@@ -129,9 +132,7 @@ describe('Perps order lifecycle — integration', () => {
129132
setupTradingReady();
130133
// Realistic flow: provider reads the position from the WS subscription
131134
// cache rather than receiving it inline from the UI.
132-
mocks.subscription.getCachedPositions.mockReturnValue([
133-
openLongBTC as never,
134-
]);
135+
mocks.subscription.getCachedPositions.mockReturnValue([openLongBTC]);
135136

136137
// Act — full close, no `size`, no `position` param
137138
const result = await provider.closePosition({

app/components/UI/Perps/integration/orderLifecycleFlow.integration.test.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* tests/integration/STRATEGY.md (Shape A vs Shape B discussion)
2020
*/
2121

22-
import { renderHook, act } from '@testing-library/react-native';
22+
import { act } from '@testing-library/react-native';
2323
import { type OrderResult, type Position } from '@metamask/perps-controller';
2424

2525
import { buildPerpsFlowHarness } from '../../../../../tests/integration/harnesses/perps-flow';
@@ -29,9 +29,9 @@ describe('Perps order lifecycle — FLOW integration', () => {
2929
describe('opening a position via the hook chain', () => {
3030
it('places a long market order through the real usePerpsTrading hook', async () => {
3131
// Arrange
32-
const { harness } = buildPerpsFlowHarness();
33-
harness.setupTradingReady();
34-
const { result } = renderHook(() => usePerpsTrading());
32+
const perps = buildPerpsFlowHarness();
33+
perps.harness.setupTradingReady();
34+
const { result } = perps.renderHookWithFlow(() => usePerpsTrading());
3535

3636
// Act
3737
let placeOrderResult: Awaited<
@@ -51,8 +51,8 @@ describe('Perps order lifecycle — FLOW integration', () => {
5151
// chain was called. Both ends of the chain verified in one test.
5252
expect(placeOrderResult).not.toBeNull();
5353
expect(placeOrderResult).toMatchObject({ success: true, orderId: '123' });
54-
expect(harness.mocks.exchangeClient.order).toHaveBeenCalledTimes(1);
55-
expect(harness.mocks.exchangeClient.order).toHaveBeenCalledWith(
54+
expect(perps.harness.mocks.exchangeClient.order).toHaveBeenCalledTimes(1);
55+
expect(perps.harness.mocks.exchangeClient.order).toHaveBeenCalledWith(
5656
expect.objectContaining({
5757
orders: [
5858
expect.objectContaining({
@@ -85,8 +85,8 @@ describe('Perps order lifecycle — FLOW integration', () => {
8585
describe('reversing a position via the hook chain', () => {
8686
it('places the flip market order end-to-end', async () => {
8787
// Arrange
88-
const { harness } = buildPerpsFlowHarness();
89-
harness.setupTradingReady();
88+
const perps = buildPerpsFlowHarness();
89+
perps.harness.setupTradingReady();
9090
const openLongBTC: Position = {
9191
symbol: 'BTC',
9292
size: '0.1', // positive = long; flipPosition will compute 2x = 0.2
@@ -102,7 +102,7 @@ describe('Perps order lifecycle — FLOW integration', () => {
102102
takeProfitCount: 0,
103103
stopLossCount: 0,
104104
};
105-
const { result } = renderHook(() => usePerpsTrading());
105+
const { result } = perps.renderHookWithFlow(() => usePerpsTrading());
106106

107107
// Act — through the real hook → real TradingService → real provider chain
108108
let flipResult: OrderResult | null = null;
@@ -120,8 +120,8 @@ describe('Perps order lifecycle — FLOW integration', () => {
120120
throw new Error('Expected flipPosition to return a result');
121121
}
122122
expect(flipResult).toMatchObject({ success: true });
123-
expect(harness.mocks.exchangeClient.order).toHaveBeenCalledTimes(1);
124-
expect(harness.mocks.exchangeClient.order).toHaveBeenCalledWith(
123+
expect(perps.harness.mocks.exchangeClient.order).toHaveBeenCalledTimes(1);
124+
expect(perps.harness.mocks.exchangeClient.order).toHaveBeenCalledWith(
125125
expect.objectContaining({
126126
orders: [
127127
expect.objectContaining({

tests/integration/harnesses/perps-component.tsx

Lines changed: 67 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -83,75 +83,6 @@ jest.mock('../../../app/util/haptics', () => {
8383
}),
8484
};
8585
});
86-
jest.mock(
87-
'../../../app/components/Views/confirmations/hooks/pay/useTransactionPayData',
88-
() => ({
89-
useTransactionPayTotals: () => null,
90-
useIsTransactionPayQuoteLoading: () => false,
91-
}),
92-
);
93-
jest.mock(
94-
'../../../app/components/Views/confirmations/hooks/pay/useTransactionPayMetrics',
95-
() => ({
96-
useTransactionPayMetrics: jest.fn(),
97-
}),
98-
);
99-
jest.mock(
100-
'../../../app/components/Views/confirmations/hooks/pay/useTransactionPayToken',
101-
() => ({
102-
useTransactionPayToken: () => ({ payToken: null }),
103-
}),
104-
);
105-
jest.mock(
106-
'../../../app/components/Views/confirmations/hooks/tokens/useAddToken',
107-
() => ({
108-
useAddToken: jest.fn(),
109-
}),
110-
);
111-
jest.mock(
112-
'../../../app/components/Views/confirmations/hooks/transactions/useTransactionConfirm',
113-
() => ({
114-
useTransactionConfirm: () => ({
115-
onConfirm: jest.fn().mockResolvedValue(undefined),
116-
}),
117-
}),
118-
);
119-
jest.mock(
120-
'../../../app/components/Views/confirmations/hooks/transactions/useTransactionCustomAmount',
121-
() => ({
122-
useTransactionCustomAmount: () => ({ setCustomAmount: jest.fn() }),
123-
}),
124-
);
125-
jest.mock(
126-
'../../../app/components/Views/confirmations/hooks/transactions/useTransactionMetadataRequest',
127-
() => ({
128-
useTransactionMetadataRequest: () => undefined,
129-
}),
130-
);
131-
jest.mock(
132-
'../../../app/components/Views/confirmations/hooks/transactions/useUpdateTokenAmount',
133-
() => ({
134-
useUpdateTokenAmount: () => jest.fn(),
135-
}),
136-
);
137-
jest.mock(
138-
'../../../app/components/Views/confirmations/hooks/useConfirmActions',
139-
() => ({
140-
useConfirmActions: () => ({ onReject: jest.fn() }),
141-
}),
142-
);
143-
jest.mock(
144-
'../../../app/components/Views/confirmations/hooks/alerts/useInsufficientPayTokenBalanceAlert',
145-
() => ({
146-
useInsufficientPayTokenBalanceAlert: () => [],
147-
}),
148-
);
149-
jest.mock(
150-
'../../../app/components/Views/confirmations/hooks/alerts/useNoPayTokenQuotesAlert',
151-
() => ({
152-
useNoPayTokenQuotesAlert: () => [],
153-
}),
154-
);
15586
jest.mock('../../../app/util/trace', () => ({
15687
TraceName: {
15788
PerpsOrderView: 'PerpsOrderView',
@@ -170,6 +101,8 @@ import React from 'react';
170101
import { View } from 'react-native';
171102
import { SafeAreaProvider } from 'react-native-safe-area-context';
172103
import { createStackNavigator } from '@react-navigation/stack';
104+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
105+
import { ConnectionStatus } from '@metamask/hw-wallet-sdk';
173106

174107
import { buildPerpsFlowHarness, type PerpsFlowHarness } from './perps-flow';
175108
import type { PerpsHarnessOptions } from './perps';
@@ -193,6 +126,9 @@ import type {
193126
import { AccessRestrictedProvider } from '../../../app/components/UI/Compliance';
194127
import Routes from '../../../app/constants/navigation/Routes';
195128
import { initialStatePerps } from '../../component-view/presets/perpsStatePreset';
129+
import HardwareWalletContext, {
130+
type HardwareWalletContextValue,
131+
} from '../../../app/core/HardwareWallet/contexts/HardwareWalletContext';
196132

197133
type ToastOptionsForHarness = ToastOptions;
198134

@@ -230,6 +166,30 @@ export interface PerpsComponentHarness extends PerpsFlowHarness {
230166

231167
const noopUnsubscribe = (): void => undefined;
232168

169+
function createTestQueryClient() {
170+
return new QueryClient({
171+
defaultOptions: {
172+
queries: { retry: false },
173+
mutations: { retry: false },
174+
},
175+
});
176+
}
177+
178+
function QueryClientBoundary({ children }: { children: React.ReactNode }) {
179+
const [queryClient] = React.useState(createTestQueryClient);
180+
181+
React.useEffect(
182+
() => () => {
183+
queryClient.clear();
184+
},
185+
[queryClient],
186+
);
187+
188+
return (
189+
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
190+
);
191+
}
192+
233193
const testConnectionValue: PerpsConnectionContextValue = {
234194
isConnected: true,
235195
isConnecting: false,
@@ -241,6 +201,31 @@ const testConnectionValue: PerpsConnectionContextValue = {
241201
reconnectWithNewContext: async (): Promise<void> => undefined,
242202
};
243203

204+
const testHardwareWalletValue: HardwareWalletContextValue = {
205+
walletType: null,
206+
deviceId: null,
207+
connectionState: { status: ConnectionStatus.Disconnected },
208+
deviceSelection: {
209+
devices: [],
210+
selectedDevice: null,
211+
isScanning: false,
212+
scanError: null,
213+
},
214+
ensureDeviceReady: async (): Promise<boolean> => true,
215+
setTargetWalletType: (): void => undefined,
216+
setPendingOperationAddress: (): void => undefined,
217+
showHardwareWalletError: (): void => undefined,
218+
showAwaitingConfirmation: (): void => undefined,
219+
hideAwaitingConfirmation: (): void => undefined,
220+
qr: {
221+
pendingScanRequest: undefined,
222+
isSigningQRObject: false,
223+
setRequestCompleted: (): void => undefined,
224+
isRequestCompleted: false,
225+
cancelQRScanRequestIfPresent: async (): Promise<void> => undefined,
226+
},
227+
};
228+
244229
function channelWithInitialValue<T>(initialValue: T) {
245230
return {
246231
subscribe: (params: { callback: (data: T) => void }): (() => void) => {
@@ -349,15 +334,19 @@ export function buildPerpsComponentHarness(
349334
insets: { top: 0, left: 0, right: 0, bottom: 0 },
350335
}}
351336
>
352-
<ToastContext.Provider value={{ toastRef }}>
353-
<AccessRestrictedProvider>
354-
<PerpsConnectionContext.Provider value={testConnectionValue}>
355-
<PerpsStreamProvider testStreamManager={streamManager}>
356-
{component}
357-
</PerpsStreamProvider>
358-
</PerpsConnectionContext.Provider>
359-
</AccessRestrictedProvider>
360-
</ToastContext.Provider>
337+
<QueryClientBoundary>
338+
<HardwareWalletContext.Provider value={testHardwareWalletValue}>
339+
<ToastContext.Provider value={{ toastRef }}>
340+
<AccessRestrictedProvider>
341+
<PerpsConnectionContext.Provider value={testConnectionValue}>
342+
<PerpsStreamProvider testStreamManager={streamManager}>
343+
{component}
344+
</PerpsStreamProvider>
345+
</PerpsConnectionContext.Provider>
346+
</AccessRestrictedProvider>
347+
</ToastContext.Provider>
348+
</HardwareWalletContext.Provider>
349+
</QueryClientBoundary>
361350
</SafeAreaProvider>
362351
);
363352

0 commit comments

Comments
 (0)