Skip to content

Commit ca5efa3

Browse files
committed
Merge branch 'chore/navigation-typing-phase-4-perps' of github.com:MetaMask/metamask-mobile into chore/navigation-typing-phase-4-perps
2 parents 0ea6447 + 17c5eda commit ca5efa3

40 files changed

Lines changed: 865 additions & 1722 deletions

.depcheckrc.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ ignores:
4646
- 'xml2js'
4747
# jest-junit is used as a Jest reporter in tests/jest.e2e.detox.config.js
4848
- 'jest-junit'
49+
# Required directly only by scripts/babel/named-capturing-groups-regex.test.ts
50+
# (a regression guard for the @react-native/babel-preset patch). It's provided
51+
# transitively via @expo/metro-config + Metro, so we don't declare it as a
52+
# direct dependency.
53+
- 'babel-preset-expo'
4954

5055
# tsconfig path alias pointing to app/controllers/perps (not an npm package)
5156
- '@metamask/perps-controller'
@@ -76,7 +81,6 @@ ignores:
7681
- 'chromedriver'
7782
- 'execa'
7883
- 'jetifier'
79-
- 'metro-react-native-babel-preset'
8084
- 'react-native-svg-asset-plugin'
8185
- 'regenerator-runtime'
8286
- 'prettier-2'
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
diff --git a/src/configs/main.js b/src/configs/main.js
2+
index a2f5e1e53a663c07ce8fa3def3a1d6d7828a4557..5179e8cf5bb6f085275daa183a05d21553cf695a 100644
3+
--- a/src/configs/main.js
4+
+++ b/src/configs/main.js
5+
@@ -120,9 +120,14 @@ const getPreset = (src, options) => {
6+
extraPlugins.push([require('@babel/plugin-transform-numeric-separator')]);
7+
extraPlugins.push([require('@babel/plugin-transform-sticky-regex')]);
8+
} else {
9+
- extraPlugins.push([
10+
- require('@babel/plugin-transform-named-capturing-groups-regex'),
11+
- ]);
12+
+ // NOTE (MetaMask): Do NOT down-level named-capture-group regexes on Hermes.
13+
+ // Current Hermes supports them natively, and the `@babel/runtime/helpers/wrapRegExp`
14+
+ // helper that this transform introduces breaks under SES lockdown (frozen
15+
+ // intrinsics), causing `RegExp.exec` to return null for matching strings
16+
+ // (e.g. NetworkController's INFURA_URL_REGEX -> "Could not derive Infura network").
17+
+ // extraPlugins.push([
18+
+ // require('@babel/plugin-transform-named-capturing-groups-regex'),
19+
+ // ]);
20+
// Needed for regenerator
21+
if (isHermes && enableRegenerator) {
22+
extraPlugins.push([

.yarn/patches/@tommasini-react-native-scrollable-tab-view-npm-1.1.1-153a9df479.patch

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ index 81a8a70576a2336fbfce44f429b80acb6bacb2c1..f8916b181efe7ba4ab598d3e6fb11477
5454
borderTopWidth: 0,
5555
borderLeftWidth: 0,
5656
diff --git a/index.js b/index.js
57-
index dc9d4f4e0bb7b5a165e0bcc89133e4bf6cdfff91..496530970bce3754e76362737be4c3491d159f05 100644
57+
index dc9d4f4e0bb7b5a165e0bcc89133e4bf6cdfff91..94f7ac60e15478b197aa3f537224745d0d5fecb6 100644
5858
--- a/index.js
5959
+++ b/index.js
6060
@@ -13,17 +13,14 @@ const {
@@ -98,3 +98,12 @@ index dc9d4f4e0bb7b5a165e0bcc89133e4bf6cdfff91..496530970bce3754e76362737be4c349
9898
)}
9999
ref={(scrollView) => { this.scrollView = scrollView; }}
100100
{...this.props.contentProps}
101+
@@ -348,7 +342,7 @@ const ScrollableTabView = createReactClass({
102+
// Need to call __makeNative manually to avoid a native animated bug. See
103+
// https://github.com/facebook/react-native/pull/14435
104+
containerWidthAnimatedValue.__makeNative();
105+
- scrollValue = Animated.divide(this.state.scrollXIOS, containerWidthAnimatedValue);
106+
+ const scrollValue = Animated.divide(this.state.scrollXIOS, containerWidthAnimatedValue);
107+
this.setState({ containerWidth: width, scrollValue, });
108+
} else {
109+
this.setState({ containerWidth: width, });

app/components/UI/Perps/Perps.testIds.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,6 @@ export const PerpsTPSLViewSelectorsIDs = {
332332
SET_BUTTON: 'perps-tpsl-set-button',
333333
TAKE_PROFIT_PRICE_INPUT: 'perps-tpsl-tp-input',
334334
STOP_LOSS_PRICE_INPUT: 'perps-tpsl-sl-input',
335-
TAKE_PROFIT_PERCENTAGE_INPUT: 'perps-tpsl-tp-percentage-input',
336-
STOP_LOSS_PERCENTAGE_INPUT: 'perps-tpsl-sl-percentage-input',
337-
TAKE_PROFIT_ROE_SIGN_BADGE: 'perps-tpsl-tp-roe-sign-badge',
338-
STOP_LOSS_ROE_SIGN_BADGE: 'perps-tpsl-sl-roe-sign-badge',
339335
} as const;
340336

341337
export const getPerpsTPSLViewSelector = {

app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.test.tsx

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2383,58 +2383,6 @@ describe('PerpsOrderView', () => {
23832383
expect(placeOrderButton).toBeDefined();
23842384
});
23852385

2386-
it('accepts a signed take profit (negative RoE below current price) after the sheet confirms', async () => {
2387-
// Arrange: long order with a TP at 2000 (below current 3000). Classic
2388-
// side rules reject this, but the Auto Close sheet allows it as a
2389-
// negative take profit. Regression for PR #32404: the order view must
2390-
// accept the signed trigger once the sheet reports its sign.
2391-
(usePerpsOrderContext as jest.Mock).mockReturnValue(
2392-
orderContextWithTPSL({ direction: 'long', takeProfitPrice: '2000' }),
2393-
);
2394-
(usePerpsOrderValidation as jest.Mock).mockReturnValue({
2395-
isValid: true,
2396-
errors: [],
2397-
isValidating: false,
2398-
});
2399-
(usePerpsOrderExecution as jest.Mock).mockReturnValue({
2400-
placeOrder: jest.fn(),
2401-
isPlacing: false,
2402-
});
2403-
2404-
render(<PerpsOrderView />, { wrapper: TestWrapper });
2405-
2406-
// Before the sheet reports a sign, the default + TP shows the warning.
2407-
await waitFor(() => {
2408-
expect(screen.getByText(/Take profit must be above/)).toBeDefined();
2409-
});
2410-
2411-
// Act: open the TP/SL sheet and confirm with a signed (negative) RoE.
2412-
const tpslRow = await screen.findByTestId(
2413-
PerpsOrderViewSelectorsIDs.STOP_LOSS_BUTTON,
2414-
);
2415-
fireEvent.press(tpslRow);
2416-
const { onConfirm } =
2417-
mockNavigate.mock.calls[mockNavigate.mock.calls.length - 1][1];
2418-
await act(async () => {
2419-
await onConfirm(undefined, '2000', undefined, {
2420-
direction: 'long',
2421-
source: 'trade_screen',
2422-
positionSize: 0,
2423-
takeProfitPercentage: -10,
2424-
});
2425-
});
2426-
2427-
// Assert: the signed trigger is accepted — no wrong-side warning and the
2428-
// Place Order button is no longer disabled on TP/SL grounds.
2429-
await waitFor(() => {
2430-
expect(screen.queryByText(/Take profit must be/)).toBeNull();
2431-
});
2432-
const placeOrderButton = await screen.findByTestId(
2433-
PerpsOrderViewSelectorsIDs.PLACE_ORDER_BUTTON,
2434-
);
2435-
expect(placeOrderButton.props.accessibilityState?.disabled).toBeFalsy();
2436-
});
2437-
24382386
describe('limit order TP/SL validates against entry price, not market price', () => {
24392387
const orderContextForLimitOrder = (overrides: {
24402388
direction: 'long' | 'short';

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

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ import {
9090
type OrderParams,
9191
type OrderType,
9292
type Position,
93-
type TPSLTrackingData,
9493
ORDER_SLIPPAGE_CONFIG,
9594
} from '@metamask/perps-controller';
9695
import {
@@ -202,9 +201,6 @@ interface PerpsOrderViewContentProps {
202201
defaultMaxLeverage?: number;
203202
}
204203

205-
const flipDirection = (direction: 'long' | 'short'): 'long' | 'short' =>
206-
direction === 'long' ? 'short' : 'long';
207-
208204
/**
209205
* PerpsOrderViewContentBase
210206
* Main content component for the Perps order view
@@ -403,14 +399,6 @@ const PerpsOrderViewContentBase: React.FC<PerpsOrderViewContentProps> = ({
403399
const [isInputFocused, setIsInputFocused] = useState(false);
404400
const [shouldOpenLimitPrice, setShouldOpenLimitPrice] = useState(false);
405401

406-
// The Auto Close sheet renders the RoE sign on a badge and persists only the
407-
// trigger price — not the sign. Track the chosen sign here so order-view
408-
// validation can accept a signed trigger (a negative take profit or a
409-
// gain-side stop loss) instead of rejecting it with the classic
410-
// long/short side rules. Defaults match the sheet (+ TP / - SL).
411-
const [takeProfitSign, setTakeProfitSign] = useState<'+' | '-'>('+');
412-
const [stopLossSign, setStopLossSign] = useState<'+' | '-'>('-');
413-
414402
// Max slippage from persisted controller state via hook so the component
415403
// never reaches into PerpsController directly (perps anti-pattern rule).
416404
// The hook also exposes the source (default vs user-configured) for
@@ -949,25 +937,13 @@ const PerpsOrderViewContentBase: React.FC<PerpsOrderViewContentProps> = ({
949937
_position?: Position,
950938
takeProfitPrice?: string,
951939
stopLossPrice?: string,
952-
trackingData?: TPSLTrackingData,
953940
) => {
954941
// Order flow: no position; just persist TP/SL in form state
955942
const tpToSet = takeProfitPrice || undefined;
956943
const slToSet = stopLossPrice || undefined;
957944

958945
setTakeProfitPrice(tpToSet);
959946
setStopLossPrice(slToSet);
960-
961-
// Recover the RoE sign from the signed percentage the sheet reports so
962-
// validation accepts a signed trigger. A negative TP percentage is a
963-
// gain-side flip (-); a positive SL percentage is a gain-side flip (+).
964-
// Absent/zero percentage falls back to the natural sign.
965-
setTakeProfitSign(
966-
(trackingData?.takeProfitPercentage ?? 0) < 0 ? '-' : '+',
967-
);
968-
setStopLossSign(
969-
(trackingData?.stopLossPercentage ?? 0) > 0 ? '+' : '-',
970-
);
971947
},
972948
});
973949
}, [
@@ -1475,26 +1451,12 @@ const PerpsOrderViewContentBase: React.FC<PerpsOrderViewContentProps> = ({
14751451

14761452
const tpslPriceType = isLimitWithPrice ? 'entry' : 'current';
14771453

1478-
// The RoE sign decides which side of the reference price the trigger must
1479-
// sit on, mirroring usePerpsTPSLForm: a + take profit / - stop loss keep the
1480-
// natural side, while a signed flip (- TP / + SL) accepts the opposite side.
1481-
// Passing the effective direction lets the existing price-side checks respect
1482-
// the sign without changing their logic, so a signed trigger isn't rejected.
1483-
const takeProfitEffectiveDirection =
1484-
takeProfitSign === '+'
1485-
? orderForm.direction
1486-
: flipDirection(orderForm.direction);
1487-
const stopLossEffectiveDirection =
1488-
stopLossSign === '-'
1489-
? orderForm.direction
1490-
: flipDirection(orderForm.direction);
1491-
14921454
const isTakeProfitPriceInvalid = Boolean(
14931455
orderForm.takeProfitPrice?.trim() &&
14941456
validationReferencePrice > 0 &&
14951457
!isValidTakeProfitPrice(orderForm.takeProfitPrice, {
14961458
currentPrice: validationReferencePrice,
1497-
direction: takeProfitEffectiveDirection,
1459+
direction: orderForm.direction,
14981460
}),
14991461
);
15001462

@@ -1503,7 +1465,7 @@ const PerpsOrderViewContentBase: React.FC<PerpsOrderViewContentProps> = ({
15031465
validationReferencePrice > 0 &&
15041466
!isValidStopLossPrice(orderForm.stopLossPrice, {
15051467
currentPrice: validationReferencePrice,
1506-
direction: stopLossEffectiveDirection,
1468+
direction: orderForm.direction,
15071469
}),
15081470
);
15091471

app/components/UI/Perps/Views/PerpsTPSLView/PerpsTPSLView.styles.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,4 @@ export const createStyles = (colors: Theme['colors']) =>
217217
footerButton: {
218218
flex: 1,
219219
},
220-
roeSignBadge: {
221-
paddingHorizontal: 8,
222-
paddingVertical: 2,
223-
marginRight: 4,
224-
borderRadius: 6,
225-
backgroundColor: colors.background.default,
226-
alignItems: 'center',
227-
justifyContent: 'center',
228-
},
229220
});

app/components/UI/Perps/Views/PerpsTPSLView/PerpsTPSLView.test.tsx

Lines changed: 0 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,6 @@ describe('PerpsTPSLView', () => {
153153
slPercentInputFocused: false,
154154
tpUsingPercentage: false,
155155
slUsingPercentage: false,
156-
takeProfitSign: '+',
157-
stopLossSign: '-',
158156
},
159157
handlers: {
160158
handleTakeProfitPriceChange: jest.fn(),
@@ -175,8 +173,6 @@ describe('PerpsTPSLView', () => {
175173
handleStopLossPercentageButton: jest.fn(),
176174
handleTakeProfitOff: jest.fn(),
177175
handleStopLossOff: jest.fn(),
178-
handleTakeProfitSignToggle: jest.fn(),
179-
handleStopLossSignToggle: jest.fn(),
180176
},
181177
validation: {
182178
isValid: true,
@@ -591,44 +587,6 @@ describe('PerpsTPSLView', () => {
591587
);
592588
});
593589

594-
it('reports signed RoE percentages in tracking metadata for negative TP and gain-side SL', async () => {
595-
const mockOnConfirm = jest.fn().mockResolvedValue(undefined);
596-
mockRouteParams = { ...defaultRouteParams, onConfirm: mockOnConfirm };
597-
renderView({
598-
formState: {
599-
...defaultMockReturn.formState,
600-
takeProfitPrice: '$2,850.00',
601-
stopLossPrice: '$3,150.00',
602-
takeProfitSign: '-',
603-
stopLossSign: '+',
604-
},
605-
display: {
606-
...defaultMockReturn.display,
607-
formattedTakeProfitPercentage: '10%',
608-
formattedStopLossPercentage: '10%',
609-
},
610-
validation: {
611-
...defaultMockReturn.validation,
612-
hasChanges: true,
613-
},
614-
});
615-
616-
const setButton = screen.getByText('perps.tpsl.set');
617-
await act(async () => {
618-
fireEvent.press(setButton);
619-
});
620-
621-
expect(mockOnConfirm).toHaveBeenCalledWith(
622-
undefined,
623-
'2850.00',
624-
'3150.00',
625-
expect.objectContaining({
626-
takeProfitPercentage: -10,
627-
stopLossPercentage: 10,
628-
}),
629-
);
630-
});
631-
632590
it('calls onConfirm with undefined when values are empty', async () => {
633591
const mockOnConfirm = jest.fn().mockResolvedValue(undefined);
634592
mockRouteParams = { ...defaultRouteParams, onConfirm: mockOnConfirm };
@@ -743,60 +701,4 @@ describe('PerpsTPSLView', () => {
743701
expect(screen.getByTestId('back-button')).toBeOnTheScreen();
744702
});
745703
});
746-
747-
// ==================== RoE Sign Badge ====================
748-
749-
describe('RoE Sign Badge', () => {
750-
it('renders the default + take profit and - stop loss signs', () => {
751-
renderView();
752-
753-
expect(
754-
screen.getByTestId('perps-tpsl-tp-roe-sign-badge'),
755-
).toHaveTextContent('+');
756-
expect(
757-
screen.getByTestId('perps-tpsl-sl-roe-sign-badge'),
758-
).toHaveTextContent('-');
759-
});
760-
761-
it('toggles the take profit sign when the badge is pressed', () => {
762-
const mockToggle = jest.fn();
763-
renderView({
764-
buttons: {
765-
...defaultMockReturn.buttons,
766-
handleTakeProfitSignToggle: mockToggle,
767-
},
768-
});
769-
770-
fireEvent.press(screen.getByTestId('perps-tpsl-tp-roe-sign-badge'));
771-
772-
expect(mockToggle).toHaveBeenCalled();
773-
});
774-
775-
it('toggles the stop loss sign when the badge is pressed', () => {
776-
const mockToggle = jest.fn();
777-
renderView({
778-
buttons: {
779-
...defaultMockReturn.buttons,
780-
handleStopLossSignToggle: mockToggle,
781-
},
782-
});
783-
784-
fireEvent.press(screen.getByTestId('perps-tpsl-sl-roe-sign-badge'));
785-
786-
expect(mockToggle).toHaveBeenCalled();
787-
});
788-
789-
it('reflects the take profit sign provided by the form hook', () => {
790-
renderView({
791-
formState: {
792-
...defaultMockReturn.formState,
793-
takeProfitSign: '-',
794-
},
795-
});
796-
797-
expect(
798-
screen.getByTestId('perps-tpsl-tp-roe-sign-badge'),
799-
).toHaveTextContent('-');
800-
});
801-
});
802704
});

0 commit comments

Comments
 (0)