Skip to content

Commit 9465e45

Browse files
committed
fix e2e
1 parent 5a68f33 commit 9465e45

File tree

15 files changed

+495
-247
lines changed

15 files changed

+495
-247
lines changed

app/components/UI/Perps/Views/PerpsEmptyState/PerpsEmptyState.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useAssetFromTheme } from '../../../../../util/theme';
99
import { useTailwind } from '@metamask/design-system-twrnc-preset';
1010
import emptyStatePerpsLight from '../../../../../images/empty-state-perps-light.png';
1111
import emptyStatePerpsDark from '../../../../../images/empty-state-perps-dark.png';
12+
import { PerpsTabViewSelectorsIDs } from '../../../../../../e2e/selectors/Perps/Perps.selectors';
1213

1314
export interface PerpsEmptyStateProps extends TabEmptyStateProps {
1415
testID?: string;
@@ -34,6 +35,9 @@ export const PerpsEmptyState: React.FC<PerpsEmptyStateProps> = ({
3435
}
3536
description={strings('perps.position.list.first_time_description')}
3637
actionButtonText={strings('perps.position.list.start_trading')}
38+
actionButtonProps={{
39+
testID: PerpsTabViewSelectorsIDs.ONBOARDING_BUTTON,
40+
}}
3741
testID={testID}
3842
{...props}
3943
/>

e2e/controller-mocking/mock-config/perps-controller-mixin.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
ClosePositionParams,
1919
LiquidationPriceParams,
2020
Funding,
21-
} from '../../../app/components/UI/Perps/controllers/types';
21+
UpdatePositionTPSLParams } from '../../../app/components/UI/Perps/controllers/types';
2222
import type { PerpsControllerState } from '../../../app/components/UI/Perps/controllers/PerpsController';
2323

2424
// Interface for controller with update method access
@@ -93,8 +93,10 @@ export class E2EControllerOverrides {
9393

9494
// Mock historical orders
9595
async getOrders(): Promise<Order[]> {
96-
const orders = this.mockService.getMockOrders();
97-
return orders;
96+
// Return combined open orders and historical (canceled/filled) orders
97+
const openOrders = this.mockService.getMockOrders();
98+
const historicalOrders = this.mockService.getMockOrdersHistory();
99+
return [...openOrders, ...historicalOrders];
98100
}
99101

100102
// Mock historical order fills (trades)
@@ -155,6 +157,32 @@ export class E2EControllerOverrides {
155157
return result;
156158
}
157159

160+
// Mock cancel order
161+
async cancelOrder(params: {
162+
orderId: string;
163+
coin: string;
164+
}): Promise<OrderResult> {
165+
const result = await this.mockService.mockCancelOrder(params.orderId);
166+
return result;
167+
}
168+
169+
// Mock TP/SL update creating trigger orders
170+
async updatePositionTPSL(
171+
params: UpdatePositionTPSLParams,
172+
): Promise<OrderResult> {
173+
const result = await this.mockService.mockUpdatePositionTPSL(params);
174+
// Refresh Redux positions after TP/SL changes
175+
const mockPositions = this.mockService.getMockPositions();
176+
(this.controller as ControllerWithUpdate).update(
177+
(state: PerpsControllerState) => {
178+
state.positions = mockPositions;
179+
state.lastUpdateTimestamp = Date.now();
180+
state.lastError = null;
181+
},
182+
);
183+
return result;
184+
}
185+
158186
// Mock account subscription
159187
subscribeToAccount(params: {
160188
callback: (data: AccountState) => void;
@@ -232,10 +260,16 @@ export function applyE2EPerpsControllerMocks(controller: unknown): void {
232260
// Override key methods with E2E mocks
233261
const methodsToOverride = [
234262
'placeOrder',
263+
'cancelOrder',
235264
'getAccountState',
236265
'getPositions',
237266
'closePosition',
267+
'updatePositionTPSL',
238268
'calculateLiquidationPrice',
269+
// Activity > Perps data sources
270+
'getOrders',
271+
'getOrderFills',
272+
'getFunding',
239273
'subscribeToAccount',
240274
'subscribeToPositions',
241275
'subscribeToOrders',

e2e/controller-mocking/mock-responses/perps/perps-e2e-mocks.ts

Lines changed: 173 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
WithdrawParams,
1919
WithdrawResult,
2020
Funding,
21-
} from '../../../../app/components/UI/Perps/controllers/types';
21+
UpdatePositionTPSLParams } from '../../../../app/components/UI/Perps/controllers/types';
2222

2323
export class PerpsE2EMockService {
2424
private static instance: PerpsE2EMockService;
@@ -36,6 +36,8 @@ export class PerpsE2EMockService {
3636

3737
private mockPositions: Position[] = [];
3838
private mockOrders: Order[] = [];
39+
// Historical orders (canceled/filled/etc.) for Activity > Orders
40+
private mockOrdersHistory: Order[] = [];
3941
private mockOrderFills: OrderFill[] = [];
4042
private mockPricesMap: Record<string, PriceUpdate> = {};
4143
private orderMeta: Record<string, { leverage: number }> = {};
@@ -83,49 +85,19 @@ export class PerpsE2EMockService {
8385
if (profile === 'no-funds' || profile === 'no-positions') {
8486
this.mockPositions = [];
8587
} else if (profile === 'position-testing') {
86-
// Rich set of positions for UI validation
88+
// Requested scenario for tests: one BTC long position, open ETH and SOL limit orders
8789
this.mockPositions = [
88-
{
89-
coin: 'ETH',
90-
entryPrice: '2500.00',
91-
size: '1.0', // long
92-
positionValue: '2500.00',
93-
unrealizedPnl: '125.00',
94-
marginUsed: '833.33',
95-
leverage: { type: 'cross', value: 3 },
96-
liquidationPrice: '2000.00',
97-
maxLeverage: 40,
98-
returnOnEquity: '0.05', // 5%
99-
cumulativeFunding: { allTime: '0', sinceChange: '0', sinceOpen: '0' },
100-
takeProfitCount: 0,
101-
stopLossCount: 0,
102-
},
10390
{
10491
coin: 'BTC',
10592
entryPrice: '45000.00',
106-
size: '-0.05', // short
107-
positionValue: '2250.00',
108-
unrealizedPnl: '-50.00',
109-
marginUsed: '225.00',
110-
leverage: { type: 'cross', value: 10 },
111-
liquidationPrice: '47250.00',
112-
maxLeverage: 40,
113-
returnOnEquity: '-0.02', // -2%
114-
cumulativeFunding: { allTime: '0', sinceChange: '0', sinceOpen: '0' },
115-
takeProfitCount: 0,
116-
stopLossCount: 0,
117-
},
118-
{
119-
coin: 'SOL',
120-
entryPrice: '100.00',
121-
size: '5', // long
122-
positionValue: '500.00',
123-
unrealizedPnl: '25.00',
124-
marginUsed: '25.00',
125-
leverage: { type: 'cross', value: 20 },
126-
liquidationPrice: '80.00',
93+
size: '0.10', // long BTC
94+
positionValue: '4500.00',
95+
unrealizedPnl: '150.00',
96+
marginUsed: '900.00',
97+
leverage: { type: 'cross', value: 5 },
98+
liquidationPrice: '36000.00',
12799
maxLeverage: 40,
128-
returnOnEquity: '0.10', // 10%
100+
returnOnEquity: '0.167',
129101
cumulativeFunding: { allTime: '0', sinceChange: '0', sinceOpen: '0' },
130102
takeProfitCount: 0,
131103
stopLossCount: 0,
@@ -165,6 +137,7 @@ export class PerpsE2EMockService {
165137
};
166138

167139
this.mockOrders = [];
140+
this.mockOrdersHistory = [];
168141
this.mockOrderFills = [];
169142
this.orderIdCounter = 1;
170143
this.fillIdCounter = 1;
@@ -177,6 +150,57 @@ export class PerpsE2EMockService {
177150

178151
// Initialize price map with default prices
179152
this.mockPricesMap = this.buildDefaultPrices();
153+
154+
// Seed default historical/open orders only when not explicitly requesting an empty profile
155+
if (profile !== 'no-positions') {
156+
// 1) Default trade (fill): Opened long BTC with a small fee
157+
const defaultTrade: OrderFill = {
158+
orderId: `seed_fill_${Date.now()}`,
159+
symbol: 'BTC',
160+
size: '0.01',
161+
price: '45000.00',
162+
timestamp: Date.now() - 60 * 60 * 1000, // 1 hour ago
163+
side: 'buy',
164+
fee: '1.25',
165+
feeToken: 'USDC',
166+
pnl: '0.00',
167+
direction: 'Open Long',
168+
};
169+
this.mockOrderFills.push(defaultTrade);
170+
171+
// 2) Open orders: ETH and SOL limit longs
172+
const seedNow = Date.now();
173+
const ethOrder: Order = {
174+
orderId: `seed_order_eth_${seedNow}`,
175+
symbol: 'ETH',
176+
side: 'buy',
177+
orderType: 'limit',
178+
size: '0.50',
179+
originalSize: '0.50',
180+
price: '2400.00',
181+
filledSize: '0',
182+
remainingSize: '0.50',
183+
status: 'open',
184+
timestamp: seedNow - 30 * 60 * 1000,
185+
isTrigger: false,
186+
};
187+
const solOrder: Order = {
188+
orderId: `seed_order_sol_${seedNow}`,
189+
symbol: 'SOL',
190+
side: 'buy',
191+
orderType: 'limit',
192+
size: '10',
193+
originalSize: '10',
194+
price: '95.00',
195+
filledSize: '0',
196+
remainingSize: '10',
197+
status: 'open',
198+
timestamp: seedNow - 25 * 60 * 1000,
199+
isTrigger: false,
200+
};
201+
this.mockOrders.push(ethOrder);
202+
this.mockOrders.push(solOrder);
203+
}
180204
}
181205

182206
// Mock successful order placement
@@ -258,7 +282,7 @@ export class PerpsE2EMockService {
258282
// Add to mock state
259283
this.mockPositions.push(mockPosition);
260284

261-
// Create mock order fill
285+
// Create mock order fill (market execution)
262286
const mockFill: OrderFill = {
263287
orderId,
264288
symbol: params.coin,
@@ -269,7 +293,7 @@ export class PerpsE2EMockService {
269293
fee: '2.50',
270294
feeToken: 'USDC',
271295
pnl: '0.00',
272-
direction: params.isBuy ? 'long' : 'short',
296+
direction: params.isBuy ? 'Open Long' : 'Open Short',
273297
};
274298

275299
this.mockOrderFills.push(mockFill);
@@ -322,7 +346,9 @@ export class PerpsE2EMockService {
322346
* Mock deposit in USD into the perps trading account.
323347
* Increases both availableBalance and totalBalance by the provided fiat amount.
324348
*/
325-
public async mockDepositUSD(amountFiat: string): Promise<{ success: boolean }>{
349+
public async mockDepositUSD(
350+
amountFiat: string,
351+
): Promise<{ success: boolean }> {
326352
const delta = parseFloat(amountFiat || '0');
327353
if (!Number.isFinite(delta) || delta <= 0) {
328354
return { success: false };
@@ -383,13 +409,16 @@ export class PerpsE2EMockService {
383409
orderId: `mock_close_${this.orderIdCounter}`,
384410
symbol: existingPosition.coin,
385411
size: Math.abs(parseFloat(existingPosition.size)).toString(),
386-
price: this.mockPricesMap[existingPosition.coin]?.price || existingPosition.entryPrice,
412+
price:
413+
this.mockPricesMap[existingPosition.coin]?.price ||
414+
existingPosition.entryPrice,
387415
timestamp: Date.now(),
388416
side: parseFloat(existingPosition.size) > 0 ? 'sell' : 'buy',
389417
fee: '0.00',
390418
feeToken: 'USDC',
391419
pnl: pnl.toFixed(2),
392-
direction: parseFloat(existingPosition.size) > 0 ? 'short' : 'long',
420+
direction:
421+
parseFloat(existingPosition.size) > 0 ? 'Close Long' : 'Close Short',
393422
};
394423
this.mockOrderFills.push(closeFill);
395424

@@ -506,6 +535,14 @@ export class PerpsE2EMockService {
506535
return [...this.mockOrders];
507536
}
508537

538+
public getMockOrdersHistory(): Order[] {
539+
return [...this.mockOrdersHistory];
540+
}
541+
542+
public getMockOrdersCombined(): Order[] {
543+
return [...this.mockOrders, ...this.mockOrdersHistory];
544+
}
545+
509546
public getMockOrderFills(): OrderFill[] {
510547
return [...this.mockOrderFills];
511548
}
@@ -564,6 +601,98 @@ export class PerpsE2EMockService {
564601
];
565602
}
566603

604+
/** Cancel an open order by id and push a canceled state into historical orders */
605+
public async mockCancelOrder(orderId: string): Promise<OrderResult> {
606+
const idx = this.mockOrders.findIndex((o) => o.orderId === orderId);
607+
if (idx === -1) {
608+
return { success: false, error: 'Order not found' };
609+
}
610+
611+
const order = { ...this.mockOrders[idx] };
612+
// Remove from open orders
613+
this.mockOrders.splice(idx, 1);
614+
615+
// Record canceled snapshot into orders history for Activity > Orders
616+
const canceledOrder: Order = {
617+
...order,
618+
status: 'canceled',
619+
lastUpdated: Date.now(),
620+
};
621+
this.mockOrdersHistory.push(canceledOrder);
622+
623+
// Notify orders subscribers
624+
this.notifyOrdersCallbacks();
625+
return { success: true, orderId };
626+
}
627+
628+
/**
629+
* Mock update of Take Profit / Stop Loss settings. This creates trigger orders
630+
* so that Activity > Perps → Orders shows TP/SL entries as open orders.
631+
*/
632+
public async mockUpdatePositionTPSL(
633+
params: UpdatePositionTPSLParams,
634+
): Promise<OrderResult> {
635+
const position = this.mockPositions.find((p) => p.coin === params.coin);
636+
if (!position) {
637+
return { success: false, error: 'Position not found' };
638+
}
639+
640+
const now = Date.now();
641+
// Update position fields
642+
position.takeProfitPrice = params.takeProfitPrice;
643+
position.stopLossPrice = params.stopLossPrice;
644+
position.takeProfitCount = params.takeProfitPrice ? 1 : 0;
645+
position.stopLossCount = params.stopLossPrice ? 1 : 0;
646+
647+
// Create trigger orders for TP/SL as open orders
648+
if (params.takeProfitPrice) {
649+
const tpOrder: Order = {
650+
orderId: `tp_${this.orderIdCounter++}`,
651+
symbol: params.coin,
652+
side: parseFloat(position.size) > 0 ? 'sell' : 'buy',
653+
orderType: 'limit',
654+
size: Math.abs(parseFloat(position.size)).toString(),
655+
originalSize: Math.abs(parseFloat(position.size)).toString(),
656+
price: params.takeProfitPrice,
657+
filledSize: '0',
658+
remainingSize: Math.abs(parseFloat(position.size)).toString(),
659+
status: 'open',
660+
timestamp: now,
661+
detailedOrderType: 'Take Profit Limit',
662+
isTrigger: true,
663+
reduceOnly: true,
664+
};
665+
this.mockOrders.push(tpOrder);
666+
}
667+
668+
if (params.stopLossPrice) {
669+
const slOrder: Order = {
670+
orderId: `sl_${this.orderIdCounter++}`,
671+
symbol: params.coin,
672+
side: parseFloat(position.size) > 0 ? 'sell' : 'buy',
673+
orderType: 'limit',
674+
size: Math.abs(parseFloat(position.size)).toString(),
675+
originalSize: Math.abs(parseFloat(position.size)).toString(),
676+
price: params.stopLossPrice,
677+
filledSize: '0',
678+
remainingSize: Math.abs(parseFloat(position.size)).toString(),
679+
status: 'open',
680+
timestamp: now,
681+
detailedOrderType: 'Stop Loss Limit',
682+
isTrigger: true,
683+
reduceOnly: true,
684+
};
685+
this.mockOrders.push(slOrder);
686+
}
687+
688+
// Notify orders subscribers
689+
this.notifyOrdersCallbacks();
690+
// Also notify position subscribers for UI counters
691+
this.notifyPositionCallbacks();
692+
693+
return { success: true };
694+
}
695+
567696
private calculateMockLiquidationPrice(
568697
entryPrice: number,
569698
leverage: number,

0 commit comments

Comments
 (0)